@palbase/backend 22.1.0 → 23.0.0

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.
Files changed (71) hide show
  1. package/dist/bin/palbase-backend.cjs +439 -18
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +4 -4
  4. package/dist/{chunk-SQC5EIWY.js → chunk-FSGSB42K.js} +11 -6
  5. package/dist/chunk-FSGSB42K.js.map +1 -0
  6. package/dist/{chunk-I3ON7MYF.js → chunk-HAF67F2H.js} +73 -16
  7. package/dist/chunk-HAF67F2H.js.map +1 -0
  8. package/dist/{chunk-QMVK4X3V.js → chunk-OMRTHM4X.js} +98 -98
  9. package/dist/chunk-OMRTHM4X.js.map +1 -0
  10. package/dist/{chunk-74XDEF5J.js → chunk-REZU6UKT.js} +432 -17
  11. package/dist/chunk-REZU6UKT.js.map +1 -0
  12. package/dist/{chunk-YL4C5NRY.js → chunk-Y5HXVUMP.js} +2 -2
  13. package/dist/{chunk-N32VDWKH.js → chunk-ZC6Q2BRD.js} +4 -59
  14. package/dist/chunk-ZC6Q2BRD.js.map +1 -0
  15. package/dist/db/env.cjs.map +1 -1
  16. package/dist/db/env.d.cts +3 -21
  17. package/dist/db/env.d.ts +3 -21
  18. package/dist/db/index.cjs +72 -15
  19. package/dist/db/index.cjs.map +1 -1
  20. package/dist/db/index.d.cts +3 -2
  21. package/dist/db/index.d.ts +3 -2
  22. package/dist/db/index.js +1 -1
  23. package/dist/{endpoint-BVT6jcVW.d.cts → endpoint-BavvbW4P.d.ts} +77 -7
  24. package/dist/{endpoint-BVT6jcVW.d.ts → endpoint-i8TTCohk.d.cts} +77 -7
  25. package/dist/engine/index.cjs +439 -18
  26. package/dist/engine/index.cjs.map +1 -1
  27. package/dist/engine/index.d.cts +6 -5
  28. package/dist/engine/index.d.ts +6 -5
  29. package/dist/engine/index.js +3 -3
  30. package/dist/{index-vwHoS0l2.d.ts → index-B3jmmItD.d.ts} +111 -5
  31. package/dist/{index-CCZqzych.d.ts → index-B7YBEG5w.d.ts} +96 -71
  32. package/dist/{index-BqCiHao8.d.cts → index-Bmvx1EvJ.d.cts} +111 -5
  33. package/dist/{index-BS1gW4nV.d.cts → index-E7OscPJT.d.cts} +96 -71
  34. package/dist/index.cjs +285 -1099
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +89 -1134
  37. package/dist/index.d.ts +89 -1134
  38. package/dist/index.js +107 -902
  39. package/dist/index.js.map +1 -1
  40. package/dist/openapi/index.cjs +3 -57
  41. package/dist/openapi/index.cjs.map +1 -1
  42. package/dist/openapi/index.d.cts +3 -2
  43. package/dist/openapi/index.d.ts +3 -2
  44. package/dist/openapi/index.js +5 -22
  45. package/dist/openapi/index.js.map +1 -1
  46. package/dist/{registry-Bsuf-orT.d.ts → registry-C3H2uPeZ.d.cts} +13 -169
  47. package/dist/{registry-BWttGlaT.d.cts → registry-DY3d9l1k.d.ts} +13 -169
  48. package/dist/{purchases/keys.cjs → stack.cjs} +4 -4
  49. package/dist/stack.cjs.map +1 -0
  50. package/dist/stack.d.cts +76 -0
  51. package/dist/stack.d.ts +76 -0
  52. package/dist/stack.js +1 -0
  53. package/docs/README.md +7 -6
  54. package/docs/llms-full.txt +7 -260
  55. package/docs/llms.txt +0 -2
  56. package/package.json +6 -6
  57. package/template/package.json +1 -1
  58. package/dist/chunk-74XDEF5J.js.map +0 -1
  59. package/dist/chunk-I3ON7MYF.js.map +0 -1
  60. package/dist/chunk-N32VDWKH.js.map +0 -1
  61. package/dist/chunk-QMVK4X3V.js.map +0 -1
  62. package/dist/chunk-SQC5EIWY.js.map +0 -1
  63. package/dist/purchases/keys.cjs.map +0 -1
  64. package/dist/purchases/keys.d.cts +0 -42
  65. package/dist/purchases/keys.d.ts +0 -42
  66. package/dist/purchases/keys.js +0 -1
  67. package/docs/config.md +0 -147
  68. package/docs/resources.md +0 -97
  69. package/template/config/secrets.ts +0 -24
  70. /package/dist/{chunk-YL4C5NRY.js.map → chunk-Y5HXVUMP.js.map} +0 -0
  71. /package/dist/{purchases/keys.js.map → stack.js.map} +0 -0
@@ -32,8 +32,8 @@ db/schema.ts # config-as-code Postgres schema (tables, co
32
32
  ```
33
33
 
34
34
  The four folders above are the daily surface. These also exist (own docs, linked
35
- below): `resources/` (external connections[resources.md](./resources.md)),
36
- `seeds/` (seed data), `jobs/` (background — [background.md](./background.md)),
35
+ below): `resources/` (plain modules a controller imports a client, a
36
+ formatter, anything shared), `seeds/` (seed data), `jobs/` (background — [background.md](./background.md)),
37
37
  `webhooks/` + `hooks/` (events — [events.md](./events.md)), `middleware/`.
38
38
 
39
39
  ### The 7 rules (checklist)
@@ -211,9 +211,11 @@ generated client surface) changes; the verb/path do not affect it.
211
211
  secrets in its environment. There is no `.env` file: nothing writes one and
212
212
  nothing reads one, so the values live in one child process and are gone when it
213
213
  exits.
214
- - `config/secrets.ts` `defineSecrets({ secrets: [secret("NAME")] })` declares
215
- WHICH secrets the backend needs (never a value). `push` refuses the deploy when
216
- a required one is missing from the target Environment, naming it.
214
+ - Secrets are written to the stack (`palbase secret set NAME --stdin`) and read
215
+ with `Secrets.get(name)`. The NAME is typed: `palbase-stack.d.ts` is generated
216
+ from the stack, so a secret it does not hold cannot be spelled a compile
217
+ error rather than a 500. There is no `config/secrets.ts`: it asked you to
218
+ restate names the vault already had, and the deploy compared the two lists.
217
219
  - `tsconfig.json` needs `"experimentalDecorators": true` (the scaffold sets it).
218
220
 
219
221
  ## Mental model
@@ -274,7 +276,6 @@ HTTP endpoints are **not** file-path routed. You author a class controller
274
276
  | Schema & typed DB | [schema.md](./schema.md) |
275
277
  | Migrations (additive vs type-change + drift-gate) | [migrations.md](./migrations.md) |
276
278
  | Services (Cache, Queue, Storage, …) | [services.md](./services.md) |
277
- | Resources (external connections) | [resources.md](./resources.md) |
278
279
  | Errors | [errors.md](./errors.md) |
279
280
  | Workers & Jobs | [background.md](./background.md) |
280
281
  | Hooks & Webhooks | [events.md](./events.md) |
@@ -1685,108 +1686,6 @@ to drive live chat, presence, dashboards, and other push features.
1685
1686
 
1686
1687
 
1687
1688
 
1688
- <!-- ===== resources.md ===== -->
1689
-
1690
- # Resources
1691
-
1692
- A `Resource` models one external connection — a pooled datastore, a stateless
1693
- API client, or a per-user factory. You put it in `resources/`, export an
1694
- instance, and **do not register it**: the framework discovers it and sets it up
1695
- once at boot. On top of that lifecycle you expose your own clean facade.
1696
-
1697
- ```ts
1698
- import { Resource } from "@palbase/backend";
1699
- ```
1700
-
1701
- ## Lifecycle (boot scope — not per request)
1702
-
1703
- A resource is created once at process boot — NOT per request. The framework:
1704
-
1705
- 1. calls `init(env)` **once**, with only the secrets the resource declared.
1706
-
1707
- That is the whole lifecycle — there is no teardown counterpart. The runtime
1708
- recycles an environment by disposing its isolate outright, with no signal
1709
- delivered into your code first, so a `shutdown()` you declare is **never
1710
- called**. Do not buffer in memory intending to flush on the way out; treat a
1711
- write as durable when the call that made it returns. The optional `shutdown()`
1712
- member survives in the type because the older process-based runtime did invoke
1713
- it on SIGTERM.
1714
-
1715
- The instance lives for the whole process; your facade methods are called
1716
- per-request. This makes "reconnect on every request" structurally impossible.
1717
-
1718
- ## Pooled datastore
1719
-
1720
- ```ts
1721
- import { Resource } from "@palbase/backend";
1722
- import neo4j, { type Driver, type Session } from "neo4j-driver";
1723
-
1724
- export class Neo4jResource extends Resource {
1725
- static secrets = ["NEO4J_URL", "NEO4J_USER", "NEO4J_PASSWORD"] as const;
1726
- private driver!: Driver;
1727
- async init(env: { NEO4J_URL: string; NEO4J_USER: string; NEO4J_PASSWORD: string }) {
1728
- this.driver = neo4j.driver(env.NEO4J_URL, neo4j.auth.basic(env.NEO4J_USER, env.NEO4J_PASSWORD));
1729
- }
1730
- session(): Session {
1731
- return this.driver.session();
1732
- }
1733
- }
1734
-
1735
- export const graph = new Neo4jResource();
1736
- ```
1737
-
1738
- ## Stateless API client — `init` only
1739
-
1740
- ```ts
1741
- import { Resource } from "@palbase/backend";
1742
- import { Client } from "@googlemaps/google-maps-services-js";
1743
-
1744
- export class GoogleResource extends Resource {
1745
- static secrets = ["GOOGLE_MAPS_KEY"] as const;
1746
- private client = new Client();
1747
- private key = "";
1748
- init(env: { GOOGLE_MAPS_KEY: string }) {
1749
- this.key = env.GOOGLE_MAPS_KEY;
1750
- }
1751
- nearby(lat: number, lng: number) {
1752
- return this.client.placesNearby({ params: { location: { lat, lng }, radius: 1500, key: this.key } });
1753
- }
1754
- }
1755
-
1756
- export const google = new GoogleResource();
1757
- ```
1758
-
1759
- A per-user (OAuth) resource adds a factory method on the base, e.g.
1760
- `github.forUser(token)` — the same single model covers pooled, stateless, and
1761
- per-user.
1762
-
1763
- ## Secrets
1764
-
1765
- `static secrets` is the contract:
1766
-
1767
- - It **types** the `env` passed to `init` — only the declared names are
1768
- present, each a `string`. An undeclared key is a compile error.
1769
- - A declared secret that is **missing at boot fails the deploy**, naming the
1770
- secret. Secrets are Environment-scoped; set them with
1771
- `palbase secret set NAME ...` or in Studio. A resource is initialised once at
1772
- boot, so rotating a secret needs a redeploy/restart.
1773
-
1774
- `secrets` is optional — a resource that needs none simply omits it and gets an
1775
- empty `env`.
1776
-
1777
- ## Using a resource
1778
-
1779
- Import the singleton and call your facade — services and handlers reach
1780
- resources the same way they reach `Database`:
1781
-
1782
- ```ts
1783
- import { google } from "../resources/google.js";
1784
-
1785
- const results = (await google.nearby(41.0, 29.0)).data.results;
1786
- ```
1787
-
1788
-
1789
-
1790
1689
  <!-- ===== errors.md ===== -->
1791
1690
 
1792
1691
  # Errors
@@ -1973,155 +1872,3 @@ handlers access Environment variables via `meta.env`. The runtime verifies the
1973
1872
  signature before dispatching to your event handlers.
1974
1873
 
1975
1874
  `meta` shape: `{ env, requestId, environmentId }`.
1976
-
1977
-
1978
-
1979
- <!-- ===== config.md ===== -->
1980
-
1981
- # Module Config (config-as-code)
1982
-
1983
- Beyond `db/schema.ts`, four more module surfaces are git-authoritative: storage
1984
- buckets, notification providers, feature-flag definitions, and the outbound-HTTP
1985
- allowlist. You declare them in `config/*.ts` files (typed, imported from
1986
- `@palbase/backend`) and on `git push` the deploy creates/updates them. Secrets
1987
- (certs, keys, API tokens) NEVER go in git — they live in a reserved encrypted env
1988
- namespace, uploaded by the guided CLI.
1989
-
1990
- You normally author these with `palbase <module> add …` (the CLI writes the
1991
- config file + uploads any secret); the files below are what it generates.
1992
-
1993
- ## Storage buckets — `config/storage.ts`
1994
-
1995
- ```ts
1996
- import { defineStorage, bucket } from "@palbase/backend";
1997
-
1998
- export default defineStorage({
1999
- buckets: {
2000
- avatars: bucket({
2001
- public: true, // served without a signed URL
2002
- fileSizeLimit: "5MB", // "5MB"/"20MB"/"1GB" or a byte number
2003
- allowedMimeTypes: ["image/png", "image/jpeg", "image/webp"],
2004
- }),
2005
- invoices: bucket({ public: false, fileSizeLimit: "20MB", allowedMimeTypes: ["application/pdf"] }),
2006
- },
2007
- });
2008
- ```
2009
-
2010
- Author it: `palbase storage buckets add avatars --public --max-size 5MB --mime image/png,image/jpeg`.
2011
- On deploy, the buckets are created/updated. A bucket REMOVED from the file is
2012
- **never auto-deleted** (its files would be lost) — drop it explicitly in Studio.
2013
- The files inside a bucket are runtime state, not config.
2014
-
2015
- ## Notification providers — `config/notifications.ts`
2016
-
2017
- Providers carry secrets (APNs `.p8`, FCM service-account JSON, Twilio token).
2018
- The config file is **structural** — it names the enabled providers + their
2019
- non-secret fields; the secret is bound by convention to a reserved env key and
2020
- NEVER appears in git.
2021
-
2022
- ```ts
2023
- import { defineNotifications } from "@palbase/backend";
2024
-
2025
- export default defineNotifications({
2026
- push: {
2027
- apns: { enabled: true, teamId: "A1B2C3D4E5", keyId: "XYZ123", bundleId: "net.example.app" },
2028
- // no p8 key here — it's in the reserved secret PB_NOTIFICATIONS_APNS_P8
2029
- },
2030
- sms: {
2031
- twilio: { enabled: true, accountSid: "AC...", messagingServiceSid: "MG..." },
2032
- },
2033
- });
2034
- ```
2035
-
2036
- Author it with the guided CLI — it knows each provider's fields and uploads the
2037
- secret for you, so you never type a secret-name string:
2038
-
2039
- ```bash
2040
- palbase notifications providers # list the catalog + what's configured
2041
- palbase notifications add apns \
2042
- --team-id A1B2C3D4E5 --key-id XYZ123 --bundle-id net.example.app \
2043
- --p8-file ./AuthKey_XYZ123.p8 # → uploads PB_NOTIFICATIONS_APNS_P8 (encrypted)
2044
- palbase notifications add twilio --account-sid AC... --messaging-sid MG...
2045
- # prompts for the auth token (hidden)
2046
- ```
2047
-
2048
- The reserved secret env keys (`PB_NOTIFICATIONS_*`) are managed by these
2049
- commands — `palbase secret set PB_*` is refused. Your own custom env
2050
- (`MY_API_KEY` etc.) is unaffected and still flows via `.env.local`. On deploy,
2051
- each enabled provider's reserved secret is resolved and the provider is
2052
- configured; a provider whose secret is missing is skipped (warned, not fatal).
2053
-
2054
- ## Feature flags — `config/flags.ts`
2055
-
2056
- Flag DEFINITIONS (key, type, default) are config; the value set for a specific
2057
- user / an A/B assignment is runtime (set via the SDK/Studio, not git).
2058
-
2059
- ```ts
2060
- import { defineFlags, flag } from "@palbase/backend";
2061
-
2062
- export default defineFlags({
2063
- flags: {
2064
- new_checkout: flag({ type: "boolean", default: false, description: "Gate the redesigned checkout" }),
2065
- upload_limit: flag({ type: "number", default: 10 }),
2066
- theme: flag({ type: "string", default: "system", variants: ["light", "dark", "system"] }),
2067
- limits: flag({ type: "json", default: { daily: 10, burst: 50 } }),
2068
- },
2069
- });
2070
- ```
2071
-
2072
- Four types: `boolean`, `number`, `string`, `json`. A `json` flag's default is an
2073
- object (not an array, not a scalar) and nests at most 3 deep. `variants` is for
2074
- `string` only — the flags service rejects it on every other type.
2075
-
2076
- Author it: `palbase flags add new_checkout --type boolean --default false`, or
2077
- `palbase flags add limits --type json --default '{"daily":10}'`. On
2078
- deploy, the definitions are upserted to the flags service (idempotent). A flag
2079
- removed from the file is **not auto-deleted** (orphan definitions are harmless).
2080
-
2081
- ## Outbound HTTP — `config/egress.ts`
2082
-
2083
- Your backend has **no ambient network**. A `fetch()` to an external host is
2084
- refused unless the host is declared here, and with no `config/egress.ts` at all
2085
- there is no outbound network whatsoever.
2086
-
2087
- ```ts
2088
- import { defineEgress } from "@palbase/backend";
2089
-
2090
- export default defineEgress({
2091
- hosts: ["api.openai.com", ".example.com"], // leading dot also covers subdomains
2092
- timeoutMs: 90_000, // per-call ceiling; omitted ⇒ 30_000
2093
- });
2094
- ```
2095
-
2096
- Hosts are bare hostnames — https on :443 only, so no scheme, port, path or
2097
- wildcard. `timeoutMs` is 1_000–300_000.
2098
-
2099
- Unlike the three above, this one is **fail-closed**: a malformed host or an
2100
- out-of-range `timeoutMs` ABORTS the deploy rather than logging a warning. An
2101
- allowlist that silently dropped an entry would be a broken feature, and one that
2102
- silently widened would be a hole; an out-of-range timeout is rejected rather than
2103
- clamped so your config file and the running system never disagree.
2104
-
2105
- ### How long a call may take
2106
-
2107
- `timeoutMs` is a ceiling, not a grant — the call still ends when the invocation
2108
- around it ends, and it covers the whole call including redirects (three hops do
2109
- not get three budgets).
2110
-
2111
- | Where the fetch runs | What else bounds it |
2112
- |---|---|
2113
- | Job (`jobs/`) | Its own `@Job({ timeout })`, max 300s — the longest budget available. |
2114
- | Endpoint / webhook | The gateway's request ceiling. Long work belongs in a job. |
2115
-
2116
- Responses are **buffered whole** (5 MB cap) before your `fetch()` resolves.
2117
- Requesting a streaming response from an upstream (`stream: true`, SSE) therefore
2118
- buys nothing: no partial output, no earlier first byte, and the entire stream
2119
- must still finish inside `timeoutMs`.
2120
-
2121
- ## How it's applied
2122
-
2123
- All four are evaluated + applied **on deploy**, the same place `db/schema.ts`
2124
- migrations run, reaching each module through your project's gateway with a
2125
- service-role key. Storage/notifications/flags are fail-soft — a config error logs
2126
- a warning but never aborts the deploy of your code. `config/egress.ts` is the
2127
- exception and is fail-closed, for the reason above.
package/docs/llms.txt CHANGED
@@ -13,8 +13,6 @@
13
13
  - [schema](./schema.md)
14
14
  - [migrations](./migrations.md)
15
15
  - [services](./services.md)
16
- - [resources](./resources.md)
17
16
  - [errors](./errors.md)
18
17
  - [background](./background.md)
19
18
  - [events](./events.md)
20
- - [config](./config.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palbase/backend",
3
- "version": "22.1.0",
3
+ "version": "23.0.0",
4
4
  "description": "Palbase Backend SDK — class controllers (@Controller/@Get/@Post + @Body/@QueryParams/@Param), error classes, schema DSL",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -53,14 +53,14 @@
53
53
  "default": "./dist/db/env.cjs"
54
54
  }
55
55
  },
56
- "./purchases": {
56
+ "./stack": {
57
57
  "import": {
58
- "types": "./dist/purchases/keys.d.ts",
59
- "default": "./dist/purchases/keys.js"
58
+ "types": "./dist/stack.d.ts",
59
+ "default": "./dist/stack.js"
60
60
  },
61
61
  "require": {
62
- "types": "./dist/purchases/keys.d.cts",
63
- "default": "./dist/purchases/keys.cjs"
62
+ "types": "./dist/stack.d.cts",
63
+ "default": "./dist/stack.cjs"
64
64
  }
65
65
  },
66
66
  "./engine": {
@@ -9,7 +9,7 @@
9
9
  "typecheck": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@palbase/backend": "^22.0.0"
12
+ "@palbase/backend": "^23.0.0"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@types/node": "^22",