@protobase/cli 0.66.4 → 0.66.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.66.4";
2
+ var version = "0.66.6";
3
3
  //#endregion
4
4
  export { version };
@@ -0,0 +1,27 @@
1
+ [
2
+ "@protobase/config",
3
+ "@protobase/schema",
4
+ "@protobase/layout",
5
+ "@protobase/layout/jsx-runtime",
6
+ "@protobase/query",
7
+ "@protobase/server",
8
+ "@protobase/server/pdf",
9
+ "@protobase/server/email",
10
+ "kysely",
11
+ "kysely/helpers/postgres",
12
+ "pg",
13
+ "postgres",
14
+ "better-auth",
15
+ "hono",
16
+ "hono/cors",
17
+ "hono/http-exception",
18
+ "hono/validator",
19
+ "jose",
20
+ "zod",
21
+ "aip-parsers",
22
+ "@hono/node-server",
23
+ "@scalar/hono-api-reference",
24
+ "react",
25
+ "react/jsx-runtime",
26
+ "@react-email/render"
27
+ ]
@@ -1,20 +1,24 @@
1
1
  import { ConnectionsBusy, poolWaitMs } from "@protobase/server";
2
2
  //#region src/project/pool-wait.ts
3
3
  /**
4
- * The pool's `connect`, where a query that finds every connection in use and waits `poolWaitMs` for one (the pool's
5
- * own `connectionTimeoutMillis`) fails with `ConnectionsBusy`, so a saturated app answers at once rather than queue
6
- * requests behind slow ones. The pool tells its timeout by no code, so a failure counts as one when every connection
7
- * was in use as the query asked; any other failure is the pool's own.
4
+ * The pool's `connect`, where a query that finds every connection in use and waits `poolWaitMs` for one fails with
5
+ * `ConnectionsBusy`, so a saturated app answers at once rather than queue requests behind slow ones. A query that finds
6
+ * a connection free, or room for a new one, waits as long as the pool lets a new connection take to open, such as while
7
+ * a database that scaled to zero wakes. The pool still hands a query that gave up the connection it waited for, which
8
+ * goes straight back.
8
9
  */
9
10
  const connectOrBusy = (pool) => async () => {
10
11
  const max = pool.options.max ?? 10;
11
- const full = pool.totalCount >= max && pool.idleCount === 0;
12
- try {
13
- return await pool.connect();
14
- } catch (error) {
15
- if (full && !pool.ending) throw new ConnectionsBusy(max, poolWaitMs, { cause: error });
16
- throw error;
17
- }
12
+ if (!(pool.totalCount >= max && pool.idleCount === 0)) return pool.connect();
13
+ const connecting = pool.connect();
14
+ let timer;
15
+ const gaveUp = new Promise((resolve) => {
16
+ timer = setTimeout(() => resolve("gave up"), poolWaitMs);
17
+ });
18
+ const first = await Promise.race([connecting, gaveUp]).finally(() => clearTimeout(timer));
19
+ if (first !== "gave up") return first;
20
+ connecting.then((client) => client.release(), () => {});
21
+ throw new ConnectionsBusy(max, poolWaitMs);
18
22
  };
19
23
  //#endregion
20
24
  export { connectOrBusy };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/cli",
3
- "version": "0.66.4",
3
+ "version": "0.66.6",
4
4
  "description": "The protobase command: scaffold, doctor, dev, build and serve a Protobase project.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -22,7 +22,8 @@
22
22
  "exports": {
23
23
  "./auth": "./dist/project-auth.js",
24
24
  "./runner": "./dist/runner.js",
25
- "./scaffold": "./dist/scaffold-api.js"
25
+ "./scaffold": "./dist/scaffold-api.js",
26
+ "./host-modules.json": "./dist/host-modules.json"
26
27
  },
27
28
  "bin": {
28
29
  "protobase": "./bin/protobase.mjs"
@@ -33,7 +34,7 @@
33
34
  "skills"
34
35
  ],
35
36
  "scripts": {
36
- "build": "bun ../../scripts/protobase-skill.ts && tsdown",
37
+ "build": "bun ../../scripts/protobase-skill.ts && tsdown --config-loader tsx",
37
38
  "prepack": "bun run build"
38
39
  },
39
40
  "publishConfig": {
@@ -42,12 +43,12 @@
42
43
  "dependencies": {
43
44
  "@better-auth/core": "^1.7.12",
44
45
  "@hono/node-server": "^2.1.3",
45
- "@protobase/config": "0.66.4",
46
- "@protobase/layout": "0.66.4",
47
- "@protobase/query": "0.66.4",
48
- "@protobase/schema": "0.66.4",
49
- "@protobase/server": "0.66.4",
50
- "@protobase/ui": "0.66.4",
46
+ "@protobase/config": "0.66.6",
47
+ "@protobase/layout": "0.66.6",
48
+ "@protobase/query": "0.66.6",
49
+ "@protobase/schema": "0.66.6",
50
+ "@protobase/server": "0.66.6",
51
+ "@protobase/ui": "0.66.6",
51
52
  "@react-email/render": "^2.1.0",
52
53
  "@scalar/hono-api-reference": "^0.12.10",
53
54
  "@tailwindcss/vite": "^4.3.3",
@@ -17,6 +17,7 @@ Develop, build and run an app, on servers, in containers and on phones.
17
17
  - [Extensions](../reference/cli.md#extensions): A host adds its own widgets, dock assistant tools (`assistant/tools.ts`) and MCP tools (`mcp/tools.ts`) to every app it builds.
18
18
  - [Native packages](../reference/cli.md#native-packages): npm packages with native add-ons are bundled for Linux.
19
19
  - [Serve runtime](../reference/cli.md#serve): Runs a bundle, set by environment variables, with a health check, startup checks, jobs and a clean shutdown.
20
+ - [Host module list](../reference/cli.md#the-config-module): Each release of `@protobase/cli` carries the modules its runtime supplies to a bundle as `@protobase/cli/host-modules.json`, for a host to check a bundle's imports against the version that serves it.
20
21
  - [Migrations at update time](../guides/deploy.md#migrations): The bundle carries the project's migrations, and the runtime applies them in one transaction before the new version serves.
21
22
  - [Single-file runtime](../reference/cli.md#build-serve): The runtime as one file, so a host needs only Bun and that file.
22
23
  - [Separate worker](../reference/cli.md#worker): Jobs and event delivery in their own process.
@@ -28,7 +28,7 @@ dist/
28
28
  plugins and the app's version
29
29
  ```
30
30
 
31
- The host serves `public/` itself and sends only the manifest's `api` paths to the runtime (`/api` and the two OAuth discovery documents of [MCP clients](../reference/auth.md#oauth-for-mcp-clients)), and `/.well-known/webauthn` for an app on [several origins](../reference/auth.md#several-origins), which the manifest does not list, so loading the UI never needs a running container. With the UI it sends `X-Frame-Options: DENY` and `Content-Security-Policy: frame-ancestors 'none'`, as `protobase serve` does, so no other site frames the app's pages. The runtime serves the API alone; `protobase serve dist` serves the whole folder the same way, to check a bundle locally. The host passes `DATABASE_URL`, `PORT` (default 8787) and optionally `PROTOBASE_DB_CONNECTIONS` (the connections the app may open, so its functions and jobs leave one to requests, see [`serve`](../reference/cli.md#serve)), `REQUEST_LOG`, `PROTOBASE_SMTP_URL` and `PROTOBASE_MAIL_FROM` (the mail server and sender for [password reset](../reference/auth.md#password-reset) and emailed sign-in codes) `PROTOBASE_APP_NAME` (the app's name in that mail, see [`name`](../reference/config/app.md#name)) `PROTOBASE_SIGN_IN_*` (a provider people with an account sign in with, the [platform sign-in provider](../reference/auth.md#platform-sign-in-provider)) `PROTOBASE_OPERATOR_*` (the provider its staff sign in with for [staff sign-in](../reference/auth.md#staff-sign-in)), `PROTOBASE_ORIGINS` and `PROTOBASE_PASSKEY_DOMAIN` (the app's [other origins](../reference/auth.md#several-origins) and its passkey domain) `PROTOBASE_CORS_ORIGINS` (the websites that may call its API, see [CORS](../reference/api.md#cors)), `PROTOBASE_TRUSTED_PROXIES` (the proxies in front of the runtime, whose `X-Forwarded-For` names the client the sign-in limits count, see [Client addresses](../reference/auth.md#client-addresses)) and `PROTOBASE_SENTRY_DSN` (the app's [error tracker](../reference/api.md#error-reporting)), `PROTOBASE_URL` and `PROTOBASE_SECRET` (the app's address and secret, unless its config sets [`url` and `secret`](../reference/config/app.md)), plus the project's own settings, which its config reads where it serves. The runtime and the config module are plain JavaScript for Bun: no install, no TypeScript, no writes. The runtime supplies protobase and the dependencies it runs on the server (`pg`, `postgres`, `kysely`, `better-auth`, ...), so the config module carries only the project's code and the other packages it uses. The manifest's `protobase` is the version the bundle was built with, the building CLI's in a [bundled project](../reference/versioning.md#bundled-and-locked-projects) and the installed one in a locked project, and the host serves the bundle on the runtime of that version (see [Versioning](../reference/versioning.md#bundles-and-runtimes)). The manifest format and the routing rules are in the CLI reference: [`build`](../reference/cli.md#build), [`build-serve`](../reference/cli.md#build-serve) and [`serve`](../reference/cli.md#serve).
31
+ The host serves `public/` itself and sends only the manifest's `api` paths to the runtime (`/api` and the two OAuth discovery documents of [MCP clients](../reference/auth.md#oauth-for-mcp-clients)), and `/.well-known/webauthn` for an app on [several origins](../reference/auth.md#several-origins), which the manifest does not list, so loading the UI never needs a running container. With the UI it sends `X-Frame-Options: DENY` and `Content-Security-Policy: frame-ancestors 'none'`, as `protobase serve` does, so no other site frames the app's pages. The runtime serves the API alone; `protobase serve dist` serves the whole folder the same way, to check a bundle locally. The host passes `DATABASE_URL`, `PORT` (default 8787) and optionally `PROTOBASE_DB_CONNECTIONS` (the connections the app may open, so its functions and jobs leave one to requests, see [`serve`](../reference/cli.md#serve)), `REQUEST_LOG`, `PROTOBASE_SMTP_URL` and `PROTOBASE_MAIL_FROM` (the mail server and sender for [password reset](../reference/auth.md#password-reset) and emailed sign-in codes) `PROTOBASE_APP_NAME` (the app's name in that mail, see [`name`](../reference/config/app.md#name)) `PROTOBASE_SIGN_IN_*` (a provider people with an account sign in with, the [platform sign-in provider](../reference/auth.md#platform-sign-in-provider)) `PROTOBASE_OPERATOR_*` (the provider its staff sign in with for [staff sign-in](../reference/auth.md#staff-sign-in)), `PROTOBASE_ORIGINS` and `PROTOBASE_PASSKEY_DOMAIN` (the app's [other origins](../reference/auth.md#several-origins) and its passkey domain) `PROTOBASE_CORS_ORIGINS` (the websites that may call its API, see [CORS](../reference/api.md#cors)), `PROTOBASE_TRUSTED_PROXIES` (the proxies in front of the runtime, whose `X-Forwarded-For` names the client the sign-in limits count, see [Client addresses](../reference/auth.md#client-addresses)) and `PROTOBASE_SENTRY_DSN` (the app's [error tracker](../reference/api.md#error-reporting)), `PROTOBASE_URL` and `PROTOBASE_SECRET` (the app's address and secret, unless its config sets [`url` and `secret`](../reference/config/app.md)), plus the project's own settings, which its config reads where it serves. The runtime and the config module are plain JavaScript for Bun: no install, no TypeScript, no writes. The runtime supplies protobase and the dependencies it runs on the server (`pg`, `postgres`, `kysely`, `better-auth`, ...), so the config module carries only the project's code and the other packages it uses. Each release lists the modules it supplies in `@protobase/cli/host-modules.json` (see [the config module](../reference/cli.md#the-config-module)), for a host to check a bundle's imports against the runtime that serves it. The manifest's `protobase` is the version the bundle was built with, the building CLI's in a [bundled project](../reference/versioning.md#bundled-and-locked-projects) and the installed one in a locked project, and the host serves the bundle on the runtime of that version (see [Versioning](../reference/versioning.md#bundles-and-runtimes)). The manifest format and the routing rules are in the CLI reference: [`build`](../reference/cli.md#build), [`build-serve`](../reference/cli.md#build-serve) and [`serve`](../reference/cli.md#serve).
32
32
 
33
33
  ## Migrations
34
34
 
@@ -201,7 +201,7 @@ The API sends no CORS headers until it has something to allow: other websites in
201
201
  ## Safety
202
202
 
203
203
  - Every request runs in a transaction with `SET LOCAL statement_timeout` (`statementTimeoutMs`, default 1 s), so a slow query fails where it runs instead of holding a connection that other requests wait for. List, search, get, histogram, series, map and seek transactions are read-only. A query that runs longer is stopped: a `504` `statement-timeout` that names the request, its limit and the ways out, such as `GET /api/v1/orders ran a query longer than 1 s, so the database stopped it. Make the query faster, such as with an index or a narrower filter, or move the work into a function or job with longQueries: true, whose queries may run up to 30 s.` It goes to the app's error hook (`hooks/error.ts`, `createAdmin`'s `options.onUnhandledError`) and the [error tracker](#error-reporting) like a `500`. A read that needs longer has no way to opt out: give it an index, or move it into a [function](../guides/api-functions.md#long-queries) or [job](jobs.md) with `longQueries`.
204
- - Under `protobase dev` and `serve`, a request waits at most 2 seconds for a free database connection. When every connection stays busy that long, it is a `503` `connections-busy` with `Retry-After: 1` that names the request, reported the same way, rather than a request that hangs behind slow ones.
204
+ - Under `protobase dev` and `serve`, a request waits at most 2 seconds for a free database connection. When every connection stays busy that long, it is a `503` `connections-busy` with `Retry-After: 1` that names the request, reported the same way, rather than a request that hangs behind slow ones. A request that finds room for a new connection waits up to 30 seconds for it to open, such as while a database that scaled to zero wakes.
205
205
  - **Scan guard**: a list, `:search`, `:seek`, `:map` page, card photos, `:facets` and `:aggregate` read only columns an index serves, so they stay cheap however many rows match. The server reads each table's valid indexes from the catalog once, then checks every column the caller's filter, the role's row filter, the sort's first field, a facet's or an aggregate's grouped field and a [`read` parent](access.md#records-that-belong-to-another)'s relation read, and the columns of the parent's own row filter against the parent's table:
206
206
  - Comparisons, `in`, null checks, sorts and facets need a btree index with the column as a key, every key before it pinned by the query: by `=` or `in` in the filter (outside `OR` and `NOT`), by the tenant or by soft delete. `(for_sale, status, published_at desc)` serves `for_sale = true AND status = "available"` sorted by `published_at desc`, but not `status` alone. `=` on text may use a pg_trgm index instead.
207
207
  - Patterns (`name = "*port*"`), `regex()`, `similar()` and `search()` need a pg_trgm (GIN or GiST) index on each column they read, a `digitsEnd` search field one on its digits; JSON `field:value` needs a GIN index.
@@ -257,7 +257,7 @@ The modules the app shares with plugins, `react`, `react/jsx-runtime`, `react-do
257
257
 
258
258
  | Modules | |
259
259
  | --- | --- |
260
- | `@protobase/schema`, `@protobase/layout`, `@protobase/layout/jsx-runtime`, `@protobase/query`, `@protobase/server` | the server entry points of the `@protobase` packages, and what [layout files](layouts.md) compile to |
260
+ | `@protobase/config`, `@protobase/schema`, `@protobase/layout`, `@protobase/layout/jsx-runtime`, `@protobase/query`, `@protobase/server` | the server entry points of the `@protobase` packages, and what [layout files](layouts.md) compile to |
261
261
  | `kysely`, `kysely/helpers/postgres`, `pg`, `postgres` | the database |
262
262
  | `better-auth`, `hono`, `jose`, `zod`, `aip-parsers`, `@hono/node-server`, `@scalar/hono-api-reference` | the main entry of each other dependency the `@protobase` packages run on the server |
263
263
  | `hono/cors`, `hono/http-exception`, `hono/validator` | Hono middleware for [API functions](../guides/api-functions.md) |
@@ -269,7 +269,7 @@ The modules the app shares with plugins, `react`, `react/jsx-runtime`, `react-do
269
269
 
270
270
  `@react-pdf/renderer` is a dependency of `@protobase/server` that the runtime keeps to itself: a config imports React PDF's components from `@protobase/server/pdf` instead.
271
271
 
272
- Importing any other `@protobase` package or dependency of one (`@protobase/ui`, `react-dom`, `better-auth/plugins`, `zod/v4`, ...) fails the build with this list, also from inside an inlined package, when the finished bundle still imports it: an inlined package may re-export a module that imports one, as `react-email` does with `tailwindcss`, as long as the project leaves that export unused. The runtime supplies them at its own versions, whichever the project installed. The list is `packages/cli/src/serve/host-module-ids.ts`; the set of packages comes from the `package.json` of `@protobase/cli` and of every `@protobase` package it depends on.
272
+ Importing any other `@protobase` package or dependency of one (`@protobase/ui`, `react-dom`, `better-auth/plugins`, `zod/v4`, ...) fails the build with this list, also from inside an inlined package, when the finished bundle still imports it: an inlined package may re-export a module that imports one, as `react-email` does with `tailwindcss`, as long as the project leaves that export unused. The runtime supplies them at its own versions, whichever the project installed. The list is `packages/cli/src/serve/host-module-ids.ts`, and each release of `@protobase/cli` publishes it as `@protobase/cli/host-modules.json`, a JSON array of the module names, so a host reads the list of the version that serves a bundle without running it; the set of packages comes from the `package.json` of `@protobase/cli` and of every `@protobase` package it depends on.
273
273
  - Packages with a native add-on stay imports too, and the bundle carries them in `node_modules/` (see [Native packages](#native-packages)). Node's built-in modules stay imports.
274
274
  - Everything else (the project's files and its other packages) is inlined. Nothing is minified.
275
275
  - Layout files (`@jsxImportSource @protobase/layout`) compile to calls of `@protobase/layout/jsx-runtime`, for production whatever `NODE_ENV` says. The build refuses one with a function, class, `new` value or hook in its JSX, naming the file, line and column ([what a layout may hold](layouts.md#what-a-layout-may-hold)).
@@ -388,7 +388,7 @@ Serves the API of a bundle from `protobase build` and owns the process around it
388
388
  | Variable | |
389
389
  | --- | --- |
390
390
  | `DATABASE_URL` | the API's pool (Kysely over `pg`, 10 connections) |
391
- | `PROTOBASE_DB_CONNECTIONS` | the connections that pool opens, such as the database's limit for the app; default 10. Sign-in's store shares them. [API functions](../guides/api-functions.md) and [jobs](jobs.md) hold at most one less, so a long import leaves a connection to page and API requests; those with [`longQueries`](../guides/api-functions.md#long-queries) take theirs from one more connection, kept for them. A query waits at most 2 seconds for a free connection, then fails with `503 connections-busy` ([Safety](api.md#safety)) |
391
+ | `PROTOBASE_DB_CONNECTIONS` | the connections that pool opens, such as the database's limit for the app; default 10. Sign-in's store shares them. [API functions](../guides/api-functions.md) and [jobs](jobs.md) hold at most one less, so a long import leaves a connection to page and API requests; those with [`longQueries`](../guides/api-functions.md#long-queries) take theirs from one more connection, kept for them. A query waits at most 2 seconds for a free connection, then fails with `503 connections-busy` ([Safety](api.md#safety)); a new connection may take up to 30 seconds to open, such as while a database that scaled to zero wakes |
392
392
  | `PORT` | default 8787 |
393
393
  | `REQUEST_LOG` | `true` logs every request; default `false` |
394
394
  | `PROTOBASE_SMTP_URL` | the SMTP server for password-reset mail, emailed sign-in and two-factor codes and the app's own [email](email.md), credential included: `smtp://user:password@host:587` (STARTTLS when offered) or `smtps://...:465`; without it they are off ([Password reset](auth.md#password-reset), [Emailed sign-in codes](auth.md#emailed-sign-in-codes)) |
@@ -69,7 +69,7 @@ A `RecordCard` (its first match) and each card of a `CardRow` hold a **record**.
69
69
  | `status`, `plan.name` | A field of the record around it, through relations. |
70
70
  | `invoices.count` | How many `invoices` the user can see (an exact count from the list API). |
71
71
 
72
- Comparisons, `AND`, `OR`, `NOT` and parentheses work. <gap>A condition cannot call functions such as `now()`, as they are not supported.</gap> A field that is missing or that the user cannot read makes the comparison false, and a `Show` that names a resource or field the user cannot see is left out of their page.
72
+ Comparisons, `AND`, `OR`, `NOT`, parentheses, `isNull(field)` and `in(field, "a", "b")` work. <gap>A condition cannot call other functions, such as `now()`, because their evaluation is not implemented.</gap> A field that is missing or that the user cannot read makes the comparison false, and a `Show` that names a resource or field the user cannot see is left out of their page.
73
73
 
74
74
  Record pages take the same conditions in the `when` of `l.section`, `l.related` and `l.sidebar`; see [Form and read view](ui-config.md#form-and-read-view).
75
75