@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/docs/errors.md CHANGED
@@ -1,48 +1,54 @@
1
1
  # Errors
2
2
 
3
- Two ways to fail a request. Both serialize to the standard Palbase error
4
- envelope:
3
+ Throw an error class to fail a request. Every one serializes to the standard
4
+ Palbase error envelope:
5
5
 
6
6
  ```json
7
7
  { "error": "todo_not_found", "error_description": "No such todo", "status": 404, "request_id": "req_…" }
8
8
  ```
9
9
 
10
- ## 1. `HttpError` (ad-hoc)
10
+ Throw anywhere — in a controller method OR in a `services/` class. No `req`, no
11
+ per-route error map: the runtime catches any thrown error class and emits the
12
+ envelope.
13
+
14
+ ## Named status classes
11
15
 
12
16
  ```ts
13
- import { HttpError } from "@palbase/backend";
14
- throw new HttpError(404, "todo_not_found", "No such todo");
15
- // optional structured payload (4th arg) rides along under `data`:
16
- throw new HttpError(423, "todo_locked", "Locked", { retryAfter: 30 });
17
+ import { BadRequest, Unauthorized, Forbidden, NotFound, Conflict, TooManyRequests } from "@palbase/backend";
18
+
19
+ throw new Conflict("title taken"); // 409 ("conflict" code by default)
20
+ throw new NotFound(); // → 404 ("not_found", "Not found")
21
+ throw new BadRequest("missing field"); // → 400
22
+ throw new Unauthorized(); // → 401
23
+ throw new Forbidden(); // → 403
24
+ throw new TooManyRequests(); // → 429
17
25
  ```
18
26
 
19
- `new HttpError(status, code, description, data?)`.
27
+ Each class fixes its HTTP status. The constructor is
28
+ `new <Class>(message?, code?, data?)`:
20
29
 
21
- ## 2. Declared errors (typed)
30
+ - `message` overrides the human-readable `error_description` (defaults to a label
31
+ derived from the class name).
32
+ - `code` overrides the wire `error` code (defaults to the class's snake_case
33
+ code, e.g. `not_found`).
34
+ - `data` rides along under the envelope's `data` field for structured context.
22
35
 
23
- Declare them on the endpoint; throw via `req.errors.<name>(...)`. Declared
24
- errors are described in the endpoint's OpenAPI and codegen'd into a typed enum
25
- for iOS callers.
36
+ ```ts
37
+ throw new NotFound("Room does not exist", "room_not_found");
38
+ throw new Conflict("locked", "title_locked", { retryAfter: 30 });
39
+ ```
40
+
41
+ ## `PalError` / `HttpError` (custom status)
42
+
43
+ For a status/code not covered by a named class:
26
44
 
27
45
  ```ts
28
- import { defineEndpoint, z, Database } from "@palbase/backend";
29
-
30
- export default defineEndpoint({
31
- method: "POST",
32
- input: z.object({ id: z.string() }),
33
- output: z.object({ ok: z.boolean() }),
34
- errors: {
35
- notFound: { status: 404, code: "todo_not_found", description: "No such todo" },
36
- locked: { status: 423, code: "todo_locked", data: z.object({ retryAfter: z.number() }) },
37
- },
38
- handler: async (req) => {
39
- const todo = await Database.findById("todos", req.input.id);
40
- if (!todo) throw req.errors.notFound(); // no data → no args
41
- if (todo.locked) throw req.errors.locked({ retryAfter: 30 }); // data schema → required arg
42
- return { ok: true };
43
- },
44
- });
46
+ import { PalError } from "@palbase/backend";
47
+ throw new PalError(418, "teapot", "I'm a teapot");
48
+ // optional structured payload (4th arg) rides along under `data`:
49
+ throw new PalError(423, "todo_locked", "Locked", { retryAfter: 30 });
45
50
  ```
46
51
 
47
- A declared error with a `data` Zod schema requires that payload as an argument;
48
- one without `data` takes no arguments. This is enforced by the types.
52
+ `PalError` (and its base `HttpError`) take `(status, code, description, data?)`.
53
+ The named classes all extend `HttpError`, so `catch (e) { if (e instanceof
54
+ HttpError) … }` matches any of them.
package/docs/events.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Hooks & Webhooks
2
2
 
3
- Like workers/jobs, hooks and webhooks use the **`ctx` model** (`ctx.db`,
4
- `ctx.log`, `ctx.cache`, `ctx.queue`, `ctx.env`) not `req`, and not imported
5
- singletons.
3
+ Like workers/jobs, hooks and webhooks use the **singleton model** — the same
4
+ imported service singletons as endpoints (`import { Database, Log } from
5
+ "@palbase/backend"`). They do **not** receive a `req`. A second `meta` argument
6
+ carries the non-service data (`env`, `projectId`, `environmentId`; webhooks also
7
+ get `requestId`).
6
8
 
7
9
  ## Hooks (platform events)
8
10
 
@@ -11,21 +13,24 @@ are imported from `@palbase/backend`: `auth`, `storage`, `documents`.
11
13
 
12
14
  ```ts
13
15
  // hooks/auth.ts
14
- import { auth } from "@palbase/backend";
16
+ import { auth, Database, Log } from "@palbase/backend";
15
17
 
16
- export const onUserCreated = auth.onUserCreated(async (ctx, event) => {
17
- ctx.log.info(`new user: ${event.user.email}`);
18
- await ctx.db.insert("profiles", {
18
+ export const onUserCreated = auth.onUserCreated(async (event, meta) => {
19
+ Log.info(`new user: ${event.user.email}`);
20
+ await Database.insert("profiles", {
19
21
  user_id: event.user.id,
20
22
  email: event.user.email,
21
23
  });
22
24
  });
23
25
 
24
- export const onSignIn = auth.onSignIn(async (ctx, event) => {
25
- ctx.log.info(`sign in: ${event.user.email} via ${event.provider}`);
26
+ export const onSignIn = auth.onSignIn(async (event, meta) => {
27
+ Log.info(`sign in: ${event.user.email} via ${event.provider}`);
26
28
  });
27
29
  ```
28
30
 
31
+ `meta` shape: `{ env, projectId, environmentId }`. Branch env vars are in
32
+ `meta.env`; services come from the imported singletons.
33
+
29
34
  Available hook builders: `auth.onUserCreated`, `auth.onSignIn`, `auth.onSignOut`,
30
35
  `auth.onPasswordReset`, `storage.onFileUploaded`, `storage.onFileDeleted`,
31
36
  `documents.onDocumentCreated`, `documents.onDocumentUpdated`,
@@ -38,22 +43,25 @@ Receive and verify webhooks from third-party providers. Files live under
38
43
 
39
44
  ```ts
40
45
  // webhooks/stripe.ts
41
- import { defineWebhook } from "@palbase/backend";
46
+ import { defineWebhook, Database, Log } from "@palbase/backend";
42
47
 
43
48
  export default defineWebhook({
44
49
  provider: "stripe",
45
50
  secret: { env: "STRIPE_WEBHOOK_SECRET" }, // signing secret resolved from env
46
51
  events: {
47
- "checkout.session.completed": async (ctx, event) => {
48
- await ctx.db.insert("orders", { status: "paid", data: event });
52
+ "checkout.session.completed": async (event, meta) => {
53
+ await Database.insert("orders", { status: "paid", data: event });
49
54
  },
50
- "payment_intent.payment_failed": async (ctx, event) => {
51
- ctx.log.error("payment failed");
52
- await ctx.db.insert("payment_failures", { data: event });
55
+ "payment_intent.payment_failed": async (event, meta) => {
56
+ Log.error("payment failed");
57
+ await Database.insert("payment_failures", { data: event });
53
58
  },
54
59
  },
55
60
  });
56
61
  ```
57
62
 
58
- The signing secret is read from the project's env/secrets (`{ env: "NAME" }`);
59
- the runtime verifies the signature before dispatching to your event handlers.
63
+ The signing secret is resolved by the runtime from `secret: { env: "NAME" }`;
64
+ your handlers access branch env vars via `meta.env`. The runtime verifies the
65
+ signature before dispatching to your event handlers.
66
+
67
+ `meta` shape: `{ env, requestId, projectId, environmentId }`.
@@ -13,7 +13,6 @@ Your project depends on the SDK and uses the Palbase CLI for the dev loop:
13
13
  "private": true,
14
14
  "scripts": {
15
15
  "dev": "palbase serve",
16
- "deploy": "palbase push",
17
16
  "typecheck": "tsc --noEmit"
18
17
  },
19
18
  "dependencies": { "@palbase/backend": "latest" },
@@ -23,27 +22,48 @@ Your project depends on the SDK and uses the Palbase CLI for the dev loop:
23
22
 
24
23
  ## Local dev loop
25
24
 
26
- - `palbase serve` — run your backend locally with hot reload.
27
- - `palbase push` deploy the current directory to your project's backend runtime.
28
- - `palbase push --branch <name>` deploy to a branch instead of `main`.
25
+ - `palbase serve` — run your backend locally with hot reload. It runs your
26
+ `controllers/` locally but proxies `Database`/`ctx.*` to the **deployed**
27
+ branch, so the branch must already be deployed (serve tells you to push first
28
+ if it isn't). See [migrations.md](./migrations.md) for the schema/migration
29
+ side of this.
30
+ - **Deploy is GitHub-native** — there is no `palbase push`. Commit and
31
+ `git push` to your project's repo; the push triggers a deploy of the backend
32
+ runtime. Push a **branch** to deploy that branch instead of `main`.
29
33
 
30
34
  ## Your first endpoint
31
35
 
32
- Create `endpoints/hello/get.ts`:
36
+ An endpoint is a method on a class controller. Declare the schemas in `models/`:
33
37
 
34
38
  ```ts
35
- import { defineEndpoint, z } from "@palbase/backend";
36
-
37
- export default defineEndpoint({
38
- method: "GET",
39
- input: z.object({ name: z.string().optional() }),
40
- output: z.object({ message: z.string(), user: z.string().nullable() }),
41
- handler: async (req) => ({
42
- message: `hello, ${req.input.name ?? "world"}!`,
43
- user: req.user?.id ?? null,
44
- }),
45
- });
39
+ // models/hello/greet.ts
40
+ import { z } from "@palbase/backend";
41
+
42
+ export const GreetQuery = z.object({ name: z.string().optional() });
43
+ export type GreetQuery = z.infer<typeof GreetQuery>;
44
+
45
+ export const HelloResponse = z.object({ message: z.string(), user: z.string().nullable() });
46
+ export type HelloResponse = z.infer<typeof HelloResponse>;
47
+ ```
48
+
49
+ Then write the controller in `controllers/hello.controller.ts`:
50
+
51
+ ```ts
52
+ import { Controller, Get, Returns, Query, OptionalUser } from "@palbase/backend";
53
+ import type { UserT } from "@palbase/backend";
54
+ import { GreetQuery, HelloResponse } from "../models/hello/greet.js";
55
+
56
+ @Controller("/hello", { auth: false })
57
+ export default class HelloController {
58
+ @Get("")
59
+ @Returns(HelloResponse)
60
+ greet(@Query(GreetQuery) q: GreetQuery, @OptionalUser() user: UserT | null): HelloResponse {
61
+ return { message: `hello, ${q.name ?? "world"}!`, user: user?.id ?? null };
62
+ }
63
+ }
46
64
  ```
47
65
 
48
- This is served at `GET /hello`. The Zod `input` schema validates the request and
49
- flows into `req.input`; the `output` schema validates your return value.
66
+ This is served at `GET /hello`. The `@Query` schema validates the query string;
67
+ the method's return type (paired with `@Returns(HelloResponse)`) validates and
68
+ describes the response. See [routing.md](./routing.md) and
69
+ [endpoints.md](./endpoints.md) for the full class-controller model.