@protobase/cli 0.66.5 → 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.
package/dist/cli/package.js
CHANGED
|
@@ -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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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.
|
|
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": {
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@better-auth/core": "^1.7.12",
|
|
45
45
|
"@hono/node-server": "^2.1.3",
|
|
46
|
-
"@protobase/config": "0.66.
|
|
47
|
-
"@protobase/layout": "0.66.
|
|
48
|
-
"@protobase/query": "0.66.
|
|
49
|
-
"@protobase/schema": "0.66.
|
|
50
|
-
"@protobase/server": "0.66.
|
|
51
|
-
"@protobase/ui": "0.66.
|
|
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",
|
|
52
52
|
"@react-email/render": "^2.1.0",
|
|
53
53
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
54
54
|
"@tailwindcss/vite": "^4.3.3",
|
|
@@ -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.
|
|
@@ -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
|
|
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
|
|