@palbase/backend 34.1.1 → 35.0.1
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/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +2 -2
- package/dist/{chunk-X2UYIXDS.js → chunk-6FEHCEB7.js} +4 -2
- package/dist/chunk-6FEHCEB7.js.map +1 -0
- package/dist/{chunk-XZPDPYVY.js → chunk-BOD3DT6D.js} +2 -2
- package/dist/{chunk-5UGPXKMJ.js → chunk-L4R7WXLH.js} +4 -1
- package/dist/chunk-L4R7WXLH.js.map +1 -0
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +1 -1
- package/dist/{endpoint-BXPdsTA5.d.ts → endpoint-BI0yGuEL.d.ts} +61 -2
- package/dist/{endpoint-DqhUP7RT.d.cts → endpoint-DaGnjt42.d.cts} +61 -2
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +3 -3
- package/dist/engine/index.d.ts +3 -3
- package/dist/engine/index.js +2 -2
- package/dist/{index-Co0Hj_aO.d.cts → index-DllNjpTl.d.cts} +2 -2
- package/dist/{index-MhCL7ll4.d.ts → index-E8Sd6NFy.d.ts} +2 -2
- package/dist/index.cjs +36 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -8
- package/dist/index.d.ts +13 -8
- package/dist/index.js +32 -3
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.d.cts +2 -2
- package/dist/openapi/index.d.ts +2 -2
- package/dist/{registry-BkIVHPzB.d.cts → registry-BnKvIyve.d.cts} +1 -1
- package/dist/{registry-CvGLR5j1.d.ts → registry-zDCj0eOU.d.ts} +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/docs/README.md +1 -1
- package/docs/database.md +97 -22
- package/docs/llms-full.txt +98 -23
- package/package.json +1 -1
- package/stager/generics.js +12 -4
- package/stager/stage.js +38 -3
- package/template/package.json +2 -2
- package/dist/chunk-5UGPXKMJ.js.map +0 -1
- package/dist/chunk-X2UYIXDS.js.map +0 -1
- /package/dist/{chunk-XZPDPYVY.js.map → chunk-BOD3DT6D.js.map} +0 -0
package/dist/openapi/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OpenApiGeneratorV31 } from '@asteasolutions/zod-to-openapi';
|
|
2
2
|
import { ZodTypeAny } from 'zod';
|
|
3
|
-
import { A as AuthSpec, R as RateLimitConfig } from '../endpoint-
|
|
4
|
-
import { P as ParamMeta, T as ThrowDescriptor, U as UploadConfig, S as SseConfig } from '../registry-
|
|
3
|
+
import { A as AuthSpec, R as RateLimitConfig } from '../endpoint-DaGnjt42.cjs';
|
|
4
|
+
import { P as ParamMeta, T as ThrowDescriptor, U as UploadConfig, S as SseConfig } from '../registry-BnKvIyve.cjs';
|
|
5
5
|
import '../stack.cjs';
|
|
6
6
|
import '../db/env.cjs';
|
|
7
7
|
import 'node:async_hooks';
|
package/dist/openapi/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OpenApiGeneratorV31 } from '@asteasolutions/zod-to-openapi';
|
|
2
2
|
import { ZodTypeAny } from 'zod';
|
|
3
|
-
import { A as AuthSpec, R as RateLimitConfig } from '../endpoint-
|
|
4
|
-
import { P as ParamMeta, T as ThrowDescriptor, U as UploadConfig, S as SseConfig } from '../registry-
|
|
3
|
+
import { A as AuthSpec, R as RateLimitConfig } from '../endpoint-BI0yGuEL.js';
|
|
4
|
+
import { P as ParamMeta, T as ThrowDescriptor, U as UploadConfig, S as SseConfig } from '../registry-zDCj0eOU.js';
|
|
5
5
|
import '../stack.js';
|
|
6
6
|
import '../db/env.js';
|
|
7
7
|
import 'node:async_hooks';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DatabaseBudget, A as AuthSpec, R as RateLimitConfig } from './endpoint-
|
|
1
|
+
import { D as DatabaseBudget, A as AuthSpec, R as RateLimitConfig } from './endpoint-DaGnjt42.cjs';
|
|
2
2
|
import { ZodTypeAny } from 'zod';
|
|
3
3
|
import { PalbaseBucketName } from './stack.cjs';
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DatabaseBudget, A as AuthSpec, R as RateLimitConfig } from './endpoint-
|
|
1
|
+
import { D as DatabaseBudget, A as AuthSpec, R as RateLimitConfig } from './endpoint-BI0yGuEL.js';
|
|
2
2
|
import { ZodTypeAny } from 'zod';
|
|
3
3
|
import { PalbaseBucketName } from './stack.js';
|
|
4
4
|
|
package/dist/test/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { T as Token } from '../module-Dl1KFVtc.cjs';
|
|
2
|
-
import { E as EnvServiceDatabase, a as DBClient, b as RuntimeServices } from '../endpoint-
|
|
2
|
+
import { E as EnvServiceDatabase, a as DBClient, b as RuntimeServices } from '../endpoint-DaGnjt42.cjs';
|
|
3
3
|
import 'zod';
|
|
4
4
|
import '../stack.cjs';
|
|
5
5
|
import '../db/env.cjs';
|
package/dist/test/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { T as Token } from '../module-Dl1KFVtc.js';
|
|
2
|
-
import { E as EnvServiceDatabase, a as DBClient, b as RuntimeServices } from '../endpoint-
|
|
2
|
+
import { E as EnvServiceDatabase, a as DBClient, b as RuntimeServices } from '../endpoint-BI0yGuEL.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
import '../stack.js';
|
|
5
5
|
import '../db/env.js';
|
package/docs/README.md
CHANGED
|
@@ -82,7 +82,7 @@ service the controllers call.
|
|
|
82
82
|
|
|
83
83
|
> **Never** emit `defineController`, `defineHandler`, `defineEndpoint`, `route.get(...)`,
|
|
84
84
|
> `req.input`, `req.params`, or `req.errors` — those are the removed legacy model
|
|
85
|
-
> and will not compile against `@palbase/backend`
|
|
85
|
+
> and will not compile against `@palbase/backend` 35.
|
|
86
86
|
|
|
87
87
|
### Complete CRUD example (copy-pasteable, compiles)
|
|
88
88
|
|
package/docs/database.md
CHANGED
|
@@ -825,7 +825,56 @@ continue to return their rows. Custom SQL drivers must expose an integer `count`
|
|
|
825
825
|
on a non-returning result. Empty input opens no connection. The batch remains one
|
|
826
826
|
statement and participates in its enclosing transaction's rollback.
|
|
827
827
|
|
|
828
|
-
##
|
|
828
|
+
## Query results and response contracts
|
|
829
|
+
|
|
830
|
+
Use the ordinary CRUD expression. A controller without a return annotation gets
|
|
831
|
+
its response validator, OpenAPI response and generated client type from the
|
|
832
|
+
TypeScript return type, following calls through injected services:
|
|
833
|
+
|
|
834
|
+
```ts
|
|
835
|
+
// Store/service
|
|
836
|
+
list(prefix: string) {
|
|
837
|
+
return Database.public.todos.findMany({
|
|
838
|
+
where: { title: { startsWith: prefix } },
|
|
839
|
+
select: ["id", "title"],
|
|
840
|
+
limit: 50,
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Controller
|
|
845
|
+
@Get("/")
|
|
846
|
+
list(@QueryParams(ListInput) input: ListInput) {
|
|
847
|
+
return this.todos.list(input.prefix);
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
The generated contract contains only `id` and `title`. `findUnique` retains
|
|
852
|
+
nullability, `page` retains its rows/pageInfo shape, and `insertMany` with
|
|
853
|
+
`returning: false` returns a count. Use `updateMany` for one bulk UPDATE; a
|
|
854
|
+
command plan is useful when several dependent operations actually belong together.
|
|
855
|
+
|
|
856
|
+
Inference does not execute application code or capture a Database/transaction
|
|
857
|
+
handle. Reuse a normal service method; inside an atomic callback, use its `tx`
|
|
858
|
+
argument. A service-role handle still owns a separate transaction and identity.
|
|
859
|
+
|
|
860
|
+
The build refuses `any`, `unknown`, unresolved imports, recursive/class/tuple
|
|
861
|
+
responses and unsupported JSON shapes. Supply a named Zod response schema when
|
|
862
|
+
needed. Existing named Zod annotations remain supported. Inference describes
|
|
863
|
+
the static JSON shape; an explicit schema remains useful for semantic constraints
|
|
864
|
+
such as UUID formats or a domain-specific numeric range. Input validation still
|
|
865
|
+
uses the existing `@Body`/`@QueryParams` schema.
|
|
866
|
+
|
|
867
|
+
Invalid response values and serialization failures roll the request transaction
|
|
868
|
+
back before COMMIT. A committed independent `$atomic` call retains its existing
|
|
869
|
+
independent commit semantics; response validation cannot undo an earlier commit.
|
|
870
|
+
An explicit nullable response returns HTTP 200 with JSON `null`; a response with
|
|
871
|
+
no body remains 204. This fixes nullable client contracts previously receiving an
|
|
872
|
+
empty body.
|
|
873
|
+
|
|
874
|
+
### Existing query definitions
|
|
875
|
+
|
|
876
|
+
`defineQuery` is deprecated for normal application work. Its existing `run`,
|
|
877
|
+
SQL inspection, manifests and validation remain supported for compatibility.
|
|
829
878
|
|
|
830
879
|
`defineQuery` combines an explicit selection with a reusable input/filter. The
|
|
831
880
|
result is also a Zod array schema: a controller can return
|
|
@@ -934,35 +983,60 @@ one invoker helper call with sequential statements inside PostgreSQL. The helper
|
|
|
934
983
|
adds JSON/dynamic-SQL processing; measure the actual plan and network before
|
|
935
984
|
selecting it for throughput. Fewer round trips alone do not guarantee a speedup.
|
|
936
985
|
|
|
937
|
-
|
|
986
|
+
Core owns admission for ordinary CRUD, raw queries, request transactions,
|
|
987
|
+
`$atomic`, service-role operations, upload authorization and scheduled database
|
|
988
|
+
work. Nested savepoints reuse their parent's admitted connection. Application
|
|
989
|
+
code does not declare concurrency or queue capacities:
|
|
938
990
|
|
|
939
991
|
```ts
|
|
940
|
-
const transfers = defineWorkload("transfers", {
|
|
941
|
-
concurrency: 16, queueLimit: 128,
|
|
942
|
-
tenantConcurrency: 8, tenantQueueLimit: 32, queueTimeoutMs: 1000,
|
|
943
|
-
});
|
|
944
|
-
|
|
945
992
|
await Database.$atomic(async tx => {
|
|
946
993
|
// Decision reads, idempotency claim, writes, ledger and outbox belong here.
|
|
947
994
|
}, {
|
|
948
|
-
workload: transfers,
|
|
949
|
-
conflictKeys: [fromAccountId, toAccountId],
|
|
950
995
|
timeoutMs: 3000, signal, retry: 2,
|
|
951
996
|
});
|
|
952
997
|
```
|
|
953
998
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
999
|
+
The core process shares one physical pool across live and candidate applications.
|
|
1000
|
+
Reloading or discarding an application does not create another pool. A retiring
|
|
1001
|
+
application drains its requests/jobs through transaction settlement before
|
|
1002
|
+
closing its own resources.
|
|
1003
|
+
|
|
1004
|
+
Before independent `$atomic` work, core releases an implicit transaction that
|
|
1005
|
+
contains only its authorization reads. This prevents requests holding the whole
|
|
1006
|
+
pool while each waits for a second connection. Role/permission decisions still
|
|
1007
|
+
come from verified identity and current database state, once per request. Once
|
|
1008
|
+
application SQL runs, its enclosing transaction is retained; atomic work never
|
|
1009
|
+
silently commits an application's earlier write or changes its identity.
|
|
1010
|
+
|
|
1011
|
+
Admission happens before BEGIN and its lease lasts through COMMIT/ROLLBACK.
|
|
1012
|
+
Full/expired queues return an explicit 429; cancellation removes queued work.
|
|
1013
|
+
The private runtime `/_internal/database` probe exposes current admission counts,
|
|
1014
|
+
rejection reasons and PostgreSQL connection groups. It is on the internal probe
|
|
1015
|
+
port, not the public edge.
|
|
1016
|
+
|
|
1017
|
+
These are resource protections, not product quotas or a transactions-per-second
|
|
1018
|
+
limit. `DB_POOL_MAX` remains a deployment setting, not a business-code parameter.
|
|
1019
|
+
It is a per-process maximum; PostgreSQL's server limit is shared by every
|
|
1020
|
+
process. Operators must account for every runtime application pool, control
|
|
1021
|
+
pool, palsvc pool, migration/admin connection and replication connection in
|
|
1022
|
+
their server resource plan. An instance-local queue is not a cluster-wide
|
|
1023
|
+
admission guarantee. Core contains no Free/Pro tier logic.
|
|
1024
|
+
|
|
1025
|
+
Optional deployment settings are `PALBASE_DB_QUEUE_LIMIT` (default: eight times
|
|
1026
|
+
the application pool size, bounded to 32–512), `PALBASE_DB_QUEUE_TIMEOUT_MS`
|
|
1027
|
+
(default: 1000), and `PALBASE_DB_TENANT_CLAIM`. These defaults bound transient
|
|
1028
|
+
memory/waiting; they are not a measured optimum for every installation. Fairness
|
|
1029
|
+
uses verified `sub` by default, with round robin under contention. One identity
|
|
1030
|
+
can use the entire available pool. A custom tenant claim must be signed and
|
|
1031
|
+
issuer-controlled; do not use editable user metadata. Missing metadata does not
|
|
1032
|
+
reduce an installation to a fraction of its pool.
|
|
1033
|
+
|
|
1034
|
+
Existing `defineWorkload` options retain their old behavior, including conflict
|
|
1035
|
+
ordering and any explicit tenant cap. They are deprecated, not silently ignored.
|
|
1036
|
+
Removing them removes that application-local scheduling policy; sorted database
|
|
1037
|
+
row locks and constraints remain the authority across all runtime instances.
|
|
1038
|
+
Do not place external service calls in a retryable callback. Use an idempotency
|
|
1039
|
+
claim and transactional outbox for effects that must survive retries.
|
|
966
1040
|
|
|
967
1041
|
`timeoutMs` includes queueing, callback work and retries. The default driver uses
|
|
968
1042
|
a separate control pool of at most two connections to cancel active PostgreSQL
|
|
@@ -1002,8 +1076,9 @@ disabled; the completion callback includes settlement timings. Only the first
|
|
|
1002
1076
|
|
|
1003
1077
|
| Metric | Meaning |
|
|
1004
1078
|
| --- | --- |
|
|
1005
|
-
| `queueMs` |
|
|
1079
|
+
| `queueMs` | Core admission and legacy workload wait, including refused waits |
|
|
1006
1080
|
| `poolMs` | Driver acquisition through transaction callback entry, including BEGIN |
|
|
1081
|
+
| `connectionMs` | Connection held from transaction callback entry through COMMIT/ROLLBACK settlement |
|
|
1007
1082
|
| `setupMs` | Isolation, cancellation ticket and role/claims setup |
|
|
1008
1083
|
| `sqlMs` | Awaited driver time, including execution, lock and network waits |
|
|
1009
1084
|
| `commitMs` / `rollbackMs` | Observed settlement waits |
|
package/docs/llms-full.txt
CHANGED
|
@@ -90,7 +90,7 @@ service the controllers call.
|
|
|
90
90
|
|
|
91
91
|
> **Never** emit `defineController`, `defineHandler`, `defineEndpoint`, `route.get(...)`,
|
|
92
92
|
> `req.input`, `req.params`, or `req.errors` — those are the removed legacy model
|
|
93
|
-
> and will not compile against `@palbase/backend`
|
|
93
|
+
> and will not compile against `@palbase/backend` 35.
|
|
94
94
|
|
|
95
95
|
### Complete CRUD example (copy-pasteable, compiles)
|
|
96
96
|
|
|
@@ -1929,7 +1929,56 @@ continue to return their rows. Custom SQL drivers must expose an integer `count`
|
|
|
1929
1929
|
on a non-returning result. Empty input opens no connection. The batch remains one
|
|
1930
1930
|
statement and participates in its enclosing transaction's rollback.
|
|
1931
1931
|
|
|
1932
|
-
##
|
|
1932
|
+
## Query results and response contracts
|
|
1933
|
+
|
|
1934
|
+
Use the ordinary CRUD expression. A controller without a return annotation gets
|
|
1935
|
+
its response validator, OpenAPI response and generated client type from the
|
|
1936
|
+
TypeScript return type, following calls through injected services:
|
|
1937
|
+
|
|
1938
|
+
```ts
|
|
1939
|
+
// Store/service
|
|
1940
|
+
list(prefix: string) {
|
|
1941
|
+
return Database.public.todos.findMany({
|
|
1942
|
+
where: { title: { startsWith: prefix } },
|
|
1943
|
+
select: ["id", "title"],
|
|
1944
|
+
limit: 50,
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// Controller
|
|
1949
|
+
@Get("/")
|
|
1950
|
+
list(@QueryParams(ListInput) input: ListInput) {
|
|
1951
|
+
return this.todos.list(input.prefix);
|
|
1952
|
+
}
|
|
1953
|
+
```
|
|
1954
|
+
|
|
1955
|
+
The generated contract contains only `id` and `title`. `findUnique` retains
|
|
1956
|
+
nullability, `page` retains its rows/pageInfo shape, and `insertMany` with
|
|
1957
|
+
`returning: false` returns a count. Use `updateMany` for one bulk UPDATE; a
|
|
1958
|
+
command plan is useful when several dependent operations actually belong together.
|
|
1959
|
+
|
|
1960
|
+
Inference does not execute application code or capture a Database/transaction
|
|
1961
|
+
handle. Reuse a normal service method; inside an atomic callback, use its `tx`
|
|
1962
|
+
argument. A service-role handle still owns a separate transaction and identity.
|
|
1963
|
+
|
|
1964
|
+
The build refuses `any`, `unknown`, unresolved imports, recursive/class/tuple
|
|
1965
|
+
responses and unsupported JSON shapes. Supply a named Zod response schema when
|
|
1966
|
+
needed. Existing named Zod annotations remain supported. Inference describes
|
|
1967
|
+
the static JSON shape; an explicit schema remains useful for semantic constraints
|
|
1968
|
+
such as UUID formats or a domain-specific numeric range. Input validation still
|
|
1969
|
+
uses the existing `@Body`/`@QueryParams` schema.
|
|
1970
|
+
|
|
1971
|
+
Invalid response values and serialization failures roll the request transaction
|
|
1972
|
+
back before COMMIT. A committed independent `$atomic` call retains its existing
|
|
1973
|
+
independent commit semantics; response validation cannot undo an earlier commit.
|
|
1974
|
+
An explicit nullable response returns HTTP 200 with JSON `null`; a response with
|
|
1975
|
+
no body remains 204. This fixes nullable client contracts previously receiving an
|
|
1976
|
+
empty body.
|
|
1977
|
+
|
|
1978
|
+
### Existing query definitions
|
|
1979
|
+
|
|
1980
|
+
`defineQuery` is deprecated for normal application work. Its existing `run`,
|
|
1981
|
+
SQL inspection, manifests and validation remain supported for compatibility.
|
|
1933
1982
|
|
|
1934
1983
|
`defineQuery` combines an explicit selection with a reusable input/filter. The
|
|
1935
1984
|
result is also a Zod array schema: a controller can return
|
|
@@ -2038,35 +2087,60 @@ one invoker helper call with sequential statements inside PostgreSQL. The helper
|
|
|
2038
2087
|
adds JSON/dynamic-SQL processing; measure the actual plan and network before
|
|
2039
2088
|
selecting it for throughput. Fewer round trips alone do not guarantee a speedup.
|
|
2040
2089
|
|
|
2041
|
-
|
|
2090
|
+
Core owns admission for ordinary CRUD, raw queries, request transactions,
|
|
2091
|
+
`$atomic`, service-role operations, upload authorization and scheduled database
|
|
2092
|
+
work. Nested savepoints reuse their parent's admitted connection. Application
|
|
2093
|
+
code does not declare concurrency or queue capacities:
|
|
2042
2094
|
|
|
2043
2095
|
```ts
|
|
2044
|
-
const transfers = defineWorkload("transfers", {
|
|
2045
|
-
concurrency: 16, queueLimit: 128,
|
|
2046
|
-
tenantConcurrency: 8, tenantQueueLimit: 32, queueTimeoutMs: 1000,
|
|
2047
|
-
});
|
|
2048
|
-
|
|
2049
2096
|
await Database.$atomic(async tx => {
|
|
2050
2097
|
// Decision reads, idempotency claim, writes, ledger and outbox belong here.
|
|
2051
2098
|
}, {
|
|
2052
|
-
workload: transfers,
|
|
2053
|
-
conflictKeys: [fromAccountId, toAccountId],
|
|
2054
2099
|
timeoutMs: 3000, signal, retry: 2,
|
|
2055
2100
|
});
|
|
2056
2101
|
```
|
|
2057
2102
|
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2103
|
+
The core process shares one physical pool across live and candidate applications.
|
|
2104
|
+
Reloading or discarding an application does not create another pool. A retiring
|
|
2105
|
+
application drains its requests/jobs through transaction settlement before
|
|
2106
|
+
closing its own resources.
|
|
2107
|
+
|
|
2108
|
+
Before independent `$atomic` work, core releases an implicit transaction that
|
|
2109
|
+
contains only its authorization reads. This prevents requests holding the whole
|
|
2110
|
+
pool while each waits for a second connection. Role/permission decisions still
|
|
2111
|
+
come from verified identity and current database state, once per request. Once
|
|
2112
|
+
application SQL runs, its enclosing transaction is retained; atomic work never
|
|
2113
|
+
silently commits an application's earlier write or changes its identity.
|
|
2114
|
+
|
|
2115
|
+
Admission happens before BEGIN and its lease lasts through COMMIT/ROLLBACK.
|
|
2116
|
+
Full/expired queues return an explicit 429; cancellation removes queued work.
|
|
2117
|
+
The private runtime `/_internal/database` probe exposes current admission counts,
|
|
2118
|
+
rejection reasons and PostgreSQL connection groups. It is on the internal probe
|
|
2119
|
+
port, not the public edge.
|
|
2120
|
+
|
|
2121
|
+
These are resource protections, not product quotas or a transactions-per-second
|
|
2122
|
+
limit. `DB_POOL_MAX` remains a deployment setting, not a business-code parameter.
|
|
2123
|
+
It is a per-process maximum; PostgreSQL's server limit is shared by every
|
|
2124
|
+
process. Operators must account for every runtime application pool, control
|
|
2125
|
+
pool, palsvc pool, migration/admin connection and replication connection in
|
|
2126
|
+
their server resource plan. An instance-local queue is not a cluster-wide
|
|
2127
|
+
admission guarantee. Core contains no Free/Pro tier logic.
|
|
2128
|
+
|
|
2129
|
+
Optional deployment settings are `PALBASE_DB_QUEUE_LIMIT` (default: eight times
|
|
2130
|
+
the application pool size, bounded to 32–512), `PALBASE_DB_QUEUE_TIMEOUT_MS`
|
|
2131
|
+
(default: 1000), and `PALBASE_DB_TENANT_CLAIM`. These defaults bound transient
|
|
2132
|
+
memory/waiting; they are not a measured optimum for every installation. Fairness
|
|
2133
|
+
uses verified `sub` by default, with round robin under contention. One identity
|
|
2134
|
+
can use the entire available pool. A custom tenant claim must be signed and
|
|
2135
|
+
issuer-controlled; do not use editable user metadata. Missing metadata does not
|
|
2136
|
+
reduce an installation to a fraction of its pool.
|
|
2137
|
+
|
|
2138
|
+
Existing `defineWorkload` options retain their old behavior, including conflict
|
|
2139
|
+
ordering and any explicit tenant cap. They are deprecated, not silently ignored.
|
|
2140
|
+
Removing them removes that application-local scheduling policy; sorted database
|
|
2141
|
+
row locks and constraints remain the authority across all runtime instances.
|
|
2142
|
+
Do not place external service calls in a retryable callback. Use an idempotency
|
|
2143
|
+
claim and transactional outbox for effects that must survive retries.
|
|
2070
2144
|
|
|
2071
2145
|
`timeoutMs` includes queueing, callback work and retries. The default driver uses
|
|
2072
2146
|
a separate control pool of at most two connections to cancel active PostgreSQL
|
|
@@ -2106,8 +2180,9 @@ disabled; the completion callback includes settlement timings. Only the first
|
|
|
2106
2180
|
|
|
2107
2181
|
| Metric | Meaning |
|
|
2108
2182
|
| --- | --- |
|
|
2109
|
-
| `queueMs` |
|
|
2183
|
+
| `queueMs` | Core admission and legacy workload wait, including refused waits |
|
|
2110
2184
|
| `poolMs` | Driver acquisition through transaction callback entry, including BEGIN |
|
|
2185
|
+
| `connectionMs` | Connection held from transaction callback entry through COMMIT/ROLLBACK settlement |
|
|
2111
2186
|
| `setupMs` | Isolation, cancellation ticket and role/claims setup |
|
|
2112
2187
|
| `sqlMs` | Awaited driver time, including execution, lock and network waits |
|
|
2113
2188
|
| `commitMs` / `rollbackMs` | Observed settlement waits |
|
package/package.json
CHANGED
package/stager/generics.js
CHANGED
|
@@ -163,6 +163,12 @@ const SKIP = new Set([
|
|
|
163
163
|
'.palbase',
|
|
164
164
|
'.palbase-staged-controllers',
|
|
165
165
|
'.palbase-build-controllers',
|
|
166
|
+
// An iOS app's build output beside the backend (stage.js SKIP, same list):
|
|
167
|
+
// not source, large, and Xcode writes symlinks into it.
|
|
168
|
+
'DerivedData',
|
|
169
|
+
'.build',
|
|
170
|
+
'Pods',
|
|
171
|
+
'xcuserdata',
|
|
166
172
|
]);
|
|
167
173
|
|
|
168
174
|
function sources(dir, root, out) {
|
|
@@ -173,12 +179,14 @@ function sources(dir, root, out) {
|
|
|
173
179
|
return out;
|
|
174
180
|
}
|
|
175
181
|
for (const entry of entries) {
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
|
|
182
|
+
if (SKIP.has(entry.name)) continue;
|
|
183
|
+
// Symlinks are skipped here for the same reason stage.js skips them: this
|
|
184
|
+
// walk runs BEFORE staging, on the whole project root, and a dangling link
|
|
185
|
+
// whose name ends in `.ts` used to refuse the entire push with ENOENT one
|
|
186
|
+
// function before the stager's own guard could (W4 r2 C-2).
|
|
187
|
+
if (entry.isSymbolicLink()) continue;
|
|
179
188
|
const full = path.join(dir, entry.name);
|
|
180
189
|
if (entry.isDirectory()) {
|
|
181
|
-
if (SKIP.has(entry.name)) continue;
|
|
182
190
|
sources(full, root, out);
|
|
183
191
|
} else if (/\.(c?ts|tsx)$/i.test(entry.name) && !/\.d\.ts$/i.test(entry.name)) {
|
|
184
192
|
out.push(full);
|
package/stager/stage.js
CHANGED
|
@@ -36,9 +36,23 @@ const throwAnalysis = require("./throw_analysis.js");
|
|
|
36
36
|
* resolves from above the staged tree anyway, and `.palbase*` holds build output
|
|
37
37
|
* including previous staging runs.
|
|
38
38
|
*/
|
|
39
|
-
|
|
39
|
+
//
|
|
40
|
+
// The last four are an iOS app's build output, which lives BESIDE the backend
|
|
41
|
+
// in a project that ships both (the 2026-09-06 demo did). None of it is source
|
|
42
|
+
// the stager could type, all of it is large, and Xcode writes symlinks into it.
|
|
43
|
+
const SKIP = new Set([
|
|
44
|
+
"node_modules",
|
|
45
|
+
"dist",
|
|
46
|
+
"build",
|
|
47
|
+
".git",
|
|
48
|
+
".palbase",
|
|
49
|
+
"DerivedData",
|
|
50
|
+
".build",
|
|
51
|
+
"Pods",
|
|
52
|
+
"xcuserdata",
|
|
53
|
+
]);
|
|
40
54
|
|
|
41
|
-
function walk(dir) {
|
|
55
|
+
function walk(dir, root = dir) {
|
|
42
56
|
const out = [];
|
|
43
57
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
44
58
|
// Skipped by NAME, not by type: `node_modules` is often a SYMLINK (pnpm, and
|
|
@@ -46,8 +60,29 @@ function walk(dir) {
|
|
|
46
60
|
// for one — so a type-gated check walked straight into it and tried to
|
|
47
61
|
// `copyFileSync` a symlinked directory. Measured: ENOTSUP, copyfile.
|
|
48
62
|
if (SKIP.has(entry.name) || entry.name.startsWith(".palbase")) continue;
|
|
63
|
+
// And every OTHER symlink is skipped by TYPE. A staged copy is a copy of
|
|
64
|
+
// the project's source; a link is a pointer to something that is either
|
|
65
|
+
// already in the tree (staged once under its real name), outside it (not
|
|
66
|
+
// this project's source), or gone. `copyFileSync` on one is ENOENT when the
|
|
67
|
+
// target is missing and ENOTSUP on a bind mount — measured 2026-09-06 on a
|
|
68
|
+
// demo whose `ios/DerivedData/Framework/Resources` was a dangling link, and
|
|
69
|
+
// that one link refused the whole push.
|
|
49
70
|
const full = path.join(dir, entry.name);
|
|
50
|
-
if (entry.
|
|
71
|
+
if (entry.isSymbolicLink()) {
|
|
72
|
+
// Said once, with the path from the project root, so a `TS2307 Cannot
|
|
73
|
+
// find module` in the staged copy has its explanation next to it: a
|
|
74
|
+
// project that symlinks a shared source directory stages a tree without
|
|
75
|
+
// it (the declared FR-037 trade-off).
|
|
76
|
+
let target = '';
|
|
77
|
+
try {
|
|
78
|
+
target = fs.readlinkSync(full);
|
|
79
|
+
} catch {
|
|
80
|
+
/* unreadable link: nothing to name */
|
|
81
|
+
}
|
|
82
|
+
process.stderr.write(`stager: skipped symlink ${path.relative(root, full)} -> ${target}\n`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (entry.isDirectory()) out.push(...walk(full, root));
|
|
51
86
|
else out.push(full);
|
|
52
87
|
}
|
|
53
88
|
return out;
|
package/template/package.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "A Palbase backend
|
|
6
|
+
"description": "A Palbase backend \u2014 class controllers, a declared database, and the secrets it needs.",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "./scripts/test.sh",
|
|
9
9
|
"typecheck": "tsc --noEmit"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@palbase/backend": "^
|
|
12
|
+
"@palbase/backend": "^35.0.0",
|
|
13
13
|
"reflect-metadata": "^0.2.2"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|