\n

{$t(\"Counter\")}

\n
\n\n\n{count}\n
\n

{$t(\"Switch language\")}

\n
locale.set(\"en-US\")}>{$t(\"English\")}
\n
locale.set(\"de-DE\")}>{$t(\"German\")}
\n```\n\n### Removed modules\n\nRemove any `@primate/types` imports and uses in your configuration file.\n\n### Removed imports\n\nDon't import `Response` anymore, it is available in the global context of all\nruntimes.\n\nIf you previously imported `Status`, import instead the individual statuses\nfrom `@rcompat/http/Status`.\n\n\n```js\nimport Status from \"@rcompat/http/Status\";\n\nexport default {\n get() {\n return new Response(\"Hello, world!\", { status: Status.OK });\n },\n};\n```\n\n### Removed Logger\n\nRemove any `Logger` imports. You can now set the log level when running\nPrimate:\n\n`npx primate --loglevel=info`\n\nThe log levels stayed the same: `info`, `warn` and `error`.\n\n### Use build.define instead of build.transform\n\nPreviously, you could define a `build.transform` and `build.mapper` to specify\ntextual replacements during build-time. We now use esbuild's identifier\nreplacement.\n\n```js caption=primate.config.js\nexport default {\n build: {\n define: {\n DEBUG: \"true\",\n APP_NAME: \"'Primate'\",\n },\n },\n};\n```\n\nNote that this is an identifier replacement, so if you want the identifier\n`DEBUG` to be replaced with boolean `true`, you'd write `DEBUG: \"true\"`, but if\nyou want the replacement to be a string, be sure to quote it properly:\n`APP_NAME: \"'Primate'\"`.\n\n[According to esbuild](https://esbuild.github.io/api/#define), the expression\nwhich the identifier is mapped to can \"either be a JSON object (null, boolean,\nnumber, string, array, or object) or a single identifier\".\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nSome of the things we plan to tackle in the upcoming weeks are,\n\n* Multidriver transactions\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Add hydration and SPA support for `@primate/vue`\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.33, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our Discord server][discord].\n\nOtherwise, have a blast with the new version!\n\n[rcompat]: /blog/introducing-rcompat\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.32.0\n[Eta]: https://eta.js.org\n[Voby]: https://github.com/vobyjs/voby\n[discord]: https://discord.gg/RSg4NNwM4f\n[HTML frontend]: /modules/html\n[route migration script]: https://github.com/primate-run/primate/tree/master/docs/migrations/0.32/routes.sh\n","html":"

Today we're announcing the availability of the Primate 0.32 preview release.\nThis release introduces support for building native applications with Primate\nNative, adds two new frontends, Voby and Eta, and includes a host of other\nchanges.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of it.

\n\n

\n Building native applications\n \n \n \n \n \n \n \n

\n

Primate native allows you to package your existing project, as-is, into a\ndesktop application.

\n

Compilation is currently only supported using Bun. In the future, as runtimes\nmature their compilation capabilities, we will add support for Node and Deno.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

npm install @primate/native

\n\n

\n Configure\n \n \n \n \n \n \n \n

\n

Import and initialize the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import native from \"@primate/native\";\n\nexport default {\n  modules: [\n    native(),\n  ],\n};

By default, when the application is launched, it will access / (the route\nat routes/index.js. Change that by setting the start property during\nconfiguration.

\n\n
\n \n \n \n \n \n \n
\n\n
import native from \"@primate/native\";\n\nexport default {\n  modules: [\n    native({\n      start: \"/home\",\n    }),\n  ],\n};
\n

\n Compile\n \n \n \n \n \n \n \n

\n

To compile your project, make sure you have Bun installed, and then run

\n

bun --bun x primate build desktop

\n\n

\n Cross-compile\n \n \n \n \n \n \n \n

\n

Choosing the desktop target will detect your current operating system and use\nit as the compilation target. To cross-compile, specify the exact target.

\n

bun --bun x primate build linux-x64

\n

Currently available targets are linux-x64, windows-x64, darwin-x64 and\ndarwin-arm64.

\n\n

\n The future of Primate Native\n \n \n \n \n \n \n \n

\n

This release is only the first step towards bringing everything Primate has to\noffer to desktop applications. In the next releases, we plan to add support for\nadditional targets such as mobile devices, as well as provide means to package\napps (msi, dmg, deb, rpm and so on). We're also planning on adding\nhelper functions to detect a user's home as well as config directory. Feedback\nand feature requests are welcome.

\n\n

\n New supported frontend: Voby\n \n \n \n \n \n \n \n

\n

Voby is a high-performance framework with fine-grained signal-based\nreactivity for building rich applications.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

npm install @primate/voby

\n\n

\n Configure\n \n \n \n \n \n \n \n

\n

Import and initialize the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import voby from \"@primate/voby\";\n\nexport default {\n  modules: [\n    voby(),\n  ],\n};
\n

\n Use\n \n \n \n \n \n \n \n

\n

Create a Voby component in components.

\n\n
\n \n \n \n \n \n \n
\n\n
export default ({ posts, title }) => {\n  return <>\n    <h1>All posts</h1>\n    {posts.map(({ id, title}) => <h2><a href={`/post/view/${id}`}>{title}</a></h2>)}\n  </>;\n}

Serve it from a route.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nconst posts = [{\n  id: 1,\n  title: \"First post\",\n}];\n\nexport default {\n  get() {\n    return view(\"PostIndex.voby\", { posts });\n  },\n};

The rendered component will be accessible at http://localhost:6161/voby.

\n\n

\n New supported frontend: Eta\n \n \n \n \n \n \n \n

\n

Eta is a faster, more lightweight, and more configurable EJS alternative.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

npm install @primate/eta

\n\n

\n Configure\n \n \n \n \n \n \n \n

\n

Import and initialize the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import eta from \"@primate/eta\";\n\nexport default {\n  modules: [\n    eta(),\n  ],\n};

Create an Eta component in components.

\n\n
\n \n \n \n \n \n \n
\n\n
<h1>All posts</h1>\n<div>\n<% it.posts.forEach(function(post){ %>\n<h2><a href=\"/post/view/<%= post.id %>\"><%= post.title %></a></h2>\n<% }) %>\n</div>

Serve it from a route.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nconst posts = [{\n  id: 1,\n  title: \"First post\",\n}];\n\nexport default {\n  get() {\n    return view(\"post-index.eta\", { posts });\n  },\n};

The rendered component will be accessible at http://localhost:6161/eta.

\n\n

\n Quality of life improvements\n \n \n \n \n \n \n \n

\n \n

\n Migrating from 0.31\n \n \n \n \n \n \n \n

\n \n

\n HTML removed from core\n \n \n \n \n \n \n \n

\n

The HTML frontend has been moved from core into its own package,\n@primate/html. If you previously used HTML components, install\n@primate/html and load it in your configuration.

\n\n

\n Normalized names for database configuration\n \n \n \n \n \n \n \n

\n

All database drivers now use the same terminology to refer to the database to\nuse. Specifically, the JSON and SQLite drivers previously used the filename\nproperty in their configuration to denote the location of the database file.\nThis is now database across the board.

\n\n

\n Changed module imports\n \n \n \n \n \n \n \n

\n

All backend, frontend and store modules now reside within their own packages.

\n\n
\n \n \n \n \n \n \n
\n\n
// previously `import { typescript } from \"@primate/binding\";`\nimport typescript from \"@primate/typescript\";\n\n// previously `import { svelte } from \"@primate/frontend\";`\nimport svelte from \"@primate/svelte\";\n\n// previously `import { sqlite } from \"@primate/store\";`\nimport sqlite from \"@primate/sqlite\";
\n

\n Debarrelled imports for handlers\n \n \n \n \n \n \n \n

\n

Primate handlers now use paths instead of named exports.

\n\n
\n \n \n \n \n \n \n
\n\n
// previously `import { view } from \"primate\";`\nimport view from \"primate/handler/view\";\n\nexport default {\n  get() {\n    return view(\"index.svelte\");\n  },\n};

You can apply a route migration script your routes directory to convert\nall routes to the new format.

\n

Note that the script won't convert combined imports of the form\nimport { view, redirect } from "primate";.

\n\n

\n Changed I18N imports\n \n \n \n \n \n \n \n

\n

The translation and locale imports of I18N are now imported directly from the\nfrontend package.

\n\n
\n \n \n \n \n \n \n
\n\n
<script>\n  // previously `import t from \"@primate/i18n/svelte\";`\n  import t from \"@primate/svelte/i18n\";\n  // previously `import { locale } from \"@primate/i18n/svelte\";`\n  import locale from \"@primate/svelte/locale\";\n\n  let count = 0;\n</script>\n<h3>{$t(\"Counter\")}</h3>\n<div>\n<button on:click={() => { count = count - 1; }}>-</button>\n<button on:click={() => { count = count + 1; }}>+</button>\n{count}\n</div>\n<h3>{$t(\"Switch language\")}</h3>\n<div><a on:click={() => locale.set(\"en-US\")}>{$t(\"English\")}</a></div>\n<div><a on:click={() => locale.set(\"de-DE\")}>{$t(\"German\")}</a></div>
\n

\n Removed modules\n \n \n \n \n \n \n \n

\n

Remove any @primate/types imports and uses in your configuration file.

\n\n

\n Removed imports\n \n \n \n \n \n \n \n

\n

Don't import Response anymore, it is available in the global context of all\nruntimes.

\n

If you previously imported Status, import instead the individual statuses\nfrom @rcompat/http/Status.

\n\n
\n \n \n \n \n \n \n
\n\n
import Status from \"@rcompat/http/Status\";\n\nexport default {\n  get() {\n    return new Response(\"Hello, world!\", { status: Status.OK });\n  },\n};
\n

\n Removed Logger\n \n \n \n \n \n \n \n

\n

Remove any Logger imports. You can now set the log level when running\nPrimate:

\n

npx primate --loglevel=info

\n

The log levels stayed the same: info, warn and error.

\n\n

\n Use build.define instead of build.transform\n \n \n \n \n \n \n \n

\n

Previously, you could define a build.transform and build.mapper to specify\ntextual replacements during build-time. We now use esbuild's identifier\nreplacement.

\n\n
\n \n \n \n \n \n \n
\n\n
export default {\n  build: {\n    define: {\n      DEBUG: \"true\",\n      APP_NAME: \"'Primate'\",\n    },\n  },\n};

Note that this is an identifier replacement, so if you want the identifier\nDEBUG to be replaced with boolean true, you'd write DEBUG: "true", but if\nyou want the replacement to be a string, be sure to quote it properly:\nAPP_NAME: "'Primate'".

\n

According to esbuild, the expression\nwhich the identifier is mapped to can "either be a JSON object (null, boolean,\nnumber, string, array, or object) or a single identifier".

\n\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

Some of the things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.33, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our Discord server.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"building-native-applications","text":"Building native applications"},{"depth":3,"slug":"install","text":"Install"},{"depth":3,"slug":"configure","text":"Configure"},{"depth":3,"slug":"compile","text":"Compile"},{"depth":3,"slug":"cross-compile","text":"Cross-compile"},{"depth":2,"slug":"the-future-of-primate-native","text":"The future of Primate Native"},{"depth":2,"slug":"new-supported-frontend-voby","text":"New supported frontend: Voby"},{"depth":2,"slug":"install","text":"Install"},{"depth":2,"slug":"configure","text":"Configure"},{"depth":2,"slug":"use","text":"Use"},{"depth":2,"slug":"new-supported-frontend-eta","text":"New supported frontend: Eta"},{"depth":3,"slug":"install","text":"Install"},{"depth":3,"slug":"configure","text":"Configure"},{"depth":2,"slug":"quality-of-life-improvements","text":"Quality of life improvements"},{"depth":2,"slug":"migrating-from-0-31","text":"Migrating from 0.31"},{"depth":3,"slug":"html-removed-from-core","text":"HTML removed from core"},{"depth":3,"slug":"normalized-names-for-database-configuration","text":"Normalized names for database configuration"},{"depth":3,"slug":"changed-module-imports","text":"Changed module imports"},{"depth":3,"slug":"debarrelled-imports-for-handlers","text":"Debarrelled imports for handlers"},{"depth":3,"slug":"changed-i18n-imports","text":"Changed I18N imports"},{"depth":3,"slug":"removed-modules","text":"Removed modules"},{"depth":3,"slug":"removed-imports","text":"Removed imports"},{"depth":3,"slug":"removed-logger","text":"Removed Logger"},{"depth":3,"slug":"use-build-define-instead-of-build-transform","text":"Use build.define instead of build.transform"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.32: Primate Native, support for Voby and Eta frontends","epoch":1722815387000,"author":"terrablue"}},{"href":"release-031","md":"Today we're announcing the availability of the Primate 0.31 preview release.\nThis release switches to fast hot reload using esbuild, adds projections and\nsorting to stores, and uses [rcompat]'s new router, adding support for optional\nand rest path parameters.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of it.\n!!!\n\n## Fast hot reload\n\nPrimate now uses esbuild as its built-in bundler via [rcompat]. This bundler\nutilizes esbuild's fast hot reloading, replacing the previous mechanism which\nused Node's file watching and was neither reliable nor fast.\n\n## Support for projections / sorting in stores\n\nPrimate stores have gained additional capabilities in this release.\n\n### Projections\n\nIt is now possible to add a projection to `Store#find` using a second parameter.\n\n```js caption=routes/user-names.js\nexport default {\n get({ store: { User } }) {\n return User.find({}, [\"name\"]);\n },\n};\n```\n\nThis will show a JSON array of objects from the `user` collection with only the\n`name` field.\n\n### Sorting\n\nIt is now possible to influence the sorting order used in `Store#find` using a\nthird parameter.\n\n```js caption=routes/user-names-sorted.js\nexport default {\n get({ store: { User } }) {\n return User.find({}, [\"name\"], { sort: { name: \"asc\" } });\n },\n};\n```\n\nThis will show a JSON array of objects from the `user` collection with only the\n`name` field, sorted by `name` ascendingly.\n\n## New filesystem router\n\nThis release now supports the whole breadth of type parameters similar to\nNext or Svelte using [rcompat][rcompat]'s new filesystem router.\n\n### Optional path parameters\n\nOptional path parameters indicate a route which will be both matched with a\npath parameter and without it.\n\nDouble brackets in route filenames, as in `user/[[action]].js`, are equivalent\nto having two identical files, `user.js` and `user/[action].js`.\n\nOptional parameters may only appear at the end of a route path and you can\ncombine them with runtime types, like non-optional path parameters.\n\n### Rest path parameters\n\nRest path parameters are used to match subpaths at the end of a route path.\n\nBrackets starting with three dots, as in `user/[...action_tree].js`, indicate a\nrest parameter. Unlike normal parameters, rest parameters match `/` as well and\ncan be thus be used to construct subpaths. For example, in\n`https://github.com/primate-run/primate/tree/master/docs/guide`, `docs/guide`\nmay be considered a subpath.\n\nRest parameters may only appear at the end of a route path. They may also be\noptional, that is, matching with and without the parameter, by using two\nbrackets.\n\n## Quality of life improvements\n\n### HTMX integration improvements\n\n#### Passing in props\n\nThe HTMX handler now supports passing in props, in JavaScript template string\nstyle. Consider the following route.\n\n```js caption=routes/htmx.js\nimport view from \"primate/handler/view\";\n\nconst posts = [{\n id: 1,\n title: \"First post\",\n}];\n\nexport default {\n get() {\n return view(\"post-index.htmx\", { posts });\n },\n};\n```\n\nAnd the following HTMX component.\n\n```html caption=components/post-index.htmx\n

All posts

\n${posts.map(post => `\n

\n \n ${post.title}\n \n

\n`).join(\"\")}\n```\n\nWith that combination, a GET call to `/htmx` yields an HTMX-driven page with\nthe posts handed in from the route.\n\nThis prop support extends to Primate's built-in `html` handler in the same\nfashion.\n\n#### Partial rendering\n\nPrimate's `view` handler generally allows passing in `{ partial: true }` as part\nof the third options parameter, which indicates the view component file to be\nrendered should *not* be embedded within the default `app.html` but delivered in\nbare form. This is great in case you use JavaScript to replace just a part of\nthe page.\n\nWhen using HTMX's DOM manipulation verbs (e.g. `hx-get`, `hx-post`, etc.), HTMX\nsends an `hx-request` header with the request. The `view` handler now, in the\ncase of HTMX, checks whether this handler was sent along the request, and in\nsuch a case renders the component in partial mode.\n\n### RequestFacade#pass\n\nIf you're using Primate as a reverse proxy, you may now use the `pass` function\non the request facade to pass a request wholesale to another backend.\n\nYou can do this generally in the `handle` hook.\n\n```js caption=primate.config.js\nexport default {\n modules: {\n name: \"proxy\",\n handle(request, next) {\n // pass any requests whose path begins with /admin to another application\n // listening at port 6363\n if (request.url.pathname.beginsWith(\"admin\")) {\n return request.pass(\"http://localhost:6363\");\n }\n\n // continue execution in this app otherwise\n return next(request);\n },\n },\n};\n```\n\nOr specifically within a given route.\n\n```js caption=routes/pass.js\nexport default {\n get(request) {\n return request.pass(\"http://localhost:6363\");\n },\n};\n```\n\nThis passes only GET requests to `/pass` to another application at port 6363.\n\nPassing a request per route usually makes sense in combination with disabled\nbody parsing, which is now possible per route.\n\n### Disabling body parsing per route\n\nIn 0.30, we added the option to [disable body parsing][disable-body-parsing]\nfor the entire application. This release adds the option to do so per route.\n\n```js caption=routes/pass.js\nexport const body = {\n parse: false,\n};\n\nexport default {\n get(request) {\n return request.pass(\"http://localhost:6363\");\n },\n};\n```\n\nA local route `body.parse` export overrides the application-wide setting. This\nalso means you could disable body parsing globally and then enable it for a\nspecific route.\n\n## Migrating from 0.30\n\n### remove @primate/build\n\nPrimate now comes bundled with esbuild; remove any use of the deprecated\n`@primate/build` package; you also do not need to depend on `esbuild` yourself\nanymore.\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nSome of the things we plan to tackle in the upcoming weeks are,\n\n* Multidriver transactions\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Add hydration and SPA support for `@primate/vue`\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.32, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with the new version!\n\n[rcompat]: /blog/introducing-rcompat\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.31.0\n[disable-body-parsing]: /blog/release-030#disabling-body-parsing\n","html":"

Today we're announcing the availability of the Primate 0.31 preview release.\nThis release switches to fast hot reload using esbuild, adds projections and\nsorting to stores, and uses rcompat's new router, adding support for optional\nand rest path parameters.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of it.

\n\n

\n Fast hot reload\n \n \n \n \n \n \n \n

\n

Primate now uses esbuild as its built-in bundler via rcompat. This bundler\nutilizes esbuild's fast hot reloading, replacing the previous mechanism which\nused Node's file watching and was neither reliable nor fast.

\n\n

\n Support for projections / sorting in stores\n \n \n \n \n \n \n \n

\n

Primate stores have gained additional capabilities in this release.

\n\n

\n Projections\n \n \n \n \n \n \n \n

\n

It is now possible to add a projection to Store#find using a second parameter.

\n\n
\n \n \n \n \n \n \n
\n\n
export default {\n  get({ store: { User } }) {\n    return User.find({}, [\"name\"]);\n  },\n};

This will show a JSON array of objects from the user collection with only the\nname field.

\n\n

\n Sorting\n \n \n \n \n \n \n \n

\n

It is now possible to influence the sorting order used in Store#find using a\nthird parameter.

\n\n
\n \n \n \n \n \n \n
\n\n
export default {\n  get({ store: { User } }) {\n    return User.find({}, [\"name\"], { sort: { name: \"asc\" } });\n  },\n};

This will show a JSON array of objects from the user collection with only the\nname field, sorted by name ascendingly.

\n\n

\n New filesystem router\n \n \n \n \n \n \n \n

\n

This release now supports the whole breadth of type parameters similar to\nNext or Svelte using rcompat's new filesystem router.

\n\n

\n Optional path parameters\n \n \n \n \n \n \n \n

\n

Optional path parameters indicate a route which will be both matched with a\npath parameter and without it.

\n

Double brackets in route filenames, as in user/[[action]].js, are equivalent\nto having two identical files, user.js and user/[action].js.

\n

Optional parameters may only appear at the end of a route path and you can\ncombine them with runtime types, like non-optional path parameters.

\n\n

\n Rest path parameters\n \n \n \n \n \n \n \n

\n

Rest path parameters are used to match subpaths at the end of a route path.

\n

Brackets starting with three dots, as in user/[...action_tree].js, indicate a\nrest parameter. Unlike normal parameters, rest parameters match / as well and\ncan be thus be used to construct subpaths. For example, in\nhttps://github.com/primate-run/primate/tree/master/docs/guide, docs/guide\nmay be considered a subpath.

\n

Rest parameters may only appear at the end of a route path. They may also be\noptional, that is, matching with and without the parameter, by using two\nbrackets.

\n\n

\n Quality of life improvements\n \n \n \n \n \n \n \n

\n \n

\n HTMX integration improvements\n \n \n \n \n \n \n \n

\n \n

\n Passing in props\n \n \n \n \n \n \n \n

\n

The HTMX handler now supports passing in props, in JavaScript template string\nstyle. Consider the following route.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nconst posts = [{\n  id: 1,\n  title: \"First post\",\n}];\n\nexport default {\n  get() {\n    return view(\"post-index.htmx\", { posts });\n  },\n};

And the following HTMX component.

\n\n
\n \n \n \n \n \n \n
\n\n
<h1>All posts</h1>\n${posts.map(post => `\n  <h2>\n    <a hx-get=\"/post/${post.id}\" href=\"/post/${post.id}\">\n      ${post.title}\n    </a>\n  </h2>\n`).join(\"\")}

With that combination, a GET call to /htmx yields an HTMX-driven page with\nthe posts handed in from the route.

\n

This prop support extends to Primate's built-in html handler in the same\nfashion.

\n\n

\n Partial rendering\n \n \n \n \n \n \n \n

\n

Primate's view handler generally allows passing in { partial: true } as part\nof the third options parameter, which indicates the view component file to be\nrendered should not be embedded within the default app.html but delivered in\nbare form. This is great in case you use JavaScript to replace just a part of\nthe page.

\n

When using HTMX's DOM manipulation verbs (e.g. hx-get, hx-post, etc.), HTMX\nsends an hx-request header with the request. The view handler now, in the\ncase of HTMX, checks whether this handler was sent along the request, and in\nsuch a case renders the component in partial mode.

\n\n

\n RequestFacade#pass\n \n \n \n \n \n \n \n

\n

If you're using Primate as a reverse proxy, you may now use the pass function\non the request facade to pass a request wholesale to another backend.

\n

You can do this generally in the handle hook.

\n\n
\n \n \n \n \n \n \n
\n\n
export default {\n  modules: {\n    name: \"proxy\",\n    handle(request, next) {\n      // pass any requests whose path begins with /admin to another application\n      // listening at port 6363\n      if (request.url.pathname.beginsWith(\"admin\")) {\n        return request.pass(\"http://localhost:6363\");\n      }\n\n      // continue execution in this app otherwise\n      return next(request);\n    },\n  },\n};

Or specifically within a given route.

\n\n
\n \n \n \n \n \n \n
\n\n
export default {\n  get(request) {\n    return request.pass(\"http://localhost:6363\");\n  },\n};

This passes only GET requests to /pass to another application at port 6363.

\n

Passing a request per route usually makes sense in combination with disabled\nbody parsing, which is now possible per route.

\n\n

\n Disabling body parsing per route\n \n \n \n \n \n \n \n

\n

In 0.30, we added the option to disable body parsing\nfor the entire application. This release adds the option to do so per route.

\n\n
\n \n \n \n \n \n \n
\n\n
export const body = {\n  parse: false,\n};\n\nexport default {\n  get(request) {\n    return request.pass(\"http://localhost:6363\");\n  },\n};

A local route body.parse export overrides the application-wide setting. This\nalso means you could disable body parsing globally and then enable it for a\nspecific route.

\n\n

\n Migrating from 0.30\n \n \n \n \n \n \n \n

\n \n

\n remove @primate/build\n \n \n \n \n \n \n \n

\n

Primate now comes bundled with esbuild; remove any use of the deprecated\n@primate/build package; you also do not need to depend on esbuild yourself\nanymore.

\n\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

Some of the things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.32, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"fast-hot-reload","text":"Fast hot reload"},{"depth":2,"slug":"support-for-projections-sorting-in-stores","text":"Support for projections / sorting in stores"},{"depth":3,"slug":"projections","text":"Projections"},{"depth":3,"slug":"sorting","text":"Sorting"},{"depth":2,"slug":"new-filesystem-router","text":"New filesystem router"},{"depth":3,"slug":"optional-path-parameters","text":"Optional path parameters"},{"depth":3,"slug":"rest-path-parameters","text":"Rest path parameters"},{"depth":2,"slug":"quality-of-life-improvements","text":"Quality of life improvements"},{"depth":3,"slug":"htmx-integration-improvements","text":"HTMX integration improvements"},{"depth":4,"slug":"passing-in-props","text":"Passing in props"},{"depth":4,"slug":"partial-rendering","text":"Partial rendering"},{"depth":3,"slug":"requestfacade-pass","text":"RequestFacade#pass"},{"depth":3,"slug":"disabling-body-parsing-per-route","text":"Disabling body parsing per route"},{"depth":2,"slug":"migrating-from-0-30","text":"Migrating from 0.30"},{"depth":3,"slug":"remove-primate-build","text":"remove @primate/build"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.31: new hot reload, projections and sorting in stores, new router","epoch":1714841883000,"author":"terrablue"}},{"href":"introducing-rcompat","md":"Today we're officially introducing rcompat, a JavaScript interoperability\nand runtime compatibility layer for servers which has been doing most of the\nheavy lifting behind Primate and its multi-runtime support for a while now.\n\nrcompat is a unified interface for [Node](https://nodejs.org/),\n[Deno](https://deno.com/) and [Bun](https://bun.sh/). You can think of\nrcompat as a server-side counterpart to jQuery.\n\n## Native speed gains\n\nWhile tools like Bun strive to be fully compatible with Node's built-in modules\nand NPM, with Deno also moving in that direction, those backwards\ncompatibilities carry a lot of cruft and in the end, you're just using another\nruntime to run code written for Node, without taking advantage of the inherent\nspeed gains that modern APIs introduce. rcompat abstracts that away for you.\nYou write code once and, wherever possible, it will take advantage of *native*\nAPIs. This allows you not only to run the same code with different runtimes,\nbut also speed it up if you choose Bun or Deno over Node during production. You\ncan easily switch between the runtimes, testing stability vs. modern features,\nfinding the best runtime for a given app.\n\n## Forward compatibility\n\nrcompat offers forward compatibility in the sense that it can add support for\nnew runtimes as they emerge *even* on minor updates (as this isn't considered\nbreaking existing code), allowing you to run old code that was written with\nrcompat by newer runtimes. No other server-side interoperability layer for\nJavaScript offers this kind of flexibility.\n\n## Batteries included\n\nrcompat is designed with many submodules in mind, including `@rcompat/fs` for\nfilesystem operations, `@rcompat/http` for using a modern HTTP server working\nwith WHATWG `Request`/`Response` (which Node doesn't support; rcompat wraps\na Node request object into a WHATWG `Request` as it comes in),\n`@rcompat/invariant` for ensuring runtime invariants, `@rcompat/object` for\nobject transformations, and many more useful modules and abstractions.\n\nThe standard library is designed to accommodate modern development needs: for\nexample, `@rcompat/http` supports WebSockets (natively on Deno/Bun, and using\nNPM's `ws` on Node), while `@rcompat/fs/file` offers globbing, listing and\nmanipulation of files, similarly to Python's `pathlib`.\n\nFor example, to set up a server with rcompat, use `@rcompat/http/serve` -- the\nserver-side equivalent of `fetch`.\n\n```js\nimport serve from \"@rcompat/http/serve\";\n\nserve(request => new Response(\"Hi!\"), { host: \"localhost\", port: 6161 });\n```\n\nThis code runs successfully with either `node app.js` (if you set your\npackage.json to `{ \"type\": \"module\" }`; otherwise use `app.mjs`),\n`deno run -A app.js` or `bun --bun app.js`, taking advantage of native\noptimizations.\n\n## Another standard library?\n\nThe JavaScript ecosystem is replete with standard libraries. To take the\nexample of filesystem access, Node has at least three ways of accessing the\nfilesystem (sync, callbacks, promises), and then there's Deno's own filesystem\nAPIs, while Bun has its APIs too. Those all have their pros and cons, and if\nyou want to target all of them, you're going to have to write a lot of\nbranching code. rcompat is an abstraction over that, as it plays the role of\nboth a standard library *and* a runtime compatibility layer -- write once,\ntarget everything.\n\nFor example, here's how you can read a file and parse it as JSON.\n\n```js\nimport file from \"@rcompat/fs/file\";\n\nconsole.log(await file(\"./users.json\").json());\n```\n\nAgain, this code runs successfully on Node, Deno or Bun, taking advantage of\noptimizations native to every runtime.\n\n## Evolving standard — input needed\n\nrcompat has been quietly developed the last few months in conjunction with\nPrimate's development and is largely influenced by its needs. We'd like to\ninvite more participation by other projects / individuals in order to converge\non APIs that best serve everyone and are the most useful on a broad basis.\n\nTo illustrate this, Primate 0.31 will be using `@rcompat/fs/router`'s upcoming\n`Router` class, which is meant to be used by frameworks using\nfilesystem-routing (such as Primate, Next, SvelteKit, etc.) to resolve requests\nto routes. The design is aimed to be generic, but undoubtedly will be\ninfluenced by Primate's needs. External feedback will help keep it useful for\nother frameworks as well. Once `Router` is ready, we will also aim to upstream\nour ideas to Bun's native [FileSystemRouter][FileSystemRouter] class such that\nrcompat can delegate to it natively on Bun.\n\n## Participation\n\nYou are cordially invited to take part in rcompat's development at\nhttps://github.com/rcompat/rcompat. We will be setting up a documentation\nwebsite in the foreseeable future, and until then, the source code is the best\nform of documentation.\n\nrcompat is MIT licensed.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat. This channel is currently also used for rcompat's development,\nuntil the need for a separate channel arises.\n\n[irc]: https://web.libera.chat#primate\n[FileSystemRouter]: https://bun.sh/docs/api/file-system-router\n","html":"

Today we're officially introducing rcompat, a JavaScript interoperability\nand runtime compatibility layer for servers which has been doing most of the\nheavy lifting behind Primate and its multi-runtime support for a while now.

\n

rcompat is a unified interface for Node,\nDeno and Bun. You can think of\nrcompat as a server-side counterpart to jQuery.

\n\n

\n Native speed gains\n \n \n \n \n \n \n \n

\n

While tools like Bun strive to be fully compatible with Node's built-in modules\nand NPM, with Deno also moving in that direction, those backwards\ncompatibilities carry a lot of cruft and in the end, you're just using another\nruntime to run code written for Node, without taking advantage of the inherent\nspeed gains that modern APIs introduce. rcompat abstracts that away for you.\nYou write code once and, wherever possible, it will take advantage of native\nAPIs. This allows you not only to run the same code with different runtimes,\nbut also speed it up if you choose Bun or Deno over Node during production. You\ncan easily switch between the runtimes, testing stability vs. modern features,\nfinding the best runtime for a given app.

\n\n

\n Forward compatibility\n \n \n \n \n \n \n \n

\n

rcompat offers forward compatibility in the sense that it can add support for\nnew runtimes as they emerge even on minor updates (as this isn't considered\nbreaking existing code), allowing you to run old code that was written with\nrcompat by newer runtimes. No other server-side interoperability layer for\nJavaScript offers this kind of flexibility.

\n\n

\n Batteries included\n \n \n \n \n \n \n \n

\n

rcompat is designed with many submodules in mind, including @rcompat/fs for\nfilesystem operations, @rcompat/http for using a modern HTTP server working\nwith WHATWG Request/Response (which Node doesn't support; rcompat wraps\na Node request object into a WHATWG Request as it comes in),\n@rcompat/invariant for ensuring runtime invariants, @rcompat/object for\nobject transformations, and many more useful modules and abstractions.

\n

The standard library is designed to accommodate modern development needs: for\nexample, @rcompat/http supports WebSockets (natively on Deno/Bun, and using\nNPM's ws on Node), while @rcompat/fs/file offers globbing, listing and\nmanipulation of files, similarly to Python's pathlib.

\n

For example, to set up a server with rcompat, use @rcompat/http/serve -- the\nserver-side equivalent of fetch.

\n\n
\n \n \n \n \n \n \n
\n\n
import serve from \"@rcompat/http/serve\";\n\nserve(request => new Response(\"Hi!\"), { host: \"localhost\", port: 6161 });

This code runs successfully with either node app.js (if you set your\npackage.json to { "type": "module" }; otherwise use app.mjs),\ndeno run -A app.js or bun --bun app.js, taking advantage of native\noptimizations.

\n\n

\n Another standard library?\n \n \n \n \n \n \n \n

\n

The JavaScript ecosystem is replete with standard libraries. To take the\nexample of filesystem access, Node has at least three ways of accessing the\nfilesystem (sync, callbacks, promises), and then there's Deno's own filesystem\nAPIs, while Bun has its APIs too. Those all have their pros and cons, and if\nyou want to target all of them, you're going to have to write a lot of\nbranching code. rcompat is an abstraction over that, as it plays the role of\nboth a standard library and a runtime compatibility layer -- write once,\ntarget everything.

\n

For example, here's how you can read a file and parse it as JSON.

\n\n
\n \n \n \n \n \n \n
\n\n
import file from \"@rcompat/fs/file\";\n\nconsole.log(await file(\"./users.json\").json());

Again, this code runs successfully on Node, Deno or Bun, taking advantage of\noptimizations native to every runtime.

\n\n

\n Evolving standard — input needed\n \n \n \n \n \n \n \n

\n

rcompat has been quietly developed the last few months in conjunction with\nPrimate's development and is largely influenced by its needs. We'd like to\ninvite more participation by other projects / individuals in order to converge\non APIs that best serve everyone and are the most useful on a broad basis.

\n

To illustrate this, Primate 0.31 will be using @rcompat/fs/router's upcoming\nRouter class, which is meant to be used by frameworks using\nfilesystem-routing (such as Primate, Next, SvelteKit, etc.) to resolve requests\nto routes. The design is aimed to be generic, but undoubtedly will be\ninfluenced by Primate's needs. External feedback will help keep it useful for\nother frameworks as well. Once Router is ready, we will also aim to upstream\nour ideas to Bun's native FileSystemRouter class such that\nrcompat can delegate to it natively on Bun.

\n\n

\n Participation\n \n \n \n \n \n \n \n

\n

You are cordially invited to take part in rcompat's development at\nhttps://github.com/rcompat/rcompat. We will be setting up a documentation\nwebsite in the foreseeable future, and until then, the source code is the best\nform of documentation.

\n

rcompat is MIT licensed.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat. This channel is currently also used for rcompat's development,\nuntil the need for a separate channel arises.

\n","toc":[{"depth":2,"slug":"native-speed-gains","text":"Native speed gains"},{"depth":2,"slug":"forward-compatibility","text":"Forward compatibility"},{"depth":2,"slug":"batteries-included","text":"Batteries included"},{"depth":2,"slug":"another-standard-library","text":"Another standard library?"},{"depth":2,"slug":"evolving-standard-input-needed","text":"Evolving standard — input needed"},{"depth":2,"slug":"participation","text":"Participation"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Introducing rcompat","epoch":1712966964000,"author":"terrablue"}},{"href":"release-030","md":"Today we're announcing the availability of the Primate 0.30 preview release.\nThis release introduces full Windows support and brings the path parameter\nstyle used by Primate's filesystem-based routes in line with other frameworks,\nin addition to several quality of life improvements.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of it.\n!!!\n\n## Windows support\n\nThis release introduces full support for running Primate on Windows, including\nWasm routes, data stores as well as frontends.\n\n## New path parameter style\n\nIn this release, Primate is switching from its original path parameter style,\nusing braces, to brackets.\n\nIf you had a path like `routes/user/{id}.js` before, you would now be using\n`routes/user/[id].js` in line with most other filesystem-based frameworks.\n\nTo illustrate, here are a few examples of paths in 0.30.\n\n* `index.js` is mapped to the root route (`/`)\n* `user.js` is mapped to `/user`\n* `user/[user_id].js` is mapped to a route with parameters, for example\n`/user/1` (but also `/user/donald`)\n* `user/[user_id=uuid].js` is mapped to a route where `user_id` is of the type\n`uuid`, for example `/user/f6a3fac2-7c1d-432d-9e1c-68d0db925adc` (but not\n`/user/1`)\n\n## Quality of life improvements\n\n### Default loose mode in stores\n\nThe default mode for `@primate/store` stores is now `loose`. This is similar to\nbefore with the addition that fields not explicitly declared in the store\ndefinition will be also saved. This is particulary useful for NoSQL databases\nthat do not have a rigid schema, in cases where you want to enforce types on\nsome fields and accept anything in others.\n\nTo make this applicable for SQL databases too, we will add a `catchall` type in\nthe future denoting a JSON column in which the data of any non-declared fields\nis saved and properly deserialized when retrieved.\n\nTo enforce strictness in stores globally, pass in `{ mode: \"strict\" }` when\nactivating the store module. To enforce strictness on store level, use\n`export const mode = \"strict\";` in the store file. To opt out of global\nstrictness per store, use `export const mode = \"loose\";`.\n\nIn the future, we will add the ability to mark fields as optional such that\nit's possible to enforce a strict mode with explicit exceptions for optional\n(nullable) fields.\n\n### Loose default CSP\n\nPreviously, Primate enforced a strict CSP policy. In this release, the defaults\nhave been changed to no CSP policy. If you create a policy directive for\n`script-src` or `style-src`, Primate will augment it with hashes for scripts\nand stylesheets.\n\n### Improved error handling of route return object\n\nStarting with this release, Primate will tell you if you forget to return data\nfrom your route or the body you return is invalid.\n\n```sh\n!! primate invalid body returned from route, got `undefined`\n++ return a proper body from route\n -> https://primate.run/guide/logging#invalid-body-returned\n```\n\n### Disabling body parsing\n\nYou can now tell Primate to not parse the body stream of the request, leaving\nit pristine, by setting `request.body.parse` to `false` in your configuration\nfile.\n\n```js caption=primate.config.js\nexport default {\n request: {\n body: {\n parse: false,\n },\n },\n};\n```\n\nThis is particularly useful if you're using Primate as a programmable reverse\nproxy with the `handle` hook and you want to pass the untouched request to\nanother application.\n\n```js caption=primate.config.js\nconst upstream = \"http://localhost:7171\";\n\nexport default {\n request: {\n body: {\n parse: false,\n },\n },\n modules: [{\n name: \"reverse-proxy\",\n handle(request) {\n const { method, headers, body } = request.original;\n const input = `${upstream}${request.url.pathname}`;\n\n return globalThis.fetch(input, { headers, method, body, duplex: \"half\" });\n },\n }],\n};\n```\n\n## Migrating from 0.29\n\n### update path parameters to new style\n\nChange any `{` to `[` and `}` to `]` in your path parameters.\n\nThe following script will change any JavaScript route files you have in your\n`routes` directory from the old to the new style.\n\n```sh\nfind -name \"*.js\" -exec rename -v \"{\" \"[\" {} \";\" &&\nfind -name \"*.js\" -exec rename -v \"}\" \"]\" {} \";\"\n```\n\nIf you used path parameters in any directory names, change them manually.\n\n### CSP policy\n\nIf you previously needed to add a CSP policy due to Primate's restrictive\ndefaults, you probably don't need to do that anymore in 0.30. Remove `http.csp`\nfrom `primate.config.js`.\n\nIf you do need a CSP policy, note that you now have to denote individual\nproperties as arrays.\n\n```js caption=primate.config.js\nexport default {\n http: {\n csp: {\n \"script-src\": [\"'self'\"],\n },\n },\n};\n```\n\nPrimate will add its own script and style hashes to the policy.\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nSome of the things we plan to tackle in the upcoming weeks are,\n\n* Add projections and relations to stores\n* Multidriver transactions\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Add hydration and SPA support for `@primate/vue`\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.31, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with the new version!\n\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.30.0\n","html":"

Today we're announcing the availability of the Primate 0.30 preview release.\nThis release introduces full Windows support and brings the path parameter\nstyle used by Primate's filesystem-based routes in line with other frameworks,\nin addition to several quality of life improvements.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of it.

\n\n

\n Windows support\n \n \n \n \n \n \n \n

\n

This release introduces full support for running Primate on Windows, including\nWasm routes, data stores as well as frontends.

\n\n

\n New path parameter style\n \n \n \n \n \n \n \n

\n

In this release, Primate is switching from its original path parameter style,\nusing braces, to brackets.

\n

If you had a path like routes/user/{id}.js before, you would now be using\nroutes/user/[id].js in line with most other filesystem-based frameworks.

\n

To illustrate, here are a few examples of paths in 0.30.

\n\n\n

\n Quality of life improvements\n \n \n \n \n \n \n \n

\n \n

\n Default loose mode in stores\n \n \n \n \n \n \n \n

\n

The default mode for @primate/store stores is now loose. This is similar to\nbefore with the addition that fields not explicitly declared in the store\ndefinition will be also saved. This is particulary useful for NoSQL databases\nthat do not have a rigid schema, in cases where you want to enforce types on\nsome fields and accept anything in others.

\n

To make this applicable for SQL databases too, we will add a catchall type in\nthe future denoting a JSON column in which the data of any non-declared fields\nis saved and properly deserialized when retrieved.

\n

To enforce strictness in stores globally, pass in { mode: "strict" } when\nactivating the store module. To enforce strictness on store level, use\nexport const mode = "strict"; in the store file. To opt out of global\nstrictness per store, use export const mode = "loose";.

\n

In the future, we will add the ability to mark fields as optional such that\nit's possible to enforce a strict mode with explicit exceptions for optional\n(nullable) fields.

\n\n

\n Loose default CSP\n \n \n \n \n \n \n \n

\n

Previously, Primate enforced a strict CSP policy. In this release, the defaults\nhave been changed to no CSP policy. If you create a policy directive for\nscript-src or style-src, Primate will augment it with hashes for scripts\nand stylesheets.

\n\n

\n Improved error handling of route return object\n \n \n \n \n \n \n \n

\n

Starting with this release, Primate will tell you if you forget to return data\nfrom your route or the body you return is invalid.

\n\n
\n \n \n \n \n \n \n
\n\n
!! primate invalid body returned from route, got `undefined`\n++ return a proper body from route\n   -> https://primate.run/guide/logging#invalid-body-returned
\n

\n Disabling body parsing\n \n \n \n \n \n \n \n

\n

You can now tell Primate to not parse the body stream of the request, leaving\nit pristine, by setting request.body.parse to false in your configuration\nfile.

\n\n
\n \n \n \n \n \n \n
\n\n
export default {\n  request: {\n    body: {\n      parse: false,\n    },\n  },\n};

This is particularly useful if you're using Primate as a programmable reverse\nproxy with the handle hook and you want to pass the untouched request to\nanother application.

\n\n
\n \n \n \n \n \n \n
\n\n
const upstream = \"http://localhost:7171\";\n\nexport default {\n  request: {\n    body: {\n      parse: false,\n    },\n  },\n  modules: [{\n    name: \"reverse-proxy\",\n    handle(request) {\n      const { method, headers, body } = request.original;\n      const input = `${upstream}${request.url.pathname}`;\n\n      return globalThis.fetch(input, { headers, method, body, duplex: \"half\" });\n    },\n  }],\n};
\n

\n Migrating from 0.29\n \n \n \n \n \n \n \n

\n \n

\n update path parameters to new style\n \n \n \n \n \n \n \n

\n

Change any { to [ and } to ] in your path parameters.

\n

The following script will change any JavaScript route files you have in your\nroutes directory from the old to the new style.

\n\n
\n \n \n \n \n \n \n
\n\n
find -name \"*.js\" -exec rename -v \"{\" \"[\" {} \";\" &&\nfind -name \"*.js\" -exec rename -v \"}\" \"]\" {} \";\"

If you used path parameters in any directory names, change them manually.

\n\n

\n CSP policy\n \n \n \n \n \n \n \n

\n

If you previously needed to add a CSP policy due to Primate's restrictive\ndefaults, you probably don't need to do that anymore in 0.30. Remove http.csp\nfrom primate.config.js.

\n

If you do need a CSP policy, note that you now have to denote individual\nproperties as arrays.

\n\n
\n \n \n \n \n \n \n
\n\n
export default {\n  http: {\n    csp: {\n      \"script-src\": [\"'self'\"],\n    },\n  },\n};

Primate will add its own script and style hashes to the policy.

\n\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

Some of the things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.31, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"windows-support","text":"Windows support"},{"depth":2,"slug":"new-path-parameter-style","text":"New path parameter style"},{"depth":2,"slug":"quality-of-life-improvements","text":"Quality of life improvements"},{"depth":3,"slug":"default-loose-mode-in-stores","text":"Default loose mode in stores"},{"depth":3,"slug":"loose-default-csp","text":"Loose default CSP"},{"depth":3,"slug":"improved-error-handling-of-route-return-object","text":"Improved error handling of route return object"},{"depth":3,"slug":"disabling-body-parsing","text":"Disabling body parsing"},{"depth":2,"slug":"migrating-from-0-29","text":"Migrating from 0.29"},{"depth":3,"slug":"update-path-parameters-to-new-style","text":"update path parameters to new style"},{"depth":3,"slug":"csp-policy","text":"CSP policy"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.30: Windows support, new path parameter style and other improvements","epoch":1708854056000,"author":"terrablue"}},{"href":"release-029","md":"Today we're announcing the availability of the Primate 0.29 preview release.\nThis release introduces support for Angular and Marko on the frontend and MySQL\non the backend, as well as two new core handlers for WebSockets and Server-sent\nevents across all runtimes.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of it.\n!!!\n\n## Angular\n\nThis release adds Angular to the list of frameworks Primate supports. With\nAngular, Primate now supports all major frontend frameworks using a unified\nview rendering API on the server. This handler supports SSR and serves Angular\ncomponents with the `.component.ts` extension.\n\n### Install\n\nTo add support for Angular, install the `@primate/frontend` module and Angular\ndependencies.\n\n`npm install @primate/frontend @angular/{compiler,core,platform-browser,platform-server,ssr}@17 esbuild@0.20`\n\n### Configure\n\nImport and initialize the module in your configuration.\n\n```js caption=primate.config.js\nimport { angular } from \"@primate/frontend\";\n\nexport default {\n modules: [\n angular(),\n ],\n};\n```\n\n### Use\n\nCreate an Angular component in `components`.\n\n```angular-ts caption=components/post-index.component.ts\nimport { Component, Input } from \"@angular/core\";\nimport { CommonModule } from \"@angular/common\";\n\n@Component({\n selector: \"post-index\",\n imports: [ CommonModule ],\n standalone: true,\n template: `\n

All posts

\n
\n

\n \n {{post.title}}\n \n

\n
\n `,\n})\nexport default class PostIndex {\n @Input() posts = [];\n}\n```\n\nServe it from a route.\n\n```js caption=routes/angular.js\nimport view from \"primate/handler/view\";\n\nconst posts = [{\n id: 1,\n title: \"First post\",\n}];\n\nexport default {\n get() {\n return view(\"post-index.component.ts\", { posts });\n },\n};\n```\n\nThe rendered component will be accessible at http://localhost:6161/angular.\n\n## Marko\n\nThis release adds Marko to the list of frameworks Primate supports. This\nhandler supports SSR and serves Marko components with the `.marko` extension.\n\n### Install\n\n`npm install @primate/frontend @marko/{compiler,translator-default}@5`\n\n### Configure\n\nImport and initialize the module in your configuration.\n\n```js caption=primate.config.js\nimport { marko } from \"@primate/frontend\";\n\nexport default {\n modules: [\n marko(),\n ],\n};\n```\n\n### Use\n\nCreate a Marko component in `components`.\n\n```marko caption=components/post-index.marko\n

All posts

\n\n

\n \n ${post.title}\n \n

\n\n```\n\nServe it from a route.\n\n```js caption=routes/marko.js\nimport view from \"primate/handler/view\";\n\nconst posts = [{\n id: 1,\n title: \"First post\",\n}];\n\nexport default {\n get() {\n return view(\"post-index.marko\", { posts });\n },\n};\n```\n\nThe rendered component will be accessible at http://localhost:6161/marko.\n\n## MySQL database support\n\nThis release introduces support for MySQL using the `mysql2` driver. The MySQL\ndriver supports all of Primate's ORM operations as well as transactions and\nconnection pools. This module requires running a MySQL server either locally\nor remotely. Visit the MySQL website or consult your operating system's manuals\non how to install and run a server.\n\n### Install\n\n`npm install @primate/mysql`\n\n### Configure\n\nThe MySQL driver uses the `host` (default `\"localhost\"`), `port` (default\n`3306`) `database`, `username`, and `password` configuration properties.\n\n```js caption=primate.config.js\nimport store from \"@primate/store\";\nimport mysql from \"@primate/mysql\";\n\nexport default {\n modules: [\n store({\n // use the MySQL server at localhost:3306 and the \"app\" database\n driver: mysql({\n // if \"localhost\", can be omitted\n host: \"localhost\",\n // if 3306, can be omitted\n port: 3306,\n database: \"app\",\n username: \"username\",\n // can be omitted\n password: \"password\",\n }),\n }),\n ],\n};\n```\n\nOnce configured, this will be the default driver for all stores.\n\n## New handlers for WebSockets and Server-sent events\n\nWith WebSocket support having moved into [rcompat][rcompat], we deprecated the\n`@primate/ws` package in favor of inclusion into core as the `ws` handler. In\naddition, we added a new handler for Server-sent events, `sse`.\n\n### WebSocket handler\n\nYou can now upgrade any `GET` route to a WebSocket route with the `ws` handler.\n\n```js caption=routes/ws.js\nimport ws from \"primate/handler/ws\";\n\nexport default {\n get(request) {\n const limit = request.query.get(\"limit\") ?? 20;\n let n = 1;\n return ws({\n open(socket) {\n // connection opens\n },\n message(socket, message) {\n if (n > 0 && n < limit) {\n n++;\n socket.send(`You wrote ${payload}`);\n }\n },\n close(socket) {\n // connection closes\n },\n });\n },\n};\n```\n\nIn this example, we have a small chat which reflects back anything to the user\nup to a given number of messages, the default being 20.\n\n```html caption=components/chat.html\n\n\n
\n\n```\n\n### Server-sent events handler\n\nSimilarly to `ws`, you can use the `sse` handler to upgrade a `GET` request to\nstream out server-sent events to the client.\n\n```js caption=routes/sse.js\nimport sse from \"primate/handler/sse\";\n\nconst passed = start_time => Math.floor((Date.now() - start_time) / 1000);\n\nexport default {\n get() {\n let interval;\n let start_time = Date.now();\n\n return sse({\n open(source) {\n // connection opens\n interval = globalThis.setInterval(() => {\n source.send(\"passed\", passed(start_time));\n }, 5000);\n },\n close() {\n // connection closes\n globalThis.clearInterval(interval);\n },\n });\n },\n};\n```\n\nIn this example, we send a `passed` event to the client every 5 seconds,\nindicating how many seconds have passed since the connection was established.\nThe client subscribes to this event and prints it to the console.\n\n```html caption=components/sse-client.html\n\n```\n\nThis client is then served using another route.\n\n```js caption=routes/sse-client.js\nimport view from \"primate/handler/view\";\n\nexport default {\n get() {\n return view(\"sse-client.html\");\n },\n};\n```\n\n## Migrating from 0.28\n\n### remove @primate/ws\n\nYou no longer need to import and use `@primate/ws` in order to use WebSockets.\nUse the built-in `ws` handler instead.\n\n### remove @primate/liveview\n\nYou no longer need to import and use `@primate/liveview` for your application\nto use SPA browsing. If you wish to deactive SPA browsing, pass `{ spa: false }`\nto the frontend handler in your configuration file.\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nSome of the things we plan to tackle in the upcoming weeks are,\n\n* Add projections and relations to stores\n* Multidriver transactions\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Add hydration and liveview support for `@primate/vue`\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.30, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with the new version!\n\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.29.0\n[rcompat]: https://github.com/rcompat/rcompat\n","html":"

Today we're announcing the availability of the Primate 0.29 preview release.\nThis release introduces support for Angular and Marko on the frontend and MySQL\non the backend, as well as two new core handlers for WebSockets and Server-sent\nevents across all runtimes.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of it.

\n\n

\n Angular\n \n \n \n \n \n \n \n

\n

This release adds Angular to the list of frameworks Primate supports. With\nAngular, Primate now supports all major frontend frameworks using a unified\nview rendering API on the server. This handler supports SSR and serves Angular\ncomponents with the .component.ts extension.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

To add support for Angular, install the @primate/frontend module and Angular\ndependencies.

\n

npm install @primate/frontend @angular/{compiler,core,platform-browser,platform-server,ssr}@17 esbuild@0.20

\n\n

\n Configure\n \n \n \n \n \n \n \n

\n

Import and initialize the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import { angular } from \"@primate/frontend\";\n\nexport default {\n  modules: [\n    angular(),\n  ],\n};
\n

\n Use\n \n \n \n \n \n \n \n

\n

Create an Angular component in components.

\n\n
\n \n \n \n \n \n \n
\n\n
import { Component, Input } from \"@angular/core\";\nimport { CommonModule } from \"@angular/common\";\n\n@Component({\n  selector: \"post-index\",\n  imports: [ CommonModule ],\n  standalone: true,\n  template: `\n    <h1>All posts</h1>\n    <div *ngFor=\"let post of posts\">\n      <h2>\n        <a href=\"/post/view/{{post.id}}\">\n          {{post.title}}\n        </a>\n      </h2>\n    </div>\n  `,\n})\nexport default class PostIndex {\n  @Input() posts = [];\n}

Serve it from a route.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nconst posts = [{\n  id: 1,\n  title: \"First post\",\n}];\n\nexport default {\n  get() {\n    return view(\"post-index.component.ts\", { posts });\n  },\n};

The rendered component will be accessible at http://localhost:6161/angular.

\n\n

\n Marko\n \n \n \n \n \n \n \n

\n

This release adds Marko to the list of frameworks Primate supports. This\nhandler supports SSR and serves Marko components with the .marko extension.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

npm install @primate/frontend @marko/{compiler,translator-default}@5

\n\n

\n Configure\n \n \n \n \n \n \n \n

\n

Import and initialize the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import { marko } from \"@primate/frontend\";\n\nexport default {\n  modules: [\n    marko(),\n  ],\n};
\n

\n Use\n \n \n \n \n \n \n \n

\n

Create a Marko component in components.

\n\n
\n \n \n \n \n \n \n
\n\n
<h1>All posts</h1>\n<for|post| of=input.posts>\n  <h2>\n    <a href=\"/post/view/${post.id}\">\n      ${post.title}\n    </a>\n  </h2>\n</for>

Serve it from a route.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nconst posts = [{\n  id: 1,\n  title: \"First post\",\n}];\n\nexport default {\n  get() {\n    return view(\"post-index.marko\", { posts });\n  },\n};

The rendered component will be accessible at http://localhost:6161/marko.

\n\n

\n MySQL database support\n \n \n \n \n \n \n \n

\n

This release introduces support for MySQL using the mysql2 driver. The MySQL\ndriver supports all of Primate's ORM operations as well as transactions and\nconnection pools. This module requires running a MySQL server either locally\nor remotely. Visit the MySQL website or consult your operating system's manuals\non how to install and run a server.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

npm install @primate/mysql

\n\n

\n Configure\n \n \n \n \n \n \n \n

\n

The MySQL driver uses the host (default "localhost"), port (default\n3306) database, username, and password configuration properties.

\n\n
\n \n \n \n \n \n \n
\n\n
import store from \"@primate/store\";\nimport mysql from \"@primate/mysql\";\n\nexport default {\n  modules: [\n    store({\n      // use the MySQL server at localhost:3306 and the \"app\" database\n      driver: mysql({\n        // if \"localhost\", can be omitted\n        host: \"localhost\",\n        // if 3306, can be omitted\n        port: 3306,\n        database: \"app\",\n        username: \"username\",\n        // can be omitted\n        password: \"password\",\n      }),\n    }),\n  ],\n};

Once configured, this will be the default driver for all stores.

\n\n

\n New handlers for WebSockets and Server-sent events\n \n \n \n \n \n \n \n

\n

With WebSocket support having moved into rcompat, we deprecated the\n@primate/ws package in favor of inclusion into core as the ws handler. In\naddition, we added a new handler for Server-sent events, sse.

\n\n

\n WebSocket handler\n \n \n \n \n \n \n \n

\n

You can now upgrade any GET route to a WebSocket route with the ws handler.

\n\n
\n \n \n \n \n \n \n
\n\n
import ws from \"primate/handler/ws\";\n\nexport default {\n  get(request) {\n    const limit = request.query.get(\"limit\") ?? 20;\n    let n = 1;\n    return ws({\n      open(socket) {\n        // connection opens\n      },\n      message(socket, message) {\n        if (n > 0 && n < limit) {\n          n++;\n          socket.send(`You wrote ${payload}`);\n        }\n      },\n      close(socket) {\n        // connection closes\n      },\n    });\n  },\n};

In this example, we have a small chat which reflects back anything to the user\nup to a given number of messages, the default being 20.

\n\n
\n \n \n \n \n \n \n
\n\n
<script>\n  window.addEventListener(\"load\", () => {\n    // number of messages to reflect\n    const limit = 20;\n    const ws = new WebSocket(`ws://localhost:6161/chat?limit=${limit}`);\n\n    ws.addEventListener(\"open\", () => {\n      document.querySelector(\"#chat\").addEventListener(\"keypress\", event => {\n        if (event.key === \"Enter\") {\n          ws.send(event.target.value);\n          event.target.value = \"\";\n        }\n      });\n    });\n\n    ws.addEventListener(\"message\", message => {\n      const div = document.createElement(\"div\");\n      div.innerText = message.data;\n      document.querySelector(\"#box\").appendChild(div);\n    });\n  });\n</script>\n<style>\n.chatbox {\n  background-color: lightgrey;\n  width: 300px;\n  height: 300px;\n  overflow: auto;\n}\n</style>\n<div class=\"chatbox\" id=\"box\"></div>\n<input id=\"chat\" placeholder=\"Type to chat\" />
\n

\n Server-sent events handler\n \n \n \n \n \n \n \n

\n

Similarly to ws, you can use the sse handler to upgrade a GET request to\nstream out server-sent events to the client.

\n\n
\n \n \n \n \n \n \n
\n\n
import sse from \"primate/handler/sse\";\n\nconst passed = start_time => Math.floor((Date.now() - start_time) / 1000);\n\nexport default {\n  get() {\n    let interval;\n    let start_time = Date.now();\n\n    return sse({\n      open(source) {\n        // connection opens\n        interval = globalThis.setInterval(() => {\n          source.send(\"passed\", passed(start_time));\n        }, 5000);\n      },\n      close() {\n        // connection closes\n        globalThis.clearInterval(interval);\n      },\n    });\n  },\n};

In this example, we send a passed event to the client every 5 seconds,\nindicating how many seconds have passed since the connection was established.\nThe client subscribes to this event and prints it to the console.

\n\n
\n \n \n \n \n \n \n
\n\n
<script>\n  new EventSource(\"/sse\").addEventListener(\"passed\", event => {\n    console.log(`${JSON.parse(event.data)} seconds since connection opened`);\n  });\n</script>

This client is then served using another route.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nexport default {\n  get() {\n    return view(\"sse-client.html\");\n  },\n};
\n

\n Migrating from 0.28\n \n \n \n \n \n \n \n

\n \n

\n remove @primate/ws\n \n \n \n \n \n \n \n

\n

You no longer need to import and use @primate/ws in order to use WebSockets.\nUse the built-in ws handler instead.

\n\n

\n remove @primate/liveview\n \n \n \n \n \n \n \n

\n

You no longer need to import and use @primate/liveview for your application\nto use SPA browsing. If you wish to deactive SPA browsing, pass { spa: false }\nto the frontend handler in your configuration file.

\n\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

Some of the things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.30, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"angular","text":"Angular"},{"depth":3,"slug":"install","text":"Install"},{"depth":3,"slug":"configure","text":"Configure"},{"depth":3,"slug":"use","text":"Use"},{"depth":2,"slug":"marko","text":"Marko"},{"depth":3,"slug":"install","text":"Install"},{"depth":3,"slug":"configure","text":"Configure"},{"depth":3,"slug":"use","text":"Use"},{"depth":2,"slug":"mysql-database-support","text":"MySQL database support"},{"depth":3,"slug":"install","text":"Install"},{"depth":3,"slug":"configure","text":"Configure"},{"depth":2,"slug":"new-handlers-for-websockets-and-server-sent-events","text":"New handlers for WebSockets and Server-sent events"},{"depth":3,"slug":"websocket-handler","text":"WebSocket handler"},{"depth":3,"slug":"server-sent-events-handler","text":"Server-sent events handler"},{"depth":2,"slug":"migrating-from-0-28","text":"Migrating from 0.28"},{"depth":3,"slug":"remove-primate-ws","text":"remove @primate/ws"},{"depth":3,"slug":"remove-primate-liveview","text":"remove @primate/liveview"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.29: Angular and Marko, MySQL, WebSockets and Server-Sent Events","epoch":1707519387000,"author":"terrablue"}},{"href":"release-028","md":"Today we're announcing the availability of the Primate 0.28 preview release.\nThis release introduces support for TypeScript and Ruby routes, a convenience\nwrapper for Web Components, as well as support for uploading files.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of it.\n!!!\n\n## TypeScript routes\n\nTypeScript now joins our ever-growing list of supported backend languages.\nWhile work on adding type definitions to Primate is ongoing, you can already\ncreate and use TypeScript routes.\n\n### Install\n\nTo add support for TypeScript, install the `@primate/typescript` module.\n\n`npm install @primate/typescript`\n\n### Configure\n\nImport and initialize the module in your configuration.\n\n```js caption=primate.config.js\nimport typescript from \"@primate/typescript\";\n\nexport default {\n modules: [\n typescript(),\n ],\n};\n```\n\n### Use\n\nWhen writing routes, you can do everything you can do in JavaScript routes, in\nTypeScript. To have your editor properly type your route handlers, simply\nimport `Route` from Primate and add `satisfies Route` to your exported route\nobject.\n\n```ts caption=routes/plain-text.ts\nimport { Route } from \"primate\";\n\nexport default {\n get() {\n return \"Donald\";\n },\n} satisfies Route;\n```\n\n!!!\nUsing `satisfies Route` is optional and will only result in your editor showing\nyou proper completions.\n!!!\n\n## Ruby routes\n\nFollowing up on our introduction of Python Wasm routes in 0.27, this release\nextends the number of backend languages we support through Wasm to three by\nadding Ruby support, adding up to a total of five supported backend languages.\nUnder the hood, we make use of the `ruby.wasm` project through WASI.\n\n### Install\n\nTo add support for Ruby, install the `@primate/ruby` package.\n\n`npm install @primate/ruby`\n\n## Configure\n\nImport and initialize the module in your configuration.\n\n```js caption=primate.config.js\nimport ruby from \"@primate/ruby\";\n\nexport default {\n modules: [\n ruby(),\n ],\n};\n```\n\n### Use\n\nWhen writing routes, you can pretty much do everything you can do in JavaScript\nroutes, in Ruby. For example, if you return strings or hashes from your Ruby\nroute, Primate will serve them as content type `text/plain` and\n`application/json`, respectively.\n\n```rb caption=routes/index.rb\ndef get(request)\n \"Donald\"\nend\n\ndef post(request)\n { name: \"Donald\" }\nend\n```\n\nIf you send a GET request to `/`, you will see a plain text response of\n`\"Donald\"`. For POST, you'll see a JSON response with `{\"name\": \"Donald\"}`.\n\nIn addition, much like with JavaScript routes, you have access to a `request`\nobject that exposes the same properties as in JavaScript.\n\nFor example, if a GET request is sent to `/?name=Donald`, it could be served by\nthe following route, returning the value of the query string parameter `name`\nas plain text.\n\n```rb caption=routes/index.rb\ndef get(request)\n # on GET /?name=Donald -> responds with text/plain \"Donald\"\n request.query.get(\"name\")\nend\n```\n\nFor the full documentation of Ruby routes, see the\n[Ruby module documentation].\n\n### Future of WebAssembly in Primate\n\nOur Ruby support is the first backend to use WASI. With Go, Python and Ruby\nsupported in Primate through WebAssembly, we are working on supporting\nadditional languages in Primate and improving existing API compatibility to\nmatch that of JavaScript. As WASI matures and is supported by more environments,\nwe intend to move existing Wasm implementations to that.\n\nIf you have a particular language you wish to see supported in Primate routes,\nplease open an issue describing your use case.\n\n## Web Components convenience wrapper\n\nThis releases introduces support for a web components wrapper using the file\nextension `.webc`. Unlike other web component frameworks, this wrapper does not\nintroduce new syntax, but simply makes it easier for you to use web components\nin your application, and in particular to pass props into them.\n\n## Install\n\n`npm install @primate/webc`\n\n## Configure\n\nImport and initialize the module in your configuration.\n\n```js caption=primate.config.js\nimport webc from \"@primate/webc\";\n\nexport default {\n modules: [\n webc(),\n ],\n};\n```\n\n## Use\n\nTo create a web component, import `@primate/webc/Component` and create a\ndefault export of class extending it. Implement the `render` function property\nof that class, which returns a string representing the HTML code of this\ncomponent. Note that `Component` extends `HTMLElement`, and is thus a proper\nweb component for any purpose.\n\nYou can include subcomponents by importing and passing instances of them to the\nrendered string. Either explicitly call their `.toString()` method or use a\nstructure which reduces them to strings.\n\nYou can use the `mounted` function proper of a `Component` instance to attach\nevent handlers after the component has been added to the DOM.\n\nCreate an web component in `components`.\n\n```html caption=components/post-index.webc\n\n```\n\nAnd another component for displaying post links.\n\n```html caption=components/post-link.webc\n\n```\n\nCreate a route and serve the `post-index` component.\n\n```js caption=routes/webc.js\nimport view from \"primate/handler/view\";\n\nconst posts = [{\n id: 1,\n title: \"First post\",\n}];\n\nexport default {\n get() {\n return view(\"post-index.webc\",\n { posts });\n },\n};\n```\n\nYour rendered web component will be accessible at http://localhost:6161/webc.\n\n### Outlook\n\nOur Web Components support is rapidly evolving, and we rely on feedback for\nprioritizing work on it. In particular, we plan to extend the wrapper by\noffering an `unmounted` property for cleanup before removal from the DOM, as\nwell as commonly used features in other frontends such as SSR and hydration.\n\n## Uploading files\n\nThis release introduces support for uploading files in HTML forms using\n`enctype=\"multipart/form-data\"`. Files uploaded this way will be available as\n`Blob` properties of the route handlers's `request.body`.\n\n```html caption=components/file-upload.html\n
\n

\n

\n \n

\n

\n

\n \n

\n

\n

\n \n

\n \n
\n```\n\nGiven the above form and the following route, `request.body` will contain three\nfields: `title` and `text`, both strings, and `attachment`, a blob.\n\n```js caption=routes/file-upload.js\nimport view from \"primate/handler/view\";\n\nexport default {\n get() {\n return view(\"file-upload.html\");\n },\n async post({ body }) {\n return body.attachment;\n },\n};\n```\n\nAs `Blob` instances are streamable in Primate, submitting the form would result\nin the uploaded file being sent back to the client as\n`application/octet-stream`.\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nSome of the things we plan to tackle in the upcoming weeks are,\n\n* Add projections and relations to stores\n* Multidriver transactions\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Add hydration and liveview support for `@primate/vue`\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.29, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with the new version!\n\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.28.0\n[Ruby module documentation]: /modules/ruby\n","html":"

Today we're announcing the availability of the Primate 0.28 preview release.\nThis release introduces support for TypeScript and Ruby routes, a convenience\nwrapper for Web Components, as well as support for uploading files.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of it.

\n\n

\n TypeScript routes\n \n \n \n \n \n \n \n

\n

TypeScript now joins our ever-growing list of supported backend languages.\nWhile work on adding type definitions to Primate is ongoing, you can already\ncreate and use TypeScript routes.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

To add support for TypeScript, install the @primate/typescript module.

\n

npm install @primate/typescript

\n\n

\n Configure\n \n \n \n \n \n \n \n

\n

Import and initialize the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import typescript from \"@primate/typescript\";\n\nexport default {\n  modules: [\n    typescript(),\n  ],\n};
\n

\n Use\n \n \n \n \n \n \n \n

\n

When writing routes, you can do everything you can do in JavaScript routes, in\nTypeScript. To have your editor properly type your route handlers, simply\nimport Route from Primate and add satisfies Route to your exported route\nobject.

\n\n
\n \n \n \n \n \n \n
\n\n
import { Route } from \"primate\";\n\nexport default {\n  get() {\n    return \"Donald\";\n  },\n} satisfies Route;

Using satisfies Route is optional and will only result in your editor showing\nyou proper completions.

\n\n

\n Ruby routes\n \n \n \n \n \n \n \n

\n

Following up on our introduction of Python Wasm routes in 0.27, this release\nextends the number of backend languages we support through Wasm to three by\nadding Ruby support, adding up to a total of five supported backend languages.\nUnder the hood, we make use of the ruby.wasm project through WASI.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

To add support for Ruby, install the @primate/ruby package.

\n

npm install @primate/ruby

\n\n

\n Configure\n \n \n \n \n \n \n \n

\n

Import and initialize the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import ruby from \"@primate/ruby\";\n\nexport default {\n  modules: [\n    ruby(),\n  ],\n};
\n

\n Use\n \n \n \n \n \n \n \n

\n

When writing routes, you can pretty much do everything you can do in JavaScript\nroutes, in Ruby. For example, if you return strings or hashes from your Ruby\nroute, Primate will serve them as content type text/plain and\napplication/json, respectively.

\n\n
\n \n \n \n \n \n \n
\n\n
def get(request)\n  \"Donald\"\nend\n\ndef post(request)\n  { name: \"Donald\" }\nend

If you send a GET request to /, you will see a plain text response of\n"Donald". For POST, you'll see a JSON response with {"name": "Donald"}.

\n

In addition, much like with JavaScript routes, you have access to a request\nobject that exposes the same properties as in JavaScript.

\n

For example, if a GET request is sent to /?name=Donald, it could be served by\nthe following route, returning the value of the query string parameter name\nas plain text.

\n\n
\n \n \n \n \n \n \n
\n\n
def get(request)\n  # on GET /?name=Donald -> responds with text/plain \"Donald\"\n  request.query.get(\"name\")\nend

For the full documentation of Ruby routes, see the\nRuby module documentation.

\n\n

\n Future of WebAssembly in Primate\n \n \n \n \n \n \n \n

\n

Our Ruby support is the first backend to use WASI. With Go, Python and Ruby\nsupported in Primate through WebAssembly, we are working on supporting\nadditional languages in Primate and improving existing API compatibility to\nmatch that of JavaScript. As WASI matures and is supported by more environments,\nwe intend to move existing Wasm implementations to that.

\n

If you have a particular language you wish to see supported in Primate routes,\nplease open an issue describing your use case.

\n\n

\n Web Components convenience wrapper\n \n \n \n \n \n \n \n

\n

This releases introduces support for a web components wrapper using the file\nextension .webc. Unlike other web component frameworks, this wrapper does not\nintroduce new syntax, but simply makes it easier for you to use web components\nin your application, and in particular to pass props into them.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

npm install @primate/webc

\n\n

\n Configure\n \n \n \n \n \n \n \n

\n

Import and initialize the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import webc from \"@primate/webc\";\n\nexport default {\n  modules: [\n    webc(),\n  ],\n};
\n

\n Use\n \n \n \n \n \n \n \n

\n

To create a web component, import @primate/webc/Component and create a\ndefault export of class extending it. Implement the render function property\nof that class, which returns a string representing the HTML code of this\ncomponent. Note that Component extends HTMLElement, and is thus a proper\nweb component for any purpose.

\n

You can include subcomponents by importing and passing instances of them to the\nrendered string. Either explicitly call their .toString() method or use a\nstructure which reduces them to strings.

\n

You can use the mounted function proper of a Component instance to attach\nevent handlers after the component has been added to the DOM.

\n

Create an web component in components.

\n\n
\n \n \n \n \n \n \n
\n\n
<script>\nimport Component from \"@primate/webc/Compontent\";\nimport PostLink from \"./post-link.webc\";\n\nexport default class extends Component {\n  mounted(root) {\n    root\n      .querySelector(\"h1\")\n      .addEventListener(\"click\",\n         _ => console.log(\"clicked!\"));\n  }\n\n  render() {\n    const { posts } = this.props;\n\n    return `<h1>All posts</h1>\n      ${posts\n        .map(post =>\n          new PostLink({ post }))\n        .join(\"\")\n      }`;\n  }\n}\n</script>

And another component for displaying post links.

\n\n
\n \n \n \n \n \n \n
\n\n
<script>\n  import Component from \"@primate/webc/Component\";\n\n  export default class extends Component {\n    render() {\n      const { post } = this.props;\n      return `<h2>\n        <a href=\"/post/view/${post.id}\">\n          ${post.title}\n        </a>\n      </h2>`;\n    }\n  }\n</script>

Create a route and serve the post-index component.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nconst posts = [{\n  id: 1,\n  title: \"First post\",\n}];\n\nexport default {\n  get() {\n    return view(\"post-index.webc\",\n      { posts });\n  },\n};

Your rendered web component will be accessible at http://localhost:6161/webc.

\n\n

\n Outlook\n \n \n \n \n \n \n \n

\n

Our Web Components support is rapidly evolving, and we rely on feedback for\nprioritizing work on it. In particular, we plan to extend the wrapper by\noffering an unmounted property for cleanup before removal from the DOM, as\nwell as commonly used features in other frontends such as SSR and hydration.

\n\n

\n Uploading files\n \n \n \n \n \n \n \n

\n

This release introduces support for uploading files in HTML forms using\nenctype="multipart/form-data". Files uploaded this way will be available as\nBlob properties of the route handlers's request.body.

\n\n
\n \n \n \n \n \n \n
\n\n
<form enctype=\"multipart/form-data\" action=\"post\">\n  <p>\n    <div><label>Title</label></div>\n    <input name=\"title\" />\n  </p>\n  <p>\n    <div><label>Text</label></div>\n    <textarea name=\"text\"></textarea>\n  </p>\n  <p>\n    <div><label>Attachment</label></div>\n    <input type=\"file\" name=\"attachment\" required />\n  </p>\n  <input type=\"submit\" value=\"Send form\" />\n</form>

Given the above form and the following route, request.body will contain three\nfields: title and text, both strings, and attachment, a blob.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nexport default {\n  get() {\n    return view(\"file-upload.html\");\n  },\n  async post({ body }) {\n     return body.attachment;\n  },\n};

As Blob instances are streamable in Primate, submitting the form would result\nin the uploaded file being sent back to the client as\napplication/octet-stream.

\n\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

Some of the things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.29, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"typescript-routes","text":"TypeScript routes"},{"depth":3,"slug":"install","text":"Install"},{"depth":3,"slug":"configure","text":"Configure"},{"depth":3,"slug":"use","text":"Use"},{"depth":2,"slug":"ruby-routes","text":"Ruby routes"},{"depth":3,"slug":"install","text":"Install"},{"depth":2,"slug":"configure","text":"Configure"},{"depth":3,"slug":"use","text":"Use"},{"depth":3,"slug":"future-of-webassembly-in-primate","text":"Future of WebAssembly in Primate"},{"depth":2,"slug":"web-components-convenience-wrapper","text":"Web Components convenience wrapper"},{"depth":2,"slug":"install","text":"Install"},{"depth":2,"slug":"configure","text":"Configure"},{"depth":2,"slug":"use","text":"Use"},{"depth":3,"slug":"outlook","text":"Outlook"},{"depth":2,"slug":"uploading-files","text":"Uploading files"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.28: TypeScript/Ruby routes, Web Components, uploading files","epoch":1706390221000,"author":"terrablue"}},{"href":"release-027","md":"Today we're announcing the availability of the Primate 0.27 preview release.\nThis release introduces support for Python routes and several quality of life\nimprovements.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of it.\n!!!\n\n## Python routes\n\nFollowing up on our introduction of Go Wasm routes, this release extends the\nnumber of backend languages we support to three by adding Python support. As\nWasm support hasn't been mainlined in Python yet, Primate uses `pyodide` under\nthe hood to support Python.\n\nPrimate has thus become the first platform to combine Python on the backend\nwith full support for a plethora of frontend frameworks such as Svelte, React\nor Solid -- including SSR, hydration, and client side rendering.\n\n### Install\n\nTo add support for Python, install the `@primate/python` package.\n\n`npm install @primate/python`\n\n### Configure\n\nImport and initialize the module in your configuration.\n\n```js caption=primate.config.js\nimport python from \"@primate/python\";\n\nexport default {\n modules: [\n python(),\n ],\n};\n```\n\n`pyodide` supports loading packages from PyPI. Add any packages (beyond the\nstandard library) you'd like to use to the `packages` configuration array of\nthe module.\n\n```js caption=primate.config.js\nimport python from \"@primate/python\";\n\nexport default {\n modules: [\n python({\n // then later in your Python route, use `import numpy`\n packages: [\"numpy\"],\n }),\n ],\n};\n```\n### Use\n\nWhen writing routes, you can pretty much do everything you can do in JavaScript\nroutes, in Python. For example, if you return strings or dictionaries from your\nPython route, Primate will serve them as content type `text/plain` and\n`application/json`, respectively.\n\n```py caption=routes/index.py\ndef get(request):\n return \"Donald\"\n\ndef post(request):\n return { \"name\": \"Donald\" }\n```\n\nIf you send a GET request to `/`, you will see a plain text response of\n`\"Donald\"`. For POST, you'll see a JSON response with `{\"name\": \"Donald\"}`.\n\nIn addition, much like with JavaScript routes, you have access to a `request`\nobject that exposes the same properties as in JavaScript.\n\nFor example, if a GET request is sent to `/?name=Donald`, it could be served by\nthe following route, returning the value of the query string parameter `name`\nas plain text.\n\n```py caption=routes/index.py\ndef get(request):\n # on GET /?name=Donald -> responds with text/plain \"Donald\"\n return request.query.get(\"name\")\n```\n\nFor the full documentation of Python routes, see the\n[Python module documentation].\n\n## Future of WebAssembly in Primate\n\nWith Go and Python Wasm supported in Primate, we are working on supporting\nadditional languages in Primate and improving existing API compatibility to\nmatch that of JavaScript. As WASI matures and is supported by more environments,\nwe intend to move on to that.\n\nIf you have a particular language you wish to see supported in Primate routes,\nplease open an issue describing your use case.\n\n## Quality of life improvements\n\nThis release introduces several quality of life improvements.\n\n### Dots in route names\n\nIt was previously impossible to use dots in the names of routes. This\nrestriction is now removed, allowing you to fake dynamic routes as static\nresources by creating route files for them.\n\n```js caption=routes/sitemap.xml.js\nexport default {\n // serve on GET /sitemap.xml\n get() {\n // dynamically serve a sitemap file\n },\n};\n```\n\n### Customs headers in view handler\n\nCombined with support for dots in route names, it is now possible to override\nthe default content type (`text/html`) or any other header when using the `view`\nhandler. This is particularly useful case you're using a template engine such\nas Handlebars to generate an XML file.\n\n```js caption=routes/sitemap.xml.js\nimport view from \"primate/handler/view\";\nimport { xml } from \"@rcompat/http/mime\";\n\n// this assumes you've imported and loaded the `handlebars` module from\n// `@primate/frontend`\nexport default {\n // serve on GET /sitemap.xml\n get() {\n // load data and save it in a variable `pages`\n // ...\n\n const headers = { \"Content-Type\": xml };\n\n // serve Handlebars template as XML\n return view(\"sitemap.hbs\", { pages }, { headers });\n },\n};\n```\n\n### Stop layouts from recursing upwards\n\nLayouts in Primate work such that content returned by layout files in inner\ndirectories is placed inside layouts higher in the routes directory hierarchy,\nrecursively\n\nYou may sometimes wish for this upwards recursion to halt at a particular\nlayout, and this is now achievable by using `export const recursive = false;`\nwithin the `+layout.js` file.\n\n```js caption=routes/inner/+layout.js\nimport view from \"primate/handler/view\" ;\n\nexport default () => {\n return view(\"inner-layout.svelte\");\n};\n\nexport const recursive = false;\n```\n\nIf a file `routes/+layout.js` exists, it will be bypassed for any routes using\nthe layout `routes/inner/+layout.js` (routes in `routes/inner` or below).\n\n### Async guards\n\nGuards can now be defined as async functions. This allows you to properly use\nasync operations such as those exposed by `request.store`.\n\n## Migrating from 0.26\n\n### request.body instead of request.body.all()\n\nAs of this release we differentiate between how `request.body` and\n`request.{path,query,cookies,headers}` behave. `request.body`'s properties\nare now accessed directly instead of with `request.body.all()` before.\n\nThis change also applies to the Go backend, where request.Body is now a\n`map[string]any`.\n\n### .all removed from dispatchers\n\nDispatchers (`request.{path,query,headers,cookies}`) no longer expose a `.all`\nmethod.\n\nThis change also applies to the Go backend, where `Dispatcher` no longer has an\n`All` function.\n\n### .all removed from request.session\n\n`request.session` no longer exposes a `.all` method.\n\nThis change also applies to the Go backend, where `Session` no longer has an\nan `All` function.\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nSome of the things we plan to tackle in the upcoming weeks are,\n\n* Add projections and relations to stores\n* Multidriver transactions\n* Introduce IDE TypeScript support\n* Add support for TypeScript (.ts) routes\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Add hydration and liveview support for `@primate/vue`\n* Support the `multipart/form-data` content type\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.28, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with the new version!\n\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[last release]: https://primate.run/blog/release-026\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.27.0\n[Python module documentation]: /modules/python\n[build]: /modules/build\n","html":"

Today we're announcing the availability of the Primate 0.27 preview release.\nThis release introduces support for Python routes and several quality of life\nimprovements.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of it.

\n\n

\n Python routes\n \n \n \n \n \n \n \n

\n

Following up on our introduction of Go Wasm routes, this release extends the\nnumber of backend languages we support to three by adding Python support. As\nWasm support hasn't been mainlined in Python yet, Primate uses pyodide under\nthe hood to support Python.

\n

Primate has thus become the first platform to combine Python on the backend\nwith full support for a plethora of frontend frameworks such as Svelte, React\nor Solid -- including SSR, hydration, and client side rendering.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

To add support for Python, install the @primate/python package.

\n

npm install @primate/python

\n\n

\n Configure\n \n \n \n \n \n \n \n

\n

Import and initialize the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import python from \"@primate/python\";\n\nexport default {\n  modules: [\n    python(),\n  ],\n};

pyodide supports loading packages from PyPI. Add any packages (beyond the\nstandard library) you'd like to use to the packages configuration array of\nthe module.

\n\n
\n \n \n \n \n \n \n
\n\n
import python from \"@primate/python\";\n\nexport default {\n  modules: [\n    python({\n      // then later in your Python route, use `import numpy`\n      packages: [\"numpy\"],\n    }),\n  ],\n};
\n

\n Use\n \n \n \n \n \n \n \n

\n

When writing routes, you can pretty much do everything you can do in JavaScript\nroutes, in Python. For example, if you return strings or dictionaries from your\nPython route, Primate will serve them as content type text/plain and\napplication/json, respectively.

\n\n
\n \n \n \n \n \n \n
\n\n
def get(request):\n    return \"Donald\"\n\ndef post(request):\n    return { \"name\": \"Donald\" }

If you send a GET request to /, you will see a plain text response of\n"Donald". For POST, you'll see a JSON response with {"name": "Donald"}.

\n

In addition, much like with JavaScript routes, you have access to a request\nobject that exposes the same properties as in JavaScript.

\n

For example, if a GET request is sent to /?name=Donald, it could be served by\nthe following route, returning the value of the query string parameter name\nas plain text.

\n\n
\n \n \n \n \n \n \n
\n\n
def get(request):\n  # on GET /?name=Donald -> responds with text/plain \"Donald\"\n  return request.query.get(\"name\")

For the full documentation of Python routes, see the\nPython module documentation.

\n\n

\n Future of WebAssembly in Primate\n \n \n \n \n \n \n \n

\n

With Go and Python Wasm supported in Primate, we are working on supporting\nadditional languages in Primate and improving existing API compatibility to\nmatch that of JavaScript. As WASI matures and is supported by more environments,\nwe intend to move on to that.

\n

If you have a particular language you wish to see supported in Primate routes,\nplease open an issue describing your use case.

\n\n

\n Quality of life improvements\n \n \n \n \n \n \n \n

\n

This release introduces several quality of life improvements.

\n\n

\n Dots in route names\n \n \n \n \n \n \n \n

\n

It was previously impossible to use dots in the names of routes. This\nrestriction is now removed, allowing you to fake dynamic routes as static\nresources by creating route files for them.

\n\n
\n \n \n \n \n \n \n
\n\n
export default {\n  // serve on GET /sitemap.xml\n  get() {\n    // dynamically serve a sitemap file\n  },\n};
\n

\n Customs headers in view handler\n \n \n \n \n \n \n \n

\n

Combined with support for dots in route names, it is now possible to override\nthe default content type (text/html) or any other header when using the view\nhandler. This is particularly useful case you're using a template engine such\nas Handlebars to generate an XML file.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\nimport { xml } from \"@rcompat/http/mime\";\n\n// this assumes you've imported and loaded the `handlebars` module from\n// `@primate/frontend`\nexport default {\n  // serve on GET /sitemap.xml\n  get() {\n    // load data and save it in a variable `pages`\n    // ...\n\n    const headers = { \"Content-Type\": xml };\n\n    // serve Handlebars template as XML\n    return view(\"sitemap.hbs\", { pages }, { headers });\n  },\n};
\n

\n Stop layouts from recursing upwards\n \n \n \n \n \n \n \n

\n

Layouts in Primate work such that content returned by layout files in inner\ndirectories is placed inside layouts higher in the routes directory hierarchy,\nrecursively

\n

You may sometimes wish for this upwards recursion to halt at a particular\nlayout, and this is now achievable by using export const recursive = false;\nwithin the +layout.js file.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\" ;\n\nexport default () => {\n  return view(\"inner-layout.svelte\");\n};\n\nexport const recursive = false;

If a file routes/+layout.js exists, it will be bypassed for any routes using\nthe layout routes/inner/+layout.js (routes in routes/inner or below).

\n\n

\n Async guards\n \n \n \n \n \n \n \n

\n

Guards can now be defined as async functions. This allows you to properly use\nasync operations such as those exposed by request.store.

\n\n

\n Migrating from 0.26\n \n \n \n \n \n \n \n

\n \n

\n request.body instead of request.body.all()\n \n \n \n \n \n \n \n

\n

As of this release we differentiate between how request.body and\nrequest.{path,query,cookies,headers} behave. request.body's properties\nare now accessed directly instead of with request.body.all() before.

\n

This change also applies to the Go backend, where request.Body is now a\nmap[string]any.

\n\n

\n .all removed from dispatchers\n \n \n \n \n \n \n \n

\n

Dispatchers (request.{path,query,headers,cookies}) no longer expose a .all\nmethod.

\n

This change also applies to the Go backend, where Dispatcher no longer has an\nAll function.

\n\n

\n .all removed from request.session\n \n \n \n \n \n \n \n

\n

request.session no longer exposes a .all method.

\n

This change also applies to the Go backend, where Session no longer has an\nan All function.

\n\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

Some of the things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.28, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"python-routes","text":"Python routes"},{"depth":3,"slug":"install","text":"Install"},{"depth":3,"slug":"configure","text":"Configure"},{"depth":3,"slug":"use","text":"Use"},{"depth":2,"slug":"future-of-webassembly-in-primate","text":"Future of WebAssembly in Primate"},{"depth":2,"slug":"quality-of-life-improvements","text":"Quality of life improvements"},{"depth":3,"slug":"dots-in-route-names","text":"Dots in route names"},{"depth":3,"slug":"customs-headers-in-view-handler","text":"Customs headers in view handler"},{"depth":3,"slug":"stop-layouts-from-recursing-upwards","text":"Stop layouts from recursing upwards"},{"depth":3,"slug":"async-guards","text":"Async guards"},{"depth":2,"slug":"migrating-from-0-26","text":"Migrating from 0.26"},{"depth":3,"slug":"request-body-instead-of-request-body-all","text":"request.body instead of request.body.all()"},{"depth":3,"slug":"all-removed-from-dispatchers","text":".all removed from dispatchers"},{"depth":3,"slug":"all-removed-from-request-session","text":".all removed from request.session"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.27: Python routes and quality of life improvements","epoch":1701017546000,"author":"terrablue"}},{"href":"release-026","md":"Today we're announcing the availability of the Primate 0.26 preview release.\nThis release introduces support for Go Wasm routes, live reload using the new\n`@primate/build` module, and HTMX extensions.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of the framework.\n!!!\n\n## Go Wasm routes\n\nPrimate is one of the first frameworks to offer writing backend logic in\ndifferent languages. In addition to its native support of JavaScript, you can\nnow write your route handlers in Go, using a nearly identical API. This gives\nyou access to Go's standard library, but also the entire Go ecosystem when\nwriting backend logic.\n\nWe believe this is an important step towards allowing more developers access to\nfrontend frameworks with all their features -- SSR, hydration, and client side\nrendering.\n\n### Install\n\nTo add support for Go, install the `@primate/go` module.\n\n`npm install @primate/go`\n\nIn addition, your system needs to have the `go` executable in its path, as it\nis used to compile Go routes into WebAssembly.\n\n### Configure\n\nImport and initialize the module in your configuration.\n\n```js caption=primate.config.js\nimport go from \"@primate/go\";\n\nexport default {\n modules: [\n go(),\n ],\n};\n```\n\n### Use\n\nWhen writing routes, you can pretty much do everything you can do in JavaScript\nroutes, in Go. For example, if you return strings or maps from your Go route,\nPrimate will serve them as content type `text/plain` and `application/json`,\nrespectively.\n\n```go caption=routes/index.go\nfunc Get(request Request) any {\n return \"Donald\";\n}\n\nfunc Post(request Request) any {\n return map[string]any{\n \"name\": \"Donald\",\n };\n}\n```\n\nIf you send a GET request to `/`, you will see a plain text response of\n`\"Donald\"`. For POST, you'll see a JSON response with `{\"name\": \"Donald\"}`.\n\nFor your convenience, Primate furnishes you with two types, `Object` which is\n`map[string]any` and `Array` which is `[]any`. If you're returning a JSON\ncontaining an array, you would write the following route.\n\n```go caption=routes/json-array.go\nfunc Get(request Request) any {\n return Array{\n Object{ \"name\": \"Donald\" },\n Object{ \"name\": \"Ryan\" },\n };\n}\n```\n\nAccessing GET at `/json-array` will return a JSON array with this data.\n\n```json\n[\n { \"name\": \"Donald\" },\n { \"name\": \"Ryan\" }\n]\n```\n\nIn addition, much like with JavaScript routes, you have access to a `Request`\nobject that exposes several properties.\n\n```go caption=Request struct\ntype Request struct {\n Url URL\n Body Dispatcher\n Path Dispatcher\n Query Dispatcher\n Cookies Dispatcher\n Headers Dispatcher\n // dynamic properties\n}\n```\n\nA `Dispatcher`, much like in JavaScript, allows you to query for subproperties\nof the field.\n\n```go caption=Dispatcher struct\ntype Dispatcher struct {\n Get func(string) any\n All func() map[string]any\n // dynamic runtime type getters\n}\n```\n\nFor example, if a GET request is sent to `/?name=Donald`, it could be served by\nthe following route, returning the value of the query string parameter `name`\nas plain text.\n\n```go caption=routes/index.go\nfunc Get(request Request) any {\n // on GET /?name=Donald -> responds with text/plain \"Donald\"\n return request.Query.Get(\"name\").(string);\n}\n```\n\nFor the full documentation of Go routes, see the [Go module documentation].\n\n\n## Future of WebAssembly in Primate\n\nWith Go Wasm supported in Primate, we are working on supporting additional\nprogramming languages in Primate and improving the Go API compatibility to match\nthat of JavaScript. As WASI becomes more mature and is supported by more\nprogramming languages and runtimes, we intend to move to that.\n\nIf you have a particular language you wish to see supported in Primate routes,\nplease open an issue describing your use case.\n\n## Live reload\n\nThis release features a rewritten `@primate/build` module, formerly named\n`@primate/esbuild`. The `esbuild` export of this module now bundles your\napplication both in development (`npx primate`) and production (`npx primate\nserve`) mode. In development mode, this module will insert a small client-side\nscript that reloads the browser whenever you edit and save a component.\n\nFor more information, see the `@primate/build` [documentation page][build].\n\n## HTMX extensions\n\nPrimate now supports HTMX extensions, using an ESM version of HTMX (`htmx-esm`\npackage) instead of the `htmx.org` package.\n\nTo use an HTMX extension, pass it to the HTMX module's `extensions` array\nproperty in your Primate configuration.\n\n```js primate.config.js\nimport htmx from \"@primate/htmx\";\n\nexport default {\n modules: [\n htmx({\n extensions: [\"head-support\"],\n }),\n ],\n};\n```\n\nIf you're using the `client-side-templates` extension, include the individual\nclient side templates in the `client_side_templates` array property.\n\n```js primate.config.js\nimport htmx from \"@primate/frontend/htmx\";\n\nexport default {\n modules: [\n htmx({\n extensions: [\"client-side-templates\", \"head-support\"],\n client_side_templates: [\"handlebars\", \"mustache\"],\n }),\n ],\n};\n```\n\n## Migrating from 0.25\n\n### Use all() instead of get()\n\nChange any uses of `request.{body, path, query, cookies, headers}.get` without\nparameters to `all`. Previously, the `get` function on a dispatcher could be\ncalled with a property, in which case it would retrieve that property's value\n(or `undefined`), or without a property, which would return the entire\nunderlying object.\n\nThe latter case has been now extracted from `get` into `all`. Calling\n`dispatcher.get` without a string argument in 0.26 will throw.\n\n### New: request.session.all()\n\nA `request.session` object is now similar to a dispatcher in that that it has\nboth `get` and `all`, to query individual session properties and the entire\nsession data. Like real dispatchers, `get` will throw if called without\narguments or with a non-string first argument.\n\n### Depend on htmx-esm instead of htmx.org\n\nAs HTMX extensions are now supported, HTMX had to be repackaged in an ESM\nfriendly way that allows bundling extensions. If you use the `@primate/frontend`\nmodule with HTMX, make sure to remove the `htmx.org` dependency and install\n`htmx-esm` instead. Primate will error out if you're missing `htmx-esm`.\n\n### Use @primate/build instead of @primate/esbuild\n\nThe `@primate/esbuild` package has been renamed `@primate/build`. If you\npreviously used `@primate/esbuild` with its default export, install\n`@primate/build` and change your configuration to use the `esbuild` export of\n`@primate/build`. Remove the old `@primate/esbuild` package.\n\n```js primate.config.js\n// old: import esbuild from \"@primate/esbuild\";\nimport { esbuild } from \"@primate/build\";\n\nexport default {\n modules: [\n esbuild(),\n ],\n};\n```\n\nAs `esbuild` has become a peer dependency, you will also now need to install\nit explicitly using `npm install esbuild`. Primate will error out if you're\nmissing it.\n\n### Convert type functions to type objects\n\nIn 0.26, it is no longer possible to export a runtime type as a function. All\nruntime types must instead export an object with `base` string and a `validate`\nfunction property. Change any runtime type functions to the new object format.\n\n```js types/uuid.js\nconst uuid = /^[^\\W_]{8}-[^\\W_]{4}-[^\\W_]{4}-[^\\W_]{4}-[^\\W_]{12}$/u;\n\n// before\n/*\nexport default value => {\n if (uuid.test(value)) {\n return value;\n }\n\n throw new Error(`${JSON.stringify(value)} is not a valid UUID`);\n};\n*/\n\n// now\nexport default {\n base: \"string\",\n validate(value) {\n if (uuid.test(value)) {\n return value;\n }\n\n throw new Error(`${JSON.stringify(value)} is not a valid UUID`);\n },\n};\n```\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nSome of the things we plan to tackle in the upcoming weeks are,\n\n* Add projections and relations to stores\n* Multidriver transactions\n* Introduce IDE TypeScript support\n* Add support for TypeScript (.ts) routes\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Add hydration and liveview support for `@primate/vue`\n* Support the `multipart/form-data` content type\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.27, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with the new version!\n\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[last release]: https://primate.run/blog/release-025\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.26.0\n[Go module documentation]: /modules/go\n[build]: /modules/build\n","html":"

Today we're announcing the availability of the Primate 0.26 preview release.\nThis release introduces support for Go Wasm routes, live reload using the new\n@primate/build module, and HTMX extensions.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of the framework.

\n\n

\n Go Wasm routes\n \n \n \n \n \n \n \n

\n

Primate is one of the first frameworks to offer writing backend logic in\ndifferent languages. In addition to its native support of JavaScript, you can\nnow write your route handlers in Go, using a nearly identical API. This gives\nyou access to Go's standard library, but also the entire Go ecosystem when\nwriting backend logic.

\n

We believe this is an important step towards allowing more developers access to\nfrontend frameworks with all their features -- SSR, hydration, and client side\nrendering.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

To add support for Go, install the @primate/go module.

\n

npm install @primate/go

\n

In addition, your system needs to have the go executable in its path, as it\nis used to compile Go routes into WebAssembly.

\n\n

\n Configure\n \n \n \n \n \n \n \n

\n

Import and initialize the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import go from \"@primate/go\";\n\nexport default {\n  modules: [\n    go(),\n  ],\n};
\n

\n Use\n \n \n \n \n \n \n \n

\n

When writing routes, you can pretty much do everything you can do in JavaScript\nroutes, in Go. For example, if you return strings or maps from your Go route,\nPrimate will serve them as content type text/plain and application/json,\nrespectively.

\n\n
\n \n \n \n \n \n \n
\n\n
func Get(request Request) any {\n  return \"Donald\";\n}\n\nfunc Post(request Request) any {\n  return map[string]any{\n    \"name\": \"Donald\",\n  };\n}

If you send a GET request to /, you will see a plain text response of\n"Donald". For POST, you'll see a JSON response with {"name": "Donald"}.

\n

For your convenience, Primate furnishes you with two types, Object which is\nmap[string]any and Array which is []any. If you're returning a JSON\ncontaining an array, you would write the following route.

\n\n
\n \n \n \n \n \n \n
\n\n
func Get(request Request) any {\n  return Array{\n    Object{ \"name\": \"Donald\" },\n    Object{ \"name\": \"Ryan\" },\n  };\n}

Accessing GET at /json-array will return a JSON array with this data.

\n\n
\n \n \n \n \n \n \n
\n\n
[\n  { \"name\": \"Donald\" },\n  { \"name\": \"Ryan\" }\n]

In addition, much like with JavaScript routes, you have access to a Request\nobject that exposes several properties.

\n\n
\n \n \n \n \n \n \n
\n\n
type Request struct {\n  Url URL\n  Body Dispatcher\n  Path Dispatcher\n  Query Dispatcher\n  Cookies Dispatcher\n  Headers Dispatcher\n  // dynamic properties\n}

A Dispatcher, much like in JavaScript, allows you to query for subproperties\nof the field.

\n\n
\n \n \n \n \n \n \n
\n\n
type Dispatcher struct {\n  Get func(string) any\n  All func() map[string]any\n  // dynamic runtime type getters\n}

For example, if a GET request is sent to /?name=Donald, it could be served by\nthe following route, returning the value of the query string parameter name\nas plain text.

\n\n
\n \n \n \n \n \n \n
\n\n
func Get(request Request) any {\n  // on GET /?name=Donald -> responds with text/plain \"Donald\"\n  return request.Query.Get(\"name\").(string);\n}

For the full documentation of Go routes, see the Go module documentation.

\n\n

\n Future of WebAssembly in Primate\n \n \n \n \n \n \n \n

\n

With Go Wasm supported in Primate, we are working on supporting additional\nprogramming languages in Primate and improving the Go API compatibility to match\nthat of JavaScript. As WASI becomes more mature and is supported by more\nprogramming languages and runtimes, we intend to move to that.

\n

If you have a particular language you wish to see supported in Primate routes,\nplease open an issue describing your use case.

\n\n

\n Live reload\n \n \n \n \n \n \n \n

\n

This release features a rewritten @primate/build module, formerly named\n@primate/esbuild. The esbuild export of this module now bundles your\napplication both in development (npx primate) and production (npx primate serve) mode. In development mode, this module will insert a small client-side\nscript that reloads the browser whenever you edit and save a component.

\n

For more information, see the @primate/build documentation page.

\n\n

\n HTMX extensions\n \n \n \n \n \n \n \n

\n

Primate now supports HTMX extensions, using an ESM version of HTMX (htmx-esm\npackage) instead of the htmx.org package.

\n

To use an HTMX extension, pass it to the HTMX module's extensions array\nproperty in your Primate configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import htmx from \"@primate/htmx\";\n\nexport default {\n  modules: [\n    htmx({\n      extensions: [\"head-support\"],\n    }),\n  ],\n};

If you're using the client-side-templates extension, include the individual\nclient side templates in the client_side_templates array property.

\n\n
\n \n \n \n \n \n \n
\n\n
import htmx from \"@primate/frontend/htmx\";\n\nexport default {\n  modules: [\n    htmx({\n      extensions: [\"client-side-templates\", \"head-support\"],\n      client_side_templates: [\"handlebars\", \"mustache\"],\n    }),\n  ],\n};
\n

\n Migrating from 0.25\n \n \n \n \n \n \n \n

\n \n

\n Use all() instead of get()\n \n \n \n \n \n \n \n

\n

Change any uses of request.{body, path, query, cookies, headers}.get without\nparameters to all. Previously, the get function on a dispatcher could be\ncalled with a property, in which case it would retrieve that property's value\n(or undefined), or without a property, which would return the entire\nunderlying object.

\n

The latter case has been now extracted from get into all. Calling\ndispatcher.get without a string argument in 0.26 will throw.

\n\n

\n New: request.session.all()\n \n \n \n \n \n \n \n

\n

A request.session object is now similar to a dispatcher in that that it has\nboth get and all, to query individual session properties and the entire\nsession data. Like real dispatchers, get will throw if called without\narguments or with a non-string first argument.

\n\n

\n Depend on htmx-esm instead of htmx.org\n \n \n \n \n \n \n \n

\n

As HTMX extensions are now supported, HTMX had to be repackaged in an ESM\nfriendly way that allows bundling extensions. If you use the @primate/frontend\nmodule with HTMX, make sure to remove the htmx.org dependency and install\nhtmx-esm instead. Primate will error out if you're missing htmx-esm.

\n\n

\n Use @primate/build instead of @primate/esbuild\n \n \n \n \n \n \n \n

\n

The @primate/esbuild package has been renamed @primate/build. If you\npreviously used @primate/esbuild with its default export, install\n@primate/build and change your configuration to use the esbuild export of\n@primate/build. Remove the old @primate/esbuild package.

\n\n
\n \n \n \n \n \n \n
\n\n
// old: import esbuild from \"@primate/esbuild\";\nimport { esbuild } from \"@primate/build\";\n\nexport default {\n  modules: [\n    esbuild(),\n  ],\n};

As esbuild has become a peer dependency, you will also now need to install\nit explicitly using npm install esbuild. Primate will error out if you're\nmissing it.

\n\n

\n Convert type functions to type objects\n \n \n \n \n \n \n \n

\n

In 0.26, it is no longer possible to export a runtime type as a function. All\nruntime types must instead export an object with base string and a validate\nfunction property. Change any runtime type functions to the new object format.

\n\n
\n \n \n \n \n \n \n
\n\n
const uuid = /^[^\\W_]{8}-[^\\W_]{4}-[^\\W_]{4}-[^\\W_]{4}-[^\\W_]{12}$/u;\n\n// before\n/*\nexport default value => {\n  if (uuid.test(value)) {\n    return value;\n  }\n\n  throw new Error(`${JSON.stringify(value)} is not a valid UUID`);\n};\n*/\n\n// now\nexport default {\n  base: \"string\",\n  validate(value) {\n    if (uuid.test(value)) {\n      return value;\n    }\n\n    throw new Error(`${JSON.stringify(value)} is not a valid UUID`);\n  },\n};
\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

Some of the things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.27, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"go-wasm-routes","text":"Go Wasm routes"},{"depth":3,"slug":"install","text":"Install"},{"depth":3,"slug":"configure","text":"Configure"},{"depth":3,"slug":"use","text":"Use"},{"depth":2,"slug":"future-of-webassembly-in-primate","text":"Future of WebAssembly in Primate"},{"depth":2,"slug":"live-reload","text":"Live reload"},{"depth":2,"slug":"htmx-extensions","text":"HTMX extensions"},{"depth":2,"slug":"migrating-from-0-25","text":"Migrating from 0.25"},{"depth":3,"slug":"use-all-instead-of-get","text":"Use all() instead of get()"},{"depth":3,"slug":"new-request-session-all","text":"New: request.session.all()"},{"depth":3,"slug":"depend-on-htmx-esm-instead-of-htmx-org","text":"Depend on htmx-esm instead of htmx.org"},{"depth":3,"slug":"use-primate-build-instead-of-primate-esbuild","text":"Use @primate/build instead of @primate/esbuild"},{"depth":3,"slug":"convert-type-functions-to-type-objects","text":"Convert type functions to type objects"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.26: Go Wasm routes, live reload, and HTMX extensions","epoch":1699820750000,"author":"terrablue"}},{"href":"release-025","md":"Today we're announcing the availability of the Primate 0.25 preview release.\nThis release introduces native Deno support, meaning Primate now supports all\nthe three significant runtimes in JS space (Node, Deno, Bun).\n\nIn addition, this release introduces `@primate/i18n`, an internationalization\nmodule with a unified API for our Svelte, React and Solid [frontend modules].\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of the framework.\n!!!\n\n## Native Deno support\n\nIn our [last release], we introduced native support for Bun, allowing you to\nrun Primate with `bun --bun x primate`, benefitting from significant speed\ngains introduced by Bun.\n\nThis release brings the number of runtimes we support up to three, adding native\nDeno support. Native means here that, like with Bun, Primate will try to use\nnative Deno routines (like `Deno.serve`) wherever possible and otherwise fall\nback to NPM.\n\nTo run your Primate app with Deno, use `deno run --allow-all npm:primate`.\n\n## I18N module for Svelte, React and Solid\n\nThis release introduces a new module, `@primate/i18n`, for handling\ninternationalization across the different frontend frameworks we support. At\nthis stage, this module supports Svelte, React and Solid using a nearly\ncompletely unified API.\n\nTo add support for multiple languages in your application, first install this\nmodule by issuing `npm install @primate/i18n`. Then, import and initialize it\nin your Primate configuration file.\n\n```js caption=primate.config.js\nimport i18n from \"@primate/i18n\";\n\nexport default {\n modules: [\n i18n(),\n ],\n};\n```\n\nTo add languages, create a locales directory `locales`. In this directory,\ncreate a JSON file for every locale you would like to support and add keys and\ntranslations.\n\n```json caption=locales/en-US.js\n{\n \"welcome\": \"Hi and welcome, {username}\",\n \"message\": \"This is my website, feel at home here.\",\n \"bye\": \"Bye\",\n \"switch-language\": \"Switch language\",\n \"english\": \"English\",\n \"german\": \"German\"\n}\n```\n\nAdd another locale.\n\n```json caption=locales/de-DE.js\n{\n \"welcome\": \"Hallo und willkommen, {username}\",\n \"message\": \"Das ist meine Website. Mache dich hier gemütlich.\",\n \"bye\": \"Tschüss\",\n \"switch-language\": \"Sprache wechseln\",\n \"english\": \"Englisch\",\n \"german\": \"Deutsch\"\n}\n```\n\nNext, import `@primate/svelte/i18n`, if you're a Svelte user.\n\n### Svelte\n\n```js caption=components/Home.svelte\n\n

{$t(\"welcome\", { username })}

\n\n

{$t(\"message\")}

\n\n{$t(\"bye\")}~\n```\n\nIn the case of Svelte, since the default export exposes a store, you need to\nsubscribe to it by prefixing it with ` wherever you use it.\n\nTo switch between locales, import `@primate/svelte/locale` and call\n`locale.set` with the new locale.\n\n```js caption=components/Home.svelte\n\n

{$t(\"welcome\", { username })}

\n\n

{$t(\"message\")}

\n\n{$t(\"bye\")}~\n\n

{$t(\"switch-language\")}

\n
locale.set(\"en-US\")}>{$t(\"English\")}
\n
locale.set(\"de-DE\")}>{$t(\"German\")}
\n```\n\n### React and Solid\n\nYou can use an almost identical API for React and Solid to achieve the same.\n\n```jsx caption=components/Home.jsx\nimport t from \"@primate/react/i18n\";\n// import t from \"@primate/solid/i18n\"; // for solid\n\nexport default function ({ username }) {\n return <>\n

{t(\"welcome\", { username })}

\n\n

{t(\"message\")}

\n\n {t(\"bye\")}~\n ;\n}\n```\n\nIn this case, since the default export exposes a function that returns a state\nvariable, you just use it as is (without prefixing it with ` as with Svelte).\n\nAgain, to switch between locales, call `locale.set` with the new locale.\n\n```jsx\nimport t from \"@primate/react/i18n\";\nimport locale from \"@primate/react/locale\";\n// for Solid\n// import t from \"@primate/solid/i18n\";\n// import locale from \"@primate/solid/locale\";\n\nexport default function ({ username }) {\n return <>\n

{t(\"welcome\", { username })}

\n\n

{t(\"message\")}

\n\n {t(\"bye\")}~\n\n

{t(\"switch-language\")}

\n
locale.set(\"en-US\")}>{t(\"English\")}
\n
locale.set(\"de-DE\")}>{t(\"German\")}
\n ;\n}\n```\n\nThis release marks only the introduction of the I18N module. In the future we\nplan to extend it with access to different translation backends and other\nfeatures commonly expected in a modern I18N library.\n\n## Executing guards just before the route handler\n\nThe only breaking feature of this release is a change to the order of execution\nfor guards. Previously guards were executed *after* a route was matched but\nbefore all route hook functions and the route handler itself were executed.\nOne of the most important route hook is the one used by `@primate/store` to\nstart a transaction before the route runs and make all\ntransactionalized data stores available to the route as `request.store`.\n\nThis meant that if you needed to access `request.store` in a guard (for\nexample, to check if a given API key is matched in the database), it would not\nbe available.\n\nThis release changes the order of execution such that guards are executed after\nall route hooks have finished running and right before the route handler\nitself is executed. Guards thus have access to exactly the same `request`\nobject that the route handler would see, including stores.\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nSome of the things we plan to tackle in the upcoming weeks are,\n\n* Add projections and relations to stores\n* Multidriver transactions\n* Introduce IDE TypeScript support\n* Add support for TypeScript (.ts) routes\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Add hydration and liveview support for `@primate/vue`\n* Support the `multipart/form-data` content type\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n* Add support for additional backends in I18N\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.26, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with the new version!\n\n[Getting started]: /guide/getting-started\n[frontend modules]: /modules/frontend\n[irc]: https://web.libera.chat#primate\n[last release]: https://primate.run/blog/release-024\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.25.0\n","html":"

Today we're announcing the availability of the Primate 0.25 preview release.\nThis release introduces native Deno support, meaning Primate now supports all\nthe three significant runtimes in JS space (Node, Deno, Bun).

\n

In addition, this release introduces @primate/i18n, an internationalization\nmodule with a unified API for our Svelte, React and Solid frontend modules.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of the framework.

\n\n

\n Native Deno support\n \n \n \n \n \n \n \n

\n

In our last release, we introduced native support for Bun, allowing you to\nrun Primate with bun --bun x primate, benefitting from significant speed\ngains introduced by Bun.

\n

This release brings the number of runtimes we support up to three, adding native\nDeno support. Native means here that, like with Bun, Primate will try to use\nnative Deno routines (like Deno.serve) wherever possible and otherwise fall\nback to NPM.

\n

To run your Primate app with Deno, use deno run --allow-all npm:primate.

\n\n

\n I18N module for Svelte, React and Solid\n \n \n \n \n \n \n \n

\n

This release introduces a new module, @primate/i18n, for handling\ninternationalization across the different frontend frameworks we support. At\nthis stage, this module supports Svelte, React and Solid using a nearly\ncompletely unified API.

\n

To add support for multiple languages in your application, first install this\nmodule by issuing npm install @primate/i18n. Then, import and initialize it\nin your Primate configuration file.

\n\n
\n \n \n \n \n \n \n
\n\n
import i18n from \"@primate/i18n\";\n\nexport default {\n  modules: [\n    i18n(),\n  ],\n};

To add languages, create a locales directory locales. In this directory,\ncreate a JSON file for every locale you would like to support and add keys and\ntranslations.

\n\n
\n \n \n \n \n \n \n
\n\n
{\n  \"welcome\": \"Hi and welcome, {username}\",\n  \"message\": \"This is my website, feel at home here.\",\n  \"bye\": \"Bye\",\n  \"switch-language\": \"Switch language\",\n  \"english\": \"English\",\n  \"german\": \"German\"\n}

Add another locale.

\n\n
\n \n \n \n \n \n \n
\n\n
{\n  \"welcome\": \"Hallo und willkommen, {username}\",\n  \"message\": \"Das ist meine Website. Mache dich hier gemütlich.\",\n  \"bye\": \"Tschüss\",\n  \"switch-language\": \"Sprache wechseln\",\n  \"english\": \"Englisch\",\n  \"german\": \"Deutsch\"\n}

Next, import @primate/svelte/i18n, if you're a Svelte user.

\n\n

\n Svelte\n \n \n \n \n \n \n \n

\n \n
\n \n \n \n \n \n \n
\n\n
<script>\n  import t from \"@primate/svelte/18n\";\n\n  export let username;\n</script>\n<h1>{$t(\"welcome\", { username })}</h1>\n\n<p>{$t(\"message\")}</p>\n\n{$t(\"bye\")}~

In the case of Svelte, since the default export exposes a store, you need to\nsubscribe to it by prefixing it with $ wherever you use it.

\n

To switch between locales, import @primate/svelte/locale and call\nlocale.set with the new locale.

\n\n
\n \n \n \n \n \n \n
\n\n
<script>\n  import t from \"@primate/svelte/i18n\";\n  import locale from \"@primate/svelte/locale\";\n\n  export let username;\n</script>\n<h1>{$t(\"welcome\", { username })}</h1>\n\n<p>{$t(\"message\")}</p>\n\n{$t(\"bye\")}~\n\n<h3>{$t(\"switch-language\")}</h3>\n<div><a on:click={() => locale.set(\"en-US\")}>{$t(\"English\")}</a></div>\n<div><a on:click={() => locale.set(\"de-DE\")}>{$t(\"German\")}</a></div>
\n

\n React and Solid\n \n \n \n \n \n \n \n

\n

You can use an almost identical API for React and Solid to achieve the same.

\n\n
\n \n \n \n \n \n \n
\n\n
import t from \"@primate/react/i18n\";\n// import t from \"@primate/solid/i18n\"; // for solid\n\nexport default function ({ username }) {\n  return <>\n    <h1>{t(\"welcome\", { username })}</h1>\n\n    <p>{t(\"message\")}</p>\n\n    {t(\"bye\")}~\n  </>;\n}

In this case, since the default export exposes a function that returns a state\nvariable, you just use it as is (without prefixing it with $ as with Svelte).

\n

Again, to switch between locales, call locale.set with the new locale.

\n\n
\n \n \n \n \n \n \n
\n\n
import t from \"@primate/react/i18n\";\nimport locale from \"@primate/react/locale\";\n// for Solid\n// import t from \"@primate/solid/i18n\";\n// import locale from \"@primate/solid/locale\";\n\nexport default function ({ username }) {\n  return <>\n    <h1>{t(\"welcome\", { username })}</h1>\n\n    <p>{t(\"message\")}</p>\n\n    {t(\"bye\")}~\n\n    <h3>{t(\"switch-language\")}</h3>\n    <div><a onClick={() => locale.set(\"en-US\")}>{t(\"English\")}</a></div>\n    <div><a onClick={() => locale.set(\"de-DE\")}>{t(\"German\")}</a></div>\n  </>;\n}

This release marks only the introduction of the I18N module. In the future we\nplan to extend it with access to different translation backends and other\nfeatures commonly expected in a modern I18N library.

\n\n

\n Executing guards just before the route handler\n \n \n \n \n \n \n \n

\n

The only breaking feature of this release is a change to the order of execution\nfor guards. Previously guards were executed after a route was matched but\nbefore all route hook functions and the route handler itself were executed.\nOne of the most important route hook is the one used by @primate/store to\nstart a transaction before the route runs and make all\ntransactionalized data stores available to the route as request.store.

\n

This meant that if you needed to access request.store in a guard (for\nexample, to check if a given API key is matched in the database), it would not\nbe available.

\n

This release changes the order of execution such that guards are executed after\nall route hooks have finished running and right before the route handler\nitself is executed. Guards thus have access to exactly the same request\nobject that the route handler would see, including stores.

\n\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

Some of the things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.26, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"native-deno-support","text":"Native Deno support"},{"depth":2,"slug":"i18n-module-for-svelte-react-and-solid","text":"I18N module for Svelte, React and Solid"},{"depth":3,"slug":"svelte","text":"Svelte"},{"depth":3,"slug":"react-and-solid","text":"React and Solid"},{"depth":2,"slug":"executing-guards-just-before-the-route-handler","text":"Executing guards just before the route handler"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.25: Native Deno support, I18N for Svelte/React/Solid","epoch":1698532413000,"author":"terrablue"}},{"href":"release-024","md":"Today we're announcing the availability of the Primate 0.24 preview release.\nThis release introduces native Bun support, enabling you to serve your\napplications significantly faster than before.\n\nBun is a new runtime based on JavaScriptCore and written in Zig. It has\nrecently had its first stable version, and has seen an uptick in interest ever\nsince.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of the framework.\n!!!\n\n## Native Bun support\n\nWhen Bun 1.0 came out, we were quick to test running Primate using Bun's NPM\ncompatibility. As it turned out, running `bun x primate` works as expected and\nruns a Primate app without issues.\n\nHowever, the real promise of Bun lies in its fast HTTP library implementation,\n`Bun.serve`. Bun's NPM compatibility is great, but to get real speed gains over\nNode, we have to use its native standard library.\n\nAs it turns out, Primate already uses a standard library of its own, `rcompat`.\nAs Primate is not the only project using it, we decided to implement native Bun\nsupport into it and thus have Primate and other projects benefit from it. This\nrelease uses the latest release of `rcompat`, which uses Bun natively wherever\npossible and falls back to Node/NPM otherwise.\n\nIn addition to the core package, we have also patched `@primate/store` to use\nBun's native `bun:sqlite` module.\n\n## Benchmarks\n\nTo illustrate the difference in serving Primate with native Bun, we created a\nthreeway benchmark: running Primate with NPM (`npx primate`), running it with\nBun's NPM compatibility (`bun x primate`), and running it natively with Bun\n(`bun --bun x primate`).\n\nUsing the `ab` testing tool, we tested two scenarios, serving plain-text\ncontent and an SSR-rendered Svelte page.\n\nOur command for benchmarking was `ab -c 350 -n 100000 [url]`. That is, send\n100000 requests with a concurrency of 350 requests at a time.\n\nThe app we used for testing was the [Primate template app][template-app].\nTo replicate the benchmarks, clone that repository and run the app.\n\nIn the following benchmarks, the quantities are:\n* **TT** Time taken for tests (seconds)\n* **RPS** Requests per second\n* **TPR** Time per request (milliseconds) (mean, across all concurrent requests)\n* **TR** Transfer rate, in Kbytes/second\n\n### Plain-text\n\nCommand used `ab -c 350 -n 100000 http://localhost:6161/benchmark`\n\n|Server |TT (s)|RPS |TPR (ms)|TR (Kbytes/s)\n|---------------------|------|-------|--------|------------|\n|`npx primate` |72.228|1384.50|0.722 |809.88 |\n|`bun x primate` |72.688|1375.74|0.727 |804.75 |\n|`bun --bun x primate`|18.488|5408.98|0.185 |3169.33 |\n\n![](/bun-benchmark-plain.png)\n\n### Svelte\n\nCommand used `ab -c 350 -n 100000 http://localhost:6161/benchmark/svelte`\n\n|Server |TT (s) |RPS |TPR (ms)|TR (Kbytes/s)\n|---------------------|-------|-------|--------|------------|\n|`npx primate` |117.646|850.01 |1.176 |2560.81 |\n|`bun x primate` |115.408|866.49 |1.154 |2610.48 |\n|`bun --bun x primate`|29.981 |3335.40|0.300 |10058.33 |\n\n![](/bun-benchmark-svelte.png)\n\n## Caveats\n\nIn upcoming releases, we'll continue to improve Primate's support for Bun,\nusing native modules and functions wherever possible. Currently, if you rely on\n`@primate/ws` for WebSockets in your application, you would have to stick to\nNPM. Bun's native WebSocket implementation is radically different from the `ws`\npackage which `@primate/ws` uses, and unifying them was deemed out of scope for\nthis release. Please reach out if you specifically need WebSockets in your\napplication so we can better prioritize our work.\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nSome of the things we plan to tackle in the upcoming weeks are,\n\n* Add projections and relations to stores\n* Multidriver transactions\n* Introduce IDE TypeScript support\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Extend `create-primate` with the ability to add new routes\n* Add hydration and liveview support for `@primate/vue`\n* Support the `multipart/form-data` content type\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.25, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with the new version!\n\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.24.0\n[template-app]: https://github.com/primate-run/app\n","html":"

Today we're announcing the availability of the Primate 0.24 preview release.\nThis release introduces native Bun support, enabling you to serve your\napplications significantly faster than before.

\n

Bun is a new runtime based on JavaScriptCore and written in Zig. It has\nrecently had its first stable version, and has seen an uptick in interest ever\nsince.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of the framework.

\n\n

\n Native Bun support\n \n \n \n \n \n \n \n

\n

When Bun 1.0 came out, we were quick to test running Primate using Bun's NPM\ncompatibility. As it turned out, running bun x primate works as expected and\nruns a Primate app without issues.

\n

However, the real promise of Bun lies in its fast HTTP library implementation,\nBun.serve. Bun's NPM compatibility is great, but to get real speed gains over\nNode, we have to use its native standard library.

\n

As it turns out, Primate already uses a standard library of its own, rcompat.\nAs Primate is not the only project using it, we decided to implement native Bun\nsupport into it and thus have Primate and other projects benefit from it. This\nrelease uses the latest release of rcompat, which uses Bun natively wherever\npossible and falls back to Node/NPM otherwise.

\n

In addition to the core package, we have also patched @primate/store to use\nBun's native bun:sqlite module.

\n\n

\n Benchmarks\n \n \n \n \n \n \n \n

\n

To illustrate the difference in serving Primate with native Bun, we created a\nthreeway benchmark: running Primate with NPM (npx primate), running it with\nBun's NPM compatibility (bun x primate), and running it natively with Bun\n(bun --bun x primate).

\n

Using the ab testing tool, we tested two scenarios, serving plain-text\ncontent and an SSR-rendered Svelte page.

\n

Our command for benchmarking was ab -c 350 -n 100000 [url]. That is, send\n100000 requests with a concurrency of 350 requests at a time.

\n

The app we used for testing was the Primate template app.\nTo replicate the benchmarks, clone that repository and run the app.

\n

In the following benchmarks, the quantities are:

\n\n\n

\n Plain-text\n \n \n \n \n \n \n \n

\n

Command used ab -c 350 -n 100000 http://localhost:6161/benchmark

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ServerTT (s)RPSTPR (ms)TR (Kbytes/s)
npx primate72.2281384.500.722809.88
bun x primate72.6881375.740.727804.75
bun --bun x primate18.4885408.980.1853169.33
\n

\"\"

\n\n

\n Svelte\n \n \n \n \n \n \n \n

\n

Command used ab -c 350 -n 100000 http://localhost:6161/benchmark/svelte

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ServerTT (s)RPSTPR (ms)TR (Kbytes/s)
npx primate117.646850.011.1762560.81
bun x primate115.408866.491.1542610.48
bun --bun x primate29.9813335.400.30010058.33
\n

\"\"

\n\n

\n Caveats\n \n \n \n \n \n \n \n

\n

In upcoming releases, we'll continue to improve Primate's support for Bun,\nusing native modules and functions wherever possible. Currently, if you rely on\n@primate/ws for WebSockets in your application, you would have to stick to\nNPM. Bun's native WebSocket implementation is radically different from the ws\npackage which @primate/ws uses, and unifying them was deemed out of scope for\nthis release. Please reach out if you specifically need WebSockets in your\napplication so we can better prioritize our work.

\n\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

Some of the things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.25, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"native-bun-support","text":"Native Bun support"},{"depth":2,"slug":"benchmarks","text":"Benchmarks"},{"depth":3,"slug":"plain-text","text":"Plain-text"},{"depth":3,"slug":"svelte","text":"Svelte"},{"depth":2,"slug":"caveats","text":"Caveats"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.24: Native Bun support","epoch":1695477283000,"author":"terrablue"}},{"href":"introducing-a-head-component","md":"Today we're introducing a `Head` component for React and Solid that mimics the\nbehavior of `` for Svelte.\n\nPrimate aims for feature parity across its supported frontend frameworks.\nSpecifically, Svelte has a feature that React and Solid lack, the ability to\nmanage the `` part of the HTML document individually from components.\nThis includes supporting client rendering, SSR, and extracting head tags from\nseveral components embedded in each other across the component hierarchy of a\npage, including layouts and imported components.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of the framework.\n!!!\n\n## Use\n\nIn a component of your choice, import `Head` from `@primate/react` and\nuse it anywhere within the component.\n\n```js caption=components/PostIndex.jsx\nimport Head from \"@primate/react/head\";\n\nexport default function (props) {\n return <>\n \n All posts ({props.posts.length})\n \n

All posts

\n {props.posts.map(({ id, title }) =>\n

{title}

\n )}\n

add post

\n ;\n}\n```\n\n!!!\nFor Solid, replace `@primate/react` with `@primate/solid`.\n!!!\n\nYou can also use `Head` in any layout. During SSR, a combined list of head\ntags will be generated and sent along with the page. Later during hydration,\nthe client components will take over management of their head tags.\n\nWhen you navigate between pages without a full reload, `Head` will manage its\nhead tags between page changes, automatically removing the tags used by the\nprevious page's components and inserting new ones. Tags in `pages/app.html`\nwon't be managed by `Head` and will be left intact.\n\n## Use outside of Primate\n\nAs `@primate/react` and `@primate/solid` exports `/Head` and have virtually no\ndependencies, you can use it even if you don't use Primate itself.\n\n### Without SSR\n\nIf you don't care for SSR, simply import `Head` and use it within your React\nor Solid components.\n\n### With SSR\n\nUnlike Svelte, both React and Solid compile a component entirely into a string.\nThat makes it difficult to extract any head parts that have been used in an\nindividual component down the hierarchy.\n\nTo extract the head part, we need to pass a function prop to `Head` that it can\nthen call with its children. This function prop then mutates a closure\nvariable.\n\nTo do so, we use contexts in both React and Solid. Contexts are a way for a\nparent component to create props that are accessible to all its children\ncomponents, and *their* children, down the tree. Our implementation, which\nyou would need to replicate if you want to support SSR, looks roughly as\nfollows.\n\nThis function mimics the signature of a Svelte component's `render` function.\n\n```js caption=server-render-react.js\nimport { renderToString } from \"react-dom/server\";\nimport { createElement } from \"react\";\n\nconst render = (component, props) => {\n const heads = [];\n const push_heads = sub_heads => {\n heads.push(...sub_heads);\n };\n const body = renderToString(createElement(component, {...props, push_heads}));\n const head = heads.join(\"\\n\");\n\n return {body, head};\n};\n```\n\nAnd the same for Solid.\n\n```js caption=server-render-solid.js\nimport { renderToString } from \"solid-js/web\";\n\nexport const render = (component, props) => {\n const heads = [];\n const push_heads = sub_heads => {\n heads.push(...sub_heads);\n };\n const body = renderToString(() => component({...props, push_heads}));\n const head = heads.join(\"\\n\");\n\n return {body, head};\n};\n```\n\nThe only thing left to do is wrap your root component with a context provider.\nIt is assumed that `body` here contains your component hierarchy.\n\n```js caption=root-component-react.jsx\nimport HeadContext from \"@primate/react/context/head\";\nimport runtime from \"@rcompat/runtime\";\n\nconst Provider = HeadContext.Provider;\n\nexport default ({ components, data, push_heads: value }) =>\n runtime === \"browser\" ? body : {body};\n```\n\nFor Solid, use `@primate/solid` instead for the import.\n\nWe use check, using `@rcompat/runtime` if we're on the client or the server.\nYou don't have to do this, but using the provider on the client doesn't make a\nlot of sense.\n\n## Fin\n\nWarm thanks to [ralyodio] for the idea and his incessant support for Primate.\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with `Head`!\n\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[ralyodio]: https://github.com/ralyodio\n","html":"

Today we're introducing a Head component for React and Solid that mimics the\nbehavior of <svelte:head> for Svelte.

\n

Primate aims for feature parity across its supported frontend frameworks.\nSpecifically, Svelte has a feature that React and Solid lack, the ability to\nmanage the <head> part of the HTML document individually from components.\nThis includes supporting client rendering, SSR, and extracting head tags from\nseveral components embedded in each other across the component hierarchy of a\npage, including layouts and imported components.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of the framework.

\n\n

\n Use\n \n \n \n \n \n \n \n

\n

In a component of your choice, import Head from @primate/react and\nuse it anywhere within the component.

\n\n
\n \n \n \n \n \n \n
\n\n
import Head from \"@primate/react/head\";\n\nexport default function (props) {\n  return <>\n    <Head>\n      <title>All posts ({props.posts.length})</title>\n    </Head>\n    <h1>All posts</h1>\n    {props.posts.map(({ id, title }) =>\n      <h2><a href={`/post/view/${id}`}>{title}</a></h2>\n    )}\n    <h3><a href=\"/post/edit/\">add post</a></h3>\n  </>;\n}

For Solid, replace @primate/react with @primate/solid.

\n

You can also use Head in any layout. During SSR, a combined list of head\ntags will be generated and sent along with the page. Later during hydration,\nthe client components will take over management of their head tags.

\n

When you navigate between pages without a full reload, Head will manage its\nhead tags between page changes, automatically removing the tags used by the\nprevious page's components and inserting new ones. Tags in pages/app.html\nwon't be managed by Head and will be left intact.

\n\n

\n Use outside of Primate\n \n \n \n \n \n \n \n

\n

As @primate/react and @primate/solid exports /Head and have virtually no\ndependencies, you can use it even if you don't use Primate itself.

\n\n

\n Without SSR\n \n \n \n \n \n \n \n

\n

If you don't care for SSR, simply import Head and use it within your React\nor Solid components.

\n\n

\n With SSR\n \n \n \n \n \n \n \n

\n

Unlike Svelte, both React and Solid compile a component entirely into a string.\nThat makes it difficult to extract any head parts that have been used in an\nindividual component down the hierarchy.

\n

To extract the head part, we need to pass a function prop to Head that it can\nthen call with its children. This function prop then mutates a closure\nvariable.

\n

To do so, we use contexts in both React and Solid. Contexts are a way for a\nparent component to create props that are accessible to all its children\ncomponents, and their children, down the tree. Our implementation, which\nyou would need to replicate if you want to support SSR, looks roughly as\nfollows.

\n

This function mimics the signature of a Svelte component's render function.

\n\n
\n \n \n \n \n \n \n
\n\n
import { renderToString } from \"react-dom/server\";\nimport { createElement } from \"react\";\n\nconst render = (component, props) => {\n  const heads = [];\n  const push_heads = sub_heads => {\n    heads.push(...sub_heads);\n  };\n  const body = renderToString(createElement(component, {...props, push_heads}));\n  const head = heads.join(\"\\n\");\n\n  return {body, head};\n};

And the same for Solid.

\n\n
\n \n \n \n \n \n \n
\n\n
import { renderToString } from \"solid-js/web\";\n\nexport const render = (component, props) => {\n  const heads = [];\n  const push_heads = sub_heads => {\n    heads.push(...sub_heads);\n  };\n  const body = renderToString(() => component({...props, push_heads}));\n  const head = heads.join(\"\\n\");\n\n  return {body, head};\n};

The only thing left to do is wrap your root component with a context provider.\nIt is assumed that body here contains your component hierarchy.

\n\n
\n \n \n \n \n \n \n
\n\n
import HeadContext from \"@primate/react/context/head\";\nimport runtime from \"@rcompat/runtime\";\n\nconst Provider = HeadContext.Provider;\n\nexport default ({ components, data, push_heads: value }) =>\n  runtime === \"browser\" ? body : <Provider value={value}>{body}</Provider>;

For Solid, use @primate/solid instead for the import.

\n

We use check, using @rcompat/runtime if we're on the client or the server.\nYou don't have to do this, but using the provider on the client doesn't make a\nlot of sense.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

Warm thanks to ralyodio for the idea and his incessant support for Primate.

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with Head!

\n","toc":[{"depth":2,"slug":"use","text":"Use"},{"depth":2,"slug":"use-outside-of-primate","text":"Use outside of Primate"},{"depth":3,"slug":"without-ssr","text":"Without SSR"},{"depth":3,"slug":"with-ssr","text":"With SSR"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Introducing a Head component","epoch":1695159400000,"author":"terrablue"}},{"href":"release-023","md":"Today we're announcing the availability of the Primate 0.23 preview release.\nThis release comes along with a new frontend handler, Handlebars, transactions\nacross most of our database drivers, bundler code splitting, as well as several\nquality of life improvements.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of the framework.\n!!!\n\n## Supporting Handlebars\n\nThis release adds support for a Handlebars frontend handler, including\nprecompiling.\n\nThe Handlebars handler works like all other frontend handlers. To activate it,\ninstall and load the module in your configuration.\n\n```js caption=primate.config.js\nimport handlebars from \"@primate/handlebars\";\n\nexport default {\n modules: [\n handlebars(),\n ],\n};\n```\n\nThen place a Handlebars file in your `components` directory.\n\n```hbs caption=components/post-index.hbs\n

All posts

\n
\n{{#each posts}}\n

{{this.title}}

\n{{/each}}\n
\n```\n\nLastly, serve your Handlebars component from a route of your choice.\n\n```js caption=routes/index.js\nimport view from \"primate/handler/view\";\n\nconst posts = [{\n id: 1,\n title: \"First post\",\n}];\n\nexport default {\n get() {\n return view(\"post-index.hbs\", { posts });\n },\n};\n```\n\nIf you then run Primate, your Handlebars component should be served at `GET /`\nas HTML.\n\n## Transactions across the board\n\nWith the exception of SurrealDB, all supported database drivers now support\ntransactions within routes. You don't need to explictly start a transaction or\ncommit it in the end; the store module does that for you automatically. If at\nany point during the route execution an error occurs, the transaction will be\nrolled back and no changes will be committed to the data store.\n\nConsider the following store.\n\n```js caption=stores/User.js\nimport primary from \"@primate/schema/primary\";\nimport string from \"@primate/schema/string\";\nimport u8 from \"@primate/schema/u8\";\nimport email from \"@primate/schema/email\";\nimport date from \"@primate/schema/date\";\n\nexport default {\n id: primary,\n name: string,\n age: u8,\n email,\n created: date,\n};\n```\n\nThe following route will try to insert a new User record into the database, but\nwith an `age` value that is larger than 2^8-1. Validation will fail, and the\nrecord won't be inserted.\n\n```js caption=routes/index.js\nimport view from \"primate/handler/view\";\n\nexport default {\n get(request) {\n const { User } = request.store;\n\n // count before\n const before = await User.count();\n\n // this works, as `age` is smaller than 255\n // user will contain a generated id and { age: 120 }\n const user = await User.insert({\n age: 120,\n });\n\n // after + 1 === before\n const after = await User.count();\n\n // at this stage, a new user has been inserted as part of this transaction\n // it's visible within this route, but not commited yet\n\n // u8 can only contain 0-255, validation will fail, throwing an error\n // as this is not handled explicitly here, the transaction will be rolled\n // back and the client redirected to an error page\n await User.insert({\n age: 256,\n });\n\n // the previous insert failed validation, this line will never be reached\n return \"user saved!\";\n },\n};\n```\n\n## Bundler code splitting\n\nThe esbuild bundler used in `@primate/esbuild` now automatically supports code\nsplitting. If you have a frontend component that dynamically imports another\nJavaScript file, esbuild will extract the dynamic import and fetch the\nfile during runtime once the code path is encountered. This is similar to the\nbehavior you would see without a bundler, where all files are kept separately.\n\n## Quality of life improvements\n\nThis release features several quality of life improvements.\n\n### Hoisted frontend props\n\nPreviously, all props passed from a route to its frontend component were\navailable under the `data` prop within the component. As of this release, the\nprops are made available directly to the frontend component.\n\nConsider this Svelte route.\n\n```js caption=routes/svelte.js\nimport view from \"primate/handler/view\";\n\nconst posts = [{\n id: 1,\n title: \"First post\",\n}];\n\nexport default {\n get() {\n return view(\"PostIndex.svelte\", {posts});\n },\n};\n```\n\nPreviously `posts` was a subproperty of the `data` prop.\n\n```svelte caption=components/PostIndex.svelte\n\n

All posts

\n{#each data.posts as {id, title}}\n

{title}

\n{/each}\n```\n\nNow, `posts` is directly exportable in the Svelte component.\n\n```svelte caption=components/PostIndex.svelte\n\n

All posts

\n{#each posts as {id, title}}\n

{title}

\n{/each}\n```\n\nThis change applies to all frontend handlers.\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nSome of the things we plan to tackle in the upcoming weeks are,\n\n* Add projections and relations to stores\n* Multidriver transactions\n* Introduce IDE TypeScript support\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Extend `create-primate` with the ability to add new routes\n* Add hydration and liveview support for `@primate/vue`\n* Support the `multipart/form-data` content type\n* Introduce a `Result` class as an alternative return value for store functions\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.24, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with the new version!\n\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.23.0\n","html":"

Today we're announcing the availability of the Primate 0.23 preview release.\nThis release comes along with a new frontend handler, Handlebars, transactions\nacross most of our database drivers, bundler code splitting, as well as several\nquality of life improvements.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of the framework.

\n\n

\n Supporting Handlebars\n \n \n \n \n \n \n \n

\n

This release adds support for a Handlebars frontend handler, including\nprecompiling.

\n

The Handlebars handler works like all other frontend handlers. To activate it,\ninstall and load the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import handlebars from \"@primate/handlebars\";\n\nexport default {\n  modules: [\n    handlebars(),\n  ],\n};

Then place a Handlebars file in your components directory.

\n\n
\n \n \n \n \n \n \n
\n\n
<h1>All posts</h1>\n<div>\n{{#each posts}}\n<h2><a href=\"hbs/post/view/{{this.id}}\">{{this.title}}</a></h2>\n{{/each}}\n</div>

Lastly, serve your Handlebars component from a route of your choice.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nconst posts = [{\n  id: 1,\n  title: \"First post\",\n}];\n\nexport default {\n  get() {\n    return view(\"post-index.hbs\", { posts });\n  },\n};

If you then run Primate, your Handlebars component should be served at GET /\nas HTML.

\n\n

\n Transactions across the board\n \n \n \n \n \n \n \n

\n

With the exception of SurrealDB, all supported database drivers now support\ntransactions within routes. You don't need to explictly start a transaction or\ncommit it in the end; the store module does that for you automatically. If at\nany point during the route execution an error occurs, the transaction will be\nrolled back and no changes will be committed to the data store.

\n

Consider the following store.

\n\n
\n \n \n \n \n \n \n
\n\n
import primary from \"@primate/schema/primary\";\nimport string from \"@primate/schema/string\";\nimport u8 from \"@primate/schema/u8\";\nimport email from \"@primate/schema/email\";\nimport date from \"@primate/schema/date\";\n\nexport default {\n  id: primary,\n  name: string,\n  age: u8,\n  email,\n  created: date,\n};

The following route will try to insert a new User record into the database, but\nwith an age value that is larger than 2^8-1. Validation will fail, and the\nrecord won't be inserted.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nexport default {\n  get(request) {\n    const { User } = request.store;\n\n    // count before\n    const before = await User.count();\n\n    // this works, as `age` is smaller than 255\n    // user will contain a generated id and { age: 120 }\n    const user = await User.insert({\n      age: 120,\n    });\n\n    // after + 1 === before\n    const after = await User.count();\n\n    // at this stage, a new user has been inserted as part of this transaction\n    // it's visible within this route, but not commited yet\n\n    // u8 can only contain 0-255, validation will fail, throwing an error\n    // as this is not handled explicitly here, the transaction will be rolled\n    // back and the client redirected to an error page\n    await User.insert({\n      age: 256,\n    });\n\n    // the previous insert failed validation, this line will never be reached\n    return \"user saved!\";\n  },\n};
\n

\n Bundler code splitting\n \n \n \n \n \n \n \n

\n

The esbuild bundler used in @primate/esbuild now automatically supports code\nsplitting. If you have a frontend component that dynamically imports another\nJavaScript file, esbuild will extract the dynamic import and fetch the\nfile during runtime once the code path is encountered. This is similar to the\nbehavior you would see without a bundler, where all files are kept separately.

\n\n

\n Quality of life improvements\n \n \n \n \n \n \n \n

\n

This release features several quality of life improvements.

\n\n

\n Hoisted frontend props\n \n \n \n \n \n \n \n

\n

Previously, all props passed from a route to its frontend component were\navailable under the data prop within the component. As of this release, the\nprops are made available directly to the frontend component.

\n

Consider this Svelte route.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nconst posts = [{\n  id: 1,\n  title: \"First post\",\n}];\n\nexport default {\n  get() {\n    return view(\"PostIndex.svelte\", {posts});\n  },\n};

Previously posts was a subproperty of the data prop.

\n\n
\n \n \n \n \n \n \n
\n\n
<script>\n  export let data;\n</script>\n<h1>All posts</h1>\n{#each data.posts as {id, title}}\n<h2><a href=\"/svelte/post/{id}\">{title}</a></h2>\n{/each}

Now, posts is directly exportable in the Svelte component.

\n\n
\n \n \n \n \n \n \n
\n\n
<script>\n  export let posts;\n</script>\n<h1>All posts</h1>\n{#each posts as {id, title}}\n<h2><a href=\"/svelte/post/{id}\">{title}</a></h2>\n{/each}

This change applies to all frontend handlers.

\n\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

Some of the things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.24, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"supporting-handlebars","text":"Supporting Handlebars"},{"depth":2,"slug":"transactions-across-the-board","text":"Transactions across the board"},{"depth":2,"slug":"bundler-code-splitting","text":"Bundler code splitting"},{"depth":2,"slug":"quality-of-life-improvements","text":"Quality of life improvements"},{"depth":3,"slug":"hoisted-frontend-props","text":"Hoisted frontend props"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.23: Handlebars support, transactions, bundler code splitting","epoch":1694873522000,"author":"terrablue"}},{"href":"release-022","md":"Today we're announcing the availability of the Primate 0.22 preview release.\nThis release comes along with 2 new frontend handlers (Solid, Markdown),\nfull React support and build transformations.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of the framework.\n!!!\n\n## New frontend handlers\n\nThis release adds 2 new frontend handlers, for Solid and Markdown. We've\nalready written separately about\n[Solid in our last post](/blog/supporting-solid).\n\nIn addition, support for Markdown (`.md`) files has been introduced with\n`@primate/markdown`. This frontend handler currently supports basic Markdown\nsyntax. In the future we plan to extend this by supporting Mermaid and other\nextended syntax, potentially via an extension system.\n\nThe Markdown handler works like all other frontend handlers. To activate it,\nload the module in your configuration.\n\n```js caption=primate.config.js\nimport markdown from \"@primate/markdown\";\n\nexport default {\n modules: [\n markdown(),\n ],\n};\n```\n\nThen place a Markdown file in your `components` directory.\n\n```md caption=components/about-us.md\n# About us\n\nOur company was formed in 2023 with the vision of supplying **unlimited**\nenergy to mankind.\n```\n\nLastly, serve your Markdown component from a route of your choice.\n\n```js caption=routes/about-us.js\nimport view from \"primate/handler/view\";\n\nexport default {\n get() {\n return view(\"about-us.md\");\n },\n};\n```\n\nIf you then run Primate, your Markdown component should be served at\n`GET /about-us` as HTML.\n\nUnlike other frontend handlers, the Markdown handler is not dynamic. Passing\nprops to it is meaningless. However, you can still use a different page with it\nby modifying the `page` property of the third (options) parameter.\n\n```js caption=routes/about-us.js\nimport view from \"primate/handler/view\";\n\nexport default {\n get() {\n return view(\"about-us.md\", {}, { page: \"alternative-page.html\" });\n },\n};\n```\n\nThe above route will serve the compiled Markdown component embedded into the\n`pages/alternative-page.html` HTML page.\n\nLike other frontend handlers, you can change the directory from which Markdown\ncomponents are loaded and the file extension associated with them by changing\nthe module configuration.\n\n```js caption=primate.config.js\nimport markdown from \"@primate/markdown\";\n\nexport default {\n modules: [\n markdown({\n // load Markdown files from $project_root$/content\n // default: `config.location.components`\n directory: \"content\",\n // using the \"markdown\" file extension\n // default: \"md\"\n extension: \"markdown\",\n }),\n ],\n};\n```\n\nIn addition, you can pass options to the underlying `marked` package used to\nconvert the Markdown files into HTML.\n\n```js caption=primate.config.js\nimport markdown from \"@primate/markdown\";\n\nexport default {\n modules: [\n markdown({\n options: {\n postprocess(html) {\n return html.replaceAll(/!!!\\n(.*?)\\n!!!/gus, (_, p1) =>\n `
${p1}
`);\n },\n },\n }),\n ],\n};\n```\n\nThe Markdown handler, when converting the component into HTML, also generates a\ntable of contents using the six Markdown heading types (which correspond to the\nh1-h6 HTML tags).\n\n```md caption=components/about-us.md\n# Heading 1\n\n## Heading 2\n\n### Heading 3\n\n## Another heading 2\n```\n\nGiven that Markdown, the following JSON file will be created.\n\n```json\n[\n {\n \"text\": \"Heading 1\",\n \"level\": 1,\n \"name\": \"heading-1\"\n },\n {\n \"text\": \"Heading 2\",\n \"level\": 2,\n \"name\": \"heading-2\"\n },\n {\n \"text\": \"Heading 3\",\n \"level\": 3,\n \"name\": \"heading-3\"\n },\n {\n \"text\": \"Another heading 2\",\n \"level\": 2,\n \"name\": \"another-heading-2\"\n },\n]\n```\n\nAt runtime, this JSON file will be written to `build/server/${directory}`\nalongside the compiled HTML file. To use this, you can override the default\nMarkdown handler function.\n\n```js caption=primate.config.js\nimport markdown from \"@primate/markdown\";\n\nexport default {\n modules: [\n markdown({\n handler: ({ content, toc }) => (app, ...rest) =>\n app.handlers.svelte(\"Markdown.svelte\", { content, toc })(app, ...rest),\n }),\n ],\n};\n```\n\nThis allows you to circumvent the default Markdown handler and hand over the\ncompiled HTML content and JSON table of contents to a dynamic component, in\nthis case Svelte, for embedding.\n\nIn addition to all of the above, you can also access the Markdown compilation\nfunction directly as an export of `@primate/markdown`. This allows you to\ncompile and serve Markdown content from a dynamic source (like a database).\n\n```js caption=routes/markdown/{page}.js\nimport view from \"primate/handler/view\";\nimport { compile } from \"@primate/markdown\";\n\nexport default {\n get(request) {\n const page = request.path.get(\"page\");\n\n // this assumes you have a data store called Markdown\n const [markdown] = await request.store.Markdown.find({ page });\n\n const { content, toc } = compile(markdown.text);\n\n return view(\"Markdown.svelte\", { content, toc });\n },\n};\n```\n\n!!!\nThe above example compiles the Markdown text on every request without any form\nof caching or checking for changes. Normally you would add a layer of\non-disk-caching in such a scenario.\n!!!\n\n## Full React support\n\nDuring this release cycle, we have moved to fully support the React handler,\nincluding hydration, layouts, and liveview. For more information see the\n[frontend frameworks](/modules/frontend) overview page.\n\nThis brings to 3 the number of frameworks we completely support: Svelte, React\nand Solid.\n\n## Build transformations\n\nWe've added a new configuration option, `build.transform`, having two\nproperties, `paths` and `mapper`. `paths` is a list of paths (glob patterns\nare supported) for which the transformation should apply, and `mapper` is a\nfunction that transforms the content of the files at the given paths. The\noutput of this function for each file is then copied onto the\n`config.location.build` directory.\n\nThis is useful if you have placeholders for environment variables that you want\nto replace in different files in your build.\n\n```js\nexport default {\n build: {\n transform: {\n\t paths: [\"components/**/*.svelte\"],\n mapper: content => content.replaceAll(\"%BASE%\", \"/\"),\n },\n },\n}\n```\n\nThe above code will replace every occurrence of `\"%BASE%\"` in every Svelte\ncomponent under `components` and its subdirectories with `\"/\"` at build time.\n\n!!!\nBe careful with injecting secrets into components: components are rendered both\non the server and on the client, which means any secret you map onto a\ncomponent will be exposed to every client and can be considered compromised.\n!!!\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nAfter an extensive 0.21, this was a relatively small release with a fair amount\nof internal changes in addition to the changes here listed.\n\nSome of the things we plan to tackle in the upcoming weeks are,\n\n* Add transactions to the PostgreSQL and MongoDB drivers\n* Introduce IDE TypeScript support\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Extend `create-primate` with the ability to add new routes\n* Add hydration and liveview support for `@primate/vue`\n* Support the `multipart/form-data` content type\n* Introduce a `Result` class as an alternative return value for store functions\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.23, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with the new version!\n\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.22.0\n","html":"

Today we're announcing the availability of the Primate 0.22 preview release.\nThis release comes along with 2 new frontend handlers (Solid, Markdown),\nfull React support and build transformations.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of the framework.

\n\n

\n New frontend handlers\n \n \n \n \n \n \n \n

\n

This release adds 2 new frontend handlers, for Solid and Markdown. We've\nalready written separately about\nSolid in our last post.

\n

In addition, support for Markdown (.md) files has been introduced with\n@primate/markdown. This frontend handler currently supports basic Markdown\nsyntax. In the future we plan to extend this by supporting Mermaid and other\nextended syntax, potentially via an extension system.

\n

The Markdown handler works like all other frontend handlers. To activate it,\nload the module in your configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import markdown from \"@primate/markdown\";\n\nexport default {\n  modules: [\n    markdown(),\n  ],\n};

Then place a Markdown file in your components directory.

\n\n
\n \n \n \n \n \n \n
\n\n
# About us\n\nOur company was formed in 2023 with the vision of supplying **unlimited**\nenergy to mankind.

Lastly, serve your Markdown component from a route of your choice.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nexport default {\n  get() {\n    return view(\"about-us.md\");\n  },\n};

If you then run Primate, your Markdown component should be served at\nGET /about-us as HTML.

\n

Unlike other frontend handlers, the Markdown handler is not dynamic. Passing\nprops to it is meaningless. However, you can still use a different page with it\nby modifying the page property of the third (options) parameter.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nexport default {\n  get() {\n    return view(\"about-us.md\", {}, { page: \"alternative-page.html\" });\n  },\n};

The above route will serve the compiled Markdown component embedded into the\npages/alternative-page.html HTML page.

\n

Like other frontend handlers, you can change the directory from which Markdown\ncomponents are loaded and the file extension associated with them by changing\nthe module configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import markdown from \"@primate/markdown\";\n\nexport default {\n  modules: [\n    markdown({\n      // load Markdown files from $project_root$/content\n      // default: `config.location.components`\n      directory: \"content\",\n      // using the \"markdown\" file extension\n      // default: \"md\"\n      extension: \"markdown\",\n    }),\n  ],\n};

In addition, you can pass options to the underlying marked package used to\nconvert the Markdown files into HTML.

\n\n
\n \n \n \n \n \n \n
\n\n
import markdown from \"@primate/markdown\";\n\nexport default {\n  modules: [\n    markdown({\n      options: {\n        postprocess(html) {\n          return html.replaceAll(/!!!\\n(.*?)\\n!!!/gus, (_, p1) =>\n            `<div class=\"box\">${p1}</div>`);\n        },\n      },\n    }),\n  ],\n};

The Markdown handler, when converting the component into HTML, also generates a\ntable of contents using the six Markdown heading types (which correspond to the\nh1-h6 HTML tags).

\n\n
\n \n \n \n \n \n \n
\n\n
# Heading 1\n\n## Heading 2\n\n### Heading 3\n\n## Another heading 2

Given that Markdown, the following JSON file will be created.

\n\n
\n \n \n \n \n \n \n
\n\n
[\n  {\n    \"text\": \"Heading 1\",\n    \"level\": 1,\n    \"name\": \"heading-1\"\n  },\n  {\n    \"text\": \"Heading 2\",\n    \"level\": 2,\n    \"name\": \"heading-2\"\n  },\n  {\n    \"text\": \"Heading 3\",\n    \"level\": 3,\n    \"name\": \"heading-3\"\n  },\n  {\n    \"text\": \"Another heading 2\",\n    \"level\": 2,\n    \"name\": \"another-heading-2\"\n  },\n]

At runtime, this JSON file will be written to build/server/${directory}\nalongside the compiled HTML file. To use this, you can override the default\nMarkdown handler function.

\n\n
\n \n \n \n \n \n \n
\n\n
import markdown from \"@primate/markdown\";\n\nexport default {\n  modules: [\n    markdown({\n      handler: ({ content, toc }) => (app, ...rest) =>\n        app.handlers.svelte(\"Markdown.svelte\", { content, toc })(app, ...rest),\n    }),\n  ],\n};

This allows you to circumvent the default Markdown handler and hand over the\ncompiled HTML content and JSON table of contents to a dynamic component, in\nthis case Svelte, for embedding.

\n

In addition to all of the above, you can also access the Markdown compilation\nfunction directly as an export of @primate/markdown. This allows you to\ncompile and serve Markdown content from a dynamic source (like a database).

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\nimport { compile } from \"@primate/markdown\";\n\nexport default {\n  get(request) {\n    const page = request.path.get(\"page\");\n\n    // this assumes you have a data store called Markdown\n    const [markdown] = await request.store.Markdown.find({ page });\n\n    const { content, toc } = compile(markdown.text);\n\n    return view(\"Markdown.svelte\", { content, toc });\n  },\n};

The above example compiles the Markdown text on every request without any form\nof caching or checking for changes. Normally you would add a layer of\non-disk-caching in such a scenario.

\n\n

\n Full React support\n \n \n \n \n \n \n \n

\n

During this release cycle, we have moved to fully support the React handler,\nincluding hydration, layouts, and liveview. For more information see the\nfrontend frameworks overview page.

\n

This brings to 3 the number of frameworks we completely support: Svelte, React\nand Solid.

\n\n

\n Build transformations\n \n \n \n \n \n \n \n

\n

We've added a new configuration option, build.transform, having two\nproperties, paths and mapper. paths is a list of paths (glob patterns\nare supported) for which the transformation should apply, and mapper is a\nfunction that transforms the content of the files at the given paths. The\noutput of this function for each file is then copied onto the\nconfig.location.build directory.

\n

This is useful if you have placeholders for environment variables that you want\nto replace in different files in your build.

\n\n
\n \n \n \n \n \n \n
\n\n
export default {\n  build: {\n    transform: {\n\t  paths: [\"components/**/*.svelte\"],\n      mapper: content => content.replaceAll(\"%BASE%\", \"/\"),\n    },\n  },\n}

The above code will replace every occurrence of "%BASE%" in every Svelte\ncomponent under components and its subdirectories with "/" at build time.

\n

Be careful with injecting secrets into components: components are rendered both\non the server and on the client, which means any secret you map onto a\ncomponent will be exposed to every client and can be considered compromised.

\n\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

After an extensive 0.21, this was a relatively small release with a fair amount\nof internal changes in addition to the changes here listed.

\n

Some of the things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.23, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"new-frontend-handlers","text":"New frontend handlers"},{"depth":2,"slug":"full-react-support","text":"Full React support"},{"depth":2,"slug":"build-transformations","text":"Build transformations"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.22: Solid and Markdown support, build transformations","epoch":1692880155000,"author":"terrablue"}},{"href":"supporting-solid","md":"Today we're adding another frontend framework to our growing list of supported\nhandlers, [Solid]. The new handler is fully compatible with the current 0.21\nrelease.\n\nSupport for Solid includes server-side rendering (SSR), hydration, layouting,\nas well as liveview integration. Having recently added hydration, layouts and\nliveview to React, this brings to three the number of frontend frameworks we\nfully support.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of the framework.\n!!!\n\n## Install\n\nTo install the Solid module, run `npm install @primate/solid`. Then add it to\nyour Primate configuration.\n\n```js caption=primate.config.js\nimport solid from \"@primate/solid\";\n\nexport default {\n modules: [\n solid(),\n ],\n};\n```\n\nSupporting Solid has been challenging in the sense that like React, it uses JSX\ncomponents, but unlike React, it uses them slightly differently, calling for\na different compiling method to JavaScript. In addition, this is the first time\nwe have encountered potential file extension conflicts, as both handlers use\nthe `jsx` file extension. To nonetheless allow users to work with both\nframeworks side by side, we have added an `extension` option you can pass the\nSolid handler, to override its `jsx` default.\n\n```js caption=primate.config.js\nimport solid from \"@primate/solid\";\n\nexport default {\n modules: [\n solid({\n extension: \"solid\",\n }),\n ],\n};\n```\n\nThis would allow you to create Solid components with the `solid` extension and\nplace them alongside React (`jsx`) components.\n\n## Use\n\nTo use a Solid component, create a route under `routes`. This example assumes\nyou have changed the Solid component file extension to `solid`.\n\n```js caption=routes/posts.js\nimport view from \"primate/handler/view\";\n\nconst posts = [{\n id: 1,\n title: \"First post\",\n}];\n\nexport default {\n get() {\n return view(\"PostIndex.solid\", { posts });\n },\n};\n```\n\nNext, create a component in `components`.\n\n```jsx caption=components/PostIndex.solid\nimport { For } from \"solid-js/web\";\n\nexport default function (props) {\n return <>\n

All posts

\n \n {post =>

{post.title}

}\n
\n

add post

\n ;\n}\n```\n\n## Add a layout\n\nCreate a `+layout.js` file alongside your routes (layouts apply to all routes\nin their directory and its subdirectories, hierarchically).\n\n```js caption=routes/+layout.js\nimport view from \"primate/handler/view\";\n\nexport default () => {\n return view(\"layout.solid\", { user: \"Tom\" });\n};\n```\n\nThen add a component for the layout in `components`.\n\n```jsx caption=components/layout.solid\nexport default function layout(props) {\n return <>\n
Hi, {props.data.user}.
\n {props.children}\n ;\n}\n```\n\n## Activate liveview\n\nTo activate liveview for Solid, turning your app into an SPA and avoiding full\npage reloads, install and load `@primate/liveview`.\n\n```js caption=primate.config.js\nimport solid from \"@primate/solid\";\nimport liveview from \"@primate/liveview\";\n\nexport default {\n modules: [\n solid(),\n liveview(),\n ],\n};\n```\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with Solid!\n\n[Getting started]: /guide/getting-started\n[Solid]: https://www.solidjs.com\n[irc]: https://web.libera.chat#primate\n","html":"

Today we're adding another frontend framework to our growing list of supported\nhandlers, Solid. The new handler is fully compatible with the current 0.21\nrelease.

\n

Support for Solid includes server-side rendering (SSR), hydration, layouting,\nas well as liveview integration. Having recently added hydration, layouts and\nliveview to React, this brings to three the number of frontend frameworks we\nfully support.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of the framework.

\n\n

\n Install\n \n \n \n \n \n \n \n

\n

To install the Solid module, run npm install @primate/solid. Then add it to\nyour Primate configuration.

\n\n
\n \n \n \n \n \n \n
\n\n
import solid from \"@primate/solid\";\n\nexport default {\n  modules: [\n    solid(),\n  ],\n};

Supporting Solid has been challenging in the sense that like React, it uses JSX\ncomponents, but unlike React, it uses them slightly differently, calling for\na different compiling method to JavaScript. In addition, this is the first time\nwe have encountered potential file extension conflicts, as both handlers use\nthe jsx file extension. To nonetheless allow users to work with both\nframeworks side by side, we have added an extension option you can pass the\nSolid handler, to override its jsx default.

\n\n
\n \n \n \n \n \n \n
\n\n
import solid from \"@primate/solid\";\n\nexport default {\n  modules: [\n    solid({\n      extension: \"solid\",\n    }),\n  ],\n};

This would allow you to create Solid components with the solid extension and\nplace them alongside React (jsx) components.

\n\n

\n Use\n \n \n \n \n \n \n \n

\n

To use a Solid component, create a route under routes. This example assumes\nyou have changed the Solid component file extension to solid.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nconst posts = [{\n  id: 1,\n  title: \"First post\",\n}];\n\nexport default {\n  get() {\n    return view(\"PostIndex.solid\", { posts });\n  },\n};

Next, create a component in components.

\n\n
\n \n \n \n \n \n \n
\n\n
import { For } from \"solid-js/web\";\n\nexport default function (props) {\n  return <>\n    <h1>All posts</h1>\n    <For each={props.data.posts}>\n      {post => <h2><a href={`/post/view/${post.id}`}>{post.title}</a></h2>}\n    </For>\n    <h3><a href=\"/post/edit/\">add post</a></h3>\n  </>;\n}
\n

\n Add a layout\n \n \n \n \n \n \n \n

\n

Create a +layout.js file alongside your routes (layouts apply to all routes\nin their directory and its subdirectories, hierarchically).

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nexport default () => {\n  return view(\"layout.solid\", { user: \"Tom\" });\n};

Then add a component for the layout in components.

\n\n
\n \n \n \n \n \n \n
\n\n
export default function layout(props) {\n  return <>\n    <div>Hi, {props.data.user}.</div>\n    {props.children}\n  </>;\n}
\n

\n Activate liveview\n \n \n \n \n \n \n \n

\n

To activate liveview for Solid, turning your app into an SPA and avoiding full\npage reloads, install and load @primate/liveview.

\n\n
\n \n \n \n \n \n \n
\n\n
import solid from \"@primate/solid\";\nimport liveview from \"@primate/liveview\";\n\nexport default {\n  modules: [\n    solid(),\n    liveview(),\n  ],\n};
\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with Solid!

\n","toc":[{"depth":2,"slug":"install","text":"Install"},{"depth":2,"slug":"use","text":"Use"},{"depth":2,"slug":"add-a-layout","text":"Add a layout"},{"depth":2,"slug":"activate-liveview","text":"Activate liveview"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Adding Solid support","epoch":1691948898000,"author":"terrablue"}},{"href":"release-021","md":"Today we're announcing the availability of the Primate 0.21 preview release.\nThis release comes along with 3 new data stores (SQLite, PostgreSQL, MongoDB),\ncustom error routes, and a client liveview mode (SPA). On the tooling side,\nwe've also added a GUI for creating Primate apps, available via\n`npm create primate`. With this release, we're also officially inaugurating our\nnew website.\n\n!!!\nIf you're new to Primate, we recommend reading the [Getting started] page to\nget an idea of the framework.\n!!!\n\n## New date stores\n\nThis release adds 3 new data store types, SQLite, PostgreSQL and MongoDB. In\naddition, SurrealDB support has been moved from its own package into\n`@primate/store`. All data store driver wrappers are now available as\n`@primate/store` exports, where the user is responsible for installing the\nunderlying driver package.\n\nFor example, if you want to use the SQLite driver wrapper, you will need to\ninstall `better-sqlite3`. Which package should be installed is documented in\nthe [driver section][drivers]. Primate will also direct you to install the\ncorrect package should it be missing.\n\nUsing the new store drivers is similar to how all drivers work. You import and\npass them to the `driver` property of the `store` module.\n\n```js caption=primate.config.js\nimport { sqlite, default as store } from \"@primate/store\";\n\nexport default {\n modules: [\n store({\n driver: sqlite({\n filename: \"/tmp/data.db\",\n }),\n }),\n ],\n};\n```\n\nIndividual driver options are documented in the [driver section][drivers].\n\n!!!\nThe 3 new drivers are considered beta at this stage. In particular, the\nPostgreSQL and MongoDB drivers do not support transaction rollbacks at the\nmoment.\n!!!\n\n## Custom error routes\n\nThe last releases have seen the addition of special `+guard.js` and `+layout.js`\nfiles to achieve scoped, recursive route path guards and layouts. This release\nadds a new special `+error.js` file placed alongside route files. Those error\nroutes export, like guards and layouts, a default function which is executed in\ncase a normal route alongside it (or below it in the filesystem hierarchy)\nencounters an error during execution.\n\nSimilarly to guards and layouts, the error route accepts a `request` parameter\nand responds with a proper handler. Here is an example with an error route\nfile rendering a Svelte component.\n\n```js caption=routes/+error.js\nimport view from \"primate/handler/view\";\n\nexport default request => view(\"ErrorPage.svelte\");\n```\n\nLike guards and layouts, error files are recursively applied. For every route,\nthe **nearest** error file to it will apply. It will first look in its own\ndirectory, and then start climbing up the filesystem hierarchy, falling back to\nany `+error.js` file it finds along the way. Unlike guards and layouts, the\nmoment an `+error.js` file is found, it will be used to handle the response.\n\n!!!\nQuick recap on guards and layouts: all guards must be fulfilled for a route to\nbe executed, starting with the root guard and going down until the nearest\nguard, if it exists. Layouts work in the opposite direction: they are\nincluded in each other, with the innermost layout including the output of the\nroute, and being recursively included itself, up to the root layout. With error\nroutes, the first one to be found, from down to up, is applied.\n!!!\n\nThe root error file located at `routes/+error.js`, if existing, has a special\nmeaning. It applies normally to every route for which no other error file can\nbe found, but it also applies in cases where no route at all could be matched.\nIt thus serves as a classic `404 Not Found` error route.\n\nAll error routes use the error page in `pages/error.html`. This file, like\n`app.html`, can have placeholders for embedding head scripts and the body. In\ncase it does not exist, Primate will fall back to its default `error.html`.\n\n```html caption=pages/error.html\n\n\n \n Error page\n \n %head%\n \n \n

Error page

\n

\n %body%\n

\n \n\n```\n\nLike normal routes, error routes can use a different error page if desired, by\npassing a `page` property to the third handler parameter. The page file itself\nmust be located under `pages`.\n\n```js caption=routes/+error.js\nimport view from \"primate/handler/view\";\n\nexport default request => view(\"ErrorPage.svelte\", {}, {\n page: \"other-error.html\",\n});\n```\n\n!!!\nError routes currently do **not** use layout files that would otherwise be\napplicable to them in the filesystem hierarchy. This behavior may change in\nthe future.\n!!!\n\n## Client liveview (SPA)\n\nAlthough Primate's frontend framework wrappers have all implemented SSR and\nsome (like Svelte) also hydration, there was until now a gap in achieving true\nSPA (single-page application) functionality. This release bridges this gap by\nadding a new module, `@primate/liveview`, that injects a small JavaScript client\ninto the build. This client uses `fetch` to manage clicking on links and\nsubmitting forms instead of reloading the entire page, and also manages browsing\nthe history.\n\nThe liveview module uses a special `X-Primate-Liveview` header to indicate to\nthe handler that instead of rendering the entire HTML page, only the reference\nto the next component and its data are required and should be returned as JSON.\nAccordingly, every frontend handler must implement support for this header, and\ncurrently the Svelte handler is the only one that does.\n\nTo activate liveview, import and load the module.\n\n```js caption=primate.config.js\nimport svelte from \"@primate/svelte\";\nimport liveview from \"@primate/liveview\";\n\nexport default {\n modules: [\n svelte(),\n liveview(),\n ],\n};\n```\n\n!!!\nLiveview is equally relevant for `@primate/react` and `@primate/vue`, as well\nas theoretically for the standard HTML handler, for rendering partial responses.\nFor the former two, we plan on supporting it as soon as we implement hydration.\nFor the latter, there is some conflict of interest with the `@primate/htmx`\nmodule that merits further investigation into the utility of supporting\nliveview for HTML.\n!!!\n\n## App creation GUI\n\nOn the tooling side, we have added a `create-primate` package which allows you\nto generate new Primate apps using a GUI. To start it, run `npm create primate`\nin your terminal.\n\nThis GUI will walk you through the process of creating a new Primate app,\nallowing you to choose from the three common templates (web app, API, static\nserver) and asking you follow-up questions depending on the chosen template.\nMost of the questions provide a link to the relevant section of the modules\ndocumentation that explains the utility of adding certain functionalities.\n\nIn future releases, we plan to expand this tool in order to be able to quickly\nscaffold apps by creating routes and other common files.\n\n## New website\n\nOver the last months we've put a lot of work into the new website. The old\nwebsite was built with MkDocs, and while writing documentation in Markdown files\nhas been serving us well and is something we wanted to keep, we believed we\nneeded something more modern and extensible in the underlying software. If\npossible, we also wanted to use Primate itself for the website.\n\nThe result is [Priss][priss], a Primate + Svelte site generator that allows us\nto use Svelte on the frontend and, if necessary, extend the site to include\ndynamic features in the future. It also showcases Primate's potential to\naddress many different use cases.\n\nThe new website comes with an extensive guide covering the base framework as\nwell as a section on officially supported modules.\n\n## Other changes\n\nConsult the [full changelog][changelog] for a list of all relevant changes.\n\n## Next on the road\n\nThis release has been unusually long in the making and had an extensive,\nambitious scope. Our next release, 0.22, is likely to be smaller, and we'll be\nlooking at building on the foundation laid forth by this release.\n\nThings we plan to tackle in the upcoming weeks are,\n\n* Add transactions to the PostgreSQL and MongoDB drivers\n* Introduce IDE TypeScript support\n* Add a `command` hook that would allow modules to register command line\n namespaces, to be able to run `npx primate [namespace] [command] [flags]`\n* Use this new hook to create database migrations for SQL-flavored databases\n* Extend `create-primate` with the ability to add new routes\n* Extend `@primate/liveview` with support for WebSocket communication in case\n `@primate/ws` has been loaded\n* Add hydration and liveview support for `@primate/react` and `@primate/vue`\n* Support the `multipart/form-data` content type\n* Introduce a `Result` class as an alternative return value for store functions\n* Flesh out stores with default values, additional predicates and relations\n between tables/collections\n* Add more type variants\n\nThis list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.22, and other features may be prioritized according to\nfeedback.\n\n## Fin\n\nIf you like Primate, consider [joining our channel #primate][irc] on\nirc.libera.chat.\n\nOtherwise, have a blast with the new version!\n\n[Getting started]: /guide/getting-started\n[irc]: https://web.libera.chat#primate\n[changelog]: https://github.com/primate-run/primate/releases/tag/0.21.2\n[drivers]: /modules/drivers\n[priss]: https://github.com/primate-run/priss\n","html":"

Today we're announcing the availability of the Primate 0.21 preview release.\nThis release comes along with 3 new data stores (SQLite, PostgreSQL, MongoDB),\ncustom error routes, and a client liveview mode (SPA). On the tooling side,\nwe've also added a GUI for creating Primate apps, available via\nnpm create primate. With this release, we're also officially inaugurating our\nnew website.

\n

If you're new to Primate, we recommend reading the Getting started page to\nget an idea of the framework.

\n\n

\n New date stores\n \n \n \n \n \n \n \n

\n

This release adds 3 new data store types, SQLite, PostgreSQL and MongoDB. In\naddition, SurrealDB support has been moved from its own package into\n@primate/store. All data store driver wrappers are now available as\n@primate/store exports, where the user is responsible for installing the\nunderlying driver package.

\n

For example, if you want to use the SQLite driver wrapper, you will need to\ninstall better-sqlite3. Which package should be installed is documented in\nthe driver section. Primate will also direct you to install the\ncorrect package should it be missing.

\n

Using the new store drivers is similar to how all drivers work. You import and\npass them to the driver property of the store module.

\n\n
\n \n \n \n \n \n \n
\n\n
import { sqlite, default as store } from \"@primate/store\";\n\nexport default {\n  modules: [\n    store({\n      driver: sqlite({\n        filename: \"/tmp/data.db\",\n      }),\n    }),\n  ],\n};

Individual driver options are documented in the driver section.

\n

The 3 new drivers are considered beta at this stage. In particular, the\nPostgreSQL and MongoDB drivers do not support transaction rollbacks at the\nmoment.

\n\n

\n Custom error routes\n \n \n \n \n \n \n \n

\n

The last releases have seen the addition of special +guard.js and +layout.js\nfiles to achieve scoped, recursive route path guards and layouts. This release\nadds a new special +error.js file placed alongside route files. Those error\nroutes export, like guards and layouts, a default function which is executed in\ncase a normal route alongside it (or below it in the filesystem hierarchy)\nencounters an error during execution.

\n

Similarly to guards and layouts, the error route accepts a request parameter\nand responds with a proper handler. Here is an example with an error route\nfile rendering a Svelte component.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nexport default request => view(\"ErrorPage.svelte\");

Like guards and layouts, error files are recursively applied. For every route,\nthe nearest error file to it will apply. It will first look in its own\ndirectory, and then start climbing up the filesystem hierarchy, falling back to\nany +error.js file it finds along the way. Unlike guards and layouts, the\nmoment an +error.js file is found, it will be used to handle the response.

\n

Quick recap on guards and layouts: all guards must be fulfilled for a route to\nbe executed, starting with the root guard and going down until the nearest\nguard, if it exists. Layouts work in the opposite direction: they are\nincluded in each other, with the innermost layout including the output of the\nroute, and being recursively included itself, up to the root layout. With error\nroutes, the first one to be found, from down to up, is applied.

\n

The root error file located at routes/+error.js, if existing, has a special\nmeaning. It applies normally to every route for which no other error file can\nbe found, but it also applies in cases where no route at all could be matched.\nIt thus serves as a classic 404 Not Found error route.

\n

All error routes use the error page in pages/error.html. This file, like\napp.html, can have placeholders for embedding head scripts and the body. In\ncase it does not exist, Primate will fall back to its default error.html.

\n\n
\n \n \n \n \n \n \n
\n\n
<!doctype html>\n<html>\n  <head>\n    <title>Error page</title>\n    <meta charset=\"utf-8\" />\n    %head%\n  </head>\n  <body>\n    <h1>Error page</h1>\n    <p>\n      %body%\n    </p>\n  </body>\n</html>

Like normal routes, error routes can use a different error page if desired, by\npassing a page property to the third handler parameter. The page file itself\nmust be located under pages.

\n\n
\n \n \n \n \n \n \n
\n\n
import view from \"primate/handler/view\";\n\nexport default request => view(\"ErrorPage.svelte\", {}, {\n  page: \"other-error.html\",\n});

Error routes currently do not use layout files that would otherwise be\napplicable to them in the filesystem hierarchy. This behavior may change in\nthe future.

\n\n

\n Client liveview (SPA)\n \n \n \n \n \n \n \n

\n

Although Primate's frontend framework wrappers have all implemented SSR and\nsome (like Svelte) also hydration, there was until now a gap in achieving true\nSPA (single-page application) functionality. This release bridges this gap by\nadding a new module, @primate/liveview, that injects a small JavaScript client\ninto the build. This client uses fetch to manage clicking on links and\nsubmitting forms instead of reloading the entire page, and also manages browsing\nthe history.

\n

The liveview module uses a special X-Primate-Liveview header to indicate to\nthe handler that instead of rendering the entire HTML page, only the reference\nto the next component and its data are required and should be returned as JSON.\nAccordingly, every frontend handler must implement support for this header, and\ncurrently the Svelte handler is the only one that does.

\n

To activate liveview, import and load the module.

\n\n
\n \n \n \n \n \n \n
\n\n
import svelte from \"@primate/svelte\";\nimport liveview from \"@primate/liveview\";\n\nexport default {\n  modules: [\n    svelte(),\n    liveview(),\n  ],\n};

Liveview is equally relevant for @primate/react and @primate/vue, as well\nas theoretically for the standard HTML handler, for rendering partial responses.\nFor the former two, we plan on supporting it as soon as we implement hydration.\nFor the latter, there is some conflict of interest with the @primate/htmx\nmodule that merits further investigation into the utility of supporting\nliveview for HTML.

\n\n

\n App creation GUI\n \n \n \n \n \n \n \n

\n

On the tooling side, we have added a create-primate package which allows you\nto generate new Primate apps using a GUI. To start it, run npm create primate\nin your terminal.

\n

This GUI will walk you through the process of creating a new Primate app,\nallowing you to choose from the three common templates (web app, API, static\nserver) and asking you follow-up questions depending on the chosen template.\nMost of the questions provide a link to the relevant section of the modules\ndocumentation that explains the utility of adding certain functionalities.

\n

In future releases, we plan to expand this tool in order to be able to quickly\nscaffold apps by creating routes and other common files.

\n\n

\n New website\n \n \n \n \n \n \n \n

\n

Over the last months we've put a lot of work into the new website. The old\nwebsite was built with MkDocs, and while writing documentation in Markdown files\nhas been serving us well and is something we wanted to keep, we believed we\nneeded something more modern and extensible in the underlying software. If\npossible, we also wanted to use Primate itself for the website.

\n

The result is Priss, a Primate + Svelte site generator that allows us\nto use Svelte on the frontend and, if necessary, extend the site to include\ndynamic features in the future. It also showcases Primate's potential to\naddress many different use cases.

\n

The new website comes with an extensive guide covering the base framework as\nwell as a section on officially supported modules.

\n\n

\n Other changes\n \n \n \n \n \n \n \n

\n

Consult the full changelog for a list of all relevant changes.

\n\n

\n Next on the road\n \n \n \n \n \n \n \n

\n

This release has been unusually long in the making and had an extensive,\nambitious scope. Our next release, 0.22, is likely to be smaller, and we'll be\nlooking at building on the foundation laid forth by this release.

\n

Things we plan to tackle in the upcoming weeks are,

\n\n

This list isn't exhaustive or binding. None, some or all of these features may\nbe included in 0.22, and other features may be prioritized according to\nfeedback.

\n\n

\n Fin\n \n \n \n \n \n \n \n

\n

If you like Primate, consider joining our channel #primate on\nirc.libera.chat.

\n

Otherwise, have a blast with the new version!

\n","toc":[{"depth":2,"slug":"new-date-stores","text":"New date stores"},{"depth":2,"slug":"custom-error-routes","text":"Custom error routes"},{"depth":2,"slug":"client-liveview-spa","text":"Client liveview (SPA)"},{"depth":2,"slug":"app-creation-gui","text":"App creation GUI"},{"depth":2,"slug":"new-website","text":"New website"},{"depth":2,"slug":"other-changes","text":"Other changes"},{"depth":2,"slug":"next-on-the-road","text":"Next on the road"},{"depth":2,"slug":"fin","text":"Fin"}],"meta":{"title":"Release 0.21: More stores, error routes, liveview, `create primate`, and new website","epoch":1690660448000,"author":"terrablue"}}]}],"request":{"context":{},"cookies":{},"headers":{"accept":"*/*","forwarded":"proto=https;for=216.73.216.13","host":"repopack.com","user-agent":"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)","x-forwarded-for":"216.73.216.13","x-forwarded-proto":"https"},"path":{},"query":{},"url":"http://repopack.com/blog"}}
Primate Logo Primate

Blog