@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.
- package/dist/chunk-EG7TTYHY.js +235 -0
- package/dist/chunk-EG7TTYHY.js.map +1 -0
- package/dist/chunk-WUQO76NW.js +101 -0
- package/dist/chunk-WUQO76NW.js.map +1 -0
- package/dist/db/env.cjs +19 -0
- package/dist/db/env.cjs.map +1 -0
- package/dist/db/env.d.cts +45 -0
- package/dist/db/env.d.ts +45 -0
- package/dist/db/env.js +1 -0
- package/dist/db/env.js.map +1 -0
- package/dist/db/index.cjs +143 -231
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +4 -20
- package/dist/db/index.d.ts +4 -20
- package/dist/db/index.js +13 -233
- package/dist/db/index.js.map +1 -1
- package/dist/{endpoint-Djk5L6G2.d.ts → endpoint-2d_DpASt.d.cts} +94 -96
- package/dist/{endpoint-BlcY2xNA.d.cts → endpoint-2d_DpASt.d.ts} +94 -96
- package/dist/index-DZW9CjiY.d.ts +463 -0
- package/dist/index-DzRFS3Tl.d.cts +463 -0
- package/dist/index.cjs +557 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +278 -161
- package/dist/index.d.ts +278 -161
- package/dist/index.js +343 -12
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +57 -2
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +1 -2
- package/dist/test/index.d.ts +1 -2
- package/dist/test/index.js +10 -2
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +33 -12
- package/docs/background.md +19 -13
- package/docs/database.md +70 -17
- package/docs/endpoints.md +103 -79
- package/docs/errors.md +37 -31
- package/docs/events.md +25 -17
- package/docs/getting-started.md +38 -18
- package/docs/llms-full.txt +758 -267
- package/docs/llms.txt +3 -1
- package/docs/migrations.md +98 -0
- package/docs/resources.md +94 -0
- package/docs/routing.md +54 -27
- package/docs/schema.md +163 -42
- package/docs/services.md +17 -14
- package/package.json +12 -2
- package/dist/chunk-4J3F32SH.js +0 -96
- package/dist/chunk-4J3F32SH.js.map +0 -1
- package/dist/chunk-L36JLUPO.js +0 -97
- package/dist/chunk-L36JLUPO.js.map +0 -1
- package/dist/schema-BqfEhIC0.d.cts +0 -133
- package/dist/schema-BqfEhIC0.d.ts +0 -133
package/docs/errors.md
CHANGED
|
@@ -1,48 +1,54 @@
|
|
|
1
1
|
# Errors
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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 {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
throw new
|
|
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
|
-
|
|
27
|
+
Each class fixes its HTTP status. The constructor is
|
|
28
|
+
`new <Class>(message?, code?, data?)`:
|
|
20
29
|
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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 {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
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 (
|
|
17
|
-
|
|
18
|
-
await
|
|
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 (
|
|
25
|
-
|
|
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 (
|
|
48
|
-
await
|
|
52
|
+
"checkout.session.completed": async (event, meta) => {
|
|
53
|
+
await Database.insert("orders", { status: "paid", data: event });
|
|
49
54
|
},
|
|
50
|
-
"payment_intent.payment_failed": async (
|
|
51
|
-
|
|
52
|
-
await
|
|
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
|
|
59
|
-
|
|
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 }`.
|
package/docs/getting-started.md
CHANGED
|
@@ -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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
36
|
+
An endpoint is a method on a class controller. Declare the schemas in `models/`:
|
|
33
37
|
|
|
34
38
|
```ts
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
49
|
-
|
|
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.
|