diff --git a/docs/blog/release-030.json b/docs/blog/release-030.json new file mode 100644 index 00000000..581a97f8 --- /dev/null +++ b/docs/blog/release-030.json @@ -0,0 +1,5 @@ +{ + "title": "Release 0.30: Windows support, new path parameter style and other improvements", + "epoch": 1708854056000, + "author": "terrablue" +} diff --git a/docs/blog/release-030.md b/docs/blog/release-030.md new file mode 100644 index 00000000..6a57a600 --- /dev/null +++ b/docs/blog/release-030.md @@ -0,0 +1,163 @@ +Today we're announcing the availability of the Primate 0.30 preview release. +This release introduces full Windows support and brings the path parameter +style used by Primate's filesystem-based routes in line with other frameworks, +in addition to several quality of life improvements. + +!!! +If you're new to Primate, we recommend reading the [Getting started] page to +get an idea of it. +!!! + +## Windows support + +This release introduces full support for running Primate on Windows, including +Wasm routes, data stores as well as frontends. + +## New path parameter style + +In this release, Primate is switching from its original path parameter style, +using braces, to brackets. + +If you had a path like `routes/user/{id}.js` before, you would now be using +`routes/user/[id].js` in line with most other filesystem-based frameworks. + +To illustrate, here are a few examples of paths in 0.30. + +* `index.js` is mapped to the root route (`/`) +* `user.js` is mapped to `/user` +* `user/[user_id].js` is mapped to a route with parameters, for example +`/user/1` (but also `/user/donald`) +* `user/[user_id=uuid].js` is mapped to a route where `user_id` is of the type +`uuid`, for example `/user/f6a3fac2-7c1d-432d-9e1c-68d0db925adc` (but not +`/user/1`) + +## Quality of life improvements + +### Default `loose` mode in stores + +The default mode for `@primate/store` stores is now `loose`. This is similar to +before with the addition that fields not explicitly declared in the store +definition will be also saved. This is particulary useful for NoSQL databases +that do not have a rigid schema, in cases where you want to enforce types on +some fields and accept anything in others. + +To make this applicable for SQL databases too, we will add a `catchall` type in +the future denoting a JSON column in which the data of any non-declared fields +is saved and properly deserialized when retrieved. + +To enforce strictness in stores globally, pass in `{ mode: "strict" }` when +activating the store module. To enforce strictness on store level, use +`export const mode = "strict";` in the store file. To opt out of global +strictness per store, use `export const mode = "loose";`. + +In the future, we will add the ability to mark fields as optional such that +it's possible to enforce a strict mode with explicit exceptions for optional +(nullable) fields. + +### Loose default CSP + +Previously, Primate enforced a strict CSP policy. In this release, the defaults +have been changed to no CSP policy. If you create a policy directive for +`script-src` or `style-src`, Primate will augment it with hashes for scripts +and stylesheets. + +### Improved error handling of route return object + +Starting with this release, Primate will tell you if you forget to return data +from your route or the body you return is invalid. + +```sh +!! primate invalid body returned from route, got `undefined` +++ return a proper body from route + -> https://primatejs.com/guide/logging#invalid-body-returned +``` + +### Disabling body parsing + +You can now tell Primate to not parse the body stream of the request, leaving +it pristine, by setting `request.body.parse` to `false` in your configuration +file. + +```js caption=primate.config.js +export default { + request: { + body: { + parse: false, + }, + }, +}; +``` + +This is particularly useful if you're using Primate as a programmable reverse +proxy with the `handle` hook and you want to pass the untouched request to +another application. + +```js caption=primate.config.js +const upstream = "http://localhost:7171"; + +export default { + request: { + body: { + parse: false, + }, + }, + modules: [{ + name: "reverse-proxy", + handle(request) { + const { method, headers, body } = request.original; + const input = `${upstream}${request.url.pathname}`; + + return globalThis.fetch(input, { headers, method, body, duplex: "half" }); + }, + }], +}; +``` + +## Migrating from 0.29 + +### update path parameters to new style + +Change any `{` to `[` and `}` to `]` in your path parameters. + +The following script will change any JavaScript route files you have in your +`routes` directory from the old to the new style. + +```sh +find -name "*.js" -exec rename -v "{" "[" {} ";" && +find -name "*.js" -exec rename -v "}" "]" {} ";" +``` + +If you used path parameters in any directory names, change them manually. + +## Other changes + +Consult the [full changelog][changelog] for a list of all relevant changes. + +## Next on the road + +Some of the things we plan to tackle in the upcoming weeks are, + +* Add projections and relations to stores +* Multidriver transactions +* Add a `command` hook that would allow modules to register command line + namespaces, to be able to run `npx primate [namespace] [command] [flags]` +* Use this new hook to create database migrations for SQL-flavored databases +* Add hydration and SPA support for `@primate/vue` +* Flesh out stores with default values, additional predicates and relations + between tables/collections +* Add more type variants + +This list isn't exhaustive or binding. None, some or all of these features may +be included in 0.31, and other features may be prioritized according to +feedback. + +## Fin + +If you like Primate, consider [joining our channel #primate][irc] on +irc.libera.chat. + +Otherwise, have a blast with the new version! + +[Getting started]: /guide/getting-started +[irc]: https://web.libera.chat#primate +[changelog]: https://github.com/primatejs/primate/releases/tag/0.30.0 diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index fce19596..c839b8a5 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -27,19 +27,16 @@ export default { http: { host: "localhost", port: 6161, - csp: { - "default-src": "'self'", - "style-src": "'self'", - "script-src": "'self'", - "object-src": "'none'", - "frame-ancestors": "'none'", - "form-action": "'self'", - "base-uri": "'self'", - }, + csp: {}, static: { root: "/", }, }, + request: + body: { + parse: true, + }, + }, location: { components: "components", pages: "pages", @@ -58,9 +55,6 @@ export default { mapper: identity, }, }, - types: { - explicit: false, - }, }; ``` @@ -106,19 +100,16 @@ export default { http: { host: "localhost", port: 6262, - csp: { - "default-src": "'self'", - "style-src": "'self'", - "script-src": "'self'", - "object-src": "'none'", - "frame-ancestors": "'none'", - "form-action": "'self'", - "base-uri": "'self'", - }, + csp: {}, static: { root: "/", }, }, + request: + body: { + parse: true, + }, + }, location: { components: "components", pages: "pages", @@ -137,9 +128,6 @@ export default { mapper: identity, }, }, - types: { - explicit: false, - }, }; ``` @@ -152,8 +140,6 @@ Default `"/"` Your app's base path. If your app is running from a domain's root path, leave the default as is. If your app is running from a subpath, adjust accordingly. -This is used in CSP paths. - ### modules Default `[]` @@ -217,27 +203,32 @@ The HTTP port to be used. This value is directly passed to the runtime. ### http.csp -Default +Default `{}` + +The Content Security Policy (CSP) to be used. Empty by default. + +If you wanted a fairly restrictive policy, you would use something like this. ```js { // all content must come from own origin, excluding subdomains -"default-src": "'self'", +"default-src": ["'self'"], // styles must come from own origin, excluding subdomains -"style-src": "'self'", +"style-src": ["'self'"], // disallow , and elements -"object-src": "'none'", +"object-src": ["'none'"], // disallow embedding -"frame-ancestors": "'none'", +"frame-ancestors": ["'none'"], // all form submissions must be to own origin -"form-action": "'self'", +"form-action": ["'self'"], // allow only own origin in -"base-uri": "'self'", +"base-uri": ["'self'"], } ``` -The Content Security Policy (CSP) to be used. Primate's defaults are intended -to be secure, and you would need to change them for decreased security. +If existing, `script-src` and `style-src` will be concatenated with hashes of +scripts and styles picked up by Primate (either through the `components` or the +`static` directory). ### http.static.root @@ -261,6 +252,18 @@ Primate does not load the key or certificate into memory. It only resolves their paths as necessary and passes them to the [rcompat](https://github.com/rcompat/rcompat). !!! +### Request options + +### request.body.parse + +Default: `true` + +Whether the body should be parsed according to the content type. Turning this +off is useful if you're using Primate as a programmable reverse proxy and +forwarding the requests to another app. The headers, the querystring and +cookies will be still parsed and available to `request`, and +`request.original` will contain the untouched original request. + ### Location options Locations of Primate standard directories. If any of these locations are @@ -355,13 +358,6 @@ A file content mapper for the files specified in `build.transform.files`. Configuring [runtime types](/guide/types). -### types.explicit - -Default `false` - -Whether Primate should autotype path parameters. If set to `true`, path -parameters and types having the exact same name won't be automatically typed. - ## pages/app.html If you use the `view` or `html` [handler](/guide/responses#view), Primate will diff --git a/docs/guide/logging.md b/docs/guide/logging.md index ee9f9855..bc9063ff 100644 --- a/docs/guide/logging.md +++ b/docs/guide/logging.md @@ -1,7 +1,7 @@ # Logging Primate has three log levels, `Error`, `Warn` and `Info`. As a general rule, -an `Error` causes severe disruptions to the application (and leads to bailout +an `Error` causes severe disruption to the application (and leads to bailout during startup), `Warn` indicates degraded functionality in an otherwise nominal system, and `Info` serves to give more information. In terms of actionability, `Error` logs **must** be addressed, `Warn` logs **should** be @@ -9,7 +9,7 @@ checked, and `Info` logs **may** be ignored. ## Configuring -By default, the error level is to set `Warn`, which logs all errors and +By default, the error level is to set to `Warn`, which logs all errors and warnings. You can change this in your configuration. ```js primate.config.js @@ -46,7 +46,7 @@ quickfix, as well as link to the website for a longer explanation and fix. ```text ?? primate/store empty store directory ++ populate /home/user/app/stores with stores - -> https://primatejs.com/reference/errors/primate/store#empty-store-directory + -> https://primatejs.com/modules/store#empty-store-directory ``` ## Error @@ -72,9 +72,9 @@ introduce ambiguity into how Primate operates. Marked by two yellow question marks, `??`. -A warning means degraded functionality, but an otherwise functional -application, and should be checked. Warnings usually indicate having configured -a certain feature (like loading a module) but not using it. +A warning means degraded functionality in an otherwise functional application, +and should be checked. Warnings usually indicate having configured a certain +feature (like loading a module) but not using it. ## Info @@ -100,32 +100,22 @@ error on the website. ## Error list -### Cannot Parse Body +### Double File Extension -Level [`Warn`][warn] | [`Possibly Intentional`][possibly-intentional] - -Cannot parse the request body due to the a mismatch between the used -`Content-Type` and the actual body, for example as follows. - -```http caption=request with invalid JSON body -POST /user -Content-Type: application/json - -~ -``` +Level [`Error`][error] | [`Bailout`][bailout] -This could be due to the client using the wrong content type or sending, as in -the given example, invalid body with its request, or intentional -client-side activity. +Two handlers are using the same file extension. File extensions must be +uniquely mappable to a handler. All handlers allow you to use an alternative +file extension. -*If deemed unintentional, use a different content type or fix the body.* +*Use a different file extension for one of the handlers.* ### Double Module Level [`Error`][error] | [`Bailout`][bailout] Two modules are using the same name in `primate.config.is`. A module's `name` -property is its unique identifier and must not be repeated. +property is its unique identifier and must not be doubled. *Load the module only once.* @@ -134,7 +124,7 @@ property is its unique identifier and must not be repeated. Level [`Error`][error] | [`Bailout`][bailout] The same parameter is used twice in one route, as in -`routes/{userId}/{userId}.js`. Path parameters are mapped to `request.query` +`routes/[user_id]/[user_id].js`. Path parameters are mapped to `request.query` and using the same name twice (with or without a type) creates ambiguity. *Disambiguate path parameters in route names.* @@ -144,12 +134,12 @@ and using the same name twice (with or without a type) creates ambiguity. Level [`Error`][error] | [`Bailout`][bailout] The same route is used twice, as in `routes/user.js` and -`routes/user/index.js` or `routes/post/{foo}.js` and `/routes/post/{bar}.js`, +`routes/user/index.js` or `routes/post/[foo].js` and `/routes/post/[bar].js`, creating mapping ambiguity. Routes must be unique, and while you can mix styles in your application (like `routes/user.js` and `routes/comment/index.js`), you must not use the same style for the same route. -*Disambiguate the routes by consolidating them into one file of the path-style +*Disambiguate the routes by consolidating them into one file of the path style of your choosing.* ### Empty Route File @@ -161,80 +151,87 @@ default export. *Add routes to the file or remove it.* -## Empty Directory +### Empty Config File Level [`Warn`][warn] -One of the default directories is empty. +The configuration file at `primate.config.js` was loaded without or with an +empty default export. -Primate is an opt-in framework, that is most of its aspects, like *routes* or -*types* are only active when their directories exist. Such an empty directory -could mean you intended to use something but haven't. It's best to either -populate the directory with files or remove it completely. - -*Populate directory or remove it.* +*Add configuration options to the file or remove it.* -### Error In Config File +### Empty Path Parameter Level [`Error`][error] | [`Bailout`][bailout] -JavaScript error encountered when importing `primate.config.js`. +A nameless path parameter (`[]`) was found. Primate cannot match empty path +parameters. -*Check and address errors in the config file by running it directly.* +*Name the path parameter or remove it.* -### Module Has No Hooks +### Empty Route File Level [`Warn`][warn] -Module loaded without hooks. +A route file without routes was found. An empty file may create the false +impression that it handles certain paths. -*If this is a ad-hoc module, add hooks to it to make effective. If a -third-party module, contact the maintainer.* +*Add routes to the file or remove it completely.* + +## Empty Directory + +Level [`Warn`][warn] + +One of the default directories is empty. + +Primate is an opt-in framework, that is most of its aspects, like *routes* or +*types* are only active when their directories exist. Such an empty directory +could mean you intended to use something but haven't. -### Modules Must Have Names +*Populate the directory with files or remove it completely.* + +### Error In Config File Level [`Error`][error] | [`Bailout`][bailout] -Module loaded without a `name` property. Without this property Primate cannot -detect if a module has been loaded more than once. +JavaScript error encountered when importing `primate.config.js`. -*If this is a ad-hoc module, add a `name` property to it to make it unique. If -a third-party module, contact its maintainer.* +*Check and address errors in the config file by running it directly.* -### Empty Config File +### Invalid Body Returned -Level [`Warn`][warn] +Level [`Error`][error] -`primate.config.js` loaded without or with an empty default export, a no-op. +Invalid body was returned from route. This is usually caused when you forget to +add a return value from your route. -*Add configuration options to the file or remove it.* +*Return a proper body from route.* -### Invalid Path Parameter +### Invalid Default Export Level [`Error`][error] | [`Bailout`][bailout] -Invalid characters in a path parameter. Path parameters are limited to -alphanumeric characters. +A file has an invalid default export. Some special files like guards, layouts +or error files must expose a function as their default export. -*Use only Latin letters and decimal digits in path parameters, that is -lowercase and uppercase A to Z as well as 0 to 9.* +*Use only functions for the default export of the given file.* -### Invalid Route Name +### Invalid Path Level [`Error`][error] | [`Bailout`][bailout] -Dots in route names (excluding `.js`). Dots are used for files, which would -create amgibuity between route and static files. +Invalid characters in a path. Path filenames (including directory filenames) +are limited to letters, digits, '_', '[', ']' or '='. -*Do not use dots in route names.* +*Use only letters, digits, '_', '[', ']' or '=' in path filenames.* -### Invalid Type +### Invalid Type Export Level [`Error`][error] | [`Bailout`][bailout] -A type file has a default export which is not a function. +A type file has an invalid default export. -*Use only functions for the default export of type files.* +*Export object with a `base` string and a `validate` function.* ### Invalid Type Name @@ -245,13 +242,22 @@ Invalid characters in the filename of a type. *Use only Latin letters and decimal digits in type filenames, that is lowercase and uppercase A to Z as well as 0 to 9.* +### Mismatched Body + +Level [`Error`][error] + +The given content type does not correspond to the actual body contents. + +*If unintentional, make sure the body payload corresponds to the used content +type.* + ### Mismatched Path Level [`Info`][info] | [`Possibly Intentional`][possibly-intentional] Type mismatch in path parameters, precluding the route from executing. -*If deemed unintentional, fix the type or the caller.* +*If unintentional, fix the type or the caller.* ### Mismatched Type @@ -260,22 +266,40 @@ Level [`Info`][info] | [`Possibly Intentional`][possibly-intentional] Type mismatch during the execution of a route function, stopping the route. The mismatch happened in a `body`, `query`, `cookies` or `headers` field. -*If deemed unintentional, fix the type or the caller.* +*If unintentional, fix the type or the caller.* -### No File For Path +### Module Has No Hooks -Level [`Info`][info] | [`Possibly Intentional`][possibly-intentional] +Level [`Warn`][warn] + +Module loaded without hooks. + +*If this is a ad-hoc module, add hooks to it to make effective. If a +third-party module, contact the maintainer.* + +### Modules Has No Name + +Level [`Error`][error] | [`Bailout`][bailout] + +Module loaded without a `name` property. Without this property Primate cannot +detect if a module has been loaded more than once. + +*If this is a ad-hoc module, add a `name` property to it to make it unique. If +it is a third-party module, contact its maintainer.* + +### Modules Must Be Array + +Level [`Error`][error] | [`Bailout`][bailout] -No file for the given path, as in `/favicon.ico` when the `static` directory -does not contain a `favicon.ico` file. +The `modules` config property must be an array. -*If deemed unintentional, create a file in your static directory.* +*Change the `module` config property to an array or remove it completely.* ### No Handler For Extension Level [`Error`][error] -No appropriate handler for the given extension using the +No appropriate handler was found for the given file extension using the [`view`](/guide/responses#view) handler. *Add a handler module for files of the given extension or change this route.* @@ -287,7 +311,7 @@ Level [`Info`][info] | [`Possibly Intentional`][possibly-intentional] Cannot map the given path to a route, as in `GET /user.js` when `route/user.js` does not exist or have a `get` property function. -*If deemed unintentional, create an appropriate route function.* +*If unintentional, create an appropriate route function.* ### Reserved Type Name diff --git a/docs/guide/project-structure.md b/docs/guide/project-structure.md index 7f1b64c2..7ef74653 100644 --- a/docs/guide/project-structure.md +++ b/docs/guide/project-structure.md @@ -6,15 +6,15 @@ setup you would use. ```sh . ├─ static/ -│ └─ [static assets] +│ └─ # static assets ├─ pages/ │ └─ app.html ├─ primate.config.js ├─ package.json ├─ routes/ -│ └─ [filesystem-based routes] +│ └─ # filesystem-based routes └─ components/ - └─ [view components] + └─ # frontend components ``` ## static @@ -63,7 +63,7 @@ were creating a blog, this is how a typical layout could look like. ├─ index.js # view homepage -> / └─ post/ ├─ add.js # add post -> /post/add - └─ {postId}/ + └─ [postId]/ ├─ comment/ │ └─ add.js # add comment on post -> /post/1/comment/add ├─ comments.js # show comments on posts -> /post/1/comments @@ -72,8 +72,8 @@ were creating a blog, this is how a typical layout could look like. ``` !!! -Some of the above route examples use `1`, where in fact any value could stand -for `{postId}`. We'll later come back to path parameters in depth. +Some of the above route examples use `1`, where in fact any value could be used +for `[postId]`. We'll later come back to path parameters in depth. !!! Here we chose our paths to represent CRUD actions. This is appropriate for a @@ -85,10 +85,10 @@ verbs. In that case, your layout might look a little different. . ├─ post.js # create post, read posts -> /post └─ post/ - ├─ {postId}.js # read, update, delete post -> post/1 - └─ {postId}/ + ├─ [postId].js # read, update, delete post -> post/1 + └─ [postId]/ ├─ comment.js # create comment, read comments -> /post/1/comment - └─ {commentId}.js # read, update, delete comment -> post/1/comment/2 + └─ [commentId].js # read, update, delete comment -> post/1/comment/2 ``` ## components diff --git a/docs/guide/routes.md b/docs/guide/routes.md index cba6adcb..26f866c3 100644 --- a/docs/guide/routes.md +++ b/docs/guide/routes.md @@ -10,12 +10,13 @@ To illustrate this, consider that inside `routes` * `index.js` is mapped to the root route (`/`) * `user.js` is mapped to `/user` -* `user/{userId}.js` is mapped to a +* `user/[user_id].js` is mapped to a [route with parameters](#parameters), for example `/user/1` (but also `/user/donald`) -* `user/{userId=uuid}.js` is mapped to a -[route with typed parameters][types] route where `userId` is of the type `uuid`, -for example `/user/f6a3fac2-7c1d-432d-9e1c-68d0db925adc` (but not `/user/1`) +* `user/[user_id=uuid].js` is mapped to a +[route with typed parameters][types] route where `user_id` is of the type +`uuid`, for example `/user/f6a3fac2-7c1d-432d-9e1c-68d0db925adc` (but not +`/user/1`) ## HTTP verbs @@ -90,7 +91,7 @@ saying Hello and the provided name. The request's path, an object containing named parameters. -```js caption=routes/users/{user}.js +```js caption=routes/users/[user].js import { error } from "primate"; const users = ["Donald", "Ryan"]; @@ -204,7 +205,7 @@ parameter in the same case twice. They must be non-empty, that is matched by at least one character. By default, parameters will match anything in the path except `/`, though they -are not greedy. A path like `/users/{userId}a.js` is unambiguous: it will match +are not greedy. A path like `/users/[userId]a.js` is unambiguous: it will match any path that starts with `/users/` followed by anything that is not `/`, provided that it ends with `a`. The last `a` can therefore not be part of the match. diff --git a/docs/guide/types.md b/docs/guide/types.md index e74f0129..8389f4f9 100644 --- a/docs/guide/types.md +++ b/docs/guide/types.md @@ -102,7 +102,7 @@ In Primate's [filesystem-based routes](/guide/routes), path parameters may be additionally specified with types to ensure the path adheres to a certain format. -```js caption=routes/user/{userId=uuid}.js +```js caption=routes/user/[userId=uuid].js export default { /* GET /user/b8c5b7b2-4f4c-4939-81d8-d1bdadd888c5 @@ -122,71 +122,6 @@ In the above example, using the `uuid` type we previously defined in `types`, we make sure the route function is only executed if the `GET` request is to a pathname starting with `user/` and followed by a valid UUID. -Parameters named the same as types will be automatically typed. Assume that we -created the following `userId` type that makes sure a dataset user exists with -the given type. - -```js caption=types/userId.js -import number from "./number.js"; - -const users = [ - { - id: 6161 - name: "Donald", - }, -]; - -export default id => { - type: "f64", - validate(id) { - // IDs must be numbers - const n = number(id); - - const user = users.find(user => user.id === n); - if (user !== undefined) { - return n; - } - throw new Error(`${id} is not a valid user ID`); - }, -}; -``` - -With that definition, using `{userId}` in any route will autotype it to the -`userId` type. - -```js caption=routes/user/{userId}.js -export default { - get(request) { - /* - GET /user/6161 - -> "User ID is 6161" - - GET /user/1616 - -> Error - */ - const userId = request.path.get("userId"); - return `User ID is ${userId}`; - } -} -``` - -Here we avoided typing out the route as `user/{userId=userId}.js` and relied -on Primate to match the type to the parameter name. In this case, `GET -/user/1616` cannot be matched to a route, as `1616` is not an ID of a user in -our dataset. - -By first checking that the given value is a number, we have also coerced it to -the correct JavaScript type, making sure the strict equally in the `find` -predicate works. Using such delegated typing allows you to distinguish between -different classes of errors: the input being a proper numeric string vs. -supplying the ID of an actual dataset user. - -!!! -If you do not wish Primate to autotype your path parameters, set -`types.explicit` to `true` in your configuration. In that case, you would need -to use the route filename `routes/user/{userId=userId}.js` instead. -!!! - ### Request query Likewise, the request's query string parts, which we previously accessed using diff --git a/docs/guide/use-cases.md b/docs/guide/use-cases.md index 0e9fed84..49201df6 100644 --- a/docs/guide/use-cases.md +++ b/docs/guide/use-cases.md @@ -27,7 +27,7 @@ Primate generally follows the OpenAPI specification in denoting path parameters with braces (`{}`) and making the body and path, query, cookie and header parameters easily accessible to the route function. -```js caption=routes/comment/{commentId}.js +```js caption=routes/comment/[commentId].js export default { post(request) { const { path, query, cookies, headers, body } = request; diff --git a/docs/modules/store.md b/docs/modules/store.md index 862a3e28..c06d90a1 100644 --- a/docs/modules/store.md +++ b/docs/modules/store.md @@ -222,14 +222,14 @@ saving this field into the database, it will use the driver's base type ### Strict -By default, fields aren't required to be non-empty (not `undefined` or `null`) -to save a new document into the document. If you wish to strictly enforce all -fields to be non-empty, export `strict = true`. +By default, fields aren't required to be non-empty (`undefined` or `null`) +to save a new document into the store. If you wish to strictly enforce all +fields to be non-empty, export `mode = "strict"`. ```js caption=stores/Comment.js import { primary, string } from "primate/@types"; -export const strict = true; +export const mode = "strict"; export default { id: primary, @@ -238,7 +238,7 @@ export default { ``` You can also globally enforce strictness for all stores by configuring this -module with `strict: true`. +module with `mode: "strict"`. ```js caption=primate.config.js import store from "@primate/store"; @@ -246,18 +246,18 @@ import store from "@primate/store"; export default { modules: [ store({ - strict: true, + mode: "strict", }), ], }; ``` In that case, you can opt-out on individual store level by exporting -`strict = false`. +`mode = "loose"`. ```js caption=stores/Comment.js import { primary, string } from "@primate/types"; -export const strict = false; +export const mode = "loose"; export default { id: primary, @@ -265,6 +265,12 @@ export default { }; ``` +!!! +The store module treats `undefined` and `null` differently on updates. When +updating a document, `undefined` means you want to leave the field's value as +is, while `null` nullifies the field. +!!! + ### Name The filenames you give to store files affect to which underlying store they are @@ -440,14 +446,24 @@ non-volatile alternative driver which stores its data in a JSON file. Other supported DMBSs are [MongoDB][mongodb],[PostgreSQL][postgresql], [MySQL][mysql] and [SQLite][sqlite]. -### strict +### mode -Default `false` +Default `"loose"` Whether all store fields must be non-empty before saving. In many cases, you -want some values to be nullable. Setting this to `true` forbids any store from -saving empty values to the database, unless it has overridden that value by -using `export const strict = false;`. +want some values to be nullable. Setting this to `"strict"` forbids any store +from saving empty values to the database, unless it has overridden that value +by using `export const mode = "strict";`. + +In addition, `loose` allows you to save to fields that haven't been explicitly +declared in your store definition. This is particulary useful for NoSQL +databases that do not a rigid schema, where you want to enforce types on some +fields and accept anything in others. + +!!! +For SQL databases, we will add the ability in the future to declare a catchall +JSON column that would serve the same purpose. +!!! ## Error list diff --git a/packages/binding/package.json b/packages/binding/package.json index 0050e3ad..da4c432f 100644 --- a/packages/binding/package.json +++ b/packages/binding/package.json @@ -1,6 +1,6 @@ { "name": "@primate/binding", - "version": "0.5.2", + "version": "0.6.0", "description": "Primate binding module", "homepage": "https://primatejs.com/modules/binding", "bugs": "https://github.com/primatejs/primate/issues", @@ -27,7 +27,7 @@ "@ruby/head-wasm-wasi": "2", "@ruby/wasm-wasi": "2", "@swc/core": "1", - "primate": "0.29", + "primate": "0.30", "pyodide": "0.25" }, "peerDependenciesMeta": { diff --git a/packages/build/package.json b/packages/build/package.json index 833bf57a..64fb36dc 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -1,6 +1,6 @@ { "name": "@primate/build", - "version": "0.4.2", + "version": "0.5.0", "description": "Primate build module", "homepage": "https://primatejs.com/modules/build", "bugs": "https://github.com/primatejs/primate/issues", @@ -23,7 +23,7 @@ }, "peerDependencies": { "esbuild": "0.20", - "primate": "0.29" + "primate": "0.30" }, "peerDependenciesMeta": { "esbuild": { diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 87e6ecc9..5df33daf 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@primate/frontend", - "version": "0.13.9", + "version": "0.14.0", "description": "Primate frontend code", "bugs": "https://github.com/primatejs/primate/issues", "license": "MIT", @@ -31,7 +31,7 @@ "handlebars": "4", "htmx-esm": "0.2", "marked": "12", - "primate": "0.29", + "primate": "0.30", "react": "18", "react-dom": "18", "solid-js": "1", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index a2ea2a5e..30f68439 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@primate/i18n", - "version": "0.5.2", + "version": "0.6.0", "description": "Primate I18N module", "homepage": "https://primatejs.com/modules/i18n", "bugs": "https://github.com/primatejs/primate/issues", @@ -19,7 +19,7 @@ "rcompat": "^0.9.4" }, "peerDependencies": { - "primate": "0.29" + "primate": "0.30" }, "type": "module", "exports": { diff --git a/packages/primate/package.json b/packages/primate/package.json index b7add183..d0812a07 100644 --- a/packages/primate/package.json +++ b/packages/primate/package.json @@ -1,6 +1,6 @@ { "name": "primate", - "version": "0.30-next", + "version": "0.30.0", "description": "Polymorphic development platform", "homepage": "https://primatejs.com", "bugs": "https://github.com/primatejs/primate/issues", diff --git a/packages/session/package.json b/packages/session/package.json index ad7e06ea..70d7b977 100644 --- a/packages/session/package.json +++ b/packages/session/package.json @@ -1,6 +1,6 @@ { "name": "@primate/session", - "version": "0.17.1", + "version": "0.18.0", "description": "Primate session module", "homepage": "https://primatejs.com/modules/session", "bugs": "https://github.com/primatejs/primate/issues", @@ -18,7 +18,7 @@ "rcompat": "^0.9.4" }, "peerDependencies": { - "primate": "0.29" + "primate": "0.30" }, "type": "module", "exports": "./src/exports.js" diff --git a/packages/store/package.json b/packages/store/package.json index 01955999..d67f6857 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -1,6 +1,6 @@ { "name": "@primate/store", - "version": "0.22.3", + "version": "0.23.0", "description": "Primate data store module", "homepage": "https://primatejs.com/modules/store", "bugs": "https://github.com/primatejs/primate/issues", @@ -22,7 +22,7 @@ "rcompat": "^0.9.4" }, "devDependencies": { - "@primate/types": "^0.13.1", + "@primate/types": "^0.14.0", "better-sqlite3": "^9.4.3", "mongodb": "^6.3.0", "mysql2": "^3.9.1", @@ -34,7 +34,7 @@ "mongodb": "6", "mysql2": "3", "postgres": "3", - "primate": "0.29", + "primate": "0.30", "surrealdb.js": "0.11" }, "peerDependenciesMeta": { diff --git a/packages/types/package.json b/packages/types/package.json index 0f7be3f5..bac6a2f7 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@primate/types", - "version": "0.13.1", + "version": "0.14.0", "description": "Primate types module", "homepage": "https://primatejs.com/modules/types", "bugs": "https://github.com/primatejs/primate/issues", @@ -18,7 +18,7 @@ "rcompat": "^0.9.4" }, "peerDependencies": { - "primate": "0.29" + "primate": "0.30" }, "type": "module", "exports": "./src/exports.js"