@palbase/backend 2.0.2 → 4.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 (53) hide show
  1. package/dist/chunk-EG7TTYHY.js +235 -0
  2. package/dist/chunk-EG7TTYHY.js.map +1 -0
  3. package/dist/chunk-WUQO76NW.js +101 -0
  4. package/dist/chunk-WUQO76NW.js.map +1 -0
  5. package/dist/db/env.cjs +19 -0
  6. package/dist/db/env.cjs.map +1 -0
  7. package/dist/db/env.d.cts +45 -0
  8. package/dist/db/env.d.ts +45 -0
  9. package/dist/db/env.js +1 -0
  10. package/dist/db/env.js.map +1 -0
  11. package/dist/db/index.cjs +143 -231
  12. package/dist/db/index.cjs.map +1 -1
  13. package/dist/db/index.d.cts +4 -20
  14. package/dist/db/index.d.ts +4 -20
  15. package/dist/db/index.js +13 -233
  16. package/dist/db/index.js.map +1 -1
  17. package/dist/{endpoint-Djk5L6G2.d.ts → endpoint-2d_DpASt.d.cts} +94 -96
  18. package/dist/{endpoint-BlcY2xNA.d.cts → endpoint-2d_DpASt.d.ts} +94 -96
  19. package/dist/index-DZW9CjiY.d.ts +463 -0
  20. package/dist/index-DzRFS3Tl.d.cts +463 -0
  21. package/dist/index.cjs +557 -60
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +278 -161
  24. package/dist/index.d.ts +278 -161
  25. package/dist/index.js +343 -12
  26. package/dist/index.js.map +1 -1
  27. package/dist/test/index.cjs +57 -2
  28. package/dist/test/index.cjs.map +1 -1
  29. package/dist/test/index.d.cts +1 -2
  30. package/dist/test/index.d.ts +1 -2
  31. package/dist/test/index.js +10 -2
  32. package/dist/test/index.js.map +1 -1
  33. package/docs/README.md +33 -12
  34. package/docs/background.md +19 -13
  35. package/docs/database.md +70 -17
  36. package/docs/endpoints.md +103 -79
  37. package/docs/errors.md +37 -31
  38. package/docs/events.md +25 -17
  39. package/docs/getting-started.md +38 -18
  40. package/docs/llms-full.txt +758 -267
  41. package/docs/llms.txt +3 -1
  42. package/docs/migrations.md +98 -0
  43. package/docs/resources.md +94 -0
  44. package/docs/routing.md +54 -27
  45. package/docs/schema.md +163 -42
  46. package/docs/services.md +17 -14
  47. package/package.json +12 -2
  48. package/dist/chunk-4J3F32SH.js +0 -96
  49. package/dist/chunk-4J3F32SH.js.map +0 -1
  50. package/dist/chunk-L36JLUPO.js +0 -97
  51. package/dist/chunk-L36JLUPO.js.map +0 -1
  52. package/dist/schema-BqfEhIC0.d.cts +0 -133
  53. package/dist/schema-BqfEhIC0.d.ts +0 -133
package/dist/index.d.cts CHANGED
@@ -1,110 +1,26 @@
1
- import { D as DBClient, C as CacheClient, P as PalbaseDocsClient, a as PalbaseFlagsClient, L as Logger, b as PalbaseNotificationsClient, Q as QueueClient, c as PalbaseStorageClient, d as PalbaseModuleClients, U as User, E as ErrorThrowers } from './endpoint-BlcY2xNA.cjs';
2
- export { A as AuthConfig, e as ClientInfo, f as EndpointConfig, g as ErrorDef, h as ErrorMap, F as FileContext, H as HttpError, i as HttpMethod, M as Middleware, j as MiddlewareContext, k as MiddlewareHandler, l as PBRequest, m as PalbaseAnalyticsClient, n as PalbaseAnalyticsManagementNamespace, o as PalbaseAnalyticsProperties, p as PalbaseAnalyticsQueryNamespace, q as PalbaseAttestAndroidParams, r as PalbaseAttestAndroidResult, s as PalbaseAttestiOSParams, t as PalbaseAttestiOSResult, u as PalbaseAuthClient, v as PalbaseBindDeviceParams, w as PalbaseBucketClient, x as PalbaseCmsClient, y as PalbaseCmsFindOneOptions, z as PalbaseCmsFindOptions, B as PalbaseCohortQueryInput, G as PalbaseCohortResult, I as PalbaseCollectionRef, J as PalbaseCountQueryInput, K as PalbaseCountResult, N as PalbaseCreateLinkParams, O as PalbaseDeviceInfo, R as PalbaseDeviceTokenView, S as PalbaseDocumentRef, T as PalbaseDocumentSnapshot, V as PalbaseEmailClient, W as PalbaseEmailSendParams, X as PalbaseEmailSendResponse, Y as PalbaseEventNamesResult, Z as PalbaseEventsQueryInput, _ as PalbaseEventsResult, $ as PalbaseFileObject, a0 as PalbaseFlag, a1 as PalbaseFlagContext, a2 as PalbaseFlagVariant, a3 as PalbaseFunctionsClient, a4 as PalbaseFunnelQueryInput, a5 as PalbaseFunnelResult, a6 as PalbaseIdentifyTraits, a7 as PalbaseInboxClient, a8 as PalbaseInboxListOptions, a9 as PalbaseInboxListResult, aa as PalbaseInboxMessage, ab as PalbaseInboxSendParams, ac as PalbaseInboxSendResponse, ad as PalbaseInitialLink, ae as PalbaseInvokeOptions, af as PalbaseLink, ag as PalbaseLinkAnalytics, ah as PalbaseLinkDetails, ai as PalbaseLinksClient, aj as PalbaseListLinksOptions, ak as PalbaseListLinksResult, al as PalbaseListOptions, am as PalbaseMatchParams, an as PalbaseMultiChannelResponse, ao as PalbaseOverviewResult, ap as PalbasePreferences, aq as PalbasePreferencesClient, ar as PalbasePublicUrlResponse, as as PalbasePushClient, at as PalbasePushSendParams, au as PalbasePushSendResponse, av as PalbaseQrCodeOptions, aw as PalbaseQuerySnapshot, ax as PalbaseRealtimeChannel, ay as PalbaseRealtimeClient, az as PalbaseRealtimeMessage, aA as PalbaseRegisterDeviceParams, aB as PalbaseResult, aC as PalbaseRetentionQueryInput, aD as PalbaseRetentionResult, aE as PalbaseSession, aF as PalbaseSignedUrlResponse, aG as PalbaseSmsClient, aH as PalbaseSmsSendParams, aI as PalbaseSmsSendResponse, aJ as PalbaseTransformOptions, aK as PalbaseUpdateLinkParams, aL as PalbaseUploadOptions, aM as PalbaseUser, aN as PalbaseUserDetailResult, aO as PalbaseUsersQueryInput, aP as PalbaseUsersResult, aQ as PalbaseVerifyRequestSignatureParams, aR as PalbaseWhereOperator, aS as RateLimitConfig, aT as TxClient, aU as defineEndpoint, aV as defineMiddleware } from './endpoint-BlcY2xNA.cjs';
1
+ import { C as CacheClient, b as PalbaseDocsClient, c as PalbaseFlagsClient, L as Logger, d as PalbaseNotificationsClient, Q as QueueClient, D as DBClient, e as PalbaseStorageClient, A as AuthSpec, R as RateLimitConfig, U as User$1 } from './endpoint-2d_DpASt.cjs';
2
+ export { f as AuthConfig, B as BadRequest, g as ClientInfo, h as Conflict, i as DBOps, E as ErrorDef, j as ErrorMap, k as ErrorThrowers, F as FileContext, l as Forbidden, H as HttpError, m as HttpMethod, M as Middleware, n as MiddlewareContext, o as MiddlewareHandler, N as NotFound, P as PBRequest, p as PalError, q as PalbaseAnalyticsClient, r as PalbaseAnalyticsManagementNamespace, s as PalbaseAnalyticsProperties, t as PalbaseAnalyticsQueryNamespace, u as PalbaseAttestAndroidParams, v as PalbaseAttestAndroidResult, w as PalbaseAttestiOSParams, x as PalbaseAttestiOSResult, y as PalbaseAuthClient, z as PalbaseBindDeviceParams, G as PalbaseBucketClient, I as PalbaseCmsClient, J as PalbaseCmsFindOneOptions, K as PalbaseCmsFindOptions, O as PalbaseCohortQueryInput, S as PalbaseCohortResult, T as PalbaseCollectionRef, V as PalbaseCountQueryInput, W as PalbaseCountResult, X as PalbaseCreateLinkParams, Y as PalbaseDeviceInfo, Z as PalbaseDeviceTokenView, _ as PalbaseDocumentRef, $ as PalbaseDocumentSnapshot, a0 as PalbaseEmailClient, a1 as PalbaseEmailSendParams, a2 as PalbaseEmailSendResponse, a3 as PalbaseEventNamesResult, a4 as PalbaseEventsQueryInput, a5 as PalbaseEventsResult, a6 as PalbaseFileObject, a7 as PalbaseFlag, a8 as PalbaseFlagContext, a9 as PalbaseFlagVariant, aa as PalbaseFunctionsClient, ab as PalbaseFunnelQueryInput, ac as PalbaseFunnelResult, ad as PalbaseIdentifyTraits, ae as PalbaseInboxClient, af as PalbaseInboxListOptions, ag as PalbaseInboxListResult, ah as PalbaseInboxMessage, ai as PalbaseInboxSendParams, aj as PalbaseInboxSendResponse, ak as PalbaseInitialLink, al as PalbaseInvokeOptions, am as PalbaseLink, an as PalbaseLinkAnalytics, ao as PalbaseLinkDetails, ap as PalbaseLinksClient, aq as PalbaseListLinksOptions, ar as PalbaseListLinksResult, as as PalbaseListOptions, at as PalbaseMatchParams, au as PalbaseMultiChannelResponse, av as PalbaseOverviewResult, aw as PalbasePreferences, ax as PalbasePreferencesClient, ay as PalbasePublicUrlResponse, az as PalbasePushClient, aA as PalbasePushSendParams, aB as PalbasePushSendResponse, aC as PalbaseQrCodeOptions, aD as PalbaseQuerySnapshot, aE as PalbaseRealtimeChannel, aF as PalbaseRealtimeClient, aG as PalbaseRealtimeMessage, aH as PalbaseRegisterDeviceParams, aI as PalbaseResult, aJ as PalbaseRetentionQueryInput, aK as PalbaseRetentionResult, aL as PalbaseSession, aM as PalbaseSignedUrlResponse, aN as PalbaseSmsClient, aO as PalbaseSmsSendParams, aP as PalbaseSmsSendResponse, aQ as PalbaseTransformOptions, aR as PalbaseUpdateLinkParams, aS as PalbaseUploadOptions, aT as PalbaseUser, aU as PalbaseUserDetailResult, aV as PalbaseUsersQueryInput, aW as PalbaseUsersResult, aX as PalbaseVerifyRequestSignatureParams, aY as PalbaseWhereOperator, aZ as TooManyRequests, a_ as TxClient, a$ as Unauthorized, b0 as defineMiddleware } from './endpoint-2d_DpASt.cjs';
3
3
  import { AsyncLocalStorage } from 'node:async_hooks';
4
- import { T as TableDef, C as ColIsOptionalOnInsert, a as ColValue, S as SchemaDef } from './schema-BqfEhIC0.cjs';
5
- export { b as ColumnBuilder, c as ColumnDef, d as ColumnType, O as OnDeleteAction, e as boolean, f as defineSchema, g as enumType, i as integer, j as jsonb, t as table, h as text, k as timestamp, u as uuid } from './schema-BqfEhIC0.cjs';
4
+ import { E as EnvTypedDatabase, S as SchemaDef } from './index-DzRFS3Tl.cjs';
5
+ export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EnvServiceDatabase, f as EnvTables, g as EnvTypedTable, h as EnvTypedTx, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RowShape, n as SchemaInput, T as TableDef, o as TableInput, p as TypedDB, q as TypedTable, r as TypedTx, s as boolean, t as defineSchema, u as enumType, v as integer, w as isPalbaseExtension, x as jsonb, y as makeTypedDB, z as policy, A as text, B as timestamp, D as uuid } from './index-DzRFS3Tl.cjs';
6
+ export { TableTypes, Tables } from './db/env.cjs';
7
+ import { ZodTypeAny } from 'zod';
6
8
  export { z } from 'zod';
7
9
 
8
- /**
9
- * typed-db.ts — Task 2: TypedDB schema-derived insert/row shapes.
10
- *
11
- * Derives INSERT and full-row TypeScript types from a `defineSchema()` result
12
- * and wraps the untyped runtime `DBClient` with a typed facade.
13
- *
14
- * No value-any. No `as unknown as X`. The two narrow `as` casts in
15
- * `makeTypedTable` are safe because:
16
- * - `data as Record<string, unknown>`: InsertShape<T> maps string keys to
17
- * typed values; all value types are subsets of `unknown`, so the cast is
18
- * structurally sound.
19
- * - `result as RowShape<T>`: The runtime DBClient returns `Record<string,
20
- * unknown>` which is the erased form of the typed row; we're narrowing back
21
- * to the precise shape that the schema declared.
22
- * Both casts are narrowing only (not widening) and correctness is guaranteed
23
- * by the schema the caller provides.
24
- */
25
-
26
- /** Keys of C whose columns are required on INSERT (not nullable, no default). */
27
- type RequiredKeys<C> = {
28
- [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? never : K;
29
- }[keyof C];
30
- /** Keys of C whose columns are optional on INSERT (nullable or has a default). */
31
- type OptionalKeys<C> = {
32
- [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? K : never;
33
- }[keyof C];
34
- /**
35
- * The TypeScript type for an INSERT payload for table `T`.
36
- * - Required: columns that are NOT NULL and have no DB-level default.
37
- * - Optional: columns that are nullable or carry a default.
38
- *
39
- * When all columns are optional, `RequiredKeys<C>` resolves to `never` and
40
- * the first part becomes `{}`, which is a neutral element for `&`.
41
- */
42
- type InsertShape<T extends TableDef> = {
43
- [K in RequiredKeys<T["columns"]>]: ColValue<T["columns"][K]>;
44
- } & {
45
- [K in OptionalKeys<T["columns"]>]?: ColValue<T["columns"][K]>;
46
- };
47
- /**
48
- * The TypeScript type for a full row returned by the DB for table `T`.
49
- * Every column is present; nullable columns resolve to `T | null`.
50
- */
51
- type RowShape<T extends TableDef> = {
52
- [K in keyof T["columns"]]: ColValue<T["columns"][K]>;
53
- };
54
- /** A typed table accessor that mirrors the runtime DBClient surface. */
55
- interface TypedTable<T extends TableDef> {
56
- insert(data: InsertShape<T>): Promise<RowShape<T>>;
57
- update(id: string, data: Partial<InsertShape<T>>): Promise<RowShape<T>>;
58
- delete(id: string): Promise<void>;
59
- findById(id: string): Promise<RowShape<T> | null>;
60
- findMany(query?: Partial<RowShape<T>>): Promise<RowShape<T>[]>;
61
- }
62
- /** A typed DB facade covering all tables declared in schema `S`. */
63
- interface TypedDB<S extends SchemaDef> {
64
- tables: {
65
- [K in keyof S["tables"]]: TypedTable<S["tables"][K]>;
66
- };
67
- transaction<T>(fn: (tx: TypedTx<S>) => Promise<T>): Promise<T>;
68
- }
69
- /** Transaction-scoped typed facade: same typed tables, no nested transaction. */
70
- interface TypedTx<S extends SchemaDef> {
71
- tables: {
72
- [K in keyof S["tables"]]: TypedTable<S["tables"][K]>;
73
- };
74
- }
75
- /**
76
- * Wraps a raw `DBClient` with the type-safe `TypedDB<S>` facade derived from
77
- * the provided schema. No behavior change — all calls delegate to `raw` with
78
- * the table name as a plain string.
79
- *
80
- * `buildTables` is the reusable factory that wraps any op-bearing client
81
- * (`TxClient` — the surface shared by `DBClient` and the transaction-scoped
82
- * client) into the typed tables map. It is used both for the top-level db
83
- * (wrapping `raw`) and inside `transaction`, where it wraps the raw `TxClient`
84
- * the runtime yields so the callback sees the same typed `.tables` API.
85
- *
86
- * `transaction` delegates straight to `raw.transaction`; the two narrow
87
- * `as TypedTx<S>` / `as TypedDB<S>` casts are single structural narrowings
88
- * from the dynamically-built tables object to the precise mapped type (TS
89
- * cannot infer through `Object.keys` iteration) — see module-level doc comment.
90
- */
91
- declare function makeTypedDB<S extends SchemaDef>(schema: S, raw: DBClient): TypedDB<S>;
92
-
93
10
  /**
94
11
  * runtime.ts — request-scoped service singletons.
95
12
  *
96
13
  * The backend SDK no longer threads a `ctx` god-object through every handler.
97
- * Instead, endpoint authors import PascalCase service singletons directly:
14
+ * Instead, controller methods import PascalCase service singletons directly:
98
15
  *
99
- * import { Database, Documents, Cache } from "@palbase/backend";
16
+ * import { Controller, Post, Body, Database } from "@palbase/backend";
100
17
  *
101
- * export default defineEndpoint({
102
- * method: "POST",
103
- * handler: async (req) => {
104
- * const row = await Database.insert("todos", { title: req.input.title });
105
- * return row;
106
- * },
107
- * });
18
+ * \@Controller("/todos")
19
+ * export default class TodosController {
20
+ * \@Post("") create(\@Body(CreateTodoBody) body: CreateTodoBody): unknown {
21
+ * return Database.insert("todos", { title: body.title });
22
+ * }
23
+ * }
108
24
  *
109
25
  * The singletons are thin Proxies. Every property access forwards to the live
110
26
  * client for the CURRENT request scope, resolved through {@link __getRuntime}.
@@ -176,10 +92,27 @@ declare function __runWithRuntime<T>(services: RuntimeServices, fn: () => T): T;
176
92
  * process-global fallback (dev-server / tests). NOT part of the public
177
93
  * author-facing API — used by the runtime and the singleton Proxies. */
178
94
  declare function __getRuntime(): RuntimeServices;
179
- /** The project's own Postgres (pgx, schema `env_<envId>`). Typed CRUD +
180
- * `query`/`transaction`. For a typed `.tables.<name>` API, wrap with
181
- * {@link typedDatabase}. */
182
- declare const Database: DBClient;
95
+ /**
96
+ * The project's own Postgres (pgx, schema `env_<envId>`).
97
+ *
98
+ * Typed by default: `Database.tables.<name>.insert({...})` is typed against
99
+ * the project's generated `palbase-env.d.ts` with NO import and NO generic.
100
+ * The raw string ops (`query`/`insert`/`update`/`delete`/`findById`/`findMany`)
101
+ * are also available for dynamic table names and read-only SQL.
102
+ *
103
+ * RLS is enforced by default (the runtime runs each op as `authenticated` with
104
+ * the verified user's claims). To bypass RLS, call `Database.asService()` —
105
+ * explicit and greppable — which runs as the `service_role` (BYPASSRLS).
106
+ *
107
+ * @example
108
+ * import { Database } from "@palbase/backend";
109
+ *
110
+ * const todo = await Database.tables.todos.insert({ title: req.input.title });
111
+ * todo.id; // string ✓
112
+ * const rows = await Database.query("SELECT id FROM todos WHERE done = $1", [false]);
113
+ * const all = await Database.asService().tables.todos.findMany({}); // RLS bypass
114
+ */
115
+ declare const Database: EnvTypedDatabase;
183
116
  /** Firestore-like document client (PalDocs). */
184
117
  declare const Documents: PalbaseDocsClient;
185
118
  /** Object storage client (buckets, signed URLs). */
@@ -194,31 +127,123 @@ declare const Log: Logger;
194
127
  declare const Notifications: PalbaseNotificationsClient;
195
128
  /** Feature flags. */
196
129
  declare const Flags: PalbaseFlagsClient;
130
+
197
131
  /**
198
- * Type the `Database` singleton against a `defineSchema()` result, returning a
199
- * facade with a typed `.tables.<name>.insert(...)` API alongside the raw
200
- * `query`/`transaction` ops.
132
+ * env-gen.ts — generate the `palbase-env.d.ts` text from a `defineSchema()`
133
+ * result.
134
+ *
135
+ * The CLI (`palbase serve` / codegen) and the deploy pipeline call
136
+ * {@link makeEnvDts} with the project's schema and write the returned string to
137
+ * `palbase-env.d.ts` at the project root. That file AUGMENTS the
138
+ * `@palbase/backend/env` `Tables` interface (controlled global augmentation,
139
+ * C5) so `Database.tables.<name>` is typed with no import and no generic.
201
140
  *
202
- * const Db = typedDatabase(schema);
203
- * const todo = await Db.tables.todos.insert({ title: "x" });
141
+ * The output is FLAT: each table gets a `{ row: {...}; insert: {...} }` entry
142
+ * with plain TypeScript object types. The phantom `ColumnBuilder<...>` type
143
+ * NEVER appears in the generated `.d.ts` — that type only lives at authoring
144
+ * time inside `db/schema.ts`.
145
+ */
146
+
147
+ /**
148
+ * Generate the full `palbase-env.d.ts` text for a schema.
204
149
  *
205
- * This is per-endpoint (not global module augmentation), so one endpoint's
206
- * tables never leak into another's `Database` type.
150
+ * @example
151
+ * import { makeEnvDts } from "@palbase/backend";
152
+ * import schema from "./db/schema.js";
153
+ * writeFileSync("palbase-env.d.ts", makeEnvDts(schema));
207
154
  */
208
- declare function typedDatabase<TSchema extends SchemaDef>(schema: TSchema): TypedDB<TSchema> & DBClient;
155
+ declare function makeEnvDts(schema: SchemaDef): string;
209
156
 
210
- /** Context injected into worker handlers — the non-request-scoped service
211
- * surface (db/log/cache/queue + module clients), plus correlation ids. Workers
212
- * are not part of the Phase 1 `ctx PBRequest` redesign, so they keep their
213
- * own `ctx`; the job payload arrives as the handler's second argument. */
214
- interface WorkerContext extends PalbaseModuleClients {
215
- user: User | null;
216
- db: DBClient;
157
+ /** Options accepted by `@Controller`. */
158
+ interface ControllerOptions {
159
+ /** Default auth for ALL routes in this controller (route-level overrides). */
160
+ auth?: AuthSpec;
161
+ }
162
+ /**
163
+ * Mark a class as a Palbase backend controller. `basePath` is the mount path
164
+ * for every route the class declares; `options.auth` sets the controller-level
165
+ * default auth (a route's own `auth` overrides it; absent ⇒ secure-by-default).
166
+ *
167
+ * @example
168
+ * \@Controller("/todos", { auth: false })
169
+ * export class TodosController {
170
+ * \@Get("") list(\@Query(ListTodosQuery) q: ListTodosQuery): TodoSchema[] { … }
171
+ * }
172
+ */
173
+ declare function Controller(basePath: string, options?: ControllerOptions): <T extends abstract new (...args: never[]) => object>(ctor: T) => T;
174
+
175
+ /** The HTTP verbs a route may declare, upper-cased (the runtime router +
176
+ * OpenAPI lower-case on their own). */
177
+ type HttpMethodUpper = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
178
+ /** Route-level options accepted by the method decorators (`@Get`/`@Post`/…). */
179
+ interface RouteOptions {
180
+ /** OVERRIDES the controller-level default auth for this one route. */
181
+ auth?: AuthSpec;
182
+ /** Per-route rate limit. */
183
+ rateLimit?: RateLimitConfig;
184
+ }
185
+
186
+ /** A legacy method decorator. */
187
+ type MethodDecorator = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
188
+ /** `@Get(subpath, options?)` — declare a GET route. */
189
+ declare const Get: (subpath: string, options?: RouteOptions) => MethodDecorator;
190
+ /** `@Post(subpath, options?)` — declare a POST route. */
191
+ declare const Post: (subpath: string, options?: RouteOptions) => MethodDecorator;
192
+ /** `@Put(subpath, options?)` — declare a PUT route. */
193
+ declare const Put: (subpath: string, options?: RouteOptions) => MethodDecorator;
194
+ /** `@Patch(subpath, options?)` — declare a PATCH route. */
195
+ declare const Patch: (subpath: string, options?: RouteOptions) => MethodDecorator;
196
+ /** `@Delete(subpath, options?)` — declare a DELETE route. */
197
+ declare const Delete: (subpath: string, options?: RouteOptions) => MethodDecorator;
198
+ /**
199
+ * `@Returns(schema)` — declare the route's success-response zod schema. The
200
+ * method's return TYPE drives compile-time checking; `@Returns` supplies the
201
+ * matching zod VALUE the OpenAPI generator reads for the 200 response. Optional:
202
+ * a `void` method (no response body) omits it.
203
+ *
204
+ * @example
205
+ * \@Get("/{id}")
206
+ * \@Returns(TodoSchema)
207
+ * getOne(\@Param("id") id: string): TodoSchema { … }
208
+ */
209
+ declare function Returns(schema: ZodTypeAny): MethodDecorator;
210
+
211
+ /** A legacy parameter decorator. */
212
+ type ParameterDecorator = (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
213
+ /** `@Body(schema)` — inject the request body, validated against `schema`. The
214
+ * developer writes `: T` (= `z.infer<schema>`, same name) for autocomplete. */
215
+ declare function Body(schema: ZodTypeAny): ParameterDecorator;
216
+ /** `@Query(schema)` — inject the parsed query params, validated against
217
+ * `schema`. */
218
+ declare function Query(schema: ZodTypeAny): ParameterDecorator;
219
+ /** `@Headers(schema?)` — inject the request headers (lowercase keys). With a
220
+ * schema, headers are validated + the codegen emits header parameters. */
221
+ declare function Headers(schema?: ZodTypeAny): ParameterDecorator;
222
+ /** `@Param("id")` — inject one matched path param by name. */
223
+ declare function Param(name: string): ParameterDecorator;
224
+ /** `@User()` — inject the authenticated user (`: User`, non-null for an
225
+ * effective-required route). The runtime resolves the effective auth. */
226
+ declare function User(): ParameterDecorator;
227
+ /** `@OptionalUser()` — inject the user as `User | null` (for routes whose
228
+ * effective auth is `false` / `{ required: false }`). */
229
+ declare function OptionalUser(): ParameterDecorator;
230
+ /** `@Client()` — inject the parsed calling-client metadata (`: ClientInfo`). */
231
+ declare function Client(): ParameterDecorator;
232
+ /** `@RequestId()` — inject the per-request id (`: string`). */
233
+ declare function RequestId(): ParameterDecorator;
234
+ /** `@TraceId()` — inject the W3C trace id (`: string`). */
235
+ declare function TraceId(): ParameterDecorator;
236
+ /** `@Req()` — inject the raw request object (escape hatch, `: PBRequest`). */
237
+ declare function Req(): ParameterDecorator;
238
+
239
+ /** Non-service, per-invocation data for background worker handlers.
240
+ * Services (Database, Log, …) are imported as singletons, not passed here. */
241
+ interface WorkerMeta {
242
+ /** Branch-scoped env vars. */
217
243
  env: Record<string, string>;
218
- log: Logger;
219
- cache: CacheClient;
220
- queue: QueueClient;
221
- errors: ErrorThrowers<undefined>;
244
+ /** The user that enqueued the job, if any (background jobs are usually system-initiated). */
245
+ user: User$1 | null;
246
+ /** Per-invocation id for correlation. */
222
247
  requestId: string;
223
248
  projectId: string;
224
249
  environmentId: string;
@@ -236,7 +261,7 @@ interface WorkerConfig<TPayload = unknown> {
236
261
  /** Backoff strategy between retries. Defaults to "exponential". */
237
262
  backoff?: BackoffStrategy;
238
263
  /** Handler function that processes the job payload. */
239
- handler: (ctx: WorkerContext, payload: TPayload) => Promise<void>;
264
+ handler: (payload: TPayload, meta: WorkerMeta) => Promise<void>;
240
265
  }
241
266
  /** Resolved worker configuration with defaults applied. */
242
267
  interface ResolvedWorkerConfig<TPayload = unknown> {
@@ -244,7 +269,7 @@ interface ResolvedWorkerConfig<TPayload = unknown> {
244
269
  retry: number;
245
270
  timeout: number;
246
271
  backoff: BackoffStrategy;
247
- handler: (ctx: WorkerContext, payload: TPayload) => Promise<void>;
272
+ handler: (payload: TPayload, meta: WorkerMeta) => Promise<void>;
248
273
  }
249
274
  /**
250
275
  * Define a background worker that processes jobs from the queue.
@@ -258,7 +283,7 @@ interface ResolvedWorkerConfig<TPayload = unknown> {
258
283
  * retry: 5,
259
284
  * timeout: 60,
260
285
  * backoff: "exponential",
261
- * handler: async (ctx, payload: { to: string; subject: string }) => {
286
+ * handler: async (payload: { to: string; subject: string }, meta) => {
262
287
  * await sendEmail(payload.to, payload.subject);
263
288
  * },
264
289
  * });
@@ -266,13 +291,11 @@ interface ResolvedWorkerConfig<TPayload = unknown> {
266
291
  */
267
292
  declare function defineWorker<TPayload = unknown>(config: WorkerConfig<TPayload>): ResolvedWorkerConfig<TPayload>;
268
293
 
269
- /** Context injected into job handlers — subset of EndpointContext without request-specific fields. */
270
- interface JobContext extends PalbaseModuleClients {
271
- db: DBClient;
294
+ /** Non-service, per-invocation data for job handlers.
295
+ * Services (Database, Log, …) are imported as singletons, not passed here. */
296
+ interface JobMeta {
297
+ /** Branch-scoped env vars. */
272
298
  env: Record<string, string>;
273
- log: Logger;
274
- cache: CacheClient;
275
- queue: QueueClient;
276
299
  projectId: string;
277
300
  environmentId: string;
278
301
  }
@@ -285,14 +308,14 @@ interface JobConfig {
285
308
  /** Execution timeout in seconds. Defaults to 30. */
286
309
  timeout?: number;
287
310
  /** Job handler function. */
288
- handler: (ctx: JobContext) => Promise<void>;
311
+ handler: (meta: JobMeta) => Promise<void>;
289
312
  }
290
313
  /** Resolved job configuration with defaults applied. */
291
314
  interface ResolvedJobConfig {
292
315
  name: string;
293
316
  schedule: string;
294
317
  timeout: number;
295
- handler: (ctx: JobContext) => Promise<void>;
318
+ handler: (meta: JobMeta) => Promise<void>;
296
319
  }
297
320
  /**
298
321
  * Define a scheduled cron job.
@@ -305,9 +328,9 @@ interface ResolvedJobConfig {
305
328
  * name: "cleanup-expired",
306
329
  * schedule: "0 3 * * *", // every day at 3 AM
307
330
  * timeout: 60,
308
- * handler: async (ctx) => {
309
- * await ctx.db.delete("sessions", "expired < NOW()");
310
- * ctx.log.info("Cleaned up expired sessions");
331
+ * handler: async (meta) => {
332
+ * await Database.delete("sessions", "expired < NOW()");
333
+ * Log.info("Cleaned up expired sessions");
311
334
  * },
312
335
  * });
313
336
  * ```
@@ -316,16 +339,15 @@ declare function defineJob(config: JobConfig): ResolvedJobConfig;
316
339
 
317
340
  /** Supported webhook provider types. */
318
341
  type WebhookProvider = "stripe" | "github" | "twilio" | "sendgrid" | "slack" | "discord" | "livekit";
319
- /** Context injected into webhook handlers — no user (webhooks are machine-to-machine). */
320
- interface WebhookContext extends PalbaseModuleClients {
321
- db: DBClient;
342
+ /** Non-service, per-invocation data for webhook handlers.
343
+ * Services (Database, Log, …) are imported as singletons, not passed here. */
344
+ interface WebhookMeta {
345
+ /** Branch-scoped env vars. */
322
346
  env: Record<string, string>;
323
- log: Logger;
324
- cache: CacheClient;
325
- queue: QueueClient;
347
+ /** Per-invocation id for correlation. */
348
+ requestId: string;
326
349
  projectId: string;
327
350
  environmentId: string;
328
- requestId: string;
329
351
  }
330
352
  /** Secret reference — resolves from environment variables at runtime. */
331
353
  interface EnvSecretRef {
@@ -386,7 +408,7 @@ interface ProviderEventMap {
386
408
  };
387
409
  }
388
410
  /** Event handler function type. */
389
- type WebhookEventHandler<TEvent = Record<string, unknown>> = (ctx: WebhookContext, event: TEvent) => Promise<void>;
411
+ type WebhookEventHandler<TEvent = Record<string, unknown>> = (event: TEvent, meta: WebhookMeta) => Promise<void>;
390
412
  /** Provider-based webhook configuration. */
391
413
  interface ProviderWebhookConfig<P extends WebhookProvider = WebhookProvider> {
392
414
  provider: P;
@@ -406,7 +428,7 @@ interface WebhookRequest {
406
428
  interface CustomWebhookConfig {
407
429
  path: string;
408
430
  verify?: (req: WebhookRequest) => Promise<boolean> | boolean;
409
- handler: (ctx: WebhookContext, payload: Record<string, unknown>) => Promise<void>;
431
+ handler: (payload: Record<string, unknown>, meta: WebhookMeta) => Promise<void>;
410
432
  }
411
433
  /** Resolved provider webhook (internal). */
412
434
  interface ResolvedProviderWebhook<P extends WebhookProvider = WebhookProvider> {
@@ -420,7 +442,7 @@ interface ResolvedCustomWebhook {
420
442
  type: "custom";
421
443
  path: string;
422
444
  verify?: (req: WebhookRequest) => Promise<boolean> | boolean;
423
- handler: (ctx: WebhookContext, payload: Record<string, unknown>) => Promise<void>;
445
+ handler: (payload: Record<string, unknown>, meta: WebhookMeta) => Promise<void>;
424
446
  }
425
447
  /** Union of resolved webhook types. */
426
448
  type ResolvedWebhookConfig = ResolvedProviderWebhook | ResolvedCustomWebhook;
@@ -433,8 +455,8 @@ type ResolvedWebhookConfig = ResolvedProviderWebhook | ResolvedCustomWebhook;
433
455
  * provider: "stripe",
434
456
  * secret: { env: "STRIPE_WEBHOOK_SECRET" },
435
457
  * events: {
436
- * "checkout.session.completed": async (ctx, event) => {
437
- * await ctx.db.insert("orders", { status: "paid" });
458
+ * "checkout.session.completed": async (event, meta) => {
459
+ * await Database.insert("orders", { status: "paid" });
438
460
  * },
439
461
  * },
440
462
  * });
@@ -449,21 +471,111 @@ declare function defineWebhook<P extends WebhookProvider>(config: ProviderWebhoo
449
471
  * export default defineWebhook({
450
472
  * path: "/webhooks/livekit",
451
473
  * verify: async (req) => req.headers["x-api-key"] === "secret",
452
- * handler: async (ctx, payload) => {
453
- * ctx.log.info("Received webhook", payload);
474
+ * handler: async (payload, meta) => {
475
+ * Log.info("Received webhook", payload);
454
476
  * },
455
477
  * });
456
478
  * ```
457
479
  */
458
480
  declare function defineWebhook(config: CustomWebhookConfig): ResolvedCustomWebhook;
459
481
 
460
- /** Context injected into hook handlers. */
461
- interface HookContext extends PalbaseModuleClients {
462
- db: DBClient;
482
+ /**
483
+ * resource.ts external connections as lifecycle-managed classes.
484
+ *
485
+ * A `Resource` subclass models one external connection (a pooled datastore, a
486
+ * stateless API client, or a per-user factory). The framework discovers each
487
+ * instance, calls `init(env)` ONCE at boot with the declared secret subset, and
488
+ * `shutdown()` (reverse order) on SIGTERM. On top of that lifecycle the author
489
+ * exposes their own clean facade methods.
490
+ *
491
+ * // resources/neo4j.ts
492
+ * import { Resource } from "@palbase/backend";
493
+ * import neo4j, { type Driver, type Session } from "neo4j-driver";
494
+ *
495
+ * export class Neo4jResource extends Resource {
496
+ * static secrets = ["NEO4J_URL", "NEO4J_USER", "NEO4J_PASSWORD"] as const;
497
+ * private driver!: Driver;
498
+ * async init(env: { NEO4J_URL: string; NEO4J_USER: string; NEO4J_PASSWORD: string }) {
499
+ * this.driver = neo4j.driver(env.NEO4J_URL, neo4j.auth.basic(env.NEO4J_USER, env.NEO4J_PASSWORD));
500
+ * }
501
+ * async shutdown() { await this.driver.close(); }
502
+ * session(): Session { return this.driver.session(); }
503
+ * }
504
+ * export const neo4j = new Neo4jResource(); // framework finds + manages it
505
+ *
506
+ * # Boot scope (NOT request-ALS)
507
+ *
508
+ * Resources are instantiated once at process boot. This is deliberately NOT the
509
+ * per-request {@link AsyncLocalStorage} scope used for `Database`/`Cache`/… — a
510
+ * connection pool must outlive a single request. The runtime discovers
511
+ * resources from the project's `resources/` directory, registers each via
512
+ * {@link __registerResource}, then calls {@link __runResourceBoot} before
513
+ * serving and {@link __shutdownResources} on SIGTERM. Discovery is a runtime
514
+ * concern; the SDK provides the base class + registry + boot/shutdown hooks.
515
+ */
516
+ /** Map a declared `secrets` tuple to the `init(env)` argument type — a record
517
+ * over the secret names, each `string`. An empty tuple maps to an empty
518
+ * record. */
519
+ type ResourceEnv<Secrets extends readonly string[]> = {
520
+ [K in Secrets[number]]: string;
521
+ };
522
+ /**
523
+ * Base class for external connections.
524
+ *
525
+ * - `static secrets` — OPTIONAL readonly tuple of env-var names this resource
526
+ * needs. Drives (a) the `env` type passed to `init` and (b) the
527
+ * missing-secret check at boot (deploy fails naming the absent secret).
528
+ * - `init(env)` — called ONCE at boot with the declared secret subset. May be
529
+ * sync (`void`) or async (`Promise<void>`).
530
+ * - `shutdown()` — OPTIONAL drain hook, called on SIGTERM in reverse boot
531
+ * order.
532
+ *
533
+ * @example
534
+ * import { Resource } from "@palbase/backend";
535
+ * import { Client } from "@googlemaps/google-maps-services-js";
536
+ *
537
+ * export class GoogleResource extends Resource {
538
+ * static secrets = ["GOOGLE_MAPS_KEY"] as const;
539
+ * private client = new Client();
540
+ * private key = "";
541
+ * init(env: { GOOGLE_MAPS_KEY: string }) { this.key = env.GOOGLE_MAPS_KEY; }
542
+ * }
543
+ * export const google = new GoogleResource();
544
+ */
545
+ declare abstract class Resource {
546
+ /** The env-var names this resource needs. Optional; omit for none. */
547
+ static secrets?: readonly string[];
548
+ /** Set up the connection from the declared secrets. Called once at boot. */
549
+ abstract init(env: Record<string, string>): void | Promise<void>;
550
+ /** Drain/close the connection on SIGTERM. Optional. */
551
+ shutdown?(): void | Promise<void>;
552
+ }
553
+ /**
554
+ * Register a resource instance with the boot registry. The runtime calls this
555
+ * for each instance discovered under the project's `resources/` directory.
556
+ * NOT part of the public author-facing API (prefixed `__`).
557
+ */
558
+ declare function __registerResource(resource: Resource): void;
559
+ /**
560
+ * Boot every registered resource that has not yet been booted: resolve its
561
+ * declared secret subset from `envMap`, then await its `init(env)`. Idempotent
562
+ * — an already-booted resource is skipped. Throws (failing deploy/boot) when a
563
+ * declared secret is absent, naming the missing secret. NOT part of the public
564
+ * author-facing API.
565
+ */
566
+ declare function __runResourceBoot(envMap: Record<string, string>): Promise<void>;
567
+ /**
568
+ * Shut down every booted resource in REVERSE registration order, awaiting each
569
+ * `shutdown()` (a no-op when undefined). Clears the registry afterwards so a
570
+ * second call is a no-op. NOT part of the public author-facing API.
571
+ */
572
+ declare function __shutdownResources(): Promise<void>;
573
+
574
+ /** Non-service, per-invocation data for hook handlers.
575
+ * Services (Database, Log, …) are imported as singletons, not passed here. */
576
+ interface HookMeta {
577
+ /** Branch-scoped env vars. */
463
578
  env: Record<string, string>;
464
- log: Logger;
465
- cache: CacheClient;
466
- queue: QueueClient;
467
579
  projectId: string;
468
580
  environmentId: string;
469
581
  }
@@ -471,7 +583,8 @@ interface HookContext extends PalbaseModuleClients {
471
583
  interface UserCreatedEvent {
472
584
  user: {
473
585
  id: string;
474
- email: string;
586
+ /** User's email, if they signed up with one (absent for phone-only users). */
587
+ email?: string;
475
588
  role: string;
476
589
  metadata: Record<string, unknown>;
477
590
  createdAt: string;
@@ -481,7 +594,8 @@ interface UserCreatedEvent {
481
594
  interface SignInEvent {
482
595
  user: {
483
596
  id: string;
484
- email: string;
597
+ /** User's email, if they have one (absent for phone-only users). */
598
+ email?: string;
485
599
  role: string;
486
600
  };
487
601
  provider: string;
@@ -491,11 +605,14 @@ interface SignInEvent {
491
605
  interface SignOutEvent {
492
606
  user: {
493
607
  id: string;
494
- email: string;
608
+ /** User's email, if they have one (absent for phone-only users). */
609
+ email?: string;
495
610
  };
496
611
  timestamp: string;
497
612
  }
498
- /** Payload for auth.onPasswordReset hook. */
613
+ /** Payload for auth.onPasswordReset hook.
614
+ * Password reset is inherently email-based, so `email` is always present here
615
+ * (a phone-only passwordless user cannot trigger this event). */
499
616
  interface PasswordResetEvent {
500
617
  user: {
501
618
  id: string;
@@ -548,7 +665,7 @@ interface DocumentDeletedEvent {
548
665
  data: Record<string, unknown>;
549
666
  };
550
667
  }
551
- type HookHandler<TEvent> = (ctx: HookContext, event: TEvent) => Promise<void>;
668
+ type HookHandler<TEvent> = (event: TEvent, meta: HookMeta) => Promise<void>;
552
669
  /** Resolved hook configuration (internal). */
553
670
  interface ResolvedHook<TEvent = unknown> {
554
671
  module: string;
@@ -571,4 +688,4 @@ declare const documents: {
571
688
  onDocumentDeleted(handler: HookHandler<DocumentDeletedEvent>): ResolvedHook<DocumentDeletedEvent>;
572
689
  };
573
690
 
574
- export { type BackoffStrategy, Cache, CacheClient, type CustomWebhookConfig, DBClient, Database, type DocumentCreatedEvent, type DocumentDeletedEvent, type DocumentUpdatedEvent, Documents, type EnvSecretRef, ErrorThrowers, type FileDeletedEvent, type FileUploadedEvent, Flags, type HookContext, type HookHandler, type InsertShape, type JobConfig, type JobContext, Log, Logger, Notifications, PalbaseDocsClient, PalbaseFlagsClient, PalbaseNotificationsClient, PalbaseStorageClient, type PasswordResetEvent, type ProviderEventMap, type ProviderWebhookConfig, Queue, QueueClient, type ResolvedCustomWebhook, type ResolvedHook, type ResolvedJobConfig, type ResolvedProviderWebhook, type ResolvedWebhookConfig, type ResolvedWorkerConfig, type RowShape, type RuntimeServices, SchemaDef, type SignInEvent, type SignOutEvent, Storage, TableDef, type TypedDB, type TypedTable, type TypedTx, User, type UserCreatedEvent, type WebhookContext, type WebhookEventHandler, type WebhookProvider, type WebhookRequest, type WorkerConfig, type WorkerContext, __getRuntime, __requestALS, __runWithRuntime, __setRuntime, auth, defineJob, defineWebhook, defineWorker, documents, makeTypedDB, storage, typedDatabase };
691
+ export { type BackoffStrategy, Body, Cache, CacheClient, Client, Controller, type ControllerOptions, type CustomWebhookConfig, DBClient, Database, Delete, type DocumentCreatedEvent, type DocumentDeletedEvent, type DocumentUpdatedEvent, Documents, type EnvSecretRef, EnvTypedDatabase, type FileDeletedEvent, type FileUploadedEvent, Flags, Get, Headers, type HookHandler, type HookMeta, type HttpMethodUpper, type JobConfig, type JobMeta, Log, Logger, Notifications, OptionalUser, PalbaseDocsClient, PalbaseFlagsClient, PalbaseNotificationsClient, PalbaseStorageClient, Param, type PasswordResetEvent, Patch, Post, type ProviderEventMap, type ProviderWebhookConfig, Put, Query, Queue, QueueClient, RateLimitConfig, Req, RequestId, type ResolvedCustomWebhook, type ResolvedHook, type ResolvedJobConfig, type ResolvedProviderWebhook, type ResolvedWebhookConfig, type ResolvedWorkerConfig, Resource, type ResourceEnv, Returns, type RouteOptions, type RuntimeServices, SchemaDef, type SignInEvent, type SignOutEvent, Storage, TraceId, User, type UserCreatedEvent, User$1 as UserT, type WebhookEventHandler, type WebhookMeta, type WebhookProvider, type WebhookRequest, type WorkerConfig, type WorkerMeta, __getRuntime, __registerResource, __requestALS, __runResourceBoot, __runWithRuntime, __setRuntime, __shutdownResources, auth, defineJob, defineWebhook, defineWorker, documents, makeEnvDts, storage };