@palbase/backend 17.4.0 → 18.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 +1848 -0
- package/dist/bin/palbase-backend.cjs.map +1 -0
- package/dist/bin/palbase-backend.d.cts +1 -0
- package/dist/bin/palbase-backend.d.ts +1 -0
- package/dist/bin/palbase-backend.js +168 -0
- package/dist/bin/palbase-backend.js.map +1 -0
- package/dist/chunk-7D4SUZUM.js +38 -0
- package/dist/chunk-7D4SUZUM.js.map +1 -0
- package/dist/chunk-N32VDWKH.js +172 -0
- package/dist/chunk-N32VDWKH.js.map +1 -0
- package/dist/chunk-POYAFBLF.js +189 -0
- package/dist/chunk-POYAFBLF.js.map +1 -0
- package/dist/chunk-QMVK4X3V.js +200 -0
- package/dist/chunk-QMVK4X3V.js.map +1 -0
- package/dist/chunk-SSGAMC26.js +342 -0
- package/dist/chunk-SSGAMC26.js.map +1 -0
- package/dist/chunk-VYH4U7ZQ.js +1138 -0
- package/dist/chunk-VYH4U7ZQ.js.map +1 -0
- package/dist/{chunk-AAN642N5.js → chunk-W5ODXPY3.js} +2 -336
- package/dist/chunk-W5ODXPY3.js.map +1 -0
- package/dist/chunk-YL4C5NRY.js +90 -0
- package/dist/chunk-YL4C5NRY.js.map +1 -0
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +21 -1
- package/dist/db/env.d.ts +21 -1
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -1
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.js +9 -6
- package/dist/{index-VLrU7rSW.d.ts → endpoint-B0LpZixz.d.cts} +124 -685
- package/dist/{index-BA_oFAz9.d.cts → endpoint-B0LpZixz.d.ts} +124 -685
- package/dist/engine/index.cjs +1797 -0
- package/dist/engine/index.cjs.map +1 -0
- package/dist/engine/index.d.cts +7 -0
- package/dist/engine/index.d.ts +7 -0
- package/dist/engine/index.js +43 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/index-B46CGNvx.d.cts +839 -0
- package/dist/index-BGSCWlUa.d.cts +674 -0
- package/dist/index-DZDUMth5.d.ts +839 -0
- package/dist/index-g-EzitI-.d.ts +674 -0
- package/dist/index.cjs +1031 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +290 -532
- package/dist/index.d.ts +290 -532
- package/dist/index.js +999 -509
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.cjs +6464 -0
- package/dist/openapi/index.cjs.map +1 -0
- package/dist/openapi/index.d.cts +170 -0
- package/dist/openapi/index.d.ts +170 -0
- package/dist/openapi/index.js +6248 -0
- package/dist/openapi/index.js.map +1 -0
- package/dist/registry-3BLYv4si.d.ts +338 -0
- package/dist/registry-Cw0YEYCg.d.cts +338 -0
- package/dist/test/index.js +2 -0
- package/dist/test/index.js.map +1 -1
- package/docs/database.md +16 -3
- package/docs/llms-full.txt +16 -3
- package/package.json +43 -13
- package/stager/package.json +4 -0
- package/stager/return_types.js +338 -0
- package/stager/stage.js +78 -0
- package/stager/throw_analysis.js +726 -0
- package/template/AGENTS.md +261 -0
- package/template/config/secrets.ts +24 -0
- package/template/controllers/health.controller.ts +30 -0
- package/template/db/schema.ts +35 -0
- package/template/package.json +18 -0
- package/template/tsconfig.json +30 -0
- package/LICENSE +0 -21
- package/dist/chunk-AAN642N5.js.map +0 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Palbase backend project
|
|
2
|
+
|
|
3
|
+
This project runs inside the Palbase backend runtime. You author TypeScript; the
|
|
4
|
+
runtime discovers it and serves it as a typed HTTP API, and generates the client
|
|
5
|
+
SDKs your apps call. Routing is explicit: a controller is a **class** whose
|
|
6
|
+
methods are routes.
|
|
7
|
+
|
|
8
|
+
Everything comes from one package:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { Controller, Get, Post, Patch, Delete, Body, QueryParams, Param, User, z, Database, Secrets, Conflict, NotFound } from "@palbase/backend";
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
> Needs `experimentalDecorators: true` — already set in this scaffold's
|
|
15
|
+
> `tsconfig.json`. They are legacy decorators; there is no `reflect-metadata`.
|
|
16
|
+
|
|
17
|
+
## What is here
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
controllers/<name>.controller.ts a @Controller class; each method is a route
|
|
21
|
+
db/schema.ts the database: tables, columns, RLS policies
|
|
22
|
+
config/secrets.ts the secrets this backend needs (names only)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Two folders appear as the project grows, and both are conventions the tooling
|
|
26
|
+
already understands:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
models/<controller>/<endpoint>.ts zod schemas, once they outgrow the controller
|
|
30
|
+
services/<name>.service.ts business logic, calls Database/Cache/Storage
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Put logic in a service. Keep controller methods thin — they validate through the
|
|
34
|
+
decorator schemas and delegate. Do not call `Database` from a controller.
|
|
35
|
+
|
|
36
|
+
## Controllers — a class of routes
|
|
37
|
+
|
|
38
|
+
`@Controller("/base", { auth? })` sets the base path and an optional default auth
|
|
39
|
+
for every route in the class. Each route is a method decorated with
|
|
40
|
+
`@Get/@Post/@Put/@Patch/@Delete/@Query("<subpath>", { auth? })`. The served path
|
|
41
|
+
is `base + subpath`.
|
|
42
|
+
|
|
43
|
+
**The class does not have to be exported.** `@Controller` records the class, so
|
|
44
|
+
importing the file IS the registration — there is no module to list it in.
|
|
45
|
+
|
|
46
|
+
The generated clients name a call `<class minus "Controller", lowercased>.<method>`:
|
|
47
|
+
`TodosController.list` is `pb.todos.list()`. The verb and the path do not affect
|
|
48
|
+
it, so restructure paths freely — but renaming a class or a method renames the
|
|
49
|
+
call in every app. Those two names are your public API.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// controllers/todos.controller.ts
|
|
53
|
+
import { Controller, Get, Post, Patch, Delete, Body, Param, User, NotFound } from "@palbase/backend";
|
|
54
|
+
import type { UserT } from "@palbase/backend";
|
|
55
|
+
import { todoService } from "../services/todo.service";
|
|
56
|
+
import { TodoSchema } from "../models/todos/shared";
|
|
57
|
+
import { CreateTodoBody } from "../models/todos/create";
|
|
58
|
+
|
|
59
|
+
@Controller("/todos")
|
|
60
|
+
class TodosController {
|
|
61
|
+
private todos = todoService;
|
|
62
|
+
|
|
63
|
+
@Get("")
|
|
64
|
+
list(@User() user: UserT): Promise<TodoSchema[]> {
|
|
65
|
+
return this.todos.list(user.id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@Post("")
|
|
69
|
+
create(@Body(CreateTodoBody) body: CreateTodoBody, @User() user: UserT): Promise<TodoSchema> {
|
|
70
|
+
return this.todos.create(user.id, body.title);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Get("/{id}")
|
|
74
|
+
async get(@Param("id") id: string, @User() user: UserT): Promise<TodoSchema> {
|
|
75
|
+
const todo = await this.todos.get(user.id, id);
|
|
76
|
+
if (!todo) throw new NotFound("no todo with that id");
|
|
77
|
+
return todo;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@Delete("/{id}")
|
|
81
|
+
remove(@Param("id") id: string, @User() user: UserT): Promise<void> {
|
|
82
|
+
return this.todos.remove(user.id, id);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Parameter decorators — each request piece, injected
|
|
88
|
+
|
|
89
|
+
| Decorator | Injects | Notes |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `@Body(schema)` | request body | annotate `: T` (the same-named `z.infer` type) |
|
|
92
|
+
| `@QueryParams(schema)` | query string | typed + validated → OpenAPI query params |
|
|
93
|
+
| `@Param("id")` | one path param | typed `string` |
|
|
94
|
+
| `@Headers(schema?)` | request headers | raw `Record<string,string>`; pass a schema to type + validate |
|
|
95
|
+
| `@User()` | the signed-in user (`: UserT`, non-null) | `@OptionalUser()` (`UserT \| null`) on `auth: false` routes |
|
|
96
|
+
| `@Client()` | parsed client info | name / version / platform |
|
|
97
|
+
| `@RequestId()` / `@TraceId()` | ids | `: string` |
|
|
98
|
+
| `@Req()` | the raw request | escape hatch |
|
|
99
|
+
|
|
100
|
+
> `@User()` (value) and `UserT` (type) are imported separately —
|
|
101
|
+
> `import { User } from "@palbase/backend"` and
|
|
102
|
+
> `import type { UserT } from "@palbase/backend"` — because the value name
|
|
103
|
+
> `User` is the decorator. Write `@User() user: UserT`.
|
|
104
|
+
|
|
105
|
+
`@Query(subpath)` is the HTTP **QUERY** method (RFC 10008): a read that is safe
|
|
106
|
+
and idempotent but carries a body. Its input goes in `@Body` — never combine it
|
|
107
|
+
with `@QueryParams`.
|
|
108
|
+
|
|
109
|
+
### Output and errors
|
|
110
|
+
|
|
111
|
+
The 200 response **is** the method's return type, and it must NAME a zod schema.
|
|
112
|
+
There is no response decorator: both the runtime's output validation and the
|
|
113
|
+
client codegen read the annotation.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
@Get("") list(): Promise<TodoSchema[]> // array (auto-wrapped in z.array)
|
|
117
|
+
@Get("/{id}") one(): Promise<TodoSchema> // one object
|
|
118
|
+
@Get("/ping") ping(): void // no response body
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`: TodoSchema` (non-Promise) and `: Promise<z.infer<typeof TodoSchema>>` are
|
|
122
|
+
equally valid. A **missing** annotation, an inline object (`Promise<{ ok: boolean }>`),
|
|
123
|
+
a union, or a name with no matching zod schema in scope is a hard build error —
|
|
124
|
+
name the schema.
|
|
125
|
+
|
|
126
|
+
Throw a global error class from anywhere; no request object needed:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { NotFound, Conflict, Forbidden, Unauthorized, BadRequest, TooManyRequests } from "@palbase/backend";
|
|
130
|
+
|
|
131
|
+
throw new Conflict("title already taken"); // → 409, standard envelope
|
|
132
|
+
throw new BadRequest({ fields: ["title"] }); // data-first: the data, then an optional message
|
|
133
|
+
throw new TooManyRequests({ retryAfter: 30 }); // data-first
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Auth is on unless you opt out
|
|
137
|
+
|
|
138
|
+
Every route requires a signed-in user by default. `@Controller("/x", { auth: false })`
|
|
139
|
+
opens all of a class's routes; `@Get("", { auth: true })` overrides it for one.
|
|
140
|
+
Resolution is route → controller → default `true`.
|
|
141
|
+
|
|
142
|
+
## Models — schemas, one file per endpoint
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
// models/todos/create.ts
|
|
146
|
+
import { z } from "@palbase/backend";
|
|
147
|
+
export const CreateTodoBody = z.object({ title: z.string().min(1) });
|
|
148
|
+
export type CreateTodoBody = z.infer<typeof CreateTodoBody>;
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Each name is a zod **value** (passed to `@Body`/`@QueryParams`, named by a return
|
|
152
|
+
type) plus an optional same-named `z.infer` **type**, which is what lets the
|
|
153
|
+
annotation read `Promise<TodoSchema>` instead of `Promise<z.infer<typeof TodoSchema>>`.
|
|
154
|
+
Shared response schemas go in `models/<controller>/shared.ts`.
|
|
155
|
+
|
|
156
|
+
## Services — business logic
|
|
157
|
+
|
|
158
|
+
Plain classes exported as a singleton. No DI container, no decorators.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
// services/todo.service.ts
|
|
162
|
+
import { Database } from "@palbase/backend";
|
|
163
|
+
|
|
164
|
+
export class TodoService {
|
|
165
|
+
list(userId: string) {
|
|
166
|
+
return Database.tables.todos.findMany({ user_id: userId });
|
|
167
|
+
}
|
|
168
|
+
create(userId: string, title: string) {
|
|
169
|
+
return Database.tables.todos.insert({ user_id: userId, title });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const todoService = new TodoService();
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`Database.tables.<name>` gives `insert`, `update(id, data)`, `delete(id)`,
|
|
177
|
+
`findById(id)`, `findMany(filter?)`, and `Database.transaction(fn)` for a plan
|
|
178
|
+
that commits or rolls back as one.
|
|
179
|
+
|
|
180
|
+
## The database — declared, not migrated
|
|
181
|
+
|
|
182
|
+
`db/schema.ts` is the database. There are no migration files to write, order, or
|
|
183
|
+
reconcile; three verbs share one engine:
|
|
184
|
+
|
|
185
|
+
- **plan** — what it would take to make the live database match the declaration
|
|
186
|
+
- **apply** — make it match, in one transaction
|
|
187
|
+
- **push** — ship the code, and REFUSE while the two still disagree
|
|
188
|
+
|
|
189
|
+
That refusal is the point: code and schema move at different speeds, and a deploy
|
|
190
|
+
that lands a handler against a column that does not exist yet fails at the first
|
|
191
|
+
request, in production. So: edit `db/schema.ts`, plan, apply, then push.
|
|
192
|
+
|
|
193
|
+
RLS is on by default and a table with no policies is deny-all. Add
|
|
194
|
+
`policies: [policy(...)]` so Postgres enforces ownership — a query that forgets
|
|
195
|
+
its `where user_id = …` still cannot read another user's rows.
|
|
196
|
+
|
|
197
|
+
After a schema change, regenerate the typed surface so `Database.tables.*` keeps
|
|
198
|
+
matching what is actually there (`palbase --help` names the command).
|
|
199
|
+
|
|
200
|
+
## Config as code
|
|
201
|
+
|
|
202
|
+
`config/*.ts` declares module configuration the same git-authoritative way the
|
|
203
|
+
schema does, applied on deploy:
|
|
204
|
+
|
|
205
|
+
- `config/secrets.ts` — `defineSecrets({ secrets: [secret("NAME", …)] })`
|
|
206
|
+
- `config/storage.ts` — `defineStorage(...)` buckets
|
|
207
|
+
- `config/notifications.ts` — `defineNotifications(...)` providers
|
|
208
|
+
- `config/flags.ts` — `defineFlags(...)` feature flags
|
|
209
|
+
|
|
210
|
+
### Secrets
|
|
211
|
+
|
|
212
|
+
A secret's **value** never enters this repository. `config/secrets.ts` declares
|
|
213
|
+
the NAME and whether it is required; the value lives in the environment's vault
|
|
214
|
+
and reaches the process through `Secrets.get("NAME")`:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { Secrets } from "@palbase/backend";
|
|
218
|
+
const dsn = await Secrets.get("SENTRY_DSN"); // string | null
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Declaring it is what turns a missing secret into a refused push that names it,
|
|
222
|
+
instead of a 500 on the first request that touches it. Never read
|
|
223
|
+
`process.env` for one, and never commit a `.env`.
|
|
224
|
+
|
|
225
|
+
Provider certs and keys are the same rule: author them with the guided CLI,
|
|
226
|
+
which uploads the file to the vault and writes only the non-secret fields into
|
|
227
|
+
`config/notifications.ts`.
|
|
228
|
+
|
|
229
|
+
## Tests
|
|
230
|
+
|
|
231
|
+
`npm test` runs Node's own test runner over every `*.test.ts`. Nothing to
|
|
232
|
+
install and nothing to configure — Node discovers the files and strips the types
|
|
233
|
+
itself.
|
|
234
|
+
|
|
235
|
+
Test the **service** layer. Controllers are thin by construction, so the logic
|
|
236
|
+
that can be wrong lives in a service, which is a plain class you construct
|
|
237
|
+
directly. A service that needs `Database` takes it as a constructor argument
|
|
238
|
+
instead of importing the singleton, and the test passes a stand-in.
|
|
239
|
+
|
|
240
|
+
Node **strips** types, it never emits code, so TypeScript features that require
|
|
241
|
+
emitted code are refused outright: parameter properties (`constructor(private x: T)`),
|
|
242
|
+
`enum`, and `namespace` all fail with `ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`. The
|
|
243
|
+
refusal is per FILE, so one parameter property anywhere in a test's import graph
|
|
244
|
+
takes down every test in that file at once — it reads like a dozen broken tests
|
|
245
|
+
and is one keyword. Assign fields in the constructor body; use a `const` object
|
|
246
|
+
or a union instead of `enum`; use a module instead of `namespace`.
|
|
247
|
+
|
|
248
|
+
Node's ESM resolver wants the extension on relative imports, so a test imports
|
|
249
|
+
`./todo.service.ts`, not `./todo.service`. This scaffold's `tsconfig.json`
|
|
250
|
+
allows that.
|
|
251
|
+
|
|
252
|
+
`npm test` answers "is the logic right". It is not the deploy's own validation —
|
|
253
|
+
decorators, return types, SDK major — which answers "would this ship".
|
|
254
|
+
|
|
255
|
+
## Commands and the full reference
|
|
256
|
+
|
|
257
|
+
This file deliberately does not list the CLI surface: commands change, and a copy
|
|
258
|
+
inside your repository goes stale silently. `palbase --help` is authoritative.
|
|
259
|
+
|
|
260
|
+
The complete SDK reference — controllers, models, schema, services, errors,
|
|
261
|
+
jobs, hooks, webhooks — lives at <https://app.dev.palbase.studio/docs/backend>.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineSecrets, secret } from "@palbase/backend";
|
|
2
|
+
|
|
3
|
+
// The secrets this backend needs, declared next to the code that needs them.
|
|
4
|
+
//
|
|
5
|
+
// This file holds NAMES, never values. A value lives only in the environment's
|
|
6
|
+
// vault and reaches the process through `Secrets.get("NAME")`; nothing here —
|
|
7
|
+
// and nothing in the repository this is committed to — ever holds one.
|
|
8
|
+
//
|
|
9
|
+
// Declaring the requirement is what makes a missing secret a sentence on your
|
|
10
|
+
// terminal instead of a 500 on the first request that touches it: `push`
|
|
11
|
+
// compares this list against the target environment's vault and refuses,
|
|
12
|
+
// naming what is missing, before anything ships.
|
|
13
|
+
//
|
|
14
|
+
// `required` defaults to true. The example below is optional because the
|
|
15
|
+
// backend genuinely runs without it — a required secret in a fresh scaffold
|
|
16
|
+
// would just block your first deploy. Delete it and declare your own.
|
|
17
|
+
export default defineSecrets({
|
|
18
|
+
secrets: [
|
|
19
|
+
secret("SENTRY_DSN", {
|
|
20
|
+
required: false,
|
|
21
|
+
description: "crash reporting endpoint — errors go unreported without it",
|
|
22
|
+
}),
|
|
23
|
+
],
|
|
24
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Controller, Get, z } from "@palbase/backend";
|
|
2
|
+
|
|
3
|
+
// A route's success response IS its return type — a NAMED zod schema, read by
|
|
4
|
+
// both the runtime (output validation) and the client codegen. Small projects
|
|
5
|
+
// declare it beside the route; once a controller grows, move the schemas to
|
|
6
|
+
// `models/<controller>/<endpoint>.ts` and import them.
|
|
7
|
+
export const HealthResponse = z.object({
|
|
8
|
+
status: z.string(),
|
|
9
|
+
});
|
|
10
|
+
export type HealthResponse = z.infer<typeof HealthResponse>;
|
|
11
|
+
|
|
12
|
+
// A controller is a CLASS whose methods are routes. There is no module to
|
|
13
|
+
// register it in and no export to remember: `@Controller` records the class,
|
|
14
|
+
// and importing the file is the registration.
|
|
15
|
+
//
|
|
16
|
+
// The full path is the controller base + the method subpath, so this serves
|
|
17
|
+
// `GET /health`. The generated clients call it `pb.health.check()` —
|
|
18
|
+
// `<class minus "Controller", lowercased>.<method>`, never the verb or the
|
|
19
|
+
// path. Renaming either renames the call in every app, so treat those two
|
|
20
|
+
// names as your public API.
|
|
21
|
+
//
|
|
22
|
+
// Auth is required unless a route opts out. A health check is the rare route
|
|
23
|
+
// that genuinely has no caller identity, hence `auth: false`.
|
|
24
|
+
@Controller("/health", { auth: false })
|
|
25
|
+
class HealthController {
|
|
26
|
+
@Get("")
|
|
27
|
+
check(): HealthResponse {
|
|
28
|
+
return { status: "ok" };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineSchema, policy, text, timestamp, uuid } from "@palbase/backend";
|
|
2
|
+
|
|
3
|
+
// `db/schema.ts` IS the database. There are no migration files to write, order,
|
|
4
|
+
// or reconcile: the CLI diffs this declaration against the live database, shows
|
|
5
|
+
// you what it would take to make them match, applies it in one transaction, and
|
|
6
|
+
// REFUSES to push code while the two still disagree.
|
|
7
|
+
//
|
|
8
|
+
// Row-Level Security is on by default. A table with RLS and no policies is
|
|
9
|
+
// deny-all — the correct starting state, because nothing reads a table until a
|
|
10
|
+
// policy says who may. `notes` below is scoped to its owner; delete it and
|
|
11
|
+
// declare your own.
|
|
12
|
+
export default defineSchema({
|
|
13
|
+
tables: {
|
|
14
|
+
notes: {
|
|
15
|
+
columns: {
|
|
16
|
+
id: uuid().primaryKey().defaultRandom(),
|
|
17
|
+
// A real foreign key to the tenant's own `auth.users`, so deleting an
|
|
18
|
+
// account takes its rows with it instead of leaving orphans an erasure
|
|
19
|
+
// request cannot reach. The referenced id is text, so this column is.
|
|
20
|
+
user_id: text().notNull().referencesAuthUser("cascade"),
|
|
21
|
+
body: text().notNull(),
|
|
22
|
+
created_at: timestamp().defaultNow(),
|
|
23
|
+
},
|
|
24
|
+
policies: [
|
|
25
|
+
// Postgres enforces ownership, not the handler: a query that forgets
|
|
26
|
+
// its `where user_id = …` still cannot see another user's rows.
|
|
27
|
+
policy("notes_owner")
|
|
28
|
+
.for("all")
|
|
29
|
+
.to("authenticated")
|
|
30
|
+
.using("user_id = (select auth.uid())")
|
|
31
|
+
.withCheck("user_id = (select auth.uid())"),
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "palbase-backend",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "A Palbase backend — class controllers, a declared database, and the secrets it needs.",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node --test --experimental-strip-types",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@palbase/backend": "^18.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^22",
|
|
16
|
+
"typescript": "^5"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"types": ["node"],
|
|
14
|
+
// The route decorators are legacy parameter decorators — no reflect-metadata,
|
|
15
|
+
// but they do not compile without this.
|
|
16
|
+
"experimentalDecorators": true,
|
|
17
|
+
// `npm test` runs Node's own test runner, whose ESM resolver requires the
|
|
18
|
+
// .ts extension on relative imports. This permits it; it does not require
|
|
19
|
+
// it, so imports elsewhere stay extensionless.
|
|
20
|
+
"allowImportingTsExtensions": true
|
|
21
|
+
},
|
|
22
|
+
"include": [
|
|
23
|
+
"controllers/**/*.ts",
|
|
24
|
+
"models/**/*.ts",
|
|
25
|
+
"services/**/*.ts",
|
|
26
|
+
"config/**/*.ts",
|
|
27
|
+
"db/**/*.ts",
|
|
28
|
+
"*.d.ts"
|
|
29
|
+
]
|
|
30
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024-present Palbase
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|