@renoxium/sdk 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +178 -3
- package/dist/authz.d.ts +31 -0
- package/dist/authz.js +39 -0
- package/dist/crypto.d.ts +6 -0
- package/dist/crypto.js +26 -0
- package/dist/ctx.d.ts +486 -0
- package/dist/ctx.js +13 -0
- package/dist/db.d.ts +36 -0
- package/dist/db.js +3 -0
- package/dist/drizzle.d.ts +8 -0
- package/dist/drizzle.js +42 -0
- package/dist/errors.d.ts +261 -0
- package/dist/errors.js +232 -0
- package/dist/fakes.d.ts +35 -0
- package/dist/fakes.js +144 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +15 -0
- package/dist/jobs.d.ts +16 -0
- package/dist/jobs.js +72 -0
- package/dist/kysely.d.ts +11 -0
- package/dist/kysely.js +126 -0
- package/dist/modules.d.ts +72 -0
- package/dist/modules.js +30 -0
- package/dist/pages.d.ts +53 -0
- package/dist/pages.js +14 -0
- package/dist/realtime.d.ts +15 -0
- package/dist/realtime.js +40 -0
- package/dist/redirect.d.ts +19 -0
- package/dist/redirect.js +57 -0
- package/dist/test.d.ts +169 -0
- package/dist/test.js +381 -0
- package/dist/tools.d.ts +24 -0
- package/dist/tools.js +105 -0
- package/dist/totp.d.ts +40 -0
- package/dist/totp.js +111 -0
- package/dist/validate.d.ts +76 -0
- package/dist/validate.js +105 -0
- package/dist/webauthn.d.ts +63 -0
- package/dist/webauthn.js +283 -0
- package/package.json +85 -6
package/dist/ctx.d.ts
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import type { Db, DbStatements } from "./db.ts";
|
|
2
|
+
import type { Session } from "./pages.ts";
|
|
3
|
+
/** Standard `fetch`; the allowlist and blocked ranges are enforced in Rust. */
|
|
4
|
+
export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
5
|
+
/** Frozen; contains only declared and set secrets. Not available at module top level. */
|
|
6
|
+
export type Secrets = Readonly<Record<string, string>>;
|
|
7
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
8
|
+
[key: string]: JsonValue;
|
|
9
|
+
};
|
|
10
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
11
|
+
export type LogFields = Record<string, JsonValue>;
|
|
12
|
+
/** Never throws; lines over the tenant's rate limit are dropped and counted. */
|
|
13
|
+
export interface Log {
|
|
14
|
+
debug(message: string, fields?: LogFields): void;
|
|
15
|
+
info(message: string, fields?: LogFields): void;
|
|
16
|
+
warn(message: string, fields?: LogFields): void;
|
|
17
|
+
error(message: string, fields?: LogFields): void;
|
|
18
|
+
}
|
|
19
|
+
export interface Deadline {
|
|
20
|
+
/** Whole milliseconds of wall-clock time left for this request, >= 0. */
|
|
21
|
+
remainingMs(): number;
|
|
22
|
+
}
|
|
23
|
+
/** Route parameters: `[id]` → string, `[...slug]` → string[]. */
|
|
24
|
+
export type Params = Record<string, string | string[]>;
|
|
25
|
+
/** Frozen and new for every request. */
|
|
26
|
+
export interface Ctx {
|
|
27
|
+
readonly req: Request;
|
|
28
|
+
readonly url: URL;
|
|
29
|
+
readonly params: Params;
|
|
30
|
+
readonly headers: Headers;
|
|
31
|
+
readonly db: Db;
|
|
32
|
+
readonly fetch: Fetch;
|
|
33
|
+
readonly secrets: Secrets;
|
|
34
|
+
readonly log: Log;
|
|
35
|
+
readonly deadline: Deadline;
|
|
36
|
+
/** The module this ctx is bound to (ops.md section 15.1); "app" for the app. */
|
|
37
|
+
readonly module: string;
|
|
38
|
+
/** The module's resolved `[config]` (section 15.5); `{}` for the app. */
|
|
39
|
+
readonly config: ModuleConfig;
|
|
40
|
+
/** Service proxies of the installed modules, as `renox:modules` (section 15.2). */
|
|
41
|
+
readonly services: Services;
|
|
42
|
+
readonly events: Events;
|
|
43
|
+
readonly hooks: Hooks;
|
|
44
|
+
/** Argon2id off the isolate thread (section 16). */
|
|
45
|
+
readonly password: Password;
|
|
46
|
+
/** The request's principal (section 17, 1.5.0); null is the anonymous principal. */
|
|
47
|
+
readonly principal: Principal | null;
|
|
48
|
+
/** Cedar decisions and role assignments (section 17, 1.5.0). */
|
|
49
|
+
readonly authz: Authz;
|
|
50
|
+
/** Topics, connections and payloads (section 18, 1.5.0); `publish` works from any handler. */
|
|
51
|
+
readonly realtime: Realtime;
|
|
52
|
+
/** Queued jobs (section 20, 1.5.0). */
|
|
53
|
+
readonly jobs: Jobs;
|
|
54
|
+
/** Files of the caller under the tenant's prefix (section 21, 1.7.0). */
|
|
55
|
+
readonly storage: Storage;
|
|
56
|
+
/** Model calls, direct or through the platform gateway (section 23, 1.7.0). */
|
|
57
|
+
readonly ai: Ai;
|
|
58
|
+
/** Templates to PDF in Rust (section 24, 1.7.0). */
|
|
59
|
+
readonly pdf: Pdf;
|
|
60
|
+
/**
|
|
61
|
+
* Renders `template` (a page or partial of the caller; for a module, relative to its own tree)
|
|
62
|
+
* with `data` to an HTML string, as a fragment: no layout (section 25, 1.8.0).
|
|
63
|
+
*/
|
|
64
|
+
render(template: string, data: Readonly<Record<string, unknown>>): Promise<string>;
|
|
65
|
+
}
|
|
66
|
+
/** One row of `renox__role_assignments` as the principal carries it. */
|
|
67
|
+
export interface PrincipalRole {
|
|
68
|
+
readonly role: string;
|
|
69
|
+
/** "global", or the `attrs` key the assignment is scoped on ("org"). */
|
|
70
|
+
readonly scope_type: string;
|
|
71
|
+
readonly scope_id: string | null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The end user as the cell resolved it through the `auth` module's
|
|
75
|
+
* `auth.principal` service, or the System principal of a job or event
|
|
76
|
+
* handler. Frozen; the same object in every module's ctx of the request.
|
|
77
|
+
*/
|
|
78
|
+
export interface Principal {
|
|
79
|
+
readonly id: string;
|
|
80
|
+
readonly kind: "user" | "system";
|
|
81
|
+
readonly roles: readonly PrincipalRole[];
|
|
82
|
+
/** What `auth.principal` and its hook fillers added (`mfa_verified`, `org`, ...). */
|
|
83
|
+
readonly attrs: Readonly<Record<string, JsonValue>>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A resource as tenant code passes it: `type` is a key of `[authz.entities]`
|
|
87
|
+
* (`"<module>:<Type>"` from the app for a module's type), `id` the entity id;
|
|
88
|
+
* only the declared attributes are kept, so a database row can be passed as it is.
|
|
89
|
+
*/
|
|
90
|
+
export type ResourceRef = {
|
|
91
|
+
readonly type: string;
|
|
92
|
+
readonly id: string;
|
|
93
|
+
} & Readonly<Record<string, unknown>>;
|
|
94
|
+
export type AuthzContext = Readonly<Record<string, JsonValue>>;
|
|
95
|
+
export interface Decision {
|
|
96
|
+
readonly allow: boolean;
|
|
97
|
+
/** The deciding policy id in dev mode, null otherwise. */
|
|
98
|
+
readonly policy: string | null;
|
|
99
|
+
}
|
|
100
|
+
export interface Explanation {
|
|
101
|
+
readonly decision: "allow" | "deny";
|
|
102
|
+
readonly reasons: readonly string[];
|
|
103
|
+
readonly errors: readonly string[];
|
|
104
|
+
}
|
|
105
|
+
export interface RoleScope {
|
|
106
|
+
readonly type: string;
|
|
107
|
+
readonly id: string | null;
|
|
108
|
+
}
|
|
109
|
+
export interface RoleAssignment {
|
|
110
|
+
readonly user_id: string;
|
|
111
|
+
readonly role: string;
|
|
112
|
+
readonly scope_type: string;
|
|
113
|
+
readonly scope_id: string | null;
|
|
114
|
+
readonly granted_by: string;
|
|
115
|
+
readonly at: number;
|
|
116
|
+
}
|
|
117
|
+
export interface AuthzRoles {
|
|
118
|
+
/** Writes `renox__role_assignments` in the current transaction and emits `authz.roles_changed`; a module may assign only its approved roles. */
|
|
119
|
+
assign(userId: string, role: string, scope?: RoleScope): Promise<void>;
|
|
120
|
+
/** Resolves true when a row was removed. */
|
|
121
|
+
revoke(userId: string, role: string, scope?: RoleScope): Promise<boolean>;
|
|
122
|
+
list(userId: string): Promise<RoleAssignment[]>;
|
|
123
|
+
}
|
|
124
|
+
export interface Authz {
|
|
125
|
+
/** Resolves when the policy allows; rejects with `ForbiddenError` otherwise. */
|
|
126
|
+
require(action: string, resource?: ResourceRef, context?: AuthzContext): Promise<void>;
|
|
127
|
+
can(action: string, resource?: ResourceRef, context?: AuthzContext): Promise<boolean>;
|
|
128
|
+
/** One boolean per resource, in order; at most 1,000 resources. */
|
|
129
|
+
canBatch(action: string, resources: readonly ResourceRef[], context?: AuthzContext): Promise<boolean[]>;
|
|
130
|
+
/** Dev mode only; `RENOX_FORBIDDEN` (`reason: "dev_only"`) in production. */
|
|
131
|
+
explain(action: string, resource?: ResourceRef, context?: AuthzContext): Promise<Explanation>;
|
|
132
|
+
readonly roles: AuthzRoles;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* `export const guard = { action, resource? }` of a page, function, ws or
|
|
136
|
+
* sse module: evaluated by the cell before the module is evaluated and
|
|
137
|
+
* before any handler runs, and on every fragment request. `resource.id` is
|
|
138
|
+
* a literal or `":<param>"`, a route parameter.
|
|
139
|
+
*/
|
|
140
|
+
export interface Guard {
|
|
141
|
+
readonly action: string;
|
|
142
|
+
readonly resource?: {
|
|
143
|
+
readonly type: string;
|
|
144
|
+
readonly id: string;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** A connection handle; only ws and sse handlers hold one. */
|
|
148
|
+
export interface Conn {
|
|
149
|
+
readonly id: string;
|
|
150
|
+
readonly transport: "ws" | "sse";
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* A text frame, JSON text, or a partial rendered by the cell off the isolate
|
|
154
|
+
* thread (`fragment` is a partial template id of the caller's module).
|
|
155
|
+
* `event` names the SSE event; it is ignored on a WebSocket.
|
|
156
|
+
*/
|
|
157
|
+
export type RealtimePayload = string | {
|
|
158
|
+
readonly text: string;
|
|
159
|
+
readonly event?: string;
|
|
160
|
+
} | {
|
|
161
|
+
readonly json: JsonValue;
|
|
162
|
+
readonly event?: string;
|
|
163
|
+
} | {
|
|
164
|
+
readonly fragment: string;
|
|
165
|
+
readonly data: Readonly<Record<string, unknown>>;
|
|
166
|
+
readonly event?: string;
|
|
167
|
+
};
|
|
168
|
+
export interface Realtime {
|
|
169
|
+
/** The Cedar action `realtime.subscribe` on `Renox::Topic`, fail closed: rejects with `ForbiddenError` on a deny. */
|
|
170
|
+
subscribe(conn: Conn, topic: string): Promise<void>;
|
|
171
|
+
unsubscribe(conn: Conn, topic: string): Promise<void>;
|
|
172
|
+
/** Resolves with the number of connections the message was queued for. */
|
|
173
|
+
publish(topic: string, payload: RealtimePayload): Promise<number>;
|
|
174
|
+
/** Rejects with `RealtimeBackpressureError` when the connection's queue is full. */
|
|
175
|
+
send(conn: Conn, payload: RealtimePayload): Promise<void>;
|
|
176
|
+
close(conn: Conn, code?: number, reason?: string): Promise<void>;
|
|
177
|
+
}
|
|
178
|
+
/** The ctx of `open` and `close`: the upgrade request plus the connection. */
|
|
179
|
+
export interface RealtimeContext extends Ctx {
|
|
180
|
+
readonly session: Session;
|
|
181
|
+
readonly conn: Conn;
|
|
182
|
+
}
|
|
183
|
+
/** The ctx of `message`: one text or binary frame. */
|
|
184
|
+
export interface MessageContext extends RealtimeContext {
|
|
185
|
+
readonly data: string | Uint8Array;
|
|
186
|
+
}
|
|
187
|
+
export interface CloseContext extends RealtimeContext {
|
|
188
|
+
readonly close: {
|
|
189
|
+
readonly code: number;
|
|
190
|
+
readonly reason: string | null;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/** Shape of a `functions/ws/**\/*.ts` module's `ws` export. */
|
|
194
|
+
export interface WsModule {
|
|
195
|
+
open?(ctx: RealtimeContext): void | Promise<void>;
|
|
196
|
+
message?(ctx: MessageContext): void | Promise<void>;
|
|
197
|
+
close?(ctx: CloseContext): void | Promise<void>;
|
|
198
|
+
}
|
|
199
|
+
/** Shape of a `functions/sse/**\/*.ts` module's `sse` export. */
|
|
200
|
+
export interface SseModule {
|
|
201
|
+
open?(ctx: RealtimeContext): void | Promise<void>;
|
|
202
|
+
close?(ctx: CloseContext): void | Promise<void>;
|
|
203
|
+
}
|
|
204
|
+
/** The ctx of a tool's `run`: the owning module's ctx without a request, as the token's user. */
|
|
205
|
+
export interface ToolContext<I = unknown> extends Omit<Ctx, "req" | "url" | "params" | "headers"> {
|
|
206
|
+
readonly tool: {
|
|
207
|
+
readonly name: string;
|
|
208
|
+
readonly input: I;
|
|
209
|
+
readonly client: {
|
|
210
|
+
readonly name: string;
|
|
211
|
+
readonly model?: string;
|
|
212
|
+
};
|
|
213
|
+
/** true on the first call of a `confirm` tool: the run's transaction is rolled back. */
|
|
214
|
+
readonly preview: boolean;
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** A schema that validates `arguments` into `I` (the Standard Schema adapter of the SDK). */
|
|
218
|
+
export interface ToolInputSchema<I> {
|
|
219
|
+
readonly "~standard": {
|
|
220
|
+
readonly version: 1;
|
|
221
|
+
readonly vendor: string;
|
|
222
|
+
validate(value: unknown): unknown;
|
|
223
|
+
};
|
|
224
|
+
readonly __input?: I;
|
|
225
|
+
}
|
|
226
|
+
export interface ToolDefinition<I> {
|
|
227
|
+
readonly name: string;
|
|
228
|
+
readonly description: string;
|
|
229
|
+
readonly input: ToolInputSchema<I>;
|
|
230
|
+
/** The Cedar action checked with the token's user before `run`. */
|
|
231
|
+
readonly action: string;
|
|
232
|
+
readonly confirm?: boolean;
|
|
233
|
+
run(ctx: ToolContext<I>): unknown;
|
|
234
|
+
}
|
|
235
|
+
/** What `tool()` returns and a `functions/tools/*.ts` module default-exports. */
|
|
236
|
+
export interface Tool<I = unknown> extends ToolDefinition<I> {
|
|
237
|
+
readonly confirm: boolean;
|
|
238
|
+
}
|
|
239
|
+
export interface EnqueueOptions {
|
|
240
|
+
/** Unix time in milliseconds; absent = as soon as possible. */
|
|
241
|
+
readonly runAt?: number;
|
|
242
|
+
}
|
|
243
|
+
export interface Jobs {
|
|
244
|
+
/** Writes a queued run of a job of this module in the current transaction; resolves with its id. */
|
|
245
|
+
enqueue(name: string, payload: Readonly<Record<string, unknown>>, options?: EnqueueOptions): Promise<string>;
|
|
246
|
+
/** 1.6.0, module `jobs` only (`RENOX_FORBIDDEN` for any other caller): one page of jobs in a state, never the payload. */
|
|
247
|
+
list(state: JobState, options?: {
|
|
248
|
+
readonly limit?: number;
|
|
249
|
+
readonly cursor?: string | null;
|
|
250
|
+
}): Promise<JobPage>;
|
|
251
|
+
/** 1.6.0, module `jobs` only: a dead letter goes back to the queue with its attempts reset. */
|
|
252
|
+
retry(id: string): Promise<void>;
|
|
253
|
+
/** 1.6.0, module `jobs` only: deletes a queued or dead row. */
|
|
254
|
+
cancel(id: string): Promise<void>;
|
|
255
|
+
}
|
|
256
|
+
export type JobState = "scheduled" | "queued" | "running" | "dead";
|
|
257
|
+
/** One job row as the `jobs` module sees it (section 20, 1.6.0). */
|
|
258
|
+
export interface JobRow {
|
|
259
|
+
readonly id: string;
|
|
260
|
+
readonly name: string;
|
|
261
|
+
readonly module: string;
|
|
262
|
+
readonly tick: number | null;
|
|
263
|
+
readonly next_at: number | null;
|
|
264
|
+
readonly run_at: number | null;
|
|
265
|
+
readonly attempts: number;
|
|
266
|
+
readonly started_at: number | null;
|
|
267
|
+
readonly finished_at: number | null;
|
|
268
|
+
/** Redacted, at most 512 characters. */
|
|
269
|
+
readonly last_error: string | null;
|
|
270
|
+
}
|
|
271
|
+
export interface JobPage {
|
|
272
|
+
readonly items: readonly JobRow[];
|
|
273
|
+
readonly next_cursor: string | null;
|
|
274
|
+
}
|
|
275
|
+
export interface JobInfo<P = unknown> {
|
|
276
|
+
readonly id: string;
|
|
277
|
+
readonly name: string;
|
|
278
|
+
/** The tick the run is for, or the `runAt` of a queued run. */
|
|
279
|
+
readonly tick: number;
|
|
280
|
+
readonly attempt: number;
|
|
281
|
+
/** null for a scheduled run. */
|
|
282
|
+
readonly payload: P | null;
|
|
283
|
+
}
|
|
284
|
+
/** The ctx of `run`: the module's ctx without the request, with the attempt's transaction and the System principal. */
|
|
285
|
+
export interface JobContext<P = unknown> extends Omit<Ctx, "req" | "url" | "params" | "headers" | "db"> {
|
|
286
|
+
readonly db: DbStatements;
|
|
287
|
+
readonly job: JobInfo<P>;
|
|
288
|
+
}
|
|
289
|
+
/** Shape of a `jobs/*.ts` module. */
|
|
290
|
+
export interface JobModule {
|
|
291
|
+
readonly schedule: string;
|
|
292
|
+
readonly tz?: string;
|
|
293
|
+
run(ctx: JobContext<never>): void | Promise<void>;
|
|
294
|
+
}
|
|
295
|
+
export type ConfigValue = string | number | boolean;
|
|
296
|
+
export type ModuleConfig = Readonly<Record<string, ConfigValue>>;
|
|
297
|
+
/** One service as the caller sees it: the broker supplies the callee's ctx. */
|
|
298
|
+
export type ServiceFunction = (...args: readonly unknown[]) => Promise<unknown>;
|
|
299
|
+
export type ModuleServices = Readonly<Record<string, ServiceFunction>>;
|
|
300
|
+
/** Installed module name → its exported services. */
|
|
301
|
+
export type Services = Readonly<Record<string, ModuleServices>>;
|
|
302
|
+
export interface EmitOptions {
|
|
303
|
+
/** Default true: into `renox__outbox` in the current transaction. false: handlers run now, in this request. */
|
|
304
|
+
durable?: boolean;
|
|
305
|
+
}
|
|
306
|
+
export interface Events {
|
|
307
|
+
/** Resolves with the event id (a ULID). Only events the module exports (section 15.4). */
|
|
308
|
+
emit(name: string, payload: Readonly<Record<string, unknown>>, options?: EmitOptions): Promise<string>;
|
|
309
|
+
}
|
|
310
|
+
export type HookRunResult = {
|
|
311
|
+
readonly halt: false;
|
|
312
|
+
} | {
|
|
313
|
+
readonly halt: true;
|
|
314
|
+
readonly response: Response;
|
|
315
|
+
readonly module: string;
|
|
316
|
+
};
|
|
317
|
+
export interface Hooks {
|
|
318
|
+
/** Runs the fillers of a hook point this module exports, in priority order (section 15.3). */
|
|
319
|
+
run(name: string, args?: unknown): Promise<HookRunResult>;
|
|
320
|
+
}
|
|
321
|
+
export interface Password {
|
|
322
|
+
/** A PHC string `$argon2id$v=19$m=65536,t=3,p=1$<salt>$<hash>`. */
|
|
323
|
+
hash(password: string): Promise<string>;
|
|
324
|
+
/** Constant-time; false for a malformed hash or one above the platform's parameters. */
|
|
325
|
+
verify(password: string, hash: string): Promise<boolean>;
|
|
326
|
+
}
|
|
327
|
+
/** What a module's `services.ts` exports: each service gets the callee's ctx first. */
|
|
328
|
+
export type ServiceImplementation = (ctx: Ctx, ...args: never[]) => unknown;
|
|
329
|
+
export type ServicesModule = Readonly<Record<string, ServiceImplementation>>;
|
|
330
|
+
/** A filler continues with `undefined`, or stops the run with a response. */
|
|
331
|
+
export type HookFillerResult = void | {
|
|
332
|
+
readonly halt: true;
|
|
333
|
+
readonly response: Response;
|
|
334
|
+
};
|
|
335
|
+
export interface HookFiller {
|
|
336
|
+
/** 0–1000, default 100; lower runs first, ties by module name. */
|
|
337
|
+
readonly priority?: number;
|
|
338
|
+
handle(ctx: Ctx, args: never): HookFillerResult | Promise<HookFillerResult>;
|
|
339
|
+
}
|
|
340
|
+
/** The event as a handler receives it (section 15.4). */
|
|
341
|
+
export interface EventInfo {
|
|
342
|
+
readonly id: string;
|
|
343
|
+
readonly name: string;
|
|
344
|
+
readonly attempt: number;
|
|
345
|
+
}
|
|
346
|
+
export interface DeliveredEvent<P = unknown> extends EventInfo {
|
|
347
|
+
readonly payload: P;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* The ctx of an event handler: the module's ctx without the request. A
|
|
351
|
+
* durable delivery's `db` is its own transaction; an in-memory handler's runs
|
|
352
|
+
* in the emitter's current transaction.
|
|
353
|
+
*/
|
|
354
|
+
export interface EventContext extends Omit<Ctx, "req" | "url" | "params" | "headers" | "db"> {
|
|
355
|
+
readonly db: DbStatements;
|
|
356
|
+
readonly event: EventInfo;
|
|
357
|
+
}
|
|
358
|
+
export type EventHandler = (ctx: EventContext, e: DeliveredEvent<never>) => void | Promise<void>;
|
|
359
|
+
/** What a module's `hooks.ts` exports. */
|
|
360
|
+
export interface HooksModule {
|
|
361
|
+
hooks?: Readonly<Record<string, HookFiller>>;
|
|
362
|
+
on?: Readonly<Record<string, EventHandler>>;
|
|
363
|
+
}
|
|
364
|
+
export type Handler = (ctx: Ctx) => Response | Promise<Response>;
|
|
365
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
|
|
366
|
+
/** Shape of a `functions/**\/*.ts` module: one optional export per method. */
|
|
367
|
+
export type FunctionModule = Partial<Record<HttpMethod, Handler>>;
|
|
368
|
+
/**
|
|
369
|
+
* Optional sugar: types a set of handlers in one place and returns it unchanged.
|
|
370
|
+
*
|
|
371
|
+
* ```ts
|
|
372
|
+
* export const { GET, POST } = defineHandlers({ GET: () => Response.json([]), POST: async ({ req }) => ... });
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
export declare function defineHandlers<const M extends FunctionModule>(handlers: M): M;
|
|
376
|
+
/** A file in the platform store under the caller's prefix. */
|
|
377
|
+
export interface StoredFile {
|
|
378
|
+
readonly key: string;
|
|
379
|
+
readonly bytes: number;
|
|
380
|
+
readonly content_type: string;
|
|
381
|
+
readonly sha256: string;
|
|
382
|
+
readonly width: number | null;
|
|
383
|
+
readonly height: number | null;
|
|
384
|
+
readonly public: boolean;
|
|
385
|
+
readonly created_at: number;
|
|
386
|
+
}
|
|
387
|
+
export interface StoragePage {
|
|
388
|
+
readonly items: readonly StoredFile[];
|
|
389
|
+
readonly next_cursor: string | null;
|
|
390
|
+
}
|
|
391
|
+
export interface StoragePutOptions {
|
|
392
|
+
readonly contentType: string;
|
|
393
|
+
readonly public?: boolean;
|
|
394
|
+
/** Set only by the `storage` module's SVG sanitiser. */
|
|
395
|
+
readonly sanitised?: boolean;
|
|
396
|
+
}
|
|
397
|
+
export interface Storage {
|
|
398
|
+
put(key: string, bytes: Uint8Array, options: StoragePutOptions): Promise<StoredFile>;
|
|
399
|
+
get(key: string): Promise<StoredFile>;
|
|
400
|
+
read(key: string): Promise<Uint8Array>;
|
|
401
|
+
delete(key: string): Promise<boolean>;
|
|
402
|
+
/** A signed URL on the app's host, `/_files/<key>?exp=&sig=`, valid for 60 s to 7 days. */
|
|
403
|
+
sign(key: string, ttlSeconds: number): Promise<{
|
|
404
|
+
readonly url: string;
|
|
405
|
+
readonly expires_at: number;
|
|
406
|
+
}>;
|
|
407
|
+
list(prefix: string, options?: {
|
|
408
|
+
readonly limit?: number;
|
|
409
|
+
readonly cursor?: string | null;
|
|
410
|
+
}): Promise<StoragePage>;
|
|
411
|
+
}
|
|
412
|
+
export type WebhookProvider = "stripe" | "checkout" | "telr" | "paytabs" | "ngenius" | "tabby" | "tamara" | "svix" | "sns" | "apple" | "google" | "generic";
|
|
413
|
+
export interface WebhookEvent {
|
|
414
|
+
readonly provider: WebhookProvider;
|
|
415
|
+
readonly event_id: string;
|
|
416
|
+
readonly received_at: number;
|
|
417
|
+
/** The verified payload; for a duplicate the handler does not go on. */
|
|
418
|
+
readonly payload: JsonValue;
|
|
419
|
+
readonly duplicate: boolean;
|
|
420
|
+
}
|
|
421
|
+
export interface Webhook {
|
|
422
|
+
/** Rejects with 401 `RENOX_WEBHOOK_SIGNATURE` (the handler does not go on) unless the signature holds. */
|
|
423
|
+
verify(req: Request, options: {
|
|
424
|
+
readonly provider: WebhookProvider;
|
|
425
|
+
} | {
|
|
426
|
+
readonly secret: string;
|
|
427
|
+
readonly header: string;
|
|
428
|
+
}): Promise<WebhookEvent>;
|
|
429
|
+
}
|
|
430
|
+
/** The ctx of a `functions/webhooks/*.ts` `POST`. */
|
|
431
|
+
export interface WebhookContext extends Ctx {
|
|
432
|
+
readonly webhook: Webhook;
|
|
433
|
+
}
|
|
434
|
+
export type AiContent = {
|
|
435
|
+
readonly type: "text";
|
|
436
|
+
readonly text: string;
|
|
437
|
+
} | {
|
|
438
|
+
readonly type: "tool_use";
|
|
439
|
+
readonly id: string;
|
|
440
|
+
readonly name: string;
|
|
441
|
+
readonly input: JsonValue;
|
|
442
|
+
} | {
|
|
443
|
+
readonly type: "tool_result";
|
|
444
|
+
readonly tool_use_id: string;
|
|
445
|
+
readonly content: string;
|
|
446
|
+
};
|
|
447
|
+
export interface AiMessage {
|
|
448
|
+
readonly role: "user" | "assistant";
|
|
449
|
+
readonly content: string | readonly AiContent[];
|
|
450
|
+
}
|
|
451
|
+
export interface AiTool {
|
|
452
|
+
readonly name: string;
|
|
453
|
+
readonly description: string;
|
|
454
|
+
readonly input_schema: JsonValue;
|
|
455
|
+
/** A local tool: run in the isolate with the caller's authorization. */
|
|
456
|
+
readonly run?: (input: JsonValue) => Promise<JsonValue> | JsonValue;
|
|
457
|
+
}
|
|
458
|
+
export interface AiRequest {
|
|
459
|
+
readonly model?: string;
|
|
460
|
+
readonly system?: string;
|
|
461
|
+
readonly messages: readonly AiMessage[];
|
|
462
|
+
readonly tools?: readonly AiTool[];
|
|
463
|
+
readonly maxTokens?: number;
|
|
464
|
+
}
|
|
465
|
+
export interface AiResult {
|
|
466
|
+
readonly id: string;
|
|
467
|
+
readonly model: string;
|
|
468
|
+
readonly content: readonly AiContent[];
|
|
469
|
+
readonly stop_reason: string;
|
|
470
|
+
readonly usage: {
|
|
471
|
+
readonly input_tokens: number;
|
|
472
|
+
readonly output_tokens: number;
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
export interface Ai {
|
|
476
|
+
chat(request: AiRequest): Promise<AiResult>;
|
|
477
|
+
}
|
|
478
|
+
export interface PdfOptions {
|
|
479
|
+
readonly page?: "A4" | "Letter" | "A5";
|
|
480
|
+
readonly orientation?: "portrait" | "landscape";
|
|
481
|
+
readonly marginMm?: number;
|
|
482
|
+
}
|
|
483
|
+
export interface Pdf {
|
|
484
|
+
/** Renders `template` (a page or partial of the caller; for a module, relative to its own tree, 1.8.0) with `data` and converts it to PDF in Rust. */
|
|
485
|
+
render(template: string, data: Readonly<Record<string, unknown>>, options?: PdfOptions): Promise<Uint8Array>;
|
|
486
|
+
}
|
package/dist/ctx.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The request context and handler shape (docs/contracts/ops.md sections 2-6,
|
|
2
|
+
// 8, 15 to 20). Types identical to docs/contracts/ctx.d.ts; tests/contract.test-d.ts
|
|
3
|
+
// enforces it.
|
|
4
|
+
/**
|
|
5
|
+
* Optional sugar: types a set of handlers in one place and returns it unchanged.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* export const { GET, POST } = defineHandlers({ GET: () => Response.json([]), POST: async ({ req }) => ... });
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export function defineHandlers(handlers) {
|
|
12
|
+
return handlers;
|
|
13
|
+
}
|
package/dist/db.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Values accepted as `?` parameters. `boolean` binds as 1/0. */
|
|
2
|
+
export type Param = null | number | bigint | string | Uint8Array | boolean;
|
|
3
|
+
/** Values returned in rows. INTEGER beyond ±(2^53 − 1) comes back as `bigint`. */
|
|
4
|
+
export type Value = null | number | bigint | string | Uint8Array;
|
|
5
|
+
/** One result row, keyed by column name, in column order. */
|
|
6
|
+
export type Row = Record<string, Value>;
|
|
7
|
+
export interface ExecuteResult {
|
|
8
|
+
changes: number;
|
|
9
|
+
lastInsertRowid: number | bigint;
|
|
10
|
+
}
|
|
11
|
+
/** Statements available both on `ctx.db` and inside `db.transaction`. */
|
|
12
|
+
export interface DbStatements {
|
|
13
|
+
query<T extends Row = Row>(sql: string, params?: readonly Param[]): Promise<T[]>;
|
|
14
|
+
queryOne<T extends Row = Row>(sql: string, params?: readonly Param[]): Promise<T | null>;
|
|
15
|
+
execute(sql: string, params?: readonly Param[]): Promise<ExecuteResult>;
|
|
16
|
+
}
|
|
17
|
+
/** The scope of `db.scoped()` (ops.md section 17): the values the owner predicates of owned tables are bound to. */
|
|
18
|
+
export interface Scope {
|
|
19
|
+
readonly user_id?: string;
|
|
20
|
+
readonly org_id?: string;
|
|
21
|
+
readonly session_id?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface Db extends DbStatements {
|
|
24
|
+
/**
|
|
25
|
+
* Runs `fn` in an IMMEDIATE transaction: commit when it resolves, rollback
|
|
26
|
+
* when it throws. Rust also rolls back if the request ends first.
|
|
27
|
+
*/
|
|
28
|
+
transaction<T>(fn: (tx: DbStatements) => Promise<T>): Promise<T>;
|
|
29
|
+
/**
|
|
30
|
+
* A `Db` whose statements carry `scope` (ops.md section 17, "db.scoped()"):
|
|
31
|
+
* owned tables get their owner predicate, an INSERT must set the owner
|
|
32
|
+
* column to the scope's value, and a statement the rewriter cannot prove is
|
|
33
|
+
* refused with `RENOX_DB_SCOPE`.
|
|
34
|
+
*/
|
|
35
|
+
scoped(scope: Scope): Db;
|
|
36
|
+
}
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy";
|
|
2
|
+
import type { Db, DbStatements } from "./db.ts";
|
|
3
|
+
/** A Drizzle database over `ctx.db` (or a transaction's `tx`). */
|
|
4
|
+
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(ctx: {
|
|
5
|
+
readonly db: Db | DbStatements;
|
|
6
|
+
}, config?: {
|
|
7
|
+
schema?: TSchema;
|
|
8
|
+
}): SqliteRemoteDatabase<TSchema>;
|
package/dist/drizzle.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// `@renoxium/sdk/drizzle`: Drizzle over the `db` op (docs/06-data.md, "API layers";
|
|
2
|
+
// #237), on drizzle's SQLite proxy driver. Every statement goes to
|
|
3
|
+
// `ctx.db.query` or `execute` with its `?` parameters in order, so the
|
|
4
|
+
// platform's limits and the module authorizer apply exactly as to raw SQL.
|
|
5
|
+
//
|
|
6
|
+
// import { drizzle } from "@renoxium/sdk/drizzle";
|
|
7
|
+
// const db = drizzle(ctx, { schema });
|
|
8
|
+
// await db.select().from(products).where(eq(products.id, id));
|
|
9
|
+
//
|
|
10
|
+
// Transactions: run the builder on the transaction, not with drizzle's own
|
|
11
|
+
// `db.transaction()` (that one sends BEGIN/COMMIT as SQL, which the platform
|
|
12
|
+
// refuses): `await ctx.db.transaction(async (tx) => drizzle({ db: tx }).insert(...))`.
|
|
13
|
+
//
|
|
14
|
+
// Errors: Drizzle wraps a failed statement in its `DrizzleQueryError`; the
|
|
15
|
+
// platform's `RenoxError` (RENOX_DB_AUTH, ...) is its `cause`, unchanged.
|
|
16
|
+
//
|
|
17
|
+
// `drizzle-orm` is an optional peer dependency: an app that never imports
|
|
18
|
+
// this subpath does not need it.
|
|
19
|
+
import { drizzle as proxy } from "drizzle-orm/sqlite-proxy";
|
|
20
|
+
/** The proxy callback: drizzle wants rows as arrays of values, in column order. */
|
|
21
|
+
function callback(db) {
|
|
22
|
+
return async (sql, params, method) => {
|
|
23
|
+
if (/^\s*(begin|commit|rollback|savepoint|release)\b/iu.test(sql)) {
|
|
24
|
+
throw new Error("@renoxium/sdk/drizzle: transactions go through ctx.db.transaction(async (tx) => drizzle({ db: tx }) ...)");
|
|
25
|
+
}
|
|
26
|
+
const ps = params;
|
|
27
|
+
if (method === "run") {
|
|
28
|
+
await db.execute(sql, ps);
|
|
29
|
+
return { rows: [] };
|
|
30
|
+
}
|
|
31
|
+
const values = (r) => Object.values(r);
|
|
32
|
+
if (method === "get") {
|
|
33
|
+
const row = await db.queryOne(sql, ps);
|
|
34
|
+
return { rows: row === null ? [] : values(row) };
|
|
35
|
+
}
|
|
36
|
+
return { rows: (await db.query(sql, ps)).map(values) };
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** A Drizzle database over `ctx.db` (or a transaction's `tx`). */
|
|
40
|
+
export function drizzle(ctx, config) {
|
|
41
|
+
return proxy(callback(ctx.db), config);
|
|
42
|
+
}
|