@palbase/backend 25.1.0 → 27.1.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/bin/palbase-backend.cjs +2432 -1039
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +87 -51
- package/dist/bin/palbase-backend.js.map +1 -1
- package/dist/chunk-BQN723PL.js +930 -0
- package/dist/chunk-BQN723PL.js.map +1 -0
- package/dist/chunk-CGNN2PUH.js +213 -0
- package/dist/chunk-CGNN2PUH.js.map +1 -0
- package/dist/{chunk-VDF2T4AS.js → chunk-EB3TUX5J.js} +1228 -591
- package/dist/chunk-EB3TUX5J.js.map +1 -0
- package/dist/chunk-JVZQCC77.js +728 -0
- package/dist/chunk-JVZQCC77.js.map +1 -0
- package/dist/chunk-OZKSM3JW.js +370 -0
- package/dist/chunk-OZKSM3JW.js.map +1 -0
- package/dist/{chunk-YOY5DFQS.js → chunk-TWX6JTGJ.js} +76 -34
- package/dist/{chunk-YOY5DFQS.js.map → chunk-TWX6JTGJ.js.map} +1 -1
- package/dist/{chunk-35PNTIRN.js → chunk-VVMJEVQP.js} +63 -162
- package/dist/chunk-VVMJEVQP.js.map +1 -0
- package/dist/{chunk-7D4SUZUM.js → chunk-VXPNPVAG.js} +3 -1
- package/dist/chunk-XABHGMUT.js +885 -0
- package/dist/chunk-XABHGMUT.js.map +1 -0
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +2 -2
- package/dist/db/env.d.ts +2 -2
- package/dist/db/index.cjs +780 -344
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +7 -4
- package/dist/engine/index.cjs +2366 -1002
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +6 -6
- package/dist/engine/index.d.ts +6 -6
- package/dist/engine/index.js +7 -6
- package/dist/{index-CUomTA3e.d.ts → index-CAKOgAlP.d.ts} +171 -296
- package/dist/index-CgE4sVhg.d.cts +4864 -0
- package/dist/{index-ClpDeSos.d.cts → index-H-0qv5d4.d.cts} +171 -296
- package/dist/index-V7QRh1wg.d.ts +4864 -0
- package/dist/index.cjs +2720 -1169
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -19
- package/dist/index.d.ts +165 -19
- package/dist/index.js +738 -477
- package/dist/index.js.map +1 -1
- package/dist/module-Dl1KFVtc.d.cts +54 -0
- package/dist/module-Dl1KFVtc.d.ts +54 -0
- package/dist/openapi/index.cjs +1330 -484
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.d.cts +4 -2
- package/dist/openapi/index.d.ts +4 -2
- package/dist/openapi/index.js +1264 -474
- package/dist/openapi/index.js.map +1 -1
- package/dist/{registry-dZZ5JKYg.d.ts → registry-4EI8aaFs.d.ts} +1 -1
- package/dist/{registry-CC0WBQq6.d.cts → registry-DHsPDY0_.d.cts} +1 -1
- package/dist/stack.cjs.map +1 -1
- package/dist/test/index.cjs +732 -141
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +30 -4
- package/dist/test/index.d.ts +30 -4
- package/dist/test/index.js +490 -124
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +33 -18
- package/docs/auth.md +1 -1
- package/docs/background.md +2 -2
- package/docs/database.md +255 -50
- package/docs/endpoints.md +3 -4
- package/docs/events.md +3 -3
- package/docs/getting-started.md +1 -1
- package/docs/llms-full.txt +435 -117
- package/docs/migrations.md +2 -2
- package/docs/schema.md +19 -10
- package/docs/services.md +116 -26
- package/package.json +8 -4
- package/stager/generics.js +205 -0
- package/stager/stage.js +39 -3
- package/template/AGENTS.md +110 -72
- package/template/db/public.ts +1 -1
- package/template/{controllers → modules/health}/health.controller.ts +1 -1
- package/template/modules/health/health.module.ts +24 -0
- package/template/modules/notes/note.service.test.ts +49 -0
- package/template/modules/notes/note.service.ts +108 -0
- package/template/{controllers → modules/notes}/notes.controller.ts +17 -11
- package/template/modules/notes/notes.module.ts +37 -0
- package/template/package.json +5 -3
- package/template/scripts/test.sh +33 -0
- package/template/tsconfig.json +29 -30
- package/dist/chunk-35PNTIRN.js.map +0 -1
- package/dist/chunk-CJSKYY76.js +0 -627
- package/dist/chunk-CJSKYY76.js.map +0 -1
- package/dist/chunk-CRQKCRGF.js +0 -276
- package/dist/chunk-CRQKCRGF.js.map +0 -1
- package/dist/chunk-G4R6BTLV.js +0 -662
- package/dist/chunk-G4R6BTLV.js.map +0 -1
- package/dist/chunk-VDF2T4AS.js.map +0 -1
- package/dist/chunk-XABBC7JP.js +0 -55
- package/dist/chunk-XABBC7JP.js.map +0 -1
- package/dist/endpoint-CTEHhb7A.d.ts +0 -2386
- package/dist/endpoint-DYHMo6cC.d.cts +0 -2386
- package/dist/index-CW21M9Z3.d.ts +0 -1222
- package/dist/index-CmBK76nx.d.cts +0 -1222
- package/template/services/note.service.test.ts +0 -45
- package/template/services/note.service.ts +0 -76
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-VXPNPVAG.js.map} +0 -0
- /package/template/{models/notes → modules/notes/dto}/create.ts +0 -0
|
@@ -1,2386 +0,0 @@
|
|
|
1
|
-
import { ZodSchema, z } from 'zod';
|
|
2
|
-
import { PalbaseFlagKey, PalbaseSecretName } from './stack.cjs';
|
|
3
|
-
|
|
4
|
-
/** Supported HTTP methods for endpoints. */
|
|
5
|
-
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
6
|
-
/**
|
|
7
|
-
* Verified device claim, minted at the platform attestation boundary (App
|
|
8
|
-
* Attest / Play Integrity). Server-owned and immutable: it is populated ONLY
|
|
9
|
-
* from a successful, request-bound verification — never from a raw client
|
|
10
|
-
* header or from user/JWT metadata.
|
|
11
|
-
*/
|
|
12
|
-
interface VerifiedDevice {
|
|
13
|
-
/** Server-minted `auth.installations` anchor (`ins_…`); may be "" until the installation binding is wired at the verify boundary. */
|
|
14
|
-
installationId: string;
|
|
15
|
-
/** Palauth device record (`dev_…`) proven by the App Attest assertion. Absent on the Android integrity path. */
|
|
16
|
-
authDeviceId?: string;
|
|
17
|
-
platform: "ios" | "android" | "web";
|
|
18
|
-
/** Proven verification tier — never a string-matched or header-asserted value. */
|
|
19
|
-
assurance: "attested" | "app_integrity" | "registered";
|
|
20
|
-
}
|
|
21
|
-
/** Authenticated user attached to the request context. */
|
|
22
|
-
interface User {
|
|
23
|
-
id: string;
|
|
24
|
-
/** User's email, if they signed up with one (absent for phone-only users). */
|
|
25
|
-
email?: string;
|
|
26
|
-
/**
|
|
27
|
-
* Whether that email address has been confirmed.
|
|
28
|
-
*
|
|
29
|
-
* Server-resolved from the verified user profile, not read from the JWT: a
|
|
30
|
-
* token claim is only true as of when the token was minted, so a user who
|
|
31
|
-
* verifies mid-session would keep reporting `false` until it expired.
|
|
32
|
-
*
|
|
33
|
-
* `false` for a phone-only user (no address to confirm) and for any user who
|
|
34
|
-
* has not clicked through yet. Before this field existed, answering "is this
|
|
35
|
-
* address confirmed" in a handler cost an extra network round-trip PER
|
|
36
|
-
* REQUEST for one boolean the runtime already had.
|
|
37
|
-
*/
|
|
38
|
-
emailVerified: boolean;
|
|
39
|
-
role: string;
|
|
40
|
-
metadata: Record<string, unknown>;
|
|
41
|
-
/**
|
|
42
|
-
* Reserved, server-owned verified device claim. `null` on an authenticated
|
|
43
|
-
* request with no verified device. The context assembler is the sole writer;
|
|
44
|
-
* user/JWT metadata can never populate it.
|
|
45
|
-
*/
|
|
46
|
-
device: VerifiedDevice | null;
|
|
47
|
-
}
|
|
48
|
-
/** Authentication configuration for an endpoint. */
|
|
49
|
-
interface AuthConfig {
|
|
50
|
-
/** Whether authentication is required. Defaults to true. */
|
|
51
|
-
required: boolean;
|
|
52
|
-
/** Required role for access. If undefined, any authenticated user is allowed.
|
|
53
|
-
*
|
|
54
|
-
* Matched against the caller's `metadata.role` — NOT `user.role`, which is the
|
|
55
|
-
* database role RLS reads and is always "authenticated" for a signed-in user.
|
|
56
|
-
* Not signed in → 401; signed in with a different or missing role → 403. */
|
|
57
|
-
role?: string;
|
|
58
|
-
/** Require a confirmed email address. An unverified caller gets 403
|
|
59
|
-
* `email_not_verified`. Fences a whole controller; for a partial rule read
|
|
60
|
-
* `user.emailVerified` in the handler instead. */
|
|
61
|
-
verifiedEmail?: boolean;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Middleware context — subset of EndpointContext without input (not yet validated). */
|
|
65
|
-
interface MiddlewareContext extends PalbaseModuleClients {
|
|
66
|
-
params: Record<string, string>;
|
|
67
|
-
query: Record<string, string>;
|
|
68
|
-
headers: Record<string, string>;
|
|
69
|
-
user: User | null;
|
|
70
|
-
db: DBClient;
|
|
71
|
-
env: Record<string, string>;
|
|
72
|
-
log: Logger;
|
|
73
|
-
cache: CacheClient;
|
|
74
|
-
requestId: string;
|
|
75
|
-
environmentId: string;
|
|
76
|
-
}
|
|
77
|
-
/** Middleware function signature — receives context and next function. */
|
|
78
|
-
type MiddlewareHandler = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
|
|
79
|
-
/**
|
|
80
|
-
* REMOVED IN BEHAVIOUR, KEPT IN NAME.
|
|
81
|
-
*
|
|
82
|
-
* There is no middleware pipeline in this runtime. No bundler reads a
|
|
83
|
-
* `middleware/` directory, the engine never calls a handler defined here, and
|
|
84
|
-
* measured on 2026-08-31 this function had no caller anywhere in the runtime or
|
|
85
|
-
* the CLI. It returned its argument unchanged, so code written against it
|
|
86
|
-
* compiled, deployed, and then never ran — with nothing reporting that.
|
|
87
|
-
*
|
|
88
|
-
* A silent shell is the worst version of a retired feature: it lets a user (or a
|
|
89
|
-
* coding assistant, which is how this surfaced) ship a request logger, an auth
|
|
90
|
-
* check or a rate limiter that simply does not exist in production. So the call
|
|
91
|
-
* refuses, and says where the work belongs.
|
|
92
|
-
*
|
|
93
|
-
* The SYMBOL survives because removing a published export costs a major and
|
|
94
|
-
* 25.0.1 had just shipped. Deleting it is a proposal for the next one; the types
|
|
95
|
-
* below stay either way, so a file that only annotates with them still compiles.
|
|
96
|
-
*
|
|
97
|
-
* This is the shape the SDK already uses for a retired surface: `@Query(schema)`
|
|
98
|
-
* on a parameter throws at decoration time with a message naming its
|
|
99
|
-
* replacement.
|
|
100
|
-
*/
|
|
101
|
-
declare function defineMiddleware(_fn: MiddlewareHandler): never;
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* tx-plan.ts — `Database.transaction()` as a PLAN, not a pinned session.
|
|
105
|
-
*
|
|
106
|
-
* A transaction used to be a conversation: BEGIN, then one network round trip
|
|
107
|
-
* per operation, then COMMIT. Each of those round trips cost ~4 ms and, because
|
|
108
|
-
* the pooler runs in transaction mode, an open transaction pinned a Postgres
|
|
109
|
-
* backend for the whole conversation. A 121-operation statement upload pinned
|
|
110
|
-
* one backend for ~490 ms.
|
|
111
|
-
*
|
|
112
|
-
* So the callback no longer TALKS to the database. It DESCRIBES what should
|
|
113
|
-
* happen; the description is serialised and sent once; the broker runs the whole
|
|
114
|
-
* thing inside one transaction and answers once. Committing on return and
|
|
115
|
-
* rolling back on throw is unchanged — that is the only property tenant code
|
|
116
|
-
* actually asked for.
|
|
117
|
-
*
|
|
118
|
-
* The consequences, stated plainly, because they are the whole design:
|
|
119
|
-
*
|
|
120
|
-
* - The callback is SYNCHRONOUS. There is nothing to await: no statement has
|
|
121
|
-
* run yet when it returns. `async` on the callback and `await` inside it are
|
|
122
|
-
* both compile errors (see {@link TxPlan} and {@link NotAwaitable}).
|
|
123
|
-
* - `insert()` does not hand back a row, it hands back {@link TxRows}. Reading
|
|
124
|
-
* a field requires `.expectOne(err)` first, which makes "what if the row
|
|
125
|
-
* isn't there" a question you cannot route around: it is the argument.
|
|
126
|
-
* - A field read from a row is a {@link Ref} — a PROMISE OF A VALUE THE SERVER
|
|
127
|
-
* WILL PRODUCE, not the value. It can be written into a later operation and
|
|
128
|
-
* it can be returned from the callback (it is substituted for the real value
|
|
129
|
-
* before `transaction()` resolves). It cannot be branched on. See the
|
|
130
|
-
* "Truthiness" note below — this is the sharp edge of the whole design.
|
|
131
|
-
* - Control flow that needs a real value must move OUT of the callback: read
|
|
132
|
-
* before the transaction, or express the condition as a guard
|
|
133
|
-
* (`updateWhere({ id, accepted_at: null }, …).expectOne(new Conflict(…))`)
|
|
134
|
-
* which the server evaluates and which rolls the whole plan back.
|
|
135
|
-
*
|
|
136
|
-
* # Truthiness — the hole this file CANNOT close
|
|
137
|
-
*
|
|
138
|
-
* JavaScript does not let a Proxy trap truthiness. `if (ref)` takes the true
|
|
139
|
-
* branch, always, for every Ref, and no `get` handler ever runs. `tsc` is silent
|
|
140
|
-
* because a Ref is a perfectly good object. So:
|
|
141
|
-
*
|
|
142
|
-
* const pot = tx.tables.pots.select({ id }, { limit: 1 }).expectOne(e);
|
|
143
|
-
* if (!pot.balance) { … } // ← ALWAYS false. Silently wrong data.
|
|
144
|
-
*
|
|
145
|
-
* What this file does close: coercion (`Symbol.toPrimitive`/`valueOf`/
|
|
146
|
-
* `toString`), awaiting (`then` is a callable member with a non-thenable
|
|
147
|
-
* signature, which is a *compile* error), serialisation (`toJSON`), and nesting
|
|
148
|
-
* a Ref inside a literal value where the server would store it as data. What it
|
|
149
|
-
* cannot close is a bare truthiness test. The real defence is the build-time
|
|
150
|
-
* static analysis (`tx_analysis.js`, phase P4); until that ships, this hole is
|
|
151
|
-
* open and this comment is the only warning.
|
|
152
|
-
*
|
|
153
|
-
* # Wire contract
|
|
154
|
-
*
|
|
155
|
-
* The JSON this file emits is consumed by
|
|
156
|
-
* the plan executor in `engine/db.ts`. That executor rejects
|
|
157
|
-
* unknown fields at every level, so an op carries EXACTLY the fields its kind
|
|
158
|
-
* takes. Everything here that looks like a needless restriction is one of the
|
|
159
|
-
* server's rules made visible early:
|
|
160
|
-
*
|
|
161
|
-
* - `$ref` only points BACKWARDS, and only at an op statically known to yield
|
|
162
|
-
* at most one row (insert, or a `one`/`atMost 1` guard, or `select limit 1`).
|
|
163
|
-
* `.expectOne()` is what this file uses to satisfy that, always.
|
|
164
|
-
* - `$expr` is a closed set: `inc`/`dec` (update only — they read the column's
|
|
165
|
-
* current value) and `now()`.
|
|
166
|
-
* - `update`/`delete` require a `where`; `insert` refuses one.
|
|
167
|
-
* - `insertMany` rows must all set the same columns.
|
|
168
|
-
* - ≤1000 ops, ≤5000 rows per insertMany, ≤8 MiB of JSON.
|
|
169
|
-
*
|
|
170
|
-
* Column keys are emitted SORTED, so the same callback always produces byte-
|
|
171
|
-
* identical JSON. That is what lets the Go decoder be locked to golden files
|
|
172
|
-
* this SDK emits (`testdata/tx_plan_golden/`).
|
|
173
|
-
*/
|
|
174
|
-
/**
|
|
175
|
-
* A plan handle was used as if it were a value: awaited, coerced to a string or
|
|
176
|
-
* number, serialised, or nested inside another value.
|
|
177
|
-
*
|
|
178
|
-
* Thrown while the callback is still BUILDING the plan, so nothing has been sent
|
|
179
|
-
* and nothing has been written.
|
|
180
|
-
*/
|
|
181
|
-
declare class TxRefError extends Error {
|
|
182
|
-
constructor(message: string);
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* The plan the callback described cannot be sent: it breaks a rule the server
|
|
186
|
-
* would reject, and rejecting it here names the line that wrote it instead of
|
|
187
|
-
* returning a 400 about an op index.
|
|
188
|
-
*/
|
|
189
|
-
declare class TxPlanError extends Error {
|
|
190
|
-
constructor(message: string);
|
|
191
|
-
}
|
|
192
|
-
/** A backwards reference to an earlier op's single-row result. */
|
|
193
|
-
interface TxWireRef {
|
|
194
|
-
$ref: {
|
|
195
|
-
op: number;
|
|
196
|
-
field: string;
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
/** A call from the server's closed function set. */
|
|
200
|
-
interface TxWireExpr {
|
|
201
|
-
$expr: {
|
|
202
|
-
fn: "inc" | "dec";
|
|
203
|
-
by: number;
|
|
204
|
-
} | {
|
|
205
|
-
fn: "now";
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
/** One value in a `values`/`set`/`where` map: a literal, a `$ref` or a `$expr`. */
|
|
209
|
-
type TxWireValue = TxWireRef | TxWireExpr | unknown;
|
|
210
|
-
/** The tenant's declared expectation about an op's row count. `slot` indexes the
|
|
211
|
-
* client-side error table — the error OBJECT never travels. */
|
|
212
|
-
interface TxWireGuard {
|
|
213
|
-
kind: "one" | "none" | "atLeast" | "atMost";
|
|
214
|
-
n: number;
|
|
215
|
-
slot: number;
|
|
216
|
-
}
|
|
217
|
-
/** One operation in the wire plan. Fields are omitted, never null: the decoder
|
|
218
|
-
* rejects a field that does not belong to the op's kind. */
|
|
219
|
-
interface TxWireOp {
|
|
220
|
-
/** upsert and insertMany: the columns Postgres matches on. */
|
|
221
|
-
onConflict?: readonly string[];
|
|
222
|
-
/** insertMany only: what a collision does. Absent means no ON CONFLICT clause
|
|
223
|
-
* at all, which is what every insertMany did before this option existed. */
|
|
224
|
-
action?: "ignore" | "update";
|
|
225
|
-
op: "insert" | "insertMany" | "upsert" | "update" | "delete" | "select";
|
|
226
|
-
table: string;
|
|
227
|
-
values?: Record<string, TxWireValue>;
|
|
228
|
-
rows?: Record<string, TxWireValue>[];
|
|
229
|
-
set?: Record<string, TxWireValue>;
|
|
230
|
-
where?: Record<string, TxWireValue>;
|
|
231
|
-
limit?: number;
|
|
232
|
-
lock?: "update";
|
|
233
|
-
guard?: TxWireGuard;
|
|
234
|
-
}
|
|
235
|
-
/** The plan the engine executes on the request's own transaction. */
|
|
236
|
-
interface TxPlanBody {
|
|
237
|
-
ops: TxWireOp[];
|
|
238
|
-
}
|
|
239
|
-
/** One op's outcome, positionally matched to the plan's ops. */
|
|
240
|
-
interface TxPlanOpResult {
|
|
241
|
-
rows: Record<string, unknown>[];
|
|
242
|
-
rows_affected: number;
|
|
243
|
-
}
|
|
244
|
-
/** One result per op, in plan order. */
|
|
245
|
-
interface TxPlanResponse {
|
|
246
|
-
results: TxPlanOpResult[];
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* The fields the runtime must copy from the broker's error envelope onto the
|
|
250
|
-
* rejection it throws out of {@link DBClient.txPlan}.
|
|
251
|
-
*
|
|
252
|
-
* `slot` is the whole point: on a guard failure the server answers with the
|
|
253
|
-
* INDEX of the expectation that did not hold, never with an error message of its
|
|
254
|
-
* own, and this SDK maps that index back to the `Error` the callback handed to
|
|
255
|
-
* `.expectOne(…)`. Without `slot` a guard failure degrades to a generic 409.
|
|
256
|
-
*/
|
|
257
|
-
interface TxPlanRejection {
|
|
258
|
-
status?: number;
|
|
259
|
-
/** `tx_plan_invalid` | `tx_guard_failed` | `tx_ref_unresolved` | a pg class. */
|
|
260
|
-
error_code?: string;
|
|
261
|
-
/** Present only for `tx_guard_failed`: the client-side error table index. */
|
|
262
|
-
slot?: number;
|
|
263
|
-
/** Present on a database error: which op failed. */
|
|
264
|
-
op?: number;
|
|
265
|
-
}
|
|
266
|
-
declare const refBrand: unique symbol;
|
|
267
|
-
declare const rowBrand: unique symbol;
|
|
268
|
-
declare const rowsBrand: unique symbol;
|
|
269
|
-
/**
|
|
270
|
-
* Makes a handle a compile error to `await`.
|
|
271
|
-
*
|
|
272
|
-
* `then` is declared as a CALLABLE member whose signature is not `PromiseLike`,
|
|
273
|
-
* which is precisely the shape TypeScript rejects: `await handle` is TS1320 and
|
|
274
|
-
* `async () => handle` is TS1058. A non-callable `then` would not do it — the
|
|
275
|
-
* compiler simply ignores those.
|
|
276
|
-
*/
|
|
277
|
-
interface NotAwaitable {
|
|
278
|
-
/** Not a promise. Nothing here has run yet; there is nothing to await. */
|
|
279
|
-
then(doNotAwaitAPlanHandle: "a transaction plan is built synchronously"): never;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* A value the SERVER will produce, standing in for a column of a row this plan
|
|
283
|
-
* writes or reads.
|
|
284
|
-
*
|
|
285
|
-
* Legal uses: write it into a later operation's `values`/`set`/`where`, or
|
|
286
|
-
* return it from the callback (it is replaced by the real value before
|
|
287
|
-
* `transaction()` resolves).
|
|
288
|
-
*
|
|
289
|
-
* Illegal, and caught: `await`, `String(ref)`, `` `${ref}` ``, `ref + 1`,
|
|
290
|
-
* `JSON.stringify(ref)`, burying it inside a jsonb object.
|
|
291
|
-
*
|
|
292
|
-
* Illegal, and NOT caught: `if (ref)`. See the truthiness note at the top.
|
|
293
|
-
*/
|
|
294
|
-
interface Ref<T> extends NotAwaitable {
|
|
295
|
-
readonly [refBrand]: T;
|
|
296
|
-
}
|
|
297
|
-
/** The brand carried by a single-row handle, and the seam `Materialized` reads
|
|
298
|
-
* to turn `return st` into the whole row. */
|
|
299
|
-
interface TxRowHandle<Row> extends NotAwaitable {
|
|
300
|
-
readonly [rowBrand]: Row;
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* A row this plan is known to produce exactly one of. Every property is a
|
|
304
|
-
* {@link Ref}; returning the handle itself yields the whole row.
|
|
305
|
-
*
|
|
306
|
-
* Only `.expectOne(err)` produces one — which is the design: a row you can read
|
|
307
|
-
* fields from is a row whose absence you have already answered for.
|
|
308
|
-
*/
|
|
309
|
-
type TxRow<Row> = {
|
|
310
|
-
readonly [K in keyof Row]: Ref<Row[K]>;
|
|
311
|
-
} & TxRowHandle<Row>;
|
|
312
|
-
/**
|
|
313
|
-
* The result of one operation, before any expectation is declared about it.
|
|
314
|
-
*
|
|
315
|
-
* Deliberately not a row and not a list: an operation's row count is not known
|
|
316
|
-
* until the server runs it, so the only thing that can be said about it here is
|
|
317
|
-
* an EXPECTATION. Declaring one is also the only way to get a readable row.
|
|
318
|
-
*
|
|
319
|
-
* At most one expectation per operation — the wire carries one guard per op, and
|
|
320
|
-
* a second call throws rather than silently dropping the first.
|
|
321
|
-
*/
|
|
322
|
-
interface TxRows<Row> extends NotAwaitable {
|
|
323
|
-
readonly [rowsBrand]: Row;
|
|
324
|
-
/**
|
|
325
|
-
* Require exactly one row, and read it. On any other count the server rolls
|
|
326
|
-
* the whole transaction back and this `error` is thrown to the caller.
|
|
327
|
-
*
|
|
328
|
-
* This is the only way to reach a row's fields, and the only shape a `$ref`
|
|
329
|
-
* may point at.
|
|
330
|
-
*/
|
|
331
|
-
expectOne(error: Error): TxRow<Row>;
|
|
332
|
-
/** Require zero rows (e.g. "this membership must not already exist"). */
|
|
333
|
-
expectNone(error: Error): void;
|
|
334
|
-
/** Require at least `n` rows. */
|
|
335
|
-
expectAtLeast(n: number, error: Error): void;
|
|
336
|
-
/** Require at most `n` rows. */
|
|
337
|
-
expectAtMost(n: number, error: Error): void;
|
|
338
|
-
}
|
|
339
|
-
/** `now()` — the server's clock, usable wherever a value is. */
|
|
340
|
-
interface TxNow extends NotAwaitable {
|
|
341
|
-
readonly $expr: {
|
|
342
|
-
fn: "now";
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
/** `inc(n)` / `dec(n)` — read the column's CURRENT value and write it back
|
|
346
|
-
* changed. Only meaningful in an update's `set`, which is where the types allow
|
|
347
|
-
* it and where the server allows it. */
|
|
348
|
-
interface TxColumnExpr extends NotAwaitable {
|
|
349
|
-
readonly $expr: {
|
|
350
|
-
fn: "inc" | "dec";
|
|
351
|
-
by: number;
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* Resolve a callback's return type against what actually comes back: every
|
|
356
|
-
* {@link Ref} becomes its value, every {@link TxRow} becomes its row, and
|
|
357
|
-
* anything else keeps its shape.
|
|
358
|
-
*
|
|
359
|
-
* A {@link TxRows} resolves to an explanatory string type rather than a row
|
|
360
|
-
* list: it has no single answer to give, and saying so in the type is louder
|
|
361
|
-
* than a runtime throw.
|
|
362
|
-
*/
|
|
363
|
-
type Materialized<T> = T extends Ref<infer U> ? U : T extends TxRowHandle<infer R> ? R : T extends TxRows<unknown> ? "a TxRows cannot leave the transaction callback — read a row with .expectOne(err) first" : T extends Date ? T : T extends object ? {
|
|
364
|
-
[K in keyof T]: Materialized<T[K]>;
|
|
365
|
-
} : T;
|
|
366
|
-
/** A value written by an INSERT: a literal, an earlier row's field, or `now()`.
|
|
367
|
-
* `inc`/`dec` are absent on purpose — they read a current value, and an inserted
|
|
368
|
-
* row has none. */
|
|
369
|
-
type TxInsertValue<V> = V | Ref<V> | TxNow;
|
|
370
|
-
/** A value written by an UPDATE's `set`: everything an insert takes, plus the
|
|
371
|
-
* read-modify-write expressions. */
|
|
372
|
-
type TxSetValue<V> = V | Ref<V> | TxNow | TxColumnExpr;
|
|
373
|
-
/** An insert payload: the table's insert shape, with refs and `now()` allowed. */
|
|
374
|
-
type TxInsertShape<Insert> = {
|
|
375
|
-
[K in keyof Insert]: TxInsertValue<Insert[K]>;
|
|
376
|
-
};
|
|
377
|
-
/** An update's `set`: any subset of the insert shape, with expressions allowed. */
|
|
378
|
-
type TxSetShape<Insert> = {
|
|
379
|
-
[K in keyof Insert]?: TxSetValue<Insert[K]>;
|
|
380
|
-
};
|
|
381
|
-
/**
|
|
382
|
-
* A filter. Every entry is an equality test and they are AND-ed; a `null`
|
|
383
|
-
* becomes `IS NULL`, which is what makes `{ accepted_at: null }` a usable
|
|
384
|
-
* "not yet accepted" guard rather than a clause that matches nothing.
|
|
385
|
-
*/
|
|
386
|
-
type TxWhere<Row> = {
|
|
387
|
-
[K in keyof Row]?: Row[K] | Ref<Row[K]>;
|
|
388
|
-
};
|
|
389
|
-
/** Options for a plan `select`. */
|
|
390
|
-
interface TxSelectOptions {
|
|
391
|
-
/** Cap the rows read. */
|
|
392
|
-
limit?: number;
|
|
393
|
-
/** Take a real `FOR UPDATE` row lock for the rest of the transaction. */
|
|
394
|
-
lock?: "update";
|
|
395
|
-
}
|
|
396
|
-
/** One table, as the plan sees it. */
|
|
397
|
-
interface TxTable<Row, Insert> {
|
|
398
|
-
/** Insert one row. Returns a handle — call `.expectOne(err)` to read fields. */
|
|
399
|
-
insert(values: TxInsertShape<Insert>): TxRows<Row>;
|
|
400
|
-
/**
|
|
401
|
-
* Insert many rows in ONE statement. Every row must set the same columns
|
|
402
|
-
* (a row that omits one would silently take the column's default).
|
|
403
|
-
*
|
|
404
|
-
* An empty list writes nothing and sends nothing.
|
|
405
|
-
*/
|
|
406
|
-
/**
|
|
407
|
-
* Insert many rows in ONE statement, optionally choosing what a collision does.
|
|
408
|
-
*
|
|
409
|
-
* Without `opts` this is a plain multi-row INSERT and a collision aborts the
|
|
410
|
-
* transaction — the behaviour every call had before the option existed.
|
|
411
|
-
*
|
|
412
|
-
* `action: "ignore"` emits `ON CONFLICT DO NOTHING`, which is how "insert the
|
|
413
|
-
* ones that are new" becomes one round-trip instead of one per row with a
|
|
414
|
-
* 23505 caught around each. **The returned rows are the ones actually
|
|
415
|
-
* INSERTED**: a row that collided is skipped, so it is absent from the result
|
|
416
|
-
* — Postgres does not return what it did not write.
|
|
417
|
-
*
|
|
418
|
-
* `action: "update"` emits `ON CONFLICT DO UPDATE`, setting every non-conflict
|
|
419
|
-
* column from the incoming row, and every row comes back.
|
|
420
|
-
*/
|
|
421
|
-
insertMany(rows: readonly TxInsertShape<Insert>[], opts?: {
|
|
422
|
-
onConflict: readonly Extract<keyof Row, string>[];
|
|
423
|
-
action?: "ignore" | "update";
|
|
424
|
-
}): TxRows<Row>;
|
|
425
|
-
/**
|
|
426
|
-
* Insert the row, or update it when it collides on `onConflict` — inside the
|
|
427
|
-
* plan's savepoint, with the same meaning `tables.<t>.upsert()` has outside it.
|
|
428
|
-
*
|
|
429
|
-
* It is an operation because the alternative is not writable here: a failed
|
|
430
|
-
* insert aborts the whole transaction, so "try, then fall back" cannot be two
|
|
431
|
-
* plan steps.
|
|
432
|
-
*/
|
|
433
|
-
upsert(values: TxInsertShape<Insert>, options: {
|
|
434
|
-
onConflict: readonly Extract<keyof Row, string>[];
|
|
435
|
-
}): TxRows<Row>;
|
|
436
|
-
/**
|
|
437
|
-
* Update every row matching `where`. The filter comes first because it is the
|
|
438
|
-
* dangerous half: an update whose `where` you got wrong rewrites rows you
|
|
439
|
-
* never looked at. The server refuses an update with no `where` at all.
|
|
440
|
-
*/
|
|
441
|
-
updateWhere(where: TxWhere<Row>, set: TxSetShape<Insert>): TxRows<Row>;
|
|
442
|
-
/** Delete every row matching `where`. The server refuses an unfiltered delete. */
|
|
443
|
-
deleteWhere(where: TxWhere<Row>): TxRows<Row>;
|
|
444
|
-
/** Read rows, optionally locking them for the rest of the transaction. */
|
|
445
|
-
select(where?: TxWhere<Row>, options?: TxSelectOptions): TxRows<Row>;
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* The handle a transaction callback receives.
|
|
449
|
-
*
|
|
450
|
-
* It carries tables and nothing else: no `query`, no `findById`, no `asService`.
|
|
451
|
-
* A read whose value the plan does not write belongs OUTSIDE the transaction,
|
|
452
|
-
* where it costs one round trip and can be branched on like an ordinary value.
|
|
453
|
-
*/
|
|
454
|
-
interface TxPlanHandle<TTables> {
|
|
455
|
-
tables: TTables;
|
|
456
|
-
}
|
|
457
|
-
/** The server's `now()`. */
|
|
458
|
-
declare function now(): TxNow;
|
|
459
|
-
/** Add `by` to the column's current value. Only valid in an update's `set`. */
|
|
460
|
-
declare function inc(by: number): TxColumnExpr;
|
|
461
|
-
/** Subtract `by` from the column's current value. Only valid in an update's `set`. */
|
|
462
|
-
declare function dec(by: number): TxColumnExpr;
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* The brand that identifies an HttpError ACROSS SDK instances.
|
|
466
|
-
*
|
|
467
|
-
* A process legitimately holds more than one copy of this SDK — the runtime
|
|
468
|
-
* loads the engine from its own node_modules while the tenant's bundle carries
|
|
469
|
-
* an inlined copy, which is why the controller registry and the error registry
|
|
470
|
-
* are both anchored on `Symbol.for`. The one place that did not follow the
|
|
471
|
-
* pattern was the engine's catch: `err instanceof HttpError` compares CLASS
|
|
472
|
-
* IDENTITY, so a `throw new NotFound()` from the bundle's copy did not match
|
|
473
|
-
* the engine's copy and every typed error in every deployed backend degraded to
|
|
474
|
-
* `500 internal_error`. Measured through the edge on a real deploy: a route
|
|
475
|
-
* throwing `NotFound` answered 500 while the runtime's own log printed the
|
|
476
|
-
* error object with `status: 404` right beside it.
|
|
477
|
-
*
|
|
478
|
-
* `Symbol.for` puts this in the cross-realm registry, so every copy of the SDK
|
|
479
|
-
* agrees on it by VALUE rather than by identity.
|
|
480
|
-
*/
|
|
481
|
-
declare const HTTP_ERROR_BRAND: unique symbol;
|
|
482
|
-
/** HTTP error with structured error response format.
|
|
483
|
-
*
|
|
484
|
-
* The base class for the throwable error classes (`PalError`, `Conflict`,
|
|
485
|
-
* `NotFound`, …). Construct one directly with `throw new HttpError(404,
|
|
486
|
-
* "todo_not_found", "No such todo")`, or throw a named subclass
|
|
487
|
-
* (`throw new NotFound("todo not found")`). The runtime catches any `HttpError`
|
|
488
|
-
* and emits the standard envelope; on the wire (and to iOS) it surfaces as
|
|
489
|
-
* `BackendError.server(code, status, message, requestId)`.
|
|
490
|
-
*
|
|
491
|
-
* The optional `data` field carries a structured payload alongside the
|
|
492
|
-
* standard envelope — for errors that need to ship extra context
|
|
493
|
-
* (e.g. `new Conflict("locked", "title_locked", { retryAfter: 30 })`). It rides
|
|
494
|
-
* through to the iOS typed enum's associated value.
|
|
495
|
-
*/
|
|
496
|
-
declare class HttpError extends Error {
|
|
497
|
-
readonly status: number;
|
|
498
|
-
readonly error: string;
|
|
499
|
-
readonly errorDescription: string;
|
|
500
|
-
readonly data?: unknown;
|
|
501
|
-
/** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
|
|
502
|
-
readonly [HTTP_ERROR_BRAND] = true;
|
|
503
|
-
constructor(status: number, error: string, errorDescription: string, data?: unknown);
|
|
504
|
-
/**
|
|
505
|
-
* Serialize to the standard Palbase error response format.
|
|
506
|
-
* The `requestId` is injected by the runtime layer from the request context.
|
|
507
|
-
* When called without arguments (e.g. JSON.stringify), request_id is omitted.
|
|
508
|
-
* When `data` is set, it is appended as a strict-superset field.
|
|
509
|
-
*/
|
|
510
|
-
toJSON(requestId?: string): {
|
|
511
|
-
error: string;
|
|
512
|
-
error_description: string;
|
|
513
|
-
status: number;
|
|
514
|
-
request_id?: string;
|
|
515
|
-
data?: unknown;
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Throw with a custom HTTP status + wire code. The general-purpose escape hatch
|
|
520
|
-
* when none of the named classes (`Conflict`/`NotFound`/…) fits.
|
|
521
|
-
*
|
|
522
|
-
* @example
|
|
523
|
-
* throw new PalError(418, "teapot", "I'm a teapot");
|
|
524
|
-
*/
|
|
525
|
-
declare class PalError extends HttpError {
|
|
526
|
-
constructor(status: number, code: string, description: string, data?: unknown);
|
|
527
|
-
}
|
|
528
|
-
/** Base for the named status classes. Each subclass fixes its HTTP status; the
|
|
529
|
-
* `code` defaults to the class's canonical wire code (overridable), and the
|
|
530
|
-
* `message` defaults to a human-readable label (overridable). */
|
|
531
|
-
declare abstract class NamedHttpError extends HttpError {
|
|
532
|
-
protected constructor(status: number, defaultCode: string, name: string, message?: string, code?: string, data?: unknown);
|
|
533
|
-
}
|
|
534
|
-
/**
|
|
535
|
-
* 400 — the request was malformed or failed validation. Carries a fixed typed
|
|
536
|
-
* payload: `new BadRequest({ fields: [{ field: "email", message: "invalid" }] })`.
|
|
537
|
-
* The shape is declared once in the SDK so codegen surfaces `error.data.fields`
|
|
538
|
-
* typed on the client.
|
|
539
|
-
*/
|
|
540
|
-
declare class BadRequest extends NamedHttpError {
|
|
541
|
-
readonly data: BadRequestData;
|
|
542
|
-
constructor(data: BadRequestData, message?: string);
|
|
543
|
-
}
|
|
544
|
-
/** 401 — the caller is not authenticated. */
|
|
545
|
-
declare class Unauthorized extends NamedHttpError {
|
|
546
|
-
constructor(message?: string, code?: string, data?: unknown);
|
|
547
|
-
}
|
|
548
|
-
/** 403 — the caller is authenticated but not allowed. */
|
|
549
|
-
declare class Forbidden extends NamedHttpError {
|
|
550
|
-
constructor(message?: string, code?: string, data?: unknown);
|
|
551
|
-
}
|
|
552
|
-
/** 404 — the requested resource does not exist. */
|
|
553
|
-
declare class NotFound extends NamedHttpError {
|
|
554
|
-
constructor(message?: string, code?: string, data?: unknown);
|
|
555
|
-
}
|
|
556
|
-
/** 409 — the request conflicts with the current state. */
|
|
557
|
-
declare class Conflict extends NamedHttpError {
|
|
558
|
-
constructor(message?: string, code?: string, data?: unknown);
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* 409 — a write was refused because it would duplicate an existing row.
|
|
562
|
-
* Carries the NAME of the unique constraint Postgres named (`users_email_key`).
|
|
563
|
-
*
|
|
564
|
-
* The engine produces it: a statement rejected with SQLSTATE `23505` is
|
|
565
|
-
* converted here rather than surfacing as an opaque driver error (see
|
|
566
|
-
* `engine/db.ts`, `diagnosingDriver`). What that removes is the string match —
|
|
567
|
-
* before this, the only way to act on a duplicate was to test the driver
|
|
568
|
-
* message for "duplicate key value violates unique constraint", a contract
|
|
569
|
-
* nobody signed that breaks on a Postgres upgrade, a locale, or a constraint
|
|
570
|
-
* rename, silently and in production.
|
|
571
|
-
*
|
|
572
|
-
* THE NAME IS A FIELD AND STAYS OUT OF THE DEFAULT MESSAGE. The two are not
|
|
573
|
-
* the same audience. `constraint` is read by the code that catches this — the
|
|
574
|
-
* developer, who already knows the schema. `errorDescription` is the HTTP
|
|
575
|
-
* response body, and an UNCAUGHT duplicate puts it in front of the
|
|
576
|
-
* application's end user: `users_email_key` there discloses how the schema is
|
|
577
|
-
* built to whoever sent the request. The platform's own data API took the same
|
|
578
|
-
* decision one surface over and wrote down why —
|
|
579
|
-
* `v2/internal/modules/database/internal/handler/pgerror.go:83-87` collapses
|
|
580
|
-
* every 23xxx to a generic conflict, "never disclose the constraint/column
|
|
581
|
-
* name". A thrower who WANTS the name on the wire passes it deliberately
|
|
582
|
-
* (`new UniqueViolation(c, \`\${c} already exists\`)`, or through `data`).
|
|
583
|
-
*
|
|
584
|
-
* @example
|
|
585
|
-
* try {
|
|
586
|
-
* await Database.tables.users.insert({ email });
|
|
587
|
-
* } catch (e) {
|
|
588
|
-
* if (UniqueViolation.is(e) && e.constraint === "users_email_key") {
|
|
589
|
-
* throw new Conflict("That email is taken", "email_taken");
|
|
590
|
-
* }
|
|
591
|
-
* throw e;
|
|
592
|
-
* }
|
|
593
|
-
*/
|
|
594
|
-
declare class UniqueViolation extends Conflict {
|
|
595
|
-
/**
|
|
596
|
-
* Whether `e` is a unique violation — REGARDLESS of which copy of this SDK
|
|
597
|
-
* constructed it.
|
|
598
|
-
*
|
|
599
|
-
* Use this instead of `instanceof`. Measured on a live stack: a controller
|
|
600
|
-
* bundle INLINES its own copy of `@palbase/backend`, and the engine that
|
|
601
|
-
* raises this error is the runtime's copy. Two copies, two class identities,
|
|
602
|
-
* and `e instanceof UniqueViolation` is false in the one place a caller
|
|
603
|
-
* writes it — a check that reads as correct and silently never matches.
|
|
604
|
-
*/
|
|
605
|
-
static is(e: unknown): e is UniqueViolation;
|
|
606
|
-
/** The unique constraint the statement violated, as Postgres named it.
|
|
607
|
-
* `""` when the driver did not say which — see `engine/db.ts`. */
|
|
608
|
-
readonly constraint: string;
|
|
609
|
-
constructor(constraint: string, message?: string, code?: string, data?: unknown);
|
|
610
|
-
}
|
|
611
|
-
/** A single field-level validation failure carried by {@link BadRequest}. */
|
|
612
|
-
interface FieldError {
|
|
613
|
-
/** The offending field's name (dotted path for nested fields). */
|
|
614
|
-
field: string;
|
|
615
|
-
/** Human-readable reason the field failed. */
|
|
616
|
-
message: string;
|
|
617
|
-
}
|
|
618
|
-
/** The fixed, typed payload {@link BadRequest} ships. */
|
|
619
|
-
interface BadRequestData {
|
|
620
|
-
/** The fields that failed validation. */
|
|
621
|
-
fields: FieldError[];
|
|
622
|
-
}
|
|
623
|
-
/** The fixed, typed payload {@link TooManyRequests} ships. */
|
|
624
|
-
interface TooManyRequestsData {
|
|
625
|
-
/** Seconds the caller should wait before retrying. */
|
|
626
|
-
retryAfter: number;
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* 429 — the caller has exceeded the rate limit. Carries a fixed typed payload:
|
|
630
|
-
* `new TooManyRequests({ retryAfter: 30 })`. The shape is declared once in the
|
|
631
|
-
* SDK (error-registry pre-seed) so codegen surfaces `error.data.retryAfter`
|
|
632
|
-
* typed on the client — no per-project definition needed.
|
|
633
|
-
*/
|
|
634
|
-
declare class TooManyRequests extends NamedHttpError {
|
|
635
|
-
readonly data: TooManyRequestsData;
|
|
636
|
-
constructor(data: TooManyRequestsData, message?: string);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
/**
|
|
640
|
-
* Local typed interfaces for the 9 Palbase module clients injected into
|
|
641
|
-
* every endpoint context via `ctx.auth`, `ctx.storage`, etc.
|
|
642
|
-
*
|
|
643
|
-
* Design intent
|
|
644
|
-
* ─────────────
|
|
645
|
-
* • @palbase/backend owns this contract — no runtime or type dependency on
|
|
646
|
-
* @palbase/server or any module package.
|
|
647
|
-
* • These interfaces are STRUCTURALLY compatible with the runtime's
|
|
648
|
-
* ServerClient module objects so assignment is valid without a cast.
|
|
649
|
-
* • All parameter and return types are defined LOCALLY here (not imported
|
|
650
|
-
* from modules/) to keep the package self-contained.
|
|
651
|
-
*
|
|
652
|
-
* Privilege note
|
|
653
|
-
* ──────────────
|
|
654
|
-
* These clients run with the project's managed-runtime (privileged) key —
|
|
655
|
-
* they bypass end-user RLS; intentional for server handlers. Treat every
|
|
656
|
-
* call as if it has admin access to the project's data.
|
|
657
|
-
*/
|
|
658
|
-
|
|
659
|
-
/** A user as returned by auth verifyUserToken. */
|
|
660
|
-
interface PalbaseUser {
|
|
661
|
-
id: string;
|
|
662
|
-
email: string;
|
|
663
|
-
emailVerified?: boolean;
|
|
664
|
-
createdAt?: string;
|
|
665
|
-
updatedAt?: string;
|
|
666
|
-
metadata?: Record<string, unknown>;
|
|
667
|
-
}
|
|
668
|
-
/** A session as returned by auth.getSession(). */
|
|
669
|
-
interface PalbaseSession {
|
|
670
|
-
accessToken: string;
|
|
671
|
-
refreshToken: string;
|
|
672
|
-
expiresAt: number;
|
|
673
|
-
}
|
|
674
|
-
/** MFA enroll result. */
|
|
675
|
-
interface PalbaseMFAEnrollResult {
|
|
676
|
-
enrollment_id?: string;
|
|
677
|
-
secret?: string;
|
|
678
|
-
otp_url?: string;
|
|
679
|
-
qr_code?: string;
|
|
680
|
-
recovery_codes?: string[];
|
|
681
|
-
status?: string;
|
|
682
|
-
}
|
|
683
|
-
/** MFA token response. */
|
|
684
|
-
interface PalbaseTokenResponse {
|
|
685
|
-
access_token: string;
|
|
686
|
-
refresh_token: string;
|
|
687
|
-
token_type: string;
|
|
688
|
-
expires_in: number;
|
|
689
|
-
}
|
|
690
|
-
/** MFA factor. */
|
|
691
|
-
interface PalbaseMFAFactor {
|
|
692
|
-
id: string;
|
|
693
|
-
type: string;
|
|
694
|
-
verified: boolean;
|
|
695
|
-
created_at: string;
|
|
696
|
-
}
|
|
697
|
-
/** Device token view returned by registerDevice. */
|
|
698
|
-
interface PalbaseDeviceTokenView {
|
|
699
|
-
id: string;
|
|
700
|
-
device_id: string;
|
|
701
|
-
platform: "android" | "ios" | "web";
|
|
702
|
-
status: "active" | "inactive";
|
|
703
|
-
created_at: string;
|
|
704
|
-
updated_at: string;
|
|
705
|
-
}
|
|
706
|
-
/** Device info item returned by device.list(). */
|
|
707
|
-
interface PalbaseDeviceInfo {
|
|
708
|
-
id: string;
|
|
709
|
-
platform: string;
|
|
710
|
-
attestation_status: string;
|
|
711
|
-
bound: boolean;
|
|
712
|
-
created_at: string;
|
|
713
|
-
}
|
|
714
|
-
/** Params for device.attestAndroid(). */
|
|
715
|
-
interface PalbaseAttestAndroidParams {
|
|
716
|
-
verdict_token: string;
|
|
717
|
-
}
|
|
718
|
-
/** Result of device.attestAndroid(). */
|
|
719
|
-
interface PalbaseAttestAndroidResult {
|
|
720
|
-
device_id: string;
|
|
721
|
-
attestation_status: string;
|
|
722
|
-
device_integrity?: string;
|
|
723
|
-
}
|
|
724
|
-
/** Params for device.attestiOS(). */
|
|
725
|
-
interface PalbaseAttestiOSParams {
|
|
726
|
-
attestation_object: string;
|
|
727
|
-
key_id: string;
|
|
728
|
-
challenge: string;
|
|
729
|
-
}
|
|
730
|
-
/** Result of device.attestiOS(). */
|
|
731
|
-
interface PalbaseAttestiOSResult {
|
|
732
|
-
device_id: string;
|
|
733
|
-
attestation_status: string;
|
|
734
|
-
}
|
|
735
|
-
/** Params for device.bind(). */
|
|
736
|
-
interface PalbaseBindDeviceParams {
|
|
737
|
-
device_id: string;
|
|
738
|
-
public_key: string;
|
|
739
|
-
platform_attestation?: string;
|
|
740
|
-
}
|
|
741
|
-
/** Params for device.verifyRequestSignature() (server-only). */
|
|
742
|
-
interface PalbaseVerifyRequestSignatureParams {
|
|
743
|
-
payload: string;
|
|
744
|
-
signature: string;
|
|
745
|
-
}
|
|
746
|
-
/** Flag context (user targeting). */
|
|
747
|
-
interface PalbaseFlagContext {
|
|
748
|
-
userId?: string;
|
|
749
|
-
properties?: Record<string, unknown>;
|
|
750
|
-
}
|
|
751
|
-
/** Feature flag variant. */
|
|
752
|
-
interface PalbaseFlagVariant {
|
|
753
|
-
name: string;
|
|
754
|
-
payload?: unknown;
|
|
755
|
-
}
|
|
756
|
-
/** Feature flag (getAll result item). */
|
|
757
|
-
interface PalbaseFlag {
|
|
758
|
-
name: string;
|
|
759
|
-
enabled: boolean;
|
|
760
|
-
variant?: PalbaseFlagVariant;
|
|
761
|
-
}
|
|
762
|
-
/**
|
|
763
|
-
* Any JSON value a feature flag (or per-user override) can hold once resolved.
|
|
764
|
-
* Mirrors the wire shape the user-flags module stores and returns.
|
|
765
|
-
*/
|
|
766
|
-
type PalbaseFlagValue = boolean | number | string | null | PalbaseFlagValue[] | {
|
|
767
|
-
[key: string]: PalbaseFlagValue;
|
|
768
|
-
};
|
|
769
|
-
/** Where a resolved flag value came from. */
|
|
770
|
-
type PalbaseFlagSource = "system" | "user";
|
|
771
|
-
/** Result of {@link PalbaseFlagsClient.setOverride} /
|
|
772
|
-
* {@link PalbaseFlagsServiceClient.setOverrideForUser}. */
|
|
773
|
-
interface PalbaseSetOverrideResult {
|
|
774
|
-
key: string;
|
|
775
|
-
value: PalbaseFlagValue;
|
|
776
|
-
source: PalbaseFlagSource;
|
|
777
|
-
}
|
|
778
|
-
/** Result of {@link PalbaseFlagsServiceClient.setOverridesForUser}. */
|
|
779
|
-
interface PalbaseSetOverridesResult {
|
|
780
|
-
values: Record<string, PalbaseFlagValue>;
|
|
781
|
-
}
|
|
782
|
-
/** Result of {@link PalbaseFlagsServiceClient.clearOverrideForUser} — `value`
|
|
783
|
-
* is the system default the user falls back to. */
|
|
784
|
-
interface PalbaseClearOverrideResult {
|
|
785
|
-
key: string;
|
|
786
|
-
value: PalbaseFlagValue;
|
|
787
|
-
source: PalbaseFlagSource;
|
|
788
|
-
}
|
|
789
|
-
/** Result of {@link PalbaseFlagsServiceClient.clearAllOverridesForUser}. */
|
|
790
|
-
interface PalbaseClearAllOverridesResult {
|
|
791
|
-
deleted: number;
|
|
792
|
-
}
|
|
793
|
-
/** One cross-user operation for {@link PalbaseFlagsServiceClient.batchSetOverrides}. */
|
|
794
|
-
interface PalbaseBatchOverrideOperation {
|
|
795
|
-
userId: string;
|
|
796
|
-
values: Record<string, PalbaseFlagValue>;
|
|
797
|
-
}
|
|
798
|
-
/** Result of {@link PalbaseFlagsServiceClient.batchSetOverrides}. */
|
|
799
|
-
interface PalbaseBatchSetOverridesResult {
|
|
800
|
-
applied: number;
|
|
801
|
-
}
|
|
802
|
-
/** File object returned by storage operations. */
|
|
803
|
-
interface PalbaseFileObject {
|
|
804
|
-
/** The object's path inside its bucket. `name` is the same value. */
|
|
805
|
-
path: string;
|
|
806
|
-
name: string;
|
|
807
|
-
bucket: string;
|
|
808
|
-
size: number;
|
|
809
|
-
contentType: string;
|
|
810
|
-
/** SHA-256 of the stored bytes, computed as they were written. */
|
|
811
|
-
checksum: string;
|
|
812
|
-
/** Present for images: the decoded dimensions of the ORIGINAL. */
|
|
813
|
-
width?: number;
|
|
814
|
-
height?: number;
|
|
815
|
-
/**
|
|
816
|
-
* A ThumbHash — a few dozen bytes that render as a blurred preview while the
|
|
817
|
-
* real image loads, with no second request to fetch it.
|
|
818
|
-
*/
|
|
819
|
-
thumbhash?: string;
|
|
820
|
-
/**
|
|
821
|
-
* Declared rendition name → the URL that serves it. Produced at upload from
|
|
822
|
-
* what `config/storage.ts` declared, so this is a lookup rather than a
|
|
823
|
-
* transform anyone pays for per request.
|
|
824
|
-
*/
|
|
825
|
-
variants: Record<string, string>;
|
|
826
|
-
}
|
|
827
|
-
/** Storage signed URL response. */
|
|
828
|
-
interface PalbaseSignedUrlResponse {
|
|
829
|
-
signedUrl: string;
|
|
830
|
-
}
|
|
831
|
-
/** Storage upload options. */
|
|
832
|
-
interface PalbaseUploadOptions {
|
|
833
|
-
contentType?: string;
|
|
834
|
-
upsert?: boolean;
|
|
835
|
-
}
|
|
836
|
-
/** Storage transform options for public URLs. */
|
|
837
|
-
interface PalbaseTransformOptions {
|
|
838
|
-
width?: number;
|
|
839
|
-
height?: number;
|
|
840
|
-
format?: "webp" | "avif" | "jpeg" | "png";
|
|
841
|
-
}
|
|
842
|
-
/** Storage list options. */
|
|
843
|
-
interface PalbaseListOptions {
|
|
844
|
-
limit?: number;
|
|
845
|
-
offset?: number;
|
|
846
|
-
sortBy?: {
|
|
847
|
-
column: string;
|
|
848
|
-
order: "asc" | "desc";
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
/** Edge-function invoke options. */
|
|
852
|
-
interface PalbaseInvokeOptions {
|
|
853
|
-
body?: unknown;
|
|
854
|
-
headers?: Record<string, string>;
|
|
855
|
-
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
856
|
-
}
|
|
857
|
-
/** Push recipient — user ID, array of user IDs, or a topic. */
|
|
858
|
-
type PalbasePushRecipient = string | string[] | {
|
|
859
|
-
topic: string;
|
|
860
|
-
};
|
|
861
|
-
/** Localized text (plain or locale map). */
|
|
862
|
-
type PalbaseLocalizedText = string | Record<string, string>;
|
|
863
|
-
/** Push send params. */
|
|
864
|
-
interface PalbasePushSendParams {
|
|
865
|
-
to: PalbasePushRecipient;
|
|
866
|
-
title?: PalbaseLocalizedText;
|
|
867
|
-
body?: PalbaseLocalizedText;
|
|
868
|
-
variables?: Record<string, string>;
|
|
869
|
-
default_locale?: string;
|
|
870
|
-
data?: Record<string, string>;
|
|
871
|
-
image?: string;
|
|
872
|
-
badge?: number;
|
|
873
|
-
sound?: string;
|
|
874
|
-
deep_link?: string;
|
|
875
|
-
collapse_key?: string;
|
|
876
|
-
priority?: "high" | "normal";
|
|
877
|
-
ttl?: number;
|
|
878
|
-
silent?: boolean;
|
|
879
|
-
content_available?: boolean;
|
|
880
|
-
category?: string;
|
|
881
|
-
metadata?: unknown;
|
|
882
|
-
channels?: Array<"push" | "inbox">;
|
|
883
|
-
inbox_action_url?: string;
|
|
884
|
-
}
|
|
885
|
-
/** Push send response. */
|
|
886
|
-
interface PalbasePushSendResponse {
|
|
887
|
-
message_id?: string;
|
|
888
|
-
message_ids?: string[];
|
|
889
|
-
recipients: number;
|
|
890
|
-
}
|
|
891
|
-
/** Email send params. */
|
|
892
|
-
interface PalbaseEmailSendParams {
|
|
893
|
-
to: string | string[];
|
|
894
|
-
subject?: string;
|
|
895
|
-
/**
|
|
896
|
-
* @deprecated The server reads `template_slug`, not `template` — this
|
|
897
|
-
* field is forwarded as-is and ignored by palnotify. Use `templateSlug`
|
|
898
|
-
* instead. Retained for source-compat with older callers.
|
|
899
|
-
*/
|
|
900
|
-
template?: string;
|
|
901
|
-
/**
|
|
902
|
-
* Server email-template slug. The notifications client maps this to the
|
|
903
|
-
* wire field `template_slug` (mutually exclusive with `subject` + body
|
|
904
|
-
* fields when not overriding template output).
|
|
905
|
-
*/
|
|
906
|
-
templateSlug?: string;
|
|
907
|
-
/**
|
|
908
|
-
* Which locale of `templateSlug` to render — a BCP47 short tag ("tr").
|
|
909
|
-
* Falls back to "en" when the slug has no row for it. Declaring a locale in
|
|
910
|
-
* config/notifications.ts is only half the feature: without this the extra
|
|
911
|
-
* rows exist and are unreachable.
|
|
912
|
-
*/
|
|
913
|
-
locale?: string;
|
|
914
|
-
variables?: Record<string, unknown>;
|
|
915
|
-
html?: string;
|
|
916
|
-
text?: string;
|
|
917
|
-
from?: {
|
|
918
|
-
email: string;
|
|
919
|
-
name?: string;
|
|
920
|
-
};
|
|
921
|
-
reply_to?: string;
|
|
922
|
-
category?: string;
|
|
923
|
-
}
|
|
924
|
-
/** Email send response. */
|
|
925
|
-
interface PalbaseEmailSendResponse {
|
|
926
|
-
message_id?: string;
|
|
927
|
-
message_ids?: string[];
|
|
928
|
-
}
|
|
929
|
-
/** SMS send params. */
|
|
930
|
-
interface PalbaseSmsSendParams {
|
|
931
|
-
to: string | string[];
|
|
932
|
-
/**
|
|
933
|
-
* SMS body text. Either `body` OR `templateSlug` must be supplied —
|
|
934
|
-
* the two are mutually exclusive at the server.
|
|
935
|
-
*/
|
|
936
|
-
body?: string;
|
|
937
|
-
/**
|
|
938
|
-
* Server SMS-template slug. The notifications client maps this to the
|
|
939
|
-
* wire field `template_slug`. When provided, palnotify renders the
|
|
940
|
-
* template with `variables` and uses the result as the SMS body.
|
|
941
|
-
*/
|
|
942
|
-
templateSlug?: string;
|
|
943
|
-
/** Which locale of `templateSlug` to render — see PalbaseEmailSendParams.locale. */
|
|
944
|
-
locale?: string;
|
|
945
|
-
variables?: Record<string, unknown>;
|
|
946
|
-
category?: string;
|
|
947
|
-
}
|
|
948
|
-
/**
|
|
949
|
-
* Phone-verification start parameters.
|
|
950
|
-
*
|
|
951
|
-
* There is no body field, by construction: the provider generates the code AND
|
|
952
|
-
* the message text from its own template. A caller-supplied body would be
|
|
953
|
-
* silently dropped — a send that reports success while delivering something the
|
|
954
|
-
* caller never wrote.
|
|
955
|
-
*/
|
|
956
|
-
interface PalbaseVerificationStartParams {
|
|
957
|
-
/** E.164 recipient, e.g. "+14155551212". */
|
|
958
|
-
to: string;
|
|
959
|
-
/** Template language (e.g. "tr", "en"). Empty uses the service default. */
|
|
960
|
-
locale?: string;
|
|
961
|
-
/** Optional user attribution for the message log. */
|
|
962
|
-
user_id?: string;
|
|
963
|
-
}
|
|
964
|
-
/** Phone-verification start response. The code is never included. */
|
|
965
|
-
interface PalbaseVerificationStartResponse {
|
|
966
|
-
/** Provider-side handle for this attempt. Not a secret, and not the code. */
|
|
967
|
-
verification_sid: string;
|
|
968
|
-
/** Provider lifecycle value, typically "pending". */
|
|
969
|
-
status: string;
|
|
970
|
-
to: string;
|
|
971
|
-
/** palnotify message-log row id, so the send appears in message history. */
|
|
972
|
-
message_id?: string;
|
|
973
|
-
}
|
|
974
|
-
/** Phone-verification check parameters. */
|
|
975
|
-
interface PalbaseVerificationCheckParams {
|
|
976
|
-
to: string;
|
|
977
|
-
code: string;
|
|
978
|
-
}
|
|
979
|
-
/**
|
|
980
|
-
* Phone-verification verdict. A wrong or expired code is `approved: false` with
|
|
981
|
-
* a successful HTTP status — a mismatch is a normal outcome, not an error.
|
|
982
|
-
*/
|
|
983
|
-
interface PalbaseVerificationCheckResponse {
|
|
984
|
-
approved: boolean;
|
|
985
|
-
}
|
|
986
|
-
/** SMS send response. */
|
|
987
|
-
interface PalbaseSmsSendResponse {
|
|
988
|
-
message_id?: string;
|
|
989
|
-
message_ids?: string[];
|
|
990
|
-
}
|
|
991
|
-
/** Inbox send params (managed-runtime: create inbox row for a user). */
|
|
992
|
-
interface PalbaseInboxSendParams {
|
|
993
|
-
to: string;
|
|
994
|
-
title?: string;
|
|
995
|
-
body: string;
|
|
996
|
-
data?: unknown;
|
|
997
|
-
action_url?: string;
|
|
998
|
-
category?: string;
|
|
999
|
-
channels?: Array<"push" | "inbox">;
|
|
1000
|
-
push_deep_link?: string;
|
|
1001
|
-
}
|
|
1002
|
-
/** Inbox send response. */
|
|
1003
|
-
interface PalbaseInboxSendResponse {
|
|
1004
|
-
message_id?: string;
|
|
1005
|
-
skipped?: boolean;
|
|
1006
|
-
}
|
|
1007
|
-
/** A per-channel status entry in a multi-channel response. */
|
|
1008
|
-
interface PalbaseChannelOutcome {
|
|
1009
|
-
status: "queued" | "sent" | "skipped" | "failed";
|
|
1010
|
-
message_id?: string;
|
|
1011
|
-
message_ids?: string[];
|
|
1012
|
-
recipients?: number;
|
|
1013
|
-
error?: string;
|
|
1014
|
-
}
|
|
1015
|
-
/** Multi-channel fan-out response. */
|
|
1016
|
-
interface PalbaseMultiChannelResponse {
|
|
1017
|
-
channels: Record<string, PalbaseChannelOutcome>;
|
|
1018
|
-
}
|
|
1019
|
-
/** An inbox message as returned by inbox.list. */
|
|
1020
|
-
interface PalbaseInboxMessage {
|
|
1021
|
-
id: string;
|
|
1022
|
-
user_id?: string;
|
|
1023
|
-
title?: string;
|
|
1024
|
-
body: string;
|
|
1025
|
-
data?: unknown;
|
|
1026
|
-
action_url?: string;
|
|
1027
|
-
category?: string;
|
|
1028
|
-
is_read: boolean;
|
|
1029
|
-
read_at?: string;
|
|
1030
|
-
created_at: string;
|
|
1031
|
-
}
|
|
1032
|
-
/** Inbox list options. */
|
|
1033
|
-
interface PalbaseInboxListOptions {
|
|
1034
|
-
cursor?: string;
|
|
1035
|
-
limit?: number;
|
|
1036
|
-
is_read?: boolean;
|
|
1037
|
-
category?: string;
|
|
1038
|
-
include_archived?: boolean;
|
|
1039
|
-
}
|
|
1040
|
-
/** Inbox list result. */
|
|
1041
|
-
interface PalbaseInboxListResult {
|
|
1042
|
-
messages: PalbaseInboxMessage[];
|
|
1043
|
-
next_cursor?: string;
|
|
1044
|
-
}
|
|
1045
|
-
/** Notification preferences (channel × category opt-in/out). */
|
|
1046
|
-
type PalbasePreferences = Partial<Record<"push" | "email" | "sms" | "inbox", Record<string, boolean>>>;
|
|
1047
|
-
/** Register device params. */
|
|
1048
|
-
interface PalbaseRegisterDeviceParams {
|
|
1049
|
-
device_id: string;
|
|
1050
|
-
token: string;
|
|
1051
|
-
platform: "android" | "ios" | "web";
|
|
1052
|
-
app_version?: string;
|
|
1053
|
-
locale?: string;
|
|
1054
|
-
}
|
|
1055
|
-
/** Email template view (camelCase). */
|
|
1056
|
-
interface PalbaseEmailTemplate {
|
|
1057
|
-
id: string;
|
|
1058
|
-
slug: string;
|
|
1059
|
-
locale: string;
|
|
1060
|
-
subject: string;
|
|
1061
|
-
htmlBody: string;
|
|
1062
|
-
textBody?: string;
|
|
1063
|
-
variables: string[];
|
|
1064
|
-
isDefault: boolean;
|
|
1065
|
-
createdAt: string;
|
|
1066
|
-
updatedAt: string;
|
|
1067
|
-
}
|
|
1068
|
-
/** Payload for templates.email.create. */
|
|
1069
|
-
interface PalbaseCreateEmailTemplateInput {
|
|
1070
|
-
slug: string;
|
|
1071
|
-
locale?: string;
|
|
1072
|
-
subject: string;
|
|
1073
|
-
htmlBody: string;
|
|
1074
|
-
textBody?: string;
|
|
1075
|
-
variables?: string[];
|
|
1076
|
-
}
|
|
1077
|
-
/** Payload for templates.email.update — patch semantics. */
|
|
1078
|
-
interface PalbaseUpdateEmailTemplateInput {
|
|
1079
|
-
subject?: string;
|
|
1080
|
-
htmlBody?: string;
|
|
1081
|
-
textBody?: string;
|
|
1082
|
-
variables?: string[];
|
|
1083
|
-
}
|
|
1084
|
-
/** SMS template view (camelCase). No subject / html / text distinction. */
|
|
1085
|
-
interface PalbaseSMSTemplate {
|
|
1086
|
-
id: string;
|
|
1087
|
-
slug: string;
|
|
1088
|
-
locale: string;
|
|
1089
|
-
body: string;
|
|
1090
|
-
variables: string[];
|
|
1091
|
-
isDefault: boolean;
|
|
1092
|
-
createdAt: string;
|
|
1093
|
-
updatedAt: string;
|
|
1094
|
-
}
|
|
1095
|
-
/** Payload for templates.sms.create. */
|
|
1096
|
-
interface PalbaseCreateSMSTemplateInput {
|
|
1097
|
-
slug: string;
|
|
1098
|
-
locale?: string;
|
|
1099
|
-
body: string;
|
|
1100
|
-
variables?: string[];
|
|
1101
|
-
}
|
|
1102
|
-
/** Payload for templates.sms.update — patch semantics. */
|
|
1103
|
-
interface PalbaseUpdateSMSTemplateInput {
|
|
1104
|
-
body?: string;
|
|
1105
|
-
variables?: string[];
|
|
1106
|
-
}
|
|
1107
|
-
/** Analytics event properties. */
|
|
1108
|
-
type PalbaseAnalyticsProperties = Record<string, unknown>;
|
|
1109
|
-
/** Analytics identify traits. */
|
|
1110
|
-
type PalbaseIdentifyTraits = Record<string, unknown>;
|
|
1111
|
-
/** Analytics count query input. */
|
|
1112
|
-
interface PalbaseCountQueryInput {
|
|
1113
|
-
eventName?: string;
|
|
1114
|
-
eventNames?: string[];
|
|
1115
|
-
from: number;
|
|
1116
|
-
to: number;
|
|
1117
|
-
interval?: "hour" | "day";
|
|
1118
|
-
filters?: Record<string, string>;
|
|
1119
|
-
breakdown?: string;
|
|
1120
|
-
}
|
|
1121
|
-
/** A single time-bucket in a count result. */
|
|
1122
|
-
interface PalbaseCountBucket {
|
|
1123
|
-
t: number;
|
|
1124
|
-
count: number;
|
|
1125
|
-
breakdown?: string;
|
|
1126
|
-
}
|
|
1127
|
-
/** Count query result. */
|
|
1128
|
-
interface PalbaseCountResult {
|
|
1129
|
-
series: PalbaseCountBucket[];
|
|
1130
|
-
from_mv: boolean;
|
|
1131
|
-
}
|
|
1132
|
-
/** Events query input. */
|
|
1133
|
-
interface PalbaseEventsQueryInput {
|
|
1134
|
-
from: number;
|
|
1135
|
-
to: number;
|
|
1136
|
-
eventName?: string;
|
|
1137
|
-
distinctId?: string;
|
|
1138
|
-
limit?: number;
|
|
1139
|
-
cursor?: string;
|
|
1140
|
-
}
|
|
1141
|
-
/** A single event row. */
|
|
1142
|
-
interface PalbaseEventRow {
|
|
1143
|
-
event_id: string;
|
|
1144
|
-
timestamp: number;
|
|
1145
|
-
event_name: string;
|
|
1146
|
-
distinct_id: string;
|
|
1147
|
-
properties: Record<string, unknown>;
|
|
1148
|
-
}
|
|
1149
|
-
/** Events query result. */
|
|
1150
|
-
interface PalbaseEventsResult {
|
|
1151
|
-
events: PalbaseEventRow[];
|
|
1152
|
-
next_cursor?: string;
|
|
1153
|
-
}
|
|
1154
|
-
/** Properties query input. */
|
|
1155
|
-
interface PalbasePropertiesQueryInput {
|
|
1156
|
-
eventName?: string;
|
|
1157
|
-
from?: number;
|
|
1158
|
-
to?: number;
|
|
1159
|
-
}
|
|
1160
|
-
/** Property descriptor (from /query/properties). */
|
|
1161
|
-
interface PalbasePropertyDescriptor {
|
|
1162
|
-
name: string;
|
|
1163
|
-
value_count: number;
|
|
1164
|
-
}
|
|
1165
|
-
/** Users query input. */
|
|
1166
|
-
interface PalbaseUsersQueryInput {
|
|
1167
|
-
from: number;
|
|
1168
|
-
to: number;
|
|
1169
|
-
filters?: Record<string, string>;
|
|
1170
|
-
limit?: number;
|
|
1171
|
-
cursor?: string;
|
|
1172
|
-
}
|
|
1173
|
-
/** A single user row. */
|
|
1174
|
-
interface PalbaseUserRow {
|
|
1175
|
-
distinct_id: string;
|
|
1176
|
-
first_seen: number;
|
|
1177
|
-
last_seen: number;
|
|
1178
|
-
event_count: number;
|
|
1179
|
-
properties?: Record<string, unknown>;
|
|
1180
|
-
}
|
|
1181
|
-
/** Users query result. */
|
|
1182
|
-
interface PalbaseUsersResult {
|
|
1183
|
-
users: PalbaseUserRow[];
|
|
1184
|
-
next_cursor?: string;
|
|
1185
|
-
}
|
|
1186
|
-
/** Funnel query input. */
|
|
1187
|
-
interface PalbaseFunnelQueryInput {
|
|
1188
|
-
steps: Array<{
|
|
1189
|
-
event_name: string;
|
|
1190
|
-
filters?: Record<string, string>;
|
|
1191
|
-
}>;
|
|
1192
|
-
from: number;
|
|
1193
|
-
to: number;
|
|
1194
|
-
conversionWindowSeconds?: number;
|
|
1195
|
-
breakdown?: string;
|
|
1196
|
-
}
|
|
1197
|
-
/** Funnel step result. */
|
|
1198
|
-
interface PalbaseFunnelStepResult {
|
|
1199
|
-
event_name: string;
|
|
1200
|
-
count: number;
|
|
1201
|
-
conversion_rate: number;
|
|
1202
|
-
}
|
|
1203
|
-
/** Funnel query result. */
|
|
1204
|
-
interface PalbaseFunnelResult {
|
|
1205
|
-
steps: PalbaseFunnelStepResult[];
|
|
1206
|
-
overall_conversion_rate: number;
|
|
1207
|
-
}
|
|
1208
|
-
/** Retention query input. */
|
|
1209
|
-
interface PalbaseRetentionQueryInput {
|
|
1210
|
-
firstEvent: string;
|
|
1211
|
-
returnEvent: string;
|
|
1212
|
-
from: number;
|
|
1213
|
-
to: number;
|
|
1214
|
-
periodDays?: number;
|
|
1215
|
-
periods?: number;
|
|
1216
|
-
}
|
|
1217
|
-
/** Retention cohort result item. */
|
|
1218
|
-
interface PalbaseRetentionCohort {
|
|
1219
|
-
cohort_start: number;
|
|
1220
|
-
size: number;
|
|
1221
|
-
periods: number[];
|
|
1222
|
-
}
|
|
1223
|
-
/** Retention query result. */
|
|
1224
|
-
interface PalbaseRetentionResult {
|
|
1225
|
-
cohorts: PalbaseRetentionCohort[];
|
|
1226
|
-
}
|
|
1227
|
-
/** Cohort query input. */
|
|
1228
|
-
interface PalbaseCohortQueryInput {
|
|
1229
|
-
name: string;
|
|
1230
|
-
rules: Array<{
|
|
1231
|
-
event_name: string;
|
|
1232
|
-
filters?: Record<string, string>;
|
|
1233
|
-
}>;
|
|
1234
|
-
from: number;
|
|
1235
|
-
to: number;
|
|
1236
|
-
}
|
|
1237
|
-
/** Cohort query result. */
|
|
1238
|
-
interface PalbaseCohortResult {
|
|
1239
|
-
size: number;
|
|
1240
|
-
distinct_ids_sample: string[];
|
|
1241
|
-
}
|
|
1242
|
-
/** Analytics overview result. */
|
|
1243
|
-
interface PalbaseOverviewResult {
|
|
1244
|
-
dau: number;
|
|
1245
|
-
wau: number;
|
|
1246
|
-
mau: number;
|
|
1247
|
-
total_events: number;
|
|
1248
|
-
top_events: Array<{
|
|
1249
|
-
event_name: string;
|
|
1250
|
-
count: number;
|
|
1251
|
-
}>;
|
|
1252
|
-
}
|
|
1253
|
-
/** Event names result. */
|
|
1254
|
-
interface PalbaseEventNamesResult {
|
|
1255
|
-
names: string[];
|
|
1256
|
-
}
|
|
1257
|
-
/** User detail result. */
|
|
1258
|
-
interface PalbaseUserDetailResult {
|
|
1259
|
-
distinct_id: string;
|
|
1260
|
-
first_seen: number;
|
|
1261
|
-
last_seen: number;
|
|
1262
|
-
event_count: number;
|
|
1263
|
-
properties: Record<string, unknown>;
|
|
1264
|
-
recent_events: PalbaseEventRow[];
|
|
1265
|
-
}
|
|
1266
|
-
/** Create link params. */
|
|
1267
|
-
interface PalbaseCreateLinkParams {
|
|
1268
|
-
url: string;
|
|
1269
|
-
title?: string;
|
|
1270
|
-
description?: string;
|
|
1271
|
-
imageUrl?: string;
|
|
1272
|
-
ios?: {
|
|
1273
|
-
bundleId: string;
|
|
1274
|
-
appStoreId?: string;
|
|
1275
|
-
fallbackUrl?: string;
|
|
1276
|
-
minimumVersion?: string;
|
|
1277
|
-
};
|
|
1278
|
-
android?: {
|
|
1279
|
-
packageName: string;
|
|
1280
|
-
fallbackUrl?: string;
|
|
1281
|
-
minimumVersion?: number;
|
|
1282
|
-
};
|
|
1283
|
-
web?: {
|
|
1284
|
-
fallbackUrl?: string;
|
|
1285
|
-
};
|
|
1286
|
-
social?: {
|
|
1287
|
-
title?: string;
|
|
1288
|
-
description?: string;
|
|
1289
|
-
imageUrl?: string;
|
|
1290
|
-
};
|
|
1291
|
-
expiresAt?: string;
|
|
1292
|
-
customShortCode?: string;
|
|
1293
|
-
}
|
|
1294
|
-
/** Update link params. */
|
|
1295
|
-
interface PalbaseUpdateLinkParams {
|
|
1296
|
-
url?: string;
|
|
1297
|
-
title?: string;
|
|
1298
|
-
description?: string;
|
|
1299
|
-
imageUrl?: string;
|
|
1300
|
-
ios?: {
|
|
1301
|
-
bundleId?: string;
|
|
1302
|
-
appStoreId?: string;
|
|
1303
|
-
fallbackUrl?: string;
|
|
1304
|
-
minimumVersion?: string;
|
|
1305
|
-
};
|
|
1306
|
-
android?: {
|
|
1307
|
-
packageName?: string;
|
|
1308
|
-
fallbackUrl?: string;
|
|
1309
|
-
minimumVersion?: number;
|
|
1310
|
-
};
|
|
1311
|
-
web?: {
|
|
1312
|
-
fallbackUrl?: string;
|
|
1313
|
-
};
|
|
1314
|
-
social?: {
|
|
1315
|
-
title?: string;
|
|
1316
|
-
description?: string;
|
|
1317
|
-
imageUrl?: string;
|
|
1318
|
-
};
|
|
1319
|
-
expiresAt?: string;
|
|
1320
|
-
}
|
|
1321
|
-
/** A link object. */
|
|
1322
|
-
interface PalbaseLink {
|
|
1323
|
-
id: string;
|
|
1324
|
-
shortCode: string;
|
|
1325
|
-
shortUrl: string;
|
|
1326
|
-
url: string;
|
|
1327
|
-
title?: string;
|
|
1328
|
-
clickCount: number;
|
|
1329
|
-
createdAt: string;
|
|
1330
|
-
}
|
|
1331
|
-
/** Link details (extended link). */
|
|
1332
|
-
interface PalbaseLinkDetails extends PalbaseLink {
|
|
1333
|
-
description?: string;
|
|
1334
|
-
imageUrl?: string;
|
|
1335
|
-
ios?: Record<string, unknown>;
|
|
1336
|
-
android?: Record<string, unknown>;
|
|
1337
|
-
web?: Record<string, unknown>;
|
|
1338
|
-
social?: Record<string, unknown>;
|
|
1339
|
-
expiresAt?: string;
|
|
1340
|
-
}
|
|
1341
|
-
/** Link analytics. */
|
|
1342
|
-
interface PalbaseLinkAnalytics {
|
|
1343
|
-
totalClicks: number;
|
|
1344
|
-
clicksByPlatform: Record<string, number>;
|
|
1345
|
-
clicksByCountry: Record<string, number>;
|
|
1346
|
-
clicksByDay: Array<{
|
|
1347
|
-
date: string;
|
|
1348
|
-
clicks: number;
|
|
1349
|
-
}>;
|
|
1350
|
-
}
|
|
1351
|
-
/** QR code options. */
|
|
1352
|
-
interface PalbaseQrCodeOptions {
|
|
1353
|
-
size?: number;
|
|
1354
|
-
format?: "png" | "svg";
|
|
1355
|
-
}
|
|
1356
|
-
/** Match params for deferred deep link resolution. */
|
|
1357
|
-
interface PalbaseMatchParams {
|
|
1358
|
-
fingerprintHash: string;
|
|
1359
|
-
}
|
|
1360
|
-
/** Initial deep link. */
|
|
1361
|
-
interface PalbaseInitialLink {
|
|
1362
|
-
url: string;
|
|
1363
|
-
params?: Record<string, string>;
|
|
1364
|
-
}
|
|
1365
|
-
/** List links options. */
|
|
1366
|
-
interface PalbaseListLinksOptions {
|
|
1367
|
-
limit?: number;
|
|
1368
|
-
offset?: number;
|
|
1369
|
-
}
|
|
1370
|
-
/** List links result. */
|
|
1371
|
-
interface PalbaseListLinksResult {
|
|
1372
|
-
links: PalbaseLink[];
|
|
1373
|
-
total: number;
|
|
1374
|
-
}
|
|
1375
|
-
/**
|
|
1376
|
-
* Auth client surface available on `ctx.auth`.
|
|
1377
|
-
* Exposes server-relevant methods only. Browser-only patterns are omitted:
|
|
1378
|
-
* — signUp / signIn / signOut / refresh / requestPasswordReset / confirmPasswordReset
|
|
1379
|
-
* / changePassword / resendVerification / verifyEmail (client flow helpers)
|
|
1380
|
-
* — onAuthStateChange / onTokenChange (subscription callbacks)
|
|
1381
|
-
* — getOAuthURL / signInWithCredential (OAuth browser redirects)
|
|
1382
|
-
* — requestMagicLink / verifyMagicLink (client flow helpers)
|
|
1383
|
-
* — setTokens / getAccessToken (internal token management)
|
|
1384
|
-
* — listSessions / revokeSession / revokeAllSessions (user-self management)
|
|
1385
|
-
* — listIdentities / linkIdentity / unlinkIdentity (user-self management)
|
|
1386
|
-
* — listTrustedDevices / registerTrustedDevice / revokeTrustedDevice (user-self management)
|
|
1387
|
-
*/
|
|
1388
|
-
interface PalbaseAuthClient {
|
|
1389
|
-
/**
|
|
1390
|
-
* Verify a user's JWT by calling GET /auth/user. Returns the user if the
|
|
1391
|
-
* token is valid; error otherwise. Service-role privileged.
|
|
1392
|
-
*/
|
|
1393
|
-
verifyUserToken(jwt: string): Promise<PalbaseResult<PalbaseUser>>;
|
|
1394
|
-
/**
|
|
1395
|
-
* Get the current session held by the client (synchronous — no network
|
|
1396
|
-
* call). On the server the managed-runtime client does not hold a user
|
|
1397
|
-
* session; this always returns `{ data: null, error: null }`.
|
|
1398
|
-
*/
|
|
1399
|
-
getSession(): {
|
|
1400
|
-
data: PalbaseSession | null;
|
|
1401
|
-
error: null;
|
|
1402
|
-
};
|
|
1403
|
-
/** MFA admin surface — enroll/verify/manage factors on behalf of users. */
|
|
1404
|
-
mfa: {
|
|
1405
|
-
/** Enroll a new MFA factor (TOTP or email). */
|
|
1406
|
-
enroll(params: {
|
|
1407
|
-
type: "totp" | "email";
|
|
1408
|
-
}): Promise<PalbaseResult<PalbaseMFAEnrollResult>>;
|
|
1409
|
-
/** Verify an enrollment code. */
|
|
1410
|
-
verifyEnrollment(code: string): Promise<PalbaseResult<{
|
|
1411
|
-
status: string;
|
|
1412
|
-
}>>;
|
|
1413
|
-
/** Challenge an MFA factor (verify code, obtain token). */
|
|
1414
|
-
challenge(params: {
|
|
1415
|
-
mfa_token: string;
|
|
1416
|
-
type: "totp" | "email";
|
|
1417
|
-
code: string;
|
|
1418
|
-
}): Promise<PalbaseResult<PalbaseTokenResponse>>;
|
|
1419
|
-
/** Recover via backup code. */
|
|
1420
|
-
recovery(params: {
|
|
1421
|
-
mfa_token: string;
|
|
1422
|
-
code: string;
|
|
1423
|
-
}): Promise<PalbaseResult<PalbaseTokenResponse>>;
|
|
1424
|
-
/** List enrolled factors. */
|
|
1425
|
-
listFactors(): Promise<PalbaseResult<{
|
|
1426
|
-
factors: PalbaseMFAFactor[];
|
|
1427
|
-
}>>;
|
|
1428
|
-
/** Remove a factor. Requires current password. */
|
|
1429
|
-
removeFactor(factorId: string, currentPassword: string): Promise<PalbaseResult<{
|
|
1430
|
-
status: string;
|
|
1431
|
-
}>>;
|
|
1432
|
-
/** Regenerate recovery codes. */
|
|
1433
|
-
regenerateRecoveryCodes(): Promise<PalbaseResult<{
|
|
1434
|
-
recovery_codes: string[];
|
|
1435
|
-
}>>;
|
|
1436
|
-
/** Email MFA: start enrollment. */
|
|
1437
|
-
emailEnroll(): Promise<PalbaseResult<{
|
|
1438
|
-
status: string;
|
|
1439
|
-
}>>;
|
|
1440
|
-
/** Email MFA: send challenge. */
|
|
1441
|
-
emailChallenge(params: {
|
|
1442
|
-
mfa_token: string;
|
|
1443
|
-
}): Promise<PalbaseResult<{
|
|
1444
|
-
status: string;
|
|
1445
|
-
}>>;
|
|
1446
|
-
/** Email MFA: verify code. */
|
|
1447
|
-
emailVerify(params: {
|
|
1448
|
-
mfa_token: string;
|
|
1449
|
-
code: string;
|
|
1450
|
-
}): Promise<PalbaseResult<PalbaseTokenResponse>>;
|
|
1451
|
-
};
|
|
1452
|
-
/** Device attestation surface (App Attest / Play Integrity). */
|
|
1453
|
-
device: {
|
|
1454
|
-
/** Generate a device attestation challenge. */
|
|
1455
|
-
generateChallenge(): Promise<PalbaseResult<{
|
|
1456
|
-
challenge: string;
|
|
1457
|
-
}>>;
|
|
1458
|
-
/** Attest an Android device with a Play Integrity verdict token. */
|
|
1459
|
-
attestAndroid(params: PalbaseAttestAndroidParams): Promise<PalbaseResult<PalbaseAttestAndroidResult>>;
|
|
1460
|
-
/** Attest an iOS device with App Attest attestation data. */
|
|
1461
|
-
attestiOS(params: PalbaseAttestiOSParams): Promise<PalbaseResult<PalbaseAttestiOSResult>>;
|
|
1462
|
-
/** Bind a verified device with a public key for request signing. */
|
|
1463
|
-
bind(params: PalbaseBindDeviceParams): Promise<PalbaseResult<{
|
|
1464
|
-
success: boolean;
|
|
1465
|
-
}>>;
|
|
1466
|
-
/** List all devices for the current user. */
|
|
1467
|
-
list(): Promise<PalbaseResult<{
|
|
1468
|
-
devices: PalbaseDeviceInfo[];
|
|
1469
|
-
}>>;
|
|
1470
|
-
/** Delete a device by ID. */
|
|
1471
|
-
delete(deviceId: string): Promise<PalbaseResult<{
|
|
1472
|
-
success: boolean;
|
|
1473
|
-
}>>;
|
|
1474
|
-
/**
|
|
1475
|
-
* Verify a request signature from a device (server-only).
|
|
1476
|
-
* Not exposed in the client SDK.
|
|
1477
|
-
*/
|
|
1478
|
-
verifyRequestSignature(deviceId: string, params: PalbaseVerifyRequestSignatureParams): Promise<PalbaseResult<{
|
|
1479
|
-
verified: boolean;
|
|
1480
|
-
}>>;
|
|
1481
|
-
/** Get the cached App Check token, or null if not available / expired. */
|
|
1482
|
-
getToken(): string | null;
|
|
1483
|
-
/** Whether App Check is active (token cached and not expired). */
|
|
1484
|
-
readonly isActive: boolean;
|
|
1485
|
-
/** Set a cached App Check token manually (e.g. after attest flow). */
|
|
1486
|
-
setCachedToken(token: string, expiresInMs: number): void;
|
|
1487
|
-
/** Clean up timers and cached state. */
|
|
1488
|
-
dispose(): void;
|
|
1489
|
-
};
|
|
1490
|
-
}
|
|
1491
|
-
/**
|
|
1492
|
-
* Bucket-level file operations available via `ctx.storage.bucket(name)`.
|
|
1493
|
-
* `getPublicUrl` is synchronous (no network call — constructs URL locally).
|
|
1494
|
-
*/
|
|
1495
|
-
interface PalbaseBucketClient {
|
|
1496
|
-
/** Upload a file. */
|
|
1497
|
-
upload(path: string, file: Blob | ArrayBuffer | ReadableStream, options?: PalbaseUploadOptions): Promise<PalbaseResult<PalbaseFileObject>>;
|
|
1498
|
-
/** Download a file as a Blob. */
|
|
1499
|
-
download(path: string): Promise<PalbaseResult<Blob>>;
|
|
1500
|
-
/**
|
|
1501
|
-
* The public URL for an object, or for one of its declared renditions.
|
|
1502
|
-
*
|
|
1503
|
-
* `variant` names a rendition from `config/storage.ts` — NOT arbitrary
|
|
1504
|
-
* dimensions. Renditions are produced at upload and served as immutable
|
|
1505
|
-
* objects, so a URL here costs nothing to serve and caches forever; an
|
|
1506
|
-
* arbitrary size would be a transform on the request path, one public URL
|
|
1507
|
-
* away from a CPU amplification attack.
|
|
1508
|
-
*
|
|
1509
|
-
* Asking for a variant the bucket does not declare is an error rather than a
|
|
1510
|
-
* silent fall back to the original: an app that requested a 200px thumbnail
|
|
1511
|
-
* and quietly received a 5 MB photograph works, looks right, and is found
|
|
1512
|
-
* months later on a bandwidth bill.
|
|
1513
|
-
*/
|
|
1514
|
-
getPublicUrl(path: string, options?: {
|
|
1515
|
-
variant?: string;
|
|
1516
|
-
}): string;
|
|
1517
|
-
/**
|
|
1518
|
-
* A time-limited URL for a private object.
|
|
1519
|
-
*
|
|
1520
|
-
* The duration is NAMED (`{ expiresIn: "1h" }`) rather than a bare number,
|
|
1521
|
-
* because "3600" reads as seconds to whoever wrote it and as minutes to
|
|
1522
|
-
* whoever maintains it. Same human spelling `fileSizeLimit` takes.
|
|
1523
|
-
*/
|
|
1524
|
-
createSignedUrl(path: string, options: {
|
|
1525
|
-
expiresIn: string;
|
|
1526
|
-
}): Promise<PalbaseResult<PalbaseSignedUrlResponse>>;
|
|
1527
|
-
/** List objects in the bucket (optionally filtered by prefix). */
|
|
1528
|
-
list(prefix?: string, options?: PalbaseListOptions): Promise<PalbaseResult<PalbaseFileObject[]>>;
|
|
1529
|
-
/** Delete one or more objects. */
|
|
1530
|
-
remove(paths: string[]): Promise<PalbaseResult<PalbaseFileObject[]>>;
|
|
1531
|
-
/** Move / rename an object. */
|
|
1532
|
-
move(from: string, to: string): Promise<PalbaseResult<void>>;
|
|
1533
|
-
/** Copy an object. */
|
|
1534
|
-
copy(from: string, to: string): Promise<PalbaseResult<void>>;
|
|
1535
|
-
}
|
|
1536
|
-
/**
|
|
1537
|
-
* Storage client available on `ctx.storage`.
|
|
1538
|
-
* Only `bucket()` is exposed — managed-runtime callers select a bucket first.
|
|
1539
|
-
*/
|
|
1540
|
-
interface PalbaseStorageClient {
|
|
1541
|
-
/** Get a bucket-scoped client for file operations. */
|
|
1542
|
-
bucket(name: string): PalbaseBucketClient;
|
|
1543
|
-
}
|
|
1544
|
-
/**
|
|
1545
|
-
* Functions client available on `ctx.functions`.
|
|
1546
|
-
* Invoke edge functions from a backend endpoint.
|
|
1547
|
-
*/
|
|
1548
|
-
interface PalbaseFunctionsClient {
|
|
1549
|
-
/** Invoke a named edge function. */
|
|
1550
|
-
invoke<T = unknown>(fnName: string, options?: PalbaseInvokeOptions): Promise<PalbaseResult<T>>;
|
|
1551
|
-
}
|
|
1552
|
-
/**
|
|
1553
|
-
* Cross-user admin write surface, reached via `Flags.asService()`.
|
|
1554
|
-
*
|
|
1555
|
-
* These five methods set/clear overrides for an ARBITRARY user (named
|
|
1556
|
-
* explicitly), so they bypass the current-request-user scope that `Flags.*`
|
|
1557
|
-
* reads and `Flags.setOverride(...)` are bound to. Mirrors the
|
|
1558
|
-
* `Database` / `Database.asService()` split: the privileged, cross-user path
|
|
1559
|
-
* is moved OFF the default surface so it is greppable and intentional — a
|
|
1560
|
-
* handler that calls `Flags.setOverrideForUser(...)` on the default surface is
|
|
1561
|
-
* a compile error and must reach for `Flags.asService()` first.
|
|
1562
|
-
*
|
|
1563
|
-
* @example
|
|
1564
|
-
* await Flags.asService().setOverrideForUser("user_123", "new_checkout", true);
|
|
1565
|
-
*/
|
|
1566
|
-
interface PalbaseFlagsServiceClient {
|
|
1567
|
-
/**
|
|
1568
|
-
* Set (or replace) a single feature-flag override for one user. The override
|
|
1569
|
-
* shadows the project (system) default for that user until cleared.
|
|
1570
|
-
*/
|
|
1571
|
-
setOverrideForUser(userId: string, key: PalbaseFlagKey, value: PalbaseFlagValue): Promise<PalbaseResult<PalbaseSetOverrideResult>>;
|
|
1572
|
-
/** Set multiple overrides for one user in a single call. */
|
|
1573
|
-
setOverridesForUser(userId: string, values: Record<string, PalbaseFlagValue>): Promise<PalbaseResult<PalbaseSetOverridesResult>>;
|
|
1574
|
-
/** Clear one override for a user, restoring the project (system) default. */
|
|
1575
|
-
clearOverrideForUser(userId: string, key: PalbaseFlagKey): Promise<PalbaseResult<PalbaseClearOverrideResult>>;
|
|
1576
|
-
/** Clear all overrides for a user, restoring project (system) defaults. */
|
|
1577
|
-
clearAllOverridesForUser(userId: string): Promise<PalbaseResult<PalbaseClearAllOverridesResult>>;
|
|
1578
|
-
/** Apply override writes across many users in one request (max 1000 ops). */
|
|
1579
|
-
batchSetOverrides(operations: ReadonlyArray<PalbaseBatchOverrideOperation>): Promise<PalbaseResult<PalbaseBatchSetOverridesResult>>;
|
|
1580
|
-
}
|
|
1581
|
-
/**
|
|
1582
|
-
* Flags client available as the `Flags` singleton (and on `ctx.flags`).
|
|
1583
|
-
* Evaluate feature flags server-side (managed-runtime key — user targeting
|
|
1584
|
-
* is optional via context).
|
|
1585
|
-
*
|
|
1586
|
-
* Writes follow the `Database` model:
|
|
1587
|
-
* — `setOverride(key, value)` (default) overrides the flag for the CURRENT
|
|
1588
|
-
* request user; no userId argument, no admin power required. It errors when
|
|
1589
|
-
* there is no signed-in user (an anonymous request).
|
|
1590
|
-
* — `asService()` returns the {@link PalbaseFlagsServiceClient} carrying the
|
|
1591
|
-
* cross-user admin writes (`setOverrideForUser`, …) for an arbitrary target
|
|
1592
|
-
* user. Greppable + explicit, exactly like `Database.asService()`.
|
|
1593
|
-
*
|
|
1594
|
-
* Reads (`isEnabled`/`getVariant`/`get`/`getAll`) stay on this default surface
|
|
1595
|
-
* and are already current-user-scoped via the request identity.
|
|
1596
|
-
*/
|
|
1597
|
-
interface PalbaseFlagsClient {
|
|
1598
|
-
/** Is a flag enabled for the given context? */
|
|
1599
|
-
isEnabled(flagName: PalbaseFlagKey, context?: PalbaseFlagContext): Promise<PalbaseResult<boolean>>;
|
|
1600
|
-
/** Get the active variant of a multivariate flag. */
|
|
1601
|
-
getVariant(flagName: PalbaseFlagKey, context?: PalbaseFlagContext): Promise<PalbaseResult<PalbaseFlagVariant>>;
|
|
1602
|
-
/** Get all flags for the project. */
|
|
1603
|
-
getAll(context?: PalbaseFlagContext): Promise<PalbaseResult<PalbaseFlag[]>>;
|
|
1604
|
-
/**
|
|
1605
|
-
* Resolve a flag's value, with an optional fallback.
|
|
1606
|
-
*
|
|
1607
|
-
* `get(key)` · `get(key, default)` · `get(key, ctx)` · `get(key, default, ctx)`.
|
|
1608
|
-
* The default is substituted when the flag is absent AND when the flags
|
|
1609
|
-
* service is unreachable — a product that hides a feature because a lookup
|
|
1610
|
-
* timed out is behaving correctly.
|
|
1611
|
-
*
|
|
1612
|
-
* Declared here as of 2026-08-15. The client has always had it and its tests
|
|
1613
|
-
* have always covered it, but the interface did not: `Flags.get("x")` was a
|
|
1614
|
-
* type error in a handler and worked at runtime. That is the same gap as
|
|
1615
|
-
* `Documents.doc(...)` — declared and never implemented — seen from the other
|
|
1616
|
-
* side, and moving the implementation into this package is what surfaced it.
|
|
1617
|
-
*/
|
|
1618
|
-
get(flagName: PalbaseFlagKey, defaultOrContext?: PalbaseFlagValue | PalbaseFlagContext, maybeContext?: PalbaseFlagContext): Promise<PalbaseResult<PalbaseFlagValue>>;
|
|
1619
|
-
/**
|
|
1620
|
-
* Set (or replace) a single feature-flag override for the CURRENT REQUEST
|
|
1621
|
-
* USER. No userId argument — the override is bound to the signed-in user the
|
|
1622
|
-
* handler is serving, so a handler can flip a flag for that user without
|
|
1623
|
-
* admin power. The override shadows the project (system) default for that
|
|
1624
|
-
* user until cleared.
|
|
1625
|
-
*
|
|
1626
|
-
* Errors when there is no signed-in user (an anonymous request); reach for
|
|
1627
|
-
* `Flags.asService().setOverrideForUser(userId, key, value)` to write a flag
|
|
1628
|
-
* for an arbitrary (cross-user) target.
|
|
1629
|
-
*
|
|
1630
|
-
* @example
|
|
1631
|
-
* await Flags.setOverride("new_checkout", true);
|
|
1632
|
-
*/
|
|
1633
|
-
setOverride(key: PalbaseFlagKey, value: PalbaseFlagValue): Promise<PalbaseResult<PalbaseSetOverrideResult>>;
|
|
1634
|
-
/**
|
|
1635
|
-
* Return the cross-user admin write surface ({@link PalbaseFlagsServiceClient}).
|
|
1636
|
-
* Use sparingly and explicitly — the default `Flags.setOverride(...)` path is
|
|
1637
|
-
* bound to the current request user; `asService()` is how you write a flag
|
|
1638
|
-
* override for an ARBITRARY user. Mirrors `Database.asService()`.
|
|
1639
|
-
*/
|
|
1640
|
-
asService(): PalbaseFlagsServiceClient;
|
|
1641
|
-
}
|
|
1642
|
-
/**
|
|
1643
|
-
* Realtime broadcast surface (backend handler → subscribed clients).
|
|
1644
|
-
*
|
|
1645
|
-
* **Every channel must be DECLARED, or the join is refused.** Channel names are
|
|
1646
|
-
* not free text: `channels.ts` at the project root publishes which names exist
|
|
1647
|
-
* and who may subscribe, publish, or write state, and a client joining a name no
|
|
1648
|
-
* declaration matches is rejected without ever reaching your code. Declare with
|
|
1649
|
-
* `ownerOnly()`, `publicChannel()`, or a custom `authorize(ctx)` — see
|
|
1650
|
-
* {@link defineChannels}. Broadcasting to an undeclared channel reaches nobody.
|
|
1651
|
-
*
|
|
1652
|
-
* Backend-side this is BROADCAST-ONLY: a handler pushes an event to a channel
|
|
1653
|
-
* and every client subscribed to that channel (via the client SDK's
|
|
1654
|
-
* `pb.realtime.channel(...).on(...)`) receives it. There is no `subscribe()` on
|
|
1655
|
-
* the backend — a stateless request handler can't hold a socket; it fires an
|
|
1656
|
-
* HTTP broadcast and returns. Delivery is fire-and-forget: `broadcast` resolves
|
|
1657
|
-
* once the broadcast is accepted (or with an `error` if it could not be sent),
|
|
1658
|
-
* but it never blocks the handler waiting on subscribers.
|
|
1659
|
-
*
|
|
1660
|
-
* Channel names are yours to choose (e.g. `"room:42"`, `"orders"`) but not yours
|
|
1661
|
-
* to leave undeclared: each must match a pattern in `channels.ts`, which is where
|
|
1662
|
-
* `"room:{roomId}"` is authorized. The Palbase-managed `flags:<ref>` channels are
|
|
1663
|
-
* internal — author channels for your own features.
|
|
1664
|
-
*
|
|
1665
|
-
* @example
|
|
1666
|
-
* // Notify everyone in a chat room that a message landed:
|
|
1667
|
-
* await Realtime.broadcast("room:42", "message", { text, from: user.id });
|
|
1668
|
-
*/
|
|
1669
|
-
interface PalbaseRealtimeClient {
|
|
1670
|
-
/**
|
|
1671
|
-
* A channel's SHARED STATE — a keyed map every subscriber sees, including
|
|
1672
|
-
* the ones that join later.
|
|
1673
|
-
*
|
|
1674
|
-
* That is the whole difference from `broadcast`: a broadcast reaches whoever
|
|
1675
|
-
* is listening at that instant, and a client connecting a second afterwards
|
|
1676
|
-
* learns nothing until the next one. State is handed to every joiner in the
|
|
1677
|
-
* snapshot they get, so "the score is 3-1" or "this flag is now on" arrives
|
|
1678
|
-
* even for a client that was not there when it changed.
|
|
1679
|
-
*/
|
|
1680
|
-
state: {
|
|
1681
|
-
/** Write one entry. Fire-and-forget, like `broadcast`. */
|
|
1682
|
-
set(topic: string, key: string, value: Record<string, unknown>): Promise<PalbaseResult<void>>;
|
|
1683
|
-
/** Remove one entry. */
|
|
1684
|
-
clear(topic: string, key: string): Promise<PalbaseResult<void>>;
|
|
1685
|
-
};
|
|
1686
|
-
/**
|
|
1687
|
-
* Broadcast `event` with `payload` to everyone subscribed to `channel`.
|
|
1688
|
-
*
|
|
1689
|
-
* Fire-and-forget: resolves `{ data: undefined, error: null }` when the
|
|
1690
|
-
* broadcast was accepted, or `{ data: null, error }` when it could not be
|
|
1691
|
-
* sent (e.g. realtime not provisioned). A failed broadcast never throws and
|
|
1692
|
-
* never fails the handler.
|
|
1693
|
-
*
|
|
1694
|
-
* @param channel App-defined channel name (e.g. `"room:42"`). Do NOT prefix
|
|
1695
|
-
* with `"realtime:"` — that prefix is internal to the transport.
|
|
1696
|
-
* @param event Event name subscribers filter on (e.g. `"message"`).
|
|
1697
|
-
* @param payload JSON-serializable event body.
|
|
1698
|
-
*/
|
|
1699
|
-
broadcast(channel: string, event: string, payload?: Record<string, unknown>): Promise<PalbaseResult<void>>;
|
|
1700
|
-
}
|
|
1701
|
-
/**
|
|
1702
|
-
* Push sub-client surface (server-only: fan-out to users / topics).
|
|
1703
|
-
*/
|
|
1704
|
-
interface PalbasePushClient {
|
|
1705
|
-
/** Send a push notification to one or more users, or a topic. */
|
|
1706
|
-
send(params: PalbasePushSendParams): Promise<PalbaseResult<PalbasePushSendResponse | PalbaseMultiChannelResponse>>;
|
|
1707
|
-
}
|
|
1708
|
-
/**
|
|
1709
|
-
* Email sub-client surface (managed-runtime).
|
|
1710
|
-
*/
|
|
1711
|
-
interface PalbaseEmailClient {
|
|
1712
|
-
/** Send a transactional email. */
|
|
1713
|
-
send(params: PalbaseEmailSendParams): Promise<PalbaseResult<PalbaseEmailSendResponse>>;
|
|
1714
|
-
}
|
|
1715
|
-
/**
|
|
1716
|
-
* SMS sub-client surface (managed-runtime).
|
|
1717
|
-
*/
|
|
1718
|
-
interface PalbaseSmsClient {
|
|
1719
|
-
/** Send an SMS message. */
|
|
1720
|
-
send(params: PalbaseSmsSendParams): Promise<PalbaseResult<PalbaseSmsSendResponse>>;
|
|
1721
|
-
}
|
|
1722
|
-
/**
|
|
1723
|
-
* Phone-verification (OTP) sub-client surface (managed-runtime).
|
|
1724
|
-
*
|
|
1725
|
-
* Separate from `sms` because a verification carries no caller-authored text.
|
|
1726
|
-
* It is also the rail that works without a registered sender: a verification
|
|
1727
|
-
* service owns its own sender identity, which plain SMS requires you to
|
|
1728
|
-
* provision (and, in many countries, register) yourself.
|
|
1729
|
-
*/
|
|
1730
|
-
interface PalbaseVerificationsClient {
|
|
1731
|
-
/** Send a verification code over SMS. */
|
|
1732
|
-
start(params: PalbaseVerificationStartParams): Promise<PalbaseResult<PalbaseVerificationStartResponse>>;
|
|
1733
|
-
/** Check a submitted code. A wrong code resolves to `approved: false`. */
|
|
1734
|
-
check(params: PalbaseVerificationCheckParams): Promise<PalbaseResult<PalbaseVerificationCheckResponse>>;
|
|
1735
|
-
}
|
|
1736
|
-
/**
|
|
1737
|
-
* Inbox sub-client surface (managed-runtime send + user read operations).
|
|
1738
|
-
*/
|
|
1739
|
-
interface PalbaseInboxClient {
|
|
1740
|
-
/** Service-role: create an inbox notification row for a user. */
|
|
1741
|
-
send(params: PalbaseInboxSendParams): Promise<PalbaseResult<PalbaseInboxSendResponse | PalbaseMultiChannelResponse>>;
|
|
1742
|
-
/** List inbox messages (user-scoped or admin). */
|
|
1743
|
-
list(options?: PalbaseInboxListOptions): Promise<PalbaseResult<PalbaseInboxListResult>>;
|
|
1744
|
-
/** Count unread messages. */
|
|
1745
|
-
unreadCount(): Promise<PalbaseResult<{
|
|
1746
|
-
count: number;
|
|
1747
|
-
}>>;
|
|
1748
|
-
/** Mark a message as read. */
|
|
1749
|
-
markRead(id: string): Promise<PalbaseResult<void>>;
|
|
1750
|
-
/** Mark all messages read. */
|
|
1751
|
-
markAllRead(): Promise<PalbaseResult<void>>;
|
|
1752
|
-
/** Archive (soft-delete) a message. */
|
|
1753
|
-
archive(id: string): Promise<PalbaseResult<void>>;
|
|
1754
|
-
}
|
|
1755
|
-
/**
|
|
1756
|
-
* Preferences sub-client surface.
|
|
1757
|
-
*/
|
|
1758
|
-
interface PalbasePreferencesClient {
|
|
1759
|
-
/** Get notification preferences. */
|
|
1760
|
-
get(): Promise<PalbaseResult<PalbasePreferences>>;
|
|
1761
|
-
/** Update notification preferences. */
|
|
1762
|
-
update(params: PalbasePreferences): Promise<PalbaseResult<PalbasePreferences>>;
|
|
1763
|
-
}
|
|
1764
|
-
/**
|
|
1765
|
-
* Email-template CRUD sub-client (managed-runtime gated server-side).
|
|
1766
|
-
*/
|
|
1767
|
-
interface PalbaseEmailTemplatesClient {
|
|
1768
|
-
/** List all email templates. */
|
|
1769
|
-
list(): Promise<PalbaseResult<PalbaseEmailTemplate[]>>;
|
|
1770
|
-
/** Get one email template by ID. */
|
|
1771
|
-
get(id: string): Promise<PalbaseResult<PalbaseEmailTemplate>>;
|
|
1772
|
-
/** Create an email template. */
|
|
1773
|
-
create(input: PalbaseCreateEmailTemplateInput): Promise<PalbaseResult<PalbaseEmailTemplate>>;
|
|
1774
|
-
/** Update an email template (patch semantics). */
|
|
1775
|
-
update(id: string, input: PalbaseUpdateEmailTemplateInput): Promise<PalbaseResult<PalbaseEmailTemplate>>;
|
|
1776
|
-
/** Delete an email template. */
|
|
1777
|
-
delete(id: string): Promise<PalbaseResult<void>>;
|
|
1778
|
-
}
|
|
1779
|
-
/**
|
|
1780
|
-
* SMS-template CRUD sub-client (managed-runtime gated server-side).
|
|
1781
|
-
*/
|
|
1782
|
-
interface PalbaseSMSTemplatesClient {
|
|
1783
|
-
/** List all SMS templates. */
|
|
1784
|
-
list(): Promise<PalbaseResult<PalbaseSMSTemplate[]>>;
|
|
1785
|
-
/** Get one SMS template by ID. */
|
|
1786
|
-
get(id: string): Promise<PalbaseResult<PalbaseSMSTemplate>>;
|
|
1787
|
-
/** Create an SMS template. */
|
|
1788
|
-
create(input: PalbaseCreateSMSTemplateInput): Promise<PalbaseResult<PalbaseSMSTemplate>>;
|
|
1789
|
-
/** Update an SMS template (patch semantics). */
|
|
1790
|
-
update(id: string, input: PalbaseUpdateSMSTemplateInput): Promise<PalbaseResult<PalbaseSMSTemplate>>;
|
|
1791
|
-
/** Delete an SMS template. */
|
|
1792
|
-
delete(id: string): Promise<PalbaseResult<void>>;
|
|
1793
|
-
}
|
|
1794
|
-
/**
|
|
1795
|
-
* Templates sub-client — parallel CRUD for email and SMS templates.
|
|
1796
|
-
* Service-role gated server-side; both surfaces map TS camelCase to
|
|
1797
|
-
* wire snake_case in the runtime client.
|
|
1798
|
-
*/
|
|
1799
|
-
interface PalbaseTemplatesClient {
|
|
1800
|
-
/** Email template CRUD. */
|
|
1801
|
-
email: PalbaseEmailTemplatesClient;
|
|
1802
|
-
/** SMS template CRUD. */
|
|
1803
|
-
sms: PalbaseSMSTemplatesClient;
|
|
1804
|
-
}
|
|
1805
|
-
/**
|
|
1806
|
-
* Notifications client available on `ctx.notifications`.
|
|
1807
|
-
* Service-role: all send operations require privileged access.
|
|
1808
|
-
* Omits ClientNotificationsClient (browser-only narrowed surface).
|
|
1809
|
-
*/
|
|
1810
|
-
interface PalbaseNotificationsClient {
|
|
1811
|
-
/** Push notification sender. */
|
|
1812
|
-
push: PalbasePushClient;
|
|
1813
|
-
/** Email sender. */
|
|
1814
|
-
email: PalbaseEmailClient;
|
|
1815
|
-
/** SMS sender. */
|
|
1816
|
-
sms: PalbaseSmsClient;
|
|
1817
|
-
/** Phone verification (OTP) sender and checker. */
|
|
1818
|
-
verifications: PalbaseVerificationsClient;
|
|
1819
|
-
/** Inbox (in-app) message sender and reader. */
|
|
1820
|
-
inbox: PalbaseInboxClient;
|
|
1821
|
-
/** Notification preferences manager. */
|
|
1822
|
-
preferences: PalbasePreferencesClient;
|
|
1823
|
-
/** Email + SMS template CRUD (managed-runtime). */
|
|
1824
|
-
templates: PalbaseTemplatesClient;
|
|
1825
|
-
/** Register a device for push notifications. */
|
|
1826
|
-
registerDevice(params: PalbaseRegisterDeviceParams): Promise<PalbaseResult<PalbaseDeviceTokenView>>;
|
|
1827
|
-
/** Remove a device registration. */
|
|
1828
|
-
unregisterDevice(deviceId: string): Promise<PalbaseResult<void>>;
|
|
1829
|
-
}
|
|
1830
|
-
/**
|
|
1831
|
-
* Analytics query namespace (read-side endpoints).
|
|
1832
|
-
*/
|
|
1833
|
-
interface PalbaseAnalyticsQueryNamespace {
|
|
1834
|
-
/** Count events over time. */
|
|
1835
|
-
count(input: PalbaseCountQueryInput): Promise<PalbaseResult<PalbaseCountResult>>;
|
|
1836
|
-
/** List raw events. */
|
|
1837
|
-
events(input: PalbaseEventsQueryInput): Promise<PalbaseResult<PalbaseEventsResult>>;
|
|
1838
|
-
/** List event property descriptors. */
|
|
1839
|
-
properties(input?: PalbasePropertiesQueryInput): Promise<PalbaseResult<PalbasePropertyDescriptor[]>>;
|
|
1840
|
-
/** Query users by filters. */
|
|
1841
|
-
users(input: PalbaseUsersQueryInput): Promise<PalbaseResult<PalbaseUsersResult>>;
|
|
1842
|
-
/** Run a funnel query. */
|
|
1843
|
-
funnel(input: PalbaseFunnelQueryInput): Promise<PalbaseResult<PalbaseFunnelResult>>;
|
|
1844
|
-
/** Run a retention query. */
|
|
1845
|
-
retention(input: PalbaseRetentionQueryInput): Promise<PalbaseResult<PalbaseRetentionResult>>;
|
|
1846
|
-
/** Run a cohort query. */
|
|
1847
|
-
cohort(input: PalbaseCohortQueryInput): Promise<PalbaseResult<PalbaseCohortResult>>;
|
|
1848
|
-
}
|
|
1849
|
-
/**
|
|
1850
|
-
* Analytics management namespace (meta-level endpoints).
|
|
1851
|
-
*/
|
|
1852
|
-
interface PalbaseAnalyticsManagementNamespace {
|
|
1853
|
-
/** Get project-level overview stats. */
|
|
1854
|
-
overview(): Promise<PalbaseResult<PalbaseOverviewResult>>;
|
|
1855
|
-
/** Get all event names seen by the project. */
|
|
1856
|
-
eventNames(): Promise<PalbaseResult<PalbaseEventNamesResult>>;
|
|
1857
|
-
/** Get details for a specific user. */
|
|
1858
|
-
userDetail(distinctId: string): Promise<PalbaseResult<PalbaseUserDetailResult>>;
|
|
1859
|
-
/** Delete all data for a user (GDPR erasure). */
|
|
1860
|
-
deleteUser(distinctId: string): Promise<PalbaseResult<void>>;
|
|
1861
|
-
}
|
|
1862
|
-
/**
|
|
1863
|
-
* Analytics client available on `ctx.analytics`.
|
|
1864
|
-
* Capture events server-side and run analytical queries.
|
|
1865
|
-
*/
|
|
1866
|
-
interface PalbaseAnalyticsClient {
|
|
1867
|
-
/** Capture a custom event. */
|
|
1868
|
-
capture(event: string, properties?: PalbaseAnalyticsProperties, distinctId?: string): Promise<PalbaseResult<void>>;
|
|
1869
|
-
/** Identify a user with traits. */
|
|
1870
|
-
identify(distinctId: string, traits?: PalbaseIdentifyTraits): Promise<PalbaseResult<void>>;
|
|
1871
|
-
/** Track a screen view. */
|
|
1872
|
-
screen(screenName: string, properties?: PalbaseAnalyticsProperties, distinctId?: string): Promise<PalbaseResult<void>>;
|
|
1873
|
-
/** Query namespace for analytics read operations. */
|
|
1874
|
-
query: PalbaseAnalyticsQueryNamespace;
|
|
1875
|
-
/** Management namespace for meta-level operations. */
|
|
1876
|
-
management: PalbaseAnalyticsManagementNamespace;
|
|
1877
|
-
}
|
|
1878
|
-
/**
|
|
1879
|
-
* Deep-links client available on `ctx.links`.
|
|
1880
|
-
* Create, manage, and resolve deep links from server endpoints.
|
|
1881
|
-
* Omits `getInitialLink()` (client SDK convenience — browser/app only).
|
|
1882
|
-
*/
|
|
1883
|
-
interface PalbaseLinksClient {
|
|
1884
|
-
/** Create a short link. */
|
|
1885
|
-
create(params: PalbaseCreateLinkParams): Promise<PalbaseResult<PalbaseLink>>;
|
|
1886
|
-
/** List links (paginated). */
|
|
1887
|
-
list(options?: PalbaseListLinksOptions): Promise<PalbaseResult<PalbaseListLinksResult>>;
|
|
1888
|
-
/** Get link details + analytics summary. */
|
|
1889
|
-
get(linkId: string): Promise<PalbaseResult<PalbaseLinkDetails>>;
|
|
1890
|
-
/** Update a link. */
|
|
1891
|
-
update(linkId: string, params: PalbaseUpdateLinkParams): Promise<PalbaseResult<PalbaseLink>>;
|
|
1892
|
-
/** Delete a link. */
|
|
1893
|
-
delete(linkId: string): Promise<PalbaseResult<{
|
|
1894
|
-
success: boolean;
|
|
1895
|
-
}>>;
|
|
1896
|
-
/** Get click analytics for a link. */
|
|
1897
|
-
analytics(linkId: string): Promise<PalbaseResult<PalbaseLinkAnalytics>>;
|
|
1898
|
-
/** Generate a QR code for a link (returns PNG or SVG Blob). */
|
|
1899
|
-
qrCode(linkId: string, options?: PalbaseQrCodeOptions): Promise<PalbaseResult<Blob>>;
|
|
1900
|
-
/** Match a deferred deep link by device fingerprint. */
|
|
1901
|
-
match(params: PalbaseMatchParams): Promise<PalbaseResult<PalbaseInitialLink | null>>;
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
/** Uploaded file metadata injected into endpoint context when a file is present.
|
|
1905
|
-
* `data` is typed as `Uint8Array` for SDK portability (Buffer extends Uint8Array in Node).
|
|
1906
|
-
*/
|
|
1907
|
-
interface FileContext {
|
|
1908
|
-
filename: string;
|
|
1909
|
-
contentType: string;
|
|
1910
|
-
size: number;
|
|
1911
|
-
data: Uint8Array;
|
|
1912
|
-
}
|
|
1913
|
-
/** Rate limit configuration for an endpoint. */
|
|
1914
|
-
interface RateLimitConfig {
|
|
1915
|
-
/** Maximum number of requests in the window. */
|
|
1916
|
-
max: number;
|
|
1917
|
-
/** Window duration in seconds. */
|
|
1918
|
-
window: number;
|
|
1919
|
-
}
|
|
1920
|
-
/** The six raw string-keyed DB operations shared by `DBClient` and the
|
|
1921
|
-
* transaction-scoped client. */
|
|
1922
|
-
interface DBOps {
|
|
1923
|
-
/**
|
|
1924
|
-
* Run SQL on the REQUEST'S OWN transaction.
|
|
1925
|
-
*
|
|
1926
|
-
* This is **not** read-only. Nothing marks that transaction `READ ONLY`, so a
|
|
1927
|
-
* write passed here executes and commits with the request — a plain `UPDATE`, a
|
|
1928
|
-
* write hidden in a CTE, or a function that writes. What stands between a
|
|
1929
|
-
* statement and your data is RLS and the role the request runs as, exactly as
|
|
1930
|
-
* for every other operation on this client.
|
|
1931
|
-
*
|
|
1932
|
-
* `INSERT … ON CONFLICT DO UPDATE` therefore works here; `tables.<t>.upsert()`
|
|
1933
|
-
* is the same thing with types. Array parameters are encoded as Postgres array
|
|
1934
|
-
* literals, so `= ANY($1::uuid[])` takes a plain JS array.
|
|
1935
|
-
*
|
|
1936
|
-
* This comment used to claim a `READ ONLY` transaction. It was wrong, and the
|
|
1937
|
-
* cost was measured: a tenant read it, concluded an upsert was impossible from
|
|
1938
|
-
* here, abandoned `ON CONFLICT` and wrote a database trigger instead.
|
|
1939
|
-
*/
|
|
1940
|
-
query(sql: string, params?: unknown[]): Promise<Record<string, unknown>[]>;
|
|
1941
|
-
insert(table: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
1942
|
-
/**
|
|
1943
|
-
* Insert the row, or update it when it collides on `onConflict` — one
|
|
1944
|
-
* statement, so two concurrent callers cannot both lose.
|
|
1945
|
-
*
|
|
1946
|
-
* Read-then-write cannot be made safe here by catching the unique violation: a
|
|
1947
|
-
* request runs in one transaction, so the failed insert aborts it and every
|
|
1948
|
-
* later statement answers `current transaction is aborted`.
|
|
1949
|
-
*/
|
|
1950
|
-
upsert(table: string, data: Record<string, unknown>, opts: {
|
|
1951
|
-
onConflict: readonly string[];
|
|
1952
|
-
}): Promise<Record<string, unknown>>;
|
|
1953
|
-
/**
|
|
1954
|
-
* Update the row with the given id and resolve to the updated row, or `null`
|
|
1955
|
-
* if no row matched (the id is absent, or the row is hidden by RLS). This is
|
|
1956
|
-
* an idempotent outcome, not an error — it mirrors `findById`. Map `null` to
|
|
1957
|
-
* a 404 in your service if a missing row should be a client error.
|
|
1958
|
-
*/
|
|
1959
|
-
update(table: string, id: string, data: Record<string, unknown>): Promise<Record<string, unknown> | null>;
|
|
1960
|
-
delete(table: string, id: string): Promise<void>;
|
|
1961
|
-
findById(table: string, id: string): Promise<Record<string, unknown> | null>;
|
|
1962
|
-
/** Hibrit arama (FR-013/014) — tek SQL, RRF; engine implement eder (T017). */
|
|
1963
|
-
search(table: string, params?: {
|
|
1964
|
-
query?: string;
|
|
1965
|
-
vector?: number[];
|
|
1966
|
-
where?: Record<string, unknown>;
|
|
1967
|
-
limit?: number;
|
|
1968
|
-
using?: string;
|
|
1969
|
-
mode?: "hybrid" | "text" | "vector";
|
|
1970
|
-
/** Nihai (RRF-sonrası) skor alt eşiği (FR-001). */
|
|
1971
|
-
minScore?: number;
|
|
1972
|
-
/** RRF-sonrası üstel tazelik çürümesi (FR-004; feed/haber düğmesi). */
|
|
1973
|
-
recency?: {
|
|
1974
|
-
field: string;
|
|
1975
|
-
halfLife: string;
|
|
1976
|
-
};
|
|
1977
|
-
/** Chunk-modunda satır başına en iyi blok sayısı (FR-015). */
|
|
1978
|
-
blocksPerRow?: number;
|
|
1979
|
-
/** Kolon başına top-20 değer sayacı — dönüş dizisinin `_facets` özelliği (FR-027). */
|
|
1980
|
-
facets?: string[];
|
|
1981
|
-
/** Satır-modunda ts_headline vurgusu: satırlara `_highlight` (FR-025; chunk-modda no-op). */
|
|
1982
|
-
highlight?: boolean;
|
|
1983
|
-
/** Validity'li tabloda zaman penceresi: vars. yalnız güncel; "all" tümü; {asOf} o an (FR-029). */
|
|
1984
|
-
validity?: "all" | {
|
|
1985
|
-
asOf: string;
|
|
1986
|
-
};
|
|
1987
|
-
/** Alan-boost (FR-030): skor * (1 + w·x/(1+x)) — sınırlı çarpan, dış servissiz. */
|
|
1988
|
-
boost?: {
|
|
1989
|
-
field: string;
|
|
1990
|
-
weight: number;
|
|
1991
|
-
};
|
|
1992
|
-
}): Promise<Record<string, unknown>[]>;
|
|
1993
|
-
/** Validity'li tabloda satırın yeni versiyonu — eski kapanır (valid_to/superseded_by),
|
|
1994
|
-
* yenisi eklenir; TEK savepoint, dönüş yeni satır (FR-029, C-9). */
|
|
1995
|
-
supersede(table: string, id: string, row: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
1996
|
-
/** Update every row the filter matches, in one statement; resolves to them.
|
|
1997
|
-
* An EMPTY filter is refused — a whole-table write is not something to reach
|
|
1998
|
-
* by accident. */
|
|
1999
|
-
updateMany(table: string, where: Record<string, unknown>, set: Record<string, unknown>): Promise<Record<string, unknown>[]>;
|
|
2000
|
-
/** Delete every row the filter matches; resolves to how many. Empty filter
|
|
2001
|
-
* refused, for the same reason and more so. */
|
|
2002
|
-
deleteMany(table: string, where: Record<string, unknown>): Promise<number>;
|
|
2003
|
-
/** How many rows match — the half of pagination limit/offset cannot supply.
|
|
2004
|
-
* An empty filter is legitimate here: counting is a read. */
|
|
2005
|
-
count(table: string, where?: Record<string, unknown>): Promise<number>;
|
|
2006
|
-
/** Filtre + sıralama + sayfa. `offset` yalnız `limit` ile birlikte geçerlidir
|
|
2007
|
-
* (limitsiz offset sayfa değildir; motor adıyla reddeder — FR-004). */
|
|
2008
|
-
findMany(table: string, query?: Record<string, unknown>, opts?: {
|
|
2009
|
-
orderBy?: {
|
|
2010
|
-
column: string;
|
|
2011
|
-
direction?: "asc" | "desc";
|
|
2012
|
-
};
|
|
2013
|
-
limit?: number;
|
|
2014
|
-
offset?: number;
|
|
2015
|
-
}): Promise<Record<string, unknown>[]>;
|
|
2016
|
-
/** "Bu satıra benzer satırlar" (FR-022) — satır-modunda kolon vektörüyle,
|
|
2017
|
-
* chunk-modunda chunk-ortalamasıyla; kendisi hariç. */
|
|
2018
|
-
similar(table: string, id: string, opts?: {
|
|
2019
|
-
limit?: number;
|
|
2020
|
-
where?: Record<string, unknown>;
|
|
2021
|
-
minScore?: number;
|
|
2022
|
-
blocksPerRow?: number;
|
|
2023
|
-
validity?: "all" | {
|
|
2024
|
-
asOf: string;
|
|
2025
|
-
};
|
|
2026
|
-
boost?: {
|
|
2027
|
-
field: string;
|
|
2028
|
-
weight: number;
|
|
2029
|
-
};
|
|
2030
|
-
}): Promise<Record<string, unknown>[]>;
|
|
2031
|
-
/** D-021 (FR-027): kolon sayaçları bağımsız dönüşle — search'ün dizi-üstü
|
|
2032
|
-
* `_facets` özelliği serileşmede kaybolur; ciddi sözleşme budur. */
|
|
2033
|
-
facets(table: string, params: {
|
|
2034
|
-
facets: string[];
|
|
2035
|
-
where?: Record<string, unknown>;
|
|
2036
|
-
validity?: "all" | {
|
|
2037
|
-
asOf: string;
|
|
2038
|
-
};
|
|
2039
|
-
}): Promise<Record<string, {
|
|
2040
|
-
value: string | null;
|
|
2041
|
-
count: number;
|
|
2042
|
-
}[]>>;
|
|
2043
|
-
/** Çok-örnekli öneri (FR-023): avg(pos) + (avg(pos) − avg(neg)); positive'ler sonuç dışı. */
|
|
2044
|
-
recommend(table: string, opts: {
|
|
2045
|
-
positive: string[];
|
|
2046
|
-
negative?: string[];
|
|
2047
|
-
limit?: number;
|
|
2048
|
-
where?: Record<string, unknown>;
|
|
2049
|
-
minScore?: number;
|
|
2050
|
-
blocksPerRow?: number;
|
|
2051
|
-
validity?: "all" | {
|
|
2052
|
-
asOf: string;
|
|
2053
|
-
};
|
|
2054
|
-
boost?: {
|
|
2055
|
-
field: string;
|
|
2056
|
-
weight: number;
|
|
2057
|
-
};
|
|
2058
|
-
}): Promise<Record<string, unknown>[]>;
|
|
2059
|
-
}
|
|
2060
|
-
/** Database client interface injected into endpoint context. */
|
|
2061
|
-
interface DBClient extends DBOps {
|
|
2062
|
-
/**
|
|
2063
|
-
* Run a whole transaction as ONE plan, executed on the request's own
|
|
2064
|
-
* transaction inside a savepoint.
|
|
2065
|
-
*
|
|
2066
|
-
* The low-level seam behind `Database.transaction(plan)`: the SDK builds the
|
|
2067
|
-
* plan, this sends it, and the broker executes every operation inside a single
|
|
2068
|
-
* transaction that commits or rolls back before the response is written.
|
|
2069
|
-
* Nothing pins a Postgres backend across round trips, because there is only
|
|
2070
|
-
* one round trip.
|
|
2071
|
-
*
|
|
2072
|
-
* On failure the runtime must reject with an error carrying the broker's
|
|
2073
|
-
* envelope fields — see {@link TxPlanRejection}. `slot` in particular is what
|
|
2074
|
-
* turns a `tx_guard_failed` back into the `Error` the tenant handed to
|
|
2075
|
-
* `.expectOne(…)`; without it a declared expectation degrades to a generic 409.
|
|
2076
|
-
*/
|
|
2077
|
-
txPlan(plan: TxPlanBody): Promise<TxPlanResponse>;
|
|
2078
|
-
/**
|
|
2079
|
-
* Run `fn` inside a SAVEPOINT on this request's transaction, so a failure in
|
|
2080
|
-
* it rolls back only what `fn`'s handle wrote.
|
|
2081
|
-
*
|
|
2082
|
-
* The handle is a PARAMETER on purpose: an ambient swap would capture writes
|
|
2083
|
-
* a concurrent `Promise.all` branch made outside the boundary.
|
|
2084
|
-
*/
|
|
2085
|
-
attempt<T>(fn: (tx: DBOps) => Promise<T>): Promise<T>;
|
|
2086
|
-
/**
|
|
2087
|
-
* Return a sibling DB client that bypasses Row-Level Security by running as
|
|
2088
|
-
* the `service_role` (BYPASSRLS). Use sparingly and explicitly — the default
|
|
2089
|
-
* `Database.*` path is RLS-enforced. The returned client exposes the same op
|
|
2090
|
-
* surface (`query`/`insert`/.../`txPlan`) but never re-exposes `asService`
|
|
2091
|
-
* (no double-bypass).
|
|
2092
|
-
*/
|
|
2093
|
-
asService(): Omit<DBClient, "asService">;
|
|
2094
|
-
}
|
|
2095
|
-
/** Logger interface injected into endpoint context. */
|
|
2096
|
-
interface Logger {
|
|
2097
|
-
info(message: string, ...args: unknown[]): void;
|
|
2098
|
-
warn(message: string, ...args: unknown[]): void;
|
|
2099
|
-
error(message: string, ...args: unknown[]): void;
|
|
2100
|
-
debug(message: string, ...args: unknown[]): void;
|
|
2101
|
-
}
|
|
2102
|
-
/**
|
|
2103
|
-
* Cache client interface injected into endpoint context.
|
|
2104
|
-
*
|
|
2105
|
-
* The cache is JSON-typed: values are serialized to/from JSON, so any JSON
|
|
2106
|
-
* value (objects, arrays, numbers, booleans, strings) round-trips. `get<T>`
|
|
2107
|
-
* therefore returns `T | null` rather than `string | null`.
|
|
2108
|
-
*/
|
|
2109
|
-
/**
|
|
2110
|
-
* The tenant's secrets, as a controller sees them.
|
|
2111
|
-
*
|
|
2112
|
-
* `get` answers null for a name this tenant has not set — including every name
|
|
2113
|
-
* the STACK holds, which is not an accident of storage but the shape of the
|
|
2114
|
-
* surface: the runtime is handed a tenant client, and there is no route that
|
|
2115
|
-
* returns a platform secret's value at all. Tenant code cannot read the keys
|
|
2116
|
-
* the stack signs with, by mistake or on purpose.
|
|
2117
|
-
*
|
|
2118
|
-
* Reads are cheap. The value is held in the process and dropped when the deploy
|
|
2119
|
-
* pointer reports a new secrets generation, so a rotated secret arrives without
|
|
2120
|
-
* a restart and a hot path pays a map lookup.
|
|
2121
|
-
*/
|
|
2122
|
-
interface SecretsService {
|
|
2123
|
-
/**
|
|
2124
|
-
* A secret's value, or `null` when this backend's vault holds no value under
|
|
2125
|
-
* that name.
|
|
2126
|
-
*
|
|
2127
|
-
* The name is a `PalbaseSecretName`, not a `string`: it comes from the
|
|
2128
|
-
* generated `palbase-stack.d.ts`, so a secret the stack does not hold cannot
|
|
2129
|
-
* be spelled here at all. THE GATE IS THE COMPILER.
|
|
2130
|
-
*
|
|
2131
|
-
* That gate replaced a declaration. `config/secrets.ts` used to restate, in
|
|
2132
|
-
* the repo, names the vault already held, and the push compared the two lists
|
|
2133
|
-
* — a check that only ran at deploy, and only if the author remembered to
|
|
2134
|
-
* declare. The type runs on every keystroke and cannot be forgotten.
|
|
2135
|
-
*/
|
|
2136
|
-
get(name: PalbaseSecretName): Promise<string | null>;
|
|
2137
|
-
}
|
|
2138
|
-
interface CacheClient {
|
|
2139
|
-
/** Read a value. Returns `null` on a cache miss. */
|
|
2140
|
-
get<T = unknown>(key: string): Promise<T | null>;
|
|
2141
|
-
/** Write a JSON-serializable value with an optional TTL (seconds). */
|
|
2142
|
-
set(key: string, value: unknown, ttl?: number): Promise<void>;
|
|
2143
|
-
/** Delete a key. */
|
|
2144
|
-
del(key: string): Promise<void>;
|
|
2145
|
-
/** Atomically increment an integer counter, returning the new value. */
|
|
2146
|
-
incr(key: string): Promise<number>;
|
|
2147
|
-
/**
|
|
2148
|
-
* Stampede-safe read-through cache fill. On a hit, returns the cached value.
|
|
2149
|
-
* On a miss, a single caller (across all pod replicas, coordinated by a
|
|
2150
|
-
* distributed lock) runs `fn`, caches the result for `ttl` seconds, and
|
|
2151
|
-
* returns it; concurrent callers wait for that result instead of also
|
|
2152
|
-
* running `fn`. If no value lands within the lock's TTL, the call rejects —
|
|
2153
|
-
* it does NOT run `fn` on timeout (that would reintroduce the stampede).
|
|
2154
|
-
*
|
|
2155
|
-
* @param ttl value TTL in seconds.
|
|
2156
|
-
*/
|
|
2157
|
-
getOrSet<T>(key: string, ttl: number, fn: () => Promise<T> | T): Promise<T>;
|
|
2158
|
-
}
|
|
2159
|
-
/** Result envelope used across the Palbase Server SDK clients. */
|
|
2160
|
-
interface PalbaseResult<T> {
|
|
2161
|
-
data: T | null;
|
|
2162
|
-
error: {
|
|
2163
|
-
message?: string;
|
|
2164
|
-
code?: string;
|
|
2165
|
-
} | null;
|
|
2166
|
-
status?: number;
|
|
2167
|
-
}
|
|
2168
|
-
/** Document snapshot returned by docs.get(). */
|
|
2169
|
-
interface PalbaseDocumentSnapshot<T = Record<string, unknown>> {
|
|
2170
|
-
id: string;
|
|
2171
|
-
exists: boolean;
|
|
2172
|
-
data(): T | undefined;
|
|
2173
|
-
ref: {
|
|
2174
|
-
path: string;
|
|
2175
|
-
};
|
|
2176
|
-
}
|
|
2177
|
-
/** Collection query snapshot returned by collection.get(). */
|
|
2178
|
-
interface PalbaseQuerySnapshot<T = Record<string, unknown>> {
|
|
2179
|
-
docs: PalbaseDocumentSnapshot<T>[];
|
|
2180
|
-
empty: boolean;
|
|
2181
|
-
size: number;
|
|
2182
|
-
/** Firestore-shaped change list. Every document of a one-shot read is "added". */
|
|
2183
|
-
docChanges(): Array<{
|
|
2184
|
-
type: "added";
|
|
2185
|
-
doc: PalbaseDocumentSnapshot<T>;
|
|
2186
|
-
}>;
|
|
2187
|
-
}
|
|
2188
|
-
/** Comparison operators supported by docs.where(). */
|
|
2189
|
-
type PalbaseWhereOperator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "array-contains";
|
|
2190
|
-
/** Document reference exposed by Documents.collection(...).doc(...). */
|
|
2191
|
-
interface PalbaseDocumentRef<T = Record<string, unknown>> {
|
|
2192
|
-
readonly path: string;
|
|
2193
|
-
set(data: T): Promise<PalbaseResult<void>>;
|
|
2194
|
-
get(): Promise<PalbaseResult<PalbaseDocumentSnapshot<T>>>;
|
|
2195
|
-
update(data: Partial<T>): Promise<PalbaseResult<void>>;
|
|
2196
|
-
delete(): Promise<PalbaseResult<void>>;
|
|
2197
|
-
/** A subcollection of this document — `rooms/1` → `rooms/1/messages`. */
|
|
2198
|
-
collection<C extends Record<string, unknown> = Record<string, unknown>>(name: string): PalbaseCollectionRef<C>;
|
|
2199
|
-
}
|
|
2200
|
-
/** Collection reference exposed by Documents.collection(...). */
|
|
2201
|
-
interface PalbaseCollectionRef<T = Record<string, unknown>> {
|
|
2202
|
-
readonly path: string;
|
|
2203
|
-
doc(id: string): PalbaseDocumentRef<T>;
|
|
2204
|
-
add(data: T): Promise<PalbaseResult<PalbaseDocumentRef<T>>>;
|
|
2205
|
-
where(field: string, op: PalbaseWhereOperator, value: unknown): PalbaseCollectionRef<T>;
|
|
2206
|
-
orderBy(field: string, direction?: "asc" | "desc"): PalbaseCollectionRef<T>;
|
|
2207
|
-
limit(n: number): PalbaseCollectionRef<T>;
|
|
2208
|
-
get(): Promise<PalbaseResult<PalbaseQuerySnapshot<T>>>;
|
|
2209
|
-
}
|
|
2210
|
-
/** Docs client surface available on the Documents singleton. */
|
|
2211
|
-
interface PalbaseDocsClient {
|
|
2212
|
-
collection<T extends Record<string, unknown> = Record<string, unknown>>(path: string): PalbaseCollectionRef<T>;
|
|
2213
|
-
doc<T extends Record<string, unknown> = Record<string, unknown>>(path: string): PalbaseDocumentRef<T>;
|
|
2214
|
-
/**
|
|
2215
|
-
* Apply up to 500 writes in one call.
|
|
2216
|
-
*
|
|
2217
|
-
* Declared here because the client has always offered it: an interface that
|
|
2218
|
-
* described less than the implementation is how `Documents.doc(...)` came to
|
|
2219
|
-
* be declared and never implemented — the gap this whole move exists to close.
|
|
2220
|
-
*/
|
|
2221
|
-
batch(operations: Array<{
|
|
2222
|
-
op: "set" | "update" | "delete";
|
|
2223
|
-
ref: {
|
|
2224
|
-
path: string;
|
|
2225
|
-
};
|
|
2226
|
-
data?: unknown;
|
|
2227
|
-
}>): Promise<PalbaseResult<void>>;
|
|
2228
|
-
}
|
|
2229
|
-
/** Calling-client metadata, derived from request headers.
|
|
2230
|
-
*
|
|
2231
|
-
* Every header is named here, because the previous version of this comment named
|
|
2232
|
-
* only two of the four and the next reader invented `X-Palbase-Platform` for the
|
|
2233
|
-
* third. The four are canonical across the platform — the same names the Go
|
|
2234
|
-
* side reads in user-flags/internal/middleware/clientcontext.go — and the deploy
|
|
2235
|
-
* gate REFUSES an `@Headers` schema that declares an `x-palbase-*` key, which
|
|
2236
|
-
* makes `@Client()` the only sanctioned way to read them.
|
|
2237
|
-
*
|
|
2238
|
-
* All fields are nullable: a request may come from a non-SDK caller (curl,
|
|
2239
|
-
* server-to-server) that sends none of them. Note that the web SDK does not
|
|
2240
|
-
* send `X-Palbase-Sdk-Version` at all, so `sdkVersion` is null for every browser
|
|
2241
|
-
* caller. The semver comparison helpers (`appVersionAtLeast`, …) arrive in
|
|
2242
|
-
* Phase 2 — Phase 1 surfaces only the raw data fields. */
|
|
2243
|
-
interface ClientInfo {
|
|
2244
|
-
/** Palbase SDK version (`X-Palbase-Sdk-Version`), or null. */
|
|
2245
|
-
sdkVersion: string | null;
|
|
2246
|
-
/** Calling app's own version (`X-Palbase-Client-Version`), or null. */
|
|
2247
|
-
appVersion: string | null;
|
|
2248
|
-
/** Platform identifier (`X-Platform`, e.g. "ios", "android", "web"), or null. */
|
|
2249
|
-
platform: string | null;
|
|
2250
|
-
/** OS version string (`X-OS-Version`), or null. */
|
|
2251
|
-
osVersion: string | null;
|
|
2252
|
-
}
|
|
2253
|
-
/** Palbase module clients, as a structural bundle.
|
|
2254
|
-
*
|
|
2255
|
-
* NOT part of the endpoint surface anymore — endpoint handlers reach services
|
|
2256
|
-
* via the PascalCase singletons (`Database`, `Documents`, …). This type is
|
|
2257
|
-
* retained as an INTERNAL shape for the sibling contexts that still carry a
|
|
2258
|
-
* `ctx` (middleware, jobs, workers, hooks, webhooks — out of Phase 1 scope).
|
|
2259
|
-
* It is intentionally not re-exported from `index.ts`.
|
|
2260
|
-
*
|
|
2261
|
-
* Structurally typed against the runtime's `ServerClient`; the runtime injects
|
|
2262
|
-
* the real client, so mismatched names would surface as `undefined is not a
|
|
2263
|
-
* function` at call time — keep these in sync with `ServerClient`. */
|
|
2264
|
-
interface PalbaseModuleClients {
|
|
2265
|
-
auth: PalbaseAuthClient;
|
|
2266
|
-
storage: PalbaseStorageClient;
|
|
2267
|
-
docs: PalbaseDocsClient;
|
|
2268
|
-
realtime: PalbaseRealtimeClient;
|
|
2269
|
-
functions: PalbaseFunctionsClient;
|
|
2270
|
-
flags: PalbaseFlagsClient;
|
|
2271
|
-
notifications: PalbaseNotificationsClient;
|
|
2272
|
-
analytics: PalbaseAnalyticsClient;
|
|
2273
|
-
links: PalbaseLinksClient;
|
|
2274
|
-
}
|
|
2275
|
-
/** Declared-error definition.
|
|
2276
|
-
*
|
|
2277
|
-
* `code` is the stable snake_case identifier that lands on the wire envelope's
|
|
2278
|
-
* `error` field. `status` is the HTTP status code returned. `data`, if set,
|
|
2279
|
-
* is a Zod schema whose value rides on the envelope's `data` field — the CLI
|
|
2280
|
-
* codegen lowers it to the typed enum's associated value on iOS.
|
|
2281
|
-
*
|
|
2282
|
-
* `description` is optional human-readable text; the OpenAPI generator uses it
|
|
2283
|
-
* for the response description and the iOS codegen surfaces it in the
|
|
2284
|
-
* generated method's doc-comment.
|
|
2285
|
-
*/
|
|
2286
|
-
interface ErrorDef<TData extends ZodSchema = ZodSchema> {
|
|
2287
|
-
status: number;
|
|
2288
|
-
code: string;
|
|
2289
|
-
description?: string;
|
|
2290
|
-
data?: TData;
|
|
2291
|
-
}
|
|
2292
|
-
/** Map of declared errors keyed by their TypeScript-side names.
|
|
2293
|
-
*
|
|
2294
|
-
* Keys are the friendly names the handler uses (`req.errors.todoLocked`);
|
|
2295
|
-
* `code` on each value is the wire identifier. The TS name is what the iOS
|
|
2296
|
-
* codegen lowers to (camelCase enum cases), and the wire `code` is what the
|
|
2297
|
-
* envelope's `error` field carries.
|
|
2298
|
-
*/
|
|
2299
|
-
type ErrorMap = Record<string, ErrorDef>;
|
|
2300
|
-
/** Throwable proxy projected onto `req.errors` when `errors` is declared.
|
|
2301
|
-
*
|
|
2302
|
-
* Each entry is a constructor: declared errors with a `data` schema demand
|
|
2303
|
-
* the payload as a required argument; declared errors without `data` take
|
|
2304
|
-
* none. Throwing the result emits the standard envelope (with `data` when
|
|
2305
|
-
* present).
|
|
2306
|
-
*
|
|
2307
|
-
* throw req.errors.todoNotFound();
|
|
2308
|
-
* throw req.errors.todoLocked({ retryAfter: 30 });
|
|
2309
|
-
*/
|
|
2310
|
-
type ErrorThrowers<TErrors extends ErrorMap | undefined> = TErrors extends ErrorMap ? {
|
|
2311
|
-
[K in keyof TErrors]: TErrors[K]["data"] extends ZodSchema ? (data: z.infer<NonNullable<TErrors[K]["data"]>>) => HttpError : () => HttpError;
|
|
2312
|
-
} : Record<string, never>;
|
|
2313
|
-
/** The request-scoped object passed to every endpoint handler.
|
|
2314
|
-
*
|
|
2315
|
-
* Replaces the old `ctx` god-object. `PBRequest` carries ONLY request-scoped
|
|
2316
|
-
* data — the typed `input`, route/query params, headers, the authenticated
|
|
2317
|
-
* `user`, calling-client metadata, trace ids, and the endpoint's declared
|
|
2318
|
-
* error throwers. Services (`Database`, `Documents`, `Cache`, …) are NOT on
|
|
2319
|
-
* the request: import them directly from `@palbase/backend` as singletons.
|
|
2320
|
-
*
|
|
2321
|
-
* import { Controller, Get, Req, Database } from "@palbase/backend";
|
|
2322
|
-
*
|
|
2323
|
-
* \@Controller("/todos")
|
|
2324
|
-
* export class TodosController {
|
|
2325
|
-
* \@Get("") list(\@Req() req: PBRequest): unknown {
|
|
2326
|
-
* return Database.findMany("todos");
|
|
2327
|
-
* }
|
|
2328
|
-
* }
|
|
2329
|
-
*
|
|
2330
|
-
* Most controller methods reach individual request slices via their own
|
|
2331
|
-
* parameter decorator (`@Body`/`@QueryParams`/`@Param`/`@User`/…); `@Req()` is the
|
|
2332
|
-
* escape hatch that injects this whole object.
|
|
2333
|
-
*
|
|
2334
|
-
* Generic parameters:
|
|
2335
|
-
* - `TInput` — the validated `input` type (the `@Body` schema's `z.infer`). The
|
|
2336
|
-
* user-facing form is single-generic: `PBRequest<TodoInput>`.
|
|
2337
|
-
* - `TAuthed` — whether `user` is non-null. DEFAULTS to `true` (the common
|
|
2338
|
-
* case; the auth pipeline returns 401 before the handler when auth is
|
|
2339
|
-
* required, so a non-null `user` is runtime-honest). A route whose effective
|
|
2340
|
-
* auth is `false` yields `User | null`.
|
|
2341
|
-
* - `TErrors` — RETAINED for back-compat of the `errors` thrower shape; the
|
|
2342
|
-
* class-controller model throws global error classes
|
|
2343
|
-
* (`Conflict`/`NotFound`/…) instead, so `req.errors` is empty in practice.
|
|
2344
|
-
*/
|
|
2345
|
-
interface PBRequest<TInput = unknown, TAuthed extends boolean = true, TErrors extends ErrorMap | undefined = undefined> {
|
|
2346
|
-
/** Validated request input (body for POST/PUT/PATCH; `{}` otherwise). */
|
|
2347
|
-
input: TInput;
|
|
2348
|
-
/** Matched route params (e.g. `{ id }` for `/todos/[id]`). */
|
|
2349
|
-
params: Record<string, string>;
|
|
2350
|
-
/** Parsed query-string params. */
|
|
2351
|
-
query: Record<string, string>;
|
|
2352
|
-
/** Request headers (lowercase keys). */
|
|
2353
|
-
headers: Record<string, string>;
|
|
2354
|
-
/** Authenticated user. Non-null (`User`) by default; `User | null` only when
|
|
2355
|
-
* the route's effective auth disables enforcement (driven by `TAuthed`, which
|
|
2356
|
-
* the runtime resolves from the route/controller `auth` cascade via
|
|
2357
|
-
* {@link IsAuthed}). */
|
|
2358
|
-
user: TAuthed extends true ? User : User | null;
|
|
2359
|
-
/** Calling-client metadata derived from request headers (all nullable). */
|
|
2360
|
-
client: ClientInfo;
|
|
2361
|
-
/** Uploaded file, or null when the request has no file part. */
|
|
2362
|
-
file: FileContext | null;
|
|
2363
|
-
/** HTTP method of the incoming request (e.g. "GET", "POST"). */
|
|
2364
|
-
method: string;
|
|
2365
|
-
/** Per-request id (`req_<…>`), preserved for back-compat correlation. */
|
|
2366
|
-
requestId: string;
|
|
2367
|
-
/** W3C trace id (primary correlation key across modules). */
|
|
2368
|
-
traceId: string;
|
|
2369
|
-
/** W3C span id for this handler invocation. */
|
|
2370
|
-
spanId: string;
|
|
2371
|
-
/** Typed throwers for the endpoint's declared errors. RETAINED for the
|
|
2372
|
-
* `@Req()` escape-hatch shape; the class-controller model throws global error
|
|
2373
|
-
* classes (`Conflict`/`NotFound`/…) instead, so this is empty in practice. */
|
|
2374
|
-
errors: ErrorThrowers<TErrors>;
|
|
2375
|
-
}
|
|
2376
|
-
/** Middleware function signature — uses MiddlewareContext (no input, not yet validated). */
|
|
2377
|
-
type Middleware = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
|
|
2378
|
-
/** The shape an endpoint's `auth` config may take.
|
|
2379
|
-
*
|
|
2380
|
-
* Either a bare boolean (`true`/`false`) or an object form (`{ required?,
|
|
2381
|
-
* role? }`). The object form is `Partial<AuthConfig>` so `required` may be
|
|
2382
|
-
* omitted — which the runtime treats as `required: true` (see {@link IsAuthed}).
|
|
2383
|
-
*/
|
|
2384
|
-
type AuthSpec = boolean | Partial<AuthConfig>;
|
|
2385
|
-
|
|
2386
|
-
export { type PalbaseCreateLinkParams as $, type AuthSpec as A, BadRequest as B, type CacheClient as C, type DBClient as D, type ErrorDef as E, type FileContext as F, type PalbaseAttestiOSParams as G, HttpError as H, type PalbaseAttestiOSResult as I, type PalbaseAuthClient as J, type PalbaseBatchOverrideOperation as K, type Logger as L, type Materialized as M, NotFound as N, type PalbaseBatchSetOverridesResult as O, type PalbaseDocsClient as P, type PalbaseBindDeviceParams as Q, type RateLimitConfig as R, type SecretsService as S, type TxPlanBody as T, type PalbaseClearAllOverridesResult as U, type PalbaseClearOverrideResult as V, type PalbaseCohortQueryInput as W, type PalbaseCohortResult as X, type PalbaseCollectionRef as Y, type PalbaseCountQueryInput as Z, type PalbaseCountResult as _, type PalbaseFlagsClient as a, type PalbaseVerifyRequestSignatureParams as a$, type PalbaseDeviceInfo as a0, type PalbaseDeviceTokenView as a1, type PalbaseDocumentRef as a2, type PalbaseDocumentSnapshot as a3, type PalbaseEmailClient as a4, type PalbaseEmailSendParams as a5, type PalbaseEmailSendResponse as a6, type PalbaseEventNamesResult as a7, type PalbaseEventsQueryInput as a8, type PalbaseEventsResult as a9, type PalbaseMatchParams as aA, type PalbaseMultiChannelResponse as aB, type PalbaseOverviewResult as aC, type PalbasePreferences as aD, type PalbasePreferencesClient as aE, type PalbasePushClient as aF, type PalbasePushSendParams as aG, type PalbasePushSendResponse as aH, type PalbaseQrCodeOptions as aI, type PalbaseQuerySnapshot as aJ, type PalbaseRegisterDeviceParams as aK, type PalbaseRetentionQueryInput as aL, type PalbaseRetentionResult as aM, type PalbaseSession as aN, type PalbaseSetOverrideResult as aO, type PalbaseSetOverridesResult as aP, type PalbaseSignedUrlResponse as aQ, type PalbaseSmsClient as aR, type PalbaseSmsSendParams as aS, type PalbaseSmsSendResponse as aT, type PalbaseTransformOptions as aU, type PalbaseUpdateLinkParams as aV, type PalbaseUploadOptions as aW, type PalbaseUser as aX, type PalbaseUserDetailResult as aY, type PalbaseUsersQueryInput as aZ, type PalbaseUsersResult as a_, type PalbaseFileObject as aa, type PalbaseFlag as ab, type PalbaseFlagContext as ac, type PalbaseFlagSource as ad, type PalbaseFlagValue as ae, type PalbaseFlagVariant as af, type PalbaseFlagsServiceClient as ag, type PalbaseFunctionsClient as ah, type PalbaseFunnelQueryInput as ai, type PalbaseFunnelResult as aj, type PalbaseIdentifyTraits as ak, type PalbaseInboxClient as al, type PalbaseInboxListOptions as am, type PalbaseInboxListResult as an, type PalbaseInboxMessage as ao, type PalbaseInboxSendParams as ap, type PalbaseInboxSendResponse as aq, type PalbaseInitialLink as ar, type PalbaseInvokeOptions as as, type PalbaseLink as at, type PalbaseLinkAnalytics as au, type PalbaseLinkDetails as av, type PalbaseLinksClient as aw, type PalbaseListLinksOptions as ax, type PalbaseListLinksResult as ay, type PalbaseListOptions as az, type PalbaseNotificationsClient as b, type PalbaseWhereOperator as b0, type Ref as b1, TooManyRequests as b2, type TxColumnExpr as b3, type TxInsertShape as b4, type TxInsertValue as b5, type TxNow as b6, TxPlanError as b7, type TxPlanHandle as b8, type TxPlanOpResult as b9, type TxPlanRejection as ba, TxRefError as bb, type TxRow as bc, type TxRows as bd, type TxSelectOptions as be, type TxSetShape as bf, type TxSetValue as bg, type TxTable as bh, type TxWhere as bi, type TxWireExpr as bj, type TxWireGuard as bk, type TxWireOp as bl, type TxWireRef as bm, type TxWireValue as bn, Unauthorized as bo, UniqueViolation as bp, type User as bq, type VerifiedDevice as br, dec as bs, defineMiddleware as bt, inc as bu, now as bv, type PalbaseRealtimeClient as c, type PalbaseStorageClient as d, type PalbaseBucketClient as e, type DBOps as f, type TxPlanResponse as g, type PalbaseResult as h, type AuthConfig as i, type ClientInfo as j, Conflict as k, type ErrorMap as l, type ErrorThrowers as m, Forbidden as n, type HttpMethod as o, type Middleware as p, type MiddlewareContext as q, type MiddlewareHandler as r, type PBRequest as s, PalError as t, type PalbaseAnalyticsClient as u, type PalbaseAnalyticsManagementNamespace as v, type PalbaseAnalyticsProperties as w, type PalbaseAnalyticsQueryNamespace as x, type PalbaseAttestAndroidParams as y, type PalbaseAttestAndroidResult as z };
|