@shaferllc/keel 0.59.0 → 0.66.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/core/application.js +12 -0
- package/dist/core/auth.d.ts +47 -0
- package/dist/core/auth.js +77 -0
- package/dist/core/authorization.d.ts +9 -1
- package/dist/core/authorization.js +22 -2
- package/dist/core/container.d.ts +20 -0
- package/dist/core/container.js +52 -0
- package/dist/core/cors.d.ts +29 -0
- package/dist/core/cors.js +72 -0
- package/dist/core/crypto.d.ts +40 -4
- package/dist/core/crypto.js +66 -6
- package/dist/core/csrf.d.ts +25 -0
- package/dist/core/csrf.js +78 -0
- package/dist/core/database.d.ts +49 -4
- package/dist/core/database.js +89 -21
- package/dist/core/helpers.d.ts +6 -0
- package/dist/core/helpers.js +12 -0
- package/dist/core/index.d.ts +18 -8
- package/dist/core/index.js +9 -4
- package/dist/core/model.d.ts +2 -0
- package/dist/core/model.js +16 -14
- package/dist/core/provider.d.ts +7 -0
- package/dist/core/provider.js +7 -0
- package/dist/core/rate-limit.js +3 -0
- package/dist/core/relations.js +13 -13
- package/dist/core/request.d.ts +26 -0
- package/dist/core/request.js +77 -0
- package/dist/core/shield.d.ts +39 -0
- package/dist/core/shield.js +60 -0
- package/dist/core/social.d.ts +173 -0
- package/dist/core/social.js +337 -0
- package/dist/core/tokens.d.ts +74 -0
- package/dist/core/tokens.js +155 -0
- package/dist/db/d1.d.ts +32 -0
- package/dist/db/d1.js +26 -0
- package/dist/db/libsql.d.ts +29 -0
- package/dist/db/libsql.js +32 -0
- package/dist/db/pg.d.ts +29 -0
- package/dist/db/pg.js +33 -0
- package/package.json +13 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSRF protection for server-rendered apps. A synchronizer token lives in the
|
|
3
|
+
* session; state-changing requests (`POST`/`PUT`/`PATCH`/`DELETE`) must echo it
|
|
4
|
+
* back, or they're rejected with `419 Page Expired`. Needs `sessionMiddleware()`.
|
|
5
|
+
*
|
|
6
|
+
* this.use(sessionMiddleware());
|
|
7
|
+
* this.use(csrf());
|
|
8
|
+
*
|
|
9
|
+
* Put the token in forms with `csrfField()`; SPAs get it for free — `csrf()` also
|
|
10
|
+
* writes an `XSRF-TOKEN` cookie that axios/fetch libraries send back as the
|
|
11
|
+
* `X-XSRF-TOKEN` header automatically. The token is also read from a `_token` or
|
|
12
|
+
* `_csrf` field, or the `X-CSRF-Token` header.
|
|
13
|
+
*/
|
|
14
|
+
import { setCookie } from "hono/cookie";
|
|
15
|
+
import { HttpException } from "./exceptions.js";
|
|
16
|
+
import { session } from "./session.js";
|
|
17
|
+
import { request } from "./request.js";
|
|
18
|
+
const KEY = "_csrf";
|
|
19
|
+
const UNSAFE = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
20
|
+
function randomToken() {
|
|
21
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
22
|
+
let s = "";
|
|
23
|
+
for (const b of bytes)
|
|
24
|
+
s += String.fromCharCode(b);
|
|
25
|
+
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
26
|
+
}
|
|
27
|
+
function safeEqual(a, b) {
|
|
28
|
+
if (a.length !== b.length)
|
|
29
|
+
return false;
|
|
30
|
+
let result = 0;
|
|
31
|
+
for (let i = 0; i < a.length; i++)
|
|
32
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
33
|
+
return result === 0;
|
|
34
|
+
}
|
|
35
|
+
/** The current request's CSRF token, minting and storing one if absent. */
|
|
36
|
+
export function csrfToken() {
|
|
37
|
+
const sess = session();
|
|
38
|
+
let token = sess.get(KEY, undefined);
|
|
39
|
+
if (!token) {
|
|
40
|
+
token = randomToken();
|
|
41
|
+
sess.put(KEY, token);
|
|
42
|
+
}
|
|
43
|
+
return token;
|
|
44
|
+
}
|
|
45
|
+
/** A hidden form input carrying the CSRF token — drop it into any `<form method="POST">`. */
|
|
46
|
+
export function csrfField() {
|
|
47
|
+
return `<input type="hidden" name="_token" value="${csrfToken()}">`;
|
|
48
|
+
}
|
|
49
|
+
async function submitted(c) {
|
|
50
|
+
const header = c.req.header("x-csrf-token") ?? c.req.header("x-xsrf-token");
|
|
51
|
+
if (header)
|
|
52
|
+
return header;
|
|
53
|
+
const all = await request.all(); // shares keel's body cache — safe to read
|
|
54
|
+
return (all._token ?? all._csrf);
|
|
55
|
+
}
|
|
56
|
+
function isExcepted(path, except) {
|
|
57
|
+
return except.some((rule) => typeof rule === "string"
|
|
58
|
+
? rule.endsWith("*")
|
|
59
|
+
? path.startsWith(rule.slice(0, -1))
|
|
60
|
+
: path === rule
|
|
61
|
+
: rule.test(path));
|
|
62
|
+
}
|
|
63
|
+
export function csrf(options = {}) {
|
|
64
|
+
const except = options.except ?? [];
|
|
65
|
+
return async (c, next) => {
|
|
66
|
+
const token = csrfToken(); // ensures a token exists in the session
|
|
67
|
+
if (options.cookie !== false) {
|
|
68
|
+
setCookie(c, "XSRF-TOKEN", token, { path: "/", sameSite: "Lax" }); // readable by JS on purpose
|
|
69
|
+
}
|
|
70
|
+
if (UNSAFE.has(c.req.method.toUpperCase()) && !isExcepted(c.req.path, except)) {
|
|
71
|
+
const provided = await submitted(c);
|
|
72
|
+
if (!provided || !safeEqual(provided, token)) {
|
|
73
|
+
throw new HttpException(419, "CSRF token mismatch");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
await next();
|
|
77
|
+
};
|
|
78
|
+
}
|
package/dist/core/database.d.ts
CHANGED
|
@@ -31,16 +31,40 @@ export interface Connection {
|
|
|
31
31
|
}
|
|
32
32
|
export type Dialect = "sqlite" | "mysql" | "postgres";
|
|
33
33
|
export type Operator = "=" | "!=" | "<" | "<=" | ">" | ">=" | "like";
|
|
34
|
-
/**
|
|
34
|
+
/** A registered connection — the driver bridge plus its SQL dialect. */
|
|
35
|
+
interface Source {
|
|
36
|
+
conn: Connection;
|
|
37
|
+
dialect: Dialect;
|
|
38
|
+
}
|
|
39
|
+
/** Register the default connection (and dialect) used by `db()`. */
|
|
35
40
|
export declare function setConnection(conn: Connection, driverDialect?: Dialect): void;
|
|
41
|
+
/**
|
|
42
|
+
* Register a *named* connection alongside any others — the way to use more than
|
|
43
|
+
* one database. Point a query or model at it by name; nothing else changes.
|
|
44
|
+
*
|
|
45
|
+
* addConnection("reporting", pgConn, "postgres");
|
|
46
|
+
* await db("events", "reporting").where("kind", "signup").count();
|
|
47
|
+
*/
|
|
48
|
+
export declare function addConnection(name: string, conn: Connection, driverDialect?: Dialect): void;
|
|
49
|
+
/** Choose which registered connection `db()` and models use when none is named. */
|
|
50
|
+
export declare function setDefaultConnection(name: string): void;
|
|
51
|
+
/** The names of every registered connection. */
|
|
52
|
+
export declare function connectionNames(): string[];
|
|
53
|
+
/** Unregister every connection — a test helper for a clean slate. */
|
|
54
|
+
export declare function clearConnections(): void;
|
|
36
55
|
export declare class QueryBuilder<T extends Row = Row> {
|
|
37
56
|
private table;
|
|
57
|
+
private getSource;
|
|
38
58
|
private wheres;
|
|
39
59
|
private orders;
|
|
40
60
|
private columns;
|
|
41
61
|
private _limit?;
|
|
42
62
|
private _offset?;
|
|
43
|
-
constructor(table: string);
|
|
63
|
+
constructor(table: string, getSource: () => Source);
|
|
64
|
+
/** Run a row-returning query on this builder's connection, dialect-adjusted. */
|
|
65
|
+
private runSelect;
|
|
66
|
+
/** Run a write on this builder's connection, dialect-adjusted. */
|
|
67
|
+
private runWrite;
|
|
44
68
|
select(...columns: string[]): this;
|
|
45
69
|
where(column: string, value: unknown): this;
|
|
46
70
|
where(column: string, operator: Operator, value: unknown): this;
|
|
@@ -79,5 +103,26 @@ export declare class QueryBuilder<T extends Row = Row> {
|
|
|
79
103
|
update(data: Row): Promise<WriteResult>;
|
|
80
104
|
delete(): Promise<WriteResult>;
|
|
81
105
|
}
|
|
82
|
-
/** Start a query against a table. */
|
|
83
|
-
export declare function db<T extends Row = Row>(table: string): QueryBuilder<T>;
|
|
106
|
+
/** Start a query against a table, on the default connection or a named one. */
|
|
107
|
+
export declare function db<T extends Row = Row>(table: string, connectionName?: string): QueryBuilder<T>;
|
|
108
|
+
/** A handle to one registered connection — query it, or run raw SQL on it. */
|
|
109
|
+
export interface ConnectionHandle {
|
|
110
|
+
/** Start a query against a table on this connection. */
|
|
111
|
+
table<T extends Row = Row>(table: string): QueryBuilder<T>;
|
|
112
|
+
/** Run a raw row-returning query (`?` placeholders, dialect-adjusted). */
|
|
113
|
+
select(sql: string, bindings?: unknown[]): Promise<Row[]>;
|
|
114
|
+
/** Run a raw write (`?` placeholders, dialect-adjusted). */
|
|
115
|
+
write(sql: string, bindings?: unknown[]): Promise<WriteResult>;
|
|
116
|
+
/** This connection's SQL dialect. */
|
|
117
|
+
readonly dialect: Dialect;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get a handle to a named connection (or the default). Use it to run several
|
|
121
|
+
* queries against one database without repeating the name, or to reach the raw
|
|
122
|
+
* `select`/`write` bridge.
|
|
123
|
+
*
|
|
124
|
+
* const reporting = connection("reporting");
|
|
125
|
+
* await reporting.table("events").where("kind", "signup").count();
|
|
126
|
+
*/
|
|
127
|
+
export declare function connection(name?: string): ConnectionHandle;
|
|
128
|
+
export {};
|
package/dist/core/database.js
CHANGED
|
@@ -9,20 +9,56 @@
|
|
|
9
9
|
* const user = await db("users").where("id", 1).first();
|
|
10
10
|
* await db("users").insert({ email });
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The connection registry. An app can talk to several databases at once —
|
|
14
|
+
* register each by name, then route a query with `db(table, name)`, a whole
|
|
15
|
+
* model with `static connection`, or a handle from `connection(name)`. The
|
|
16
|
+
* unnamed default lives under `"default"` so `db(table)` and `setConnection()`
|
|
17
|
+
* keep working unchanged.
|
|
18
|
+
*/
|
|
19
|
+
const registry = new Map();
|
|
20
|
+
let defaultConnection = "default";
|
|
21
|
+
/** Register the default connection (and dialect) used by `db()`. */
|
|
15
22
|
export function setConnection(conn, driverDialect = "sqlite") {
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
registry.set("default", { conn, dialect: driverDialect });
|
|
24
|
+
defaultConnection = "default";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Register a *named* connection alongside any others — the way to use more than
|
|
28
|
+
* one database. Point a query or model at it by name; nothing else changes.
|
|
29
|
+
*
|
|
30
|
+
* addConnection("reporting", pgConn, "postgres");
|
|
31
|
+
* await db("events", "reporting").where("kind", "signup").count();
|
|
32
|
+
*/
|
|
33
|
+
export function addConnection(name, conn, driverDialect = "sqlite") {
|
|
34
|
+
registry.set(name, { conn, dialect: driverDialect });
|
|
35
|
+
}
|
|
36
|
+
/** Choose which registered connection `db()` and models use when none is named. */
|
|
37
|
+
export function setDefaultConnection(name) {
|
|
38
|
+
if (!registry.has(name))
|
|
39
|
+
throw new Error(`No database connection "${name}" to make default.`);
|
|
40
|
+
defaultConnection = name;
|
|
41
|
+
}
|
|
42
|
+
/** The names of every registered connection. */
|
|
43
|
+
export function connectionNames() {
|
|
44
|
+
return [...registry.keys()];
|
|
18
45
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
46
|
+
/** Unregister every connection — a test helper for a clean slate. */
|
|
47
|
+
export function clearConnections() {
|
|
48
|
+
registry.clear();
|
|
49
|
+
defaultConnection = "default";
|
|
23
50
|
}
|
|
24
|
-
/**
|
|
25
|
-
function
|
|
51
|
+
/** Resolve a connection by name (or the default); throws if it isn't registered. */
|
|
52
|
+
function resolve(name) {
|
|
53
|
+
const source = registry.get(name ?? defaultConnection);
|
|
54
|
+
if (!source) {
|
|
55
|
+
throw new Error(`No database connection${name ? ` "${name}"` : ""}. ` +
|
|
56
|
+
`Call setConnection(conn, dialect) or addConnection(name, conn, dialect).`);
|
|
57
|
+
}
|
|
58
|
+
return source;
|
|
59
|
+
}
|
|
60
|
+
/** Render `?` placeholders for a dialect (Postgres uses $1, $2, …). */
|
|
61
|
+
function placeholders(sql, dialect) {
|
|
26
62
|
if (dialect !== "postgres")
|
|
27
63
|
return sql;
|
|
28
64
|
let i = 0;
|
|
@@ -30,13 +66,28 @@ function placeholders(sql) {
|
|
|
30
66
|
}
|
|
31
67
|
export class QueryBuilder {
|
|
32
68
|
table;
|
|
69
|
+
getSource;
|
|
33
70
|
wheres = [];
|
|
34
71
|
orders = [];
|
|
35
72
|
columns = "*";
|
|
36
73
|
_limit;
|
|
37
74
|
_offset;
|
|
38
|
-
|
|
75
|
+
// The connection is resolved lazily, when a query actually runs — so building
|
|
76
|
+
// a query never throws, and an unregistered connection surfaces as a rejected
|
|
77
|
+
// read/write rather than a synchronous error at construction.
|
|
78
|
+
constructor(table, getSource) {
|
|
39
79
|
this.table = table;
|
|
80
|
+
this.getSource = getSource;
|
|
81
|
+
}
|
|
82
|
+
/** Run a row-returning query on this builder's connection, dialect-adjusted. */
|
|
83
|
+
runSelect(sql, bindings) {
|
|
84
|
+
const source = this.getSource();
|
|
85
|
+
return source.conn.select(placeholders(sql, source.dialect), bindings);
|
|
86
|
+
}
|
|
87
|
+
/** Run a write on this builder's connection, dialect-adjusted. */
|
|
88
|
+
runWrite(sql, bindings) {
|
|
89
|
+
const source = this.getSource();
|
|
90
|
+
return source.conn.write(placeholders(sql, source.dialect), bindings);
|
|
40
91
|
}
|
|
41
92
|
select(...columns) {
|
|
42
93
|
this.columns = columns.length ? columns.join(", ") : "*";
|
|
@@ -114,7 +165,7 @@ export class QueryBuilder {
|
|
|
114
165
|
sql += ` LIMIT ${this._limit}`;
|
|
115
166
|
if (this._offset != null)
|
|
116
167
|
sql += ` OFFSET ${this._offset}`;
|
|
117
|
-
return (await
|
|
168
|
+
return (await this.runSelect(sql, where.bindings));
|
|
118
169
|
}
|
|
119
170
|
async first() {
|
|
120
171
|
this._limit = 1;
|
|
@@ -123,7 +174,7 @@ export class QueryBuilder {
|
|
|
123
174
|
}
|
|
124
175
|
async count() {
|
|
125
176
|
const where = this.whereClause();
|
|
126
|
-
const rows = (await
|
|
177
|
+
const rows = (await this.runSelect(`SELECT COUNT(*) AS count FROM ${this.table}${where.sql}`, where.bindings));
|
|
127
178
|
return Number(rows[0]?.count ?? 0);
|
|
128
179
|
}
|
|
129
180
|
async exists() {
|
|
@@ -131,7 +182,7 @@ export class QueryBuilder {
|
|
|
131
182
|
}
|
|
132
183
|
async aggregate(fn, column) {
|
|
133
184
|
const where = this.whereClause();
|
|
134
|
-
const rows = (await
|
|
185
|
+
const rows = (await this.runSelect(`SELECT ${fn}(${column}) AS agg FROM ${this.table}${where.sql}`, where.bindings));
|
|
135
186
|
return Number(rows[0]?.agg ?? 0);
|
|
136
187
|
}
|
|
137
188
|
sum(column) {
|
|
@@ -178,7 +229,7 @@ export class QueryBuilder {
|
|
|
178
229
|
const sql = `INSERT INTO ${this.table} (${keys.join(", ")}) VALUES (${keys
|
|
179
230
|
.map(() => "?")
|
|
180
231
|
.join(", ")})`;
|
|
181
|
-
return
|
|
232
|
+
return this.runWrite(sql, Object.values(data));
|
|
182
233
|
}
|
|
183
234
|
async insertGetId(data) {
|
|
184
235
|
return (await this.insert(data)).insertId;
|
|
@@ -187,17 +238,34 @@ export class QueryBuilder {
|
|
|
187
238
|
const keys = Object.keys(data);
|
|
188
239
|
const set = keys.map((k) => `${k} = ?`).join(", ");
|
|
189
240
|
const where = this.whereClause();
|
|
190
|
-
return
|
|
241
|
+
return this.runWrite(`UPDATE ${this.table} SET ${set}${where.sql}`, [
|
|
191
242
|
...Object.values(data),
|
|
192
243
|
...where.bindings,
|
|
193
244
|
]);
|
|
194
245
|
}
|
|
195
246
|
async delete() {
|
|
196
247
|
const where = this.whereClause();
|
|
197
|
-
return
|
|
248
|
+
return this.runWrite(`DELETE FROM ${this.table}${where.sql}`, where.bindings);
|
|
198
249
|
}
|
|
199
250
|
}
|
|
200
|
-
/** Start a query against a table. */
|
|
201
|
-
export function db(table) {
|
|
202
|
-
return new QueryBuilder(table);
|
|
251
|
+
/** Start a query against a table, on the default connection or a named one. */
|
|
252
|
+
export function db(table, connectionName) {
|
|
253
|
+
return new QueryBuilder(table, () => resolve(connectionName));
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get a handle to a named connection (or the default). Use it to run several
|
|
257
|
+
* queries against one database without repeating the name, or to reach the raw
|
|
258
|
+
* `select`/`write` bridge.
|
|
259
|
+
*
|
|
260
|
+
* const reporting = connection("reporting");
|
|
261
|
+
* await reporting.table("events").where("kind", "signup").count();
|
|
262
|
+
*/
|
|
263
|
+
export function connection(name) {
|
|
264
|
+
const source = resolve(name);
|
|
265
|
+
return {
|
|
266
|
+
table: (t) => new QueryBuilder(t, () => source),
|
|
267
|
+
select: (sql, bindings = []) => source.conn.select(placeholders(sql, source.dialect), bindings),
|
|
268
|
+
write: (sql, bindings = []) => source.conn.write(placeholders(sql, source.dialect), bindings),
|
|
269
|
+
dialect: source.dialect,
|
|
270
|
+
};
|
|
203
271
|
}
|
package/dist/core/helpers.d.ts
CHANGED
|
@@ -38,6 +38,12 @@ export declare function instance<T>(token: Token<T>, value: T): T;
|
|
|
38
38
|
export declare function make<T>(token: Token<T>): T;
|
|
39
39
|
/** Whether a token is bound or has a cached instance. */
|
|
40
40
|
export declare function bound(token: Token): boolean;
|
|
41
|
+
/** Register an alias token that resolves to another token. */
|
|
42
|
+
export declare function alias<T>(aliasToken: Token<T>, target: Token<T>): void;
|
|
43
|
+
/** Temporarily replace a binding with a fake (tests). Undo with `restore()`. */
|
|
44
|
+
export declare function swap<T>(token: Token<T>, factory: Factory<T>): void;
|
|
45
|
+
/** Undo a `swap()` — restore the original binding. No token restores every swap. */
|
|
46
|
+
export declare function restore(token?: Token): void;
|
|
41
47
|
/** The application's event emitter. */
|
|
42
48
|
export declare function events(): Events;
|
|
43
49
|
/** Emit an event, awaiting every listener. */
|
package/dist/core/helpers.js
CHANGED
|
@@ -65,6 +65,18 @@ export function make(token) {
|
|
|
65
65
|
export function bound(token) {
|
|
66
66
|
return app().bound(token);
|
|
67
67
|
}
|
|
68
|
+
/** Register an alias token that resolves to another token. */
|
|
69
|
+
export function alias(aliasToken, target) {
|
|
70
|
+
app().alias(aliasToken, target);
|
|
71
|
+
}
|
|
72
|
+
/** Temporarily replace a binding with a fake (tests). Undo with `restore()`. */
|
|
73
|
+
export function swap(token, factory) {
|
|
74
|
+
app().swap(token, factory);
|
|
75
|
+
}
|
|
76
|
+
/** Undo a `swap()` — restore the original binding. No token restores every swap. */
|
|
77
|
+
export function restore(token) {
|
|
78
|
+
app().restore(token);
|
|
79
|
+
}
|
|
68
80
|
/* ------------------------------- events -------------------------------- */
|
|
69
81
|
/** The application's event emitter. */
|
|
70
82
|
export function events() {
|
package/dist/core/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type { Token, Constructor, Factory } from "./container.js";
|
|
|
4
4
|
export { Application } from "./application.js";
|
|
5
5
|
export type { BootOptions, LifecycleHook, Configurator } from "./application.js";
|
|
6
6
|
export { Config, env } from "./config.js";
|
|
7
|
-
export { app, config, view, bind, singleton, instance, make, bound, events, emit, listen, cache, logger, onReady, onShutdown, terminate, } from "./helpers.js";
|
|
7
|
+
export { app, config, view, bind, singleton, instance, make, bound, alias, swap, restore, events, emit, listen, cache, logger, onReady, onShutdown, terminate, } from "./helpers.js";
|
|
8
8
|
export { Logger } from "./logger.js";
|
|
9
9
|
export type { LogLevel, LoggerOptions } from "./logger.js";
|
|
10
10
|
export { requestLogger, requestLog } from "./request-logger.js";
|
|
@@ -19,11 +19,17 @@ export { Storage, MemoryDisk, storage, setDisk } from "./storage.js";
|
|
|
19
19
|
export type { Disk, Contents } from "./storage.js";
|
|
20
20
|
export { dump, dd } from "./debug.js";
|
|
21
21
|
export { hash, encryption, jwt } from "./crypto.js";
|
|
22
|
-
export type { JwtPayload, JwtSignOptions, JwtVerifyOptions } from "./crypto.js";
|
|
22
|
+
export type { JwtPayload, JwtSignOptions, JwtVerifyOptions, EncryptOptions } from "./crypto.js";
|
|
23
23
|
export { rateLimiter } from "./rate-limit.js";
|
|
24
24
|
export type { RateLimiterOptions } from "./rate-limit.js";
|
|
25
|
-
export {
|
|
26
|
-
export type {
|
|
25
|
+
export { cors } from "./cors.js";
|
|
26
|
+
export type { CorsOptions } from "./cors.js";
|
|
27
|
+
export { securityHeaders } from "./shield.js";
|
|
28
|
+
export type { SecurityHeadersOptions, HstsOptions } from "./shield.js";
|
|
29
|
+
export { csrf, csrfToken, csrfField } from "./csrf.js";
|
|
30
|
+
export type { CsrfOptions } from "./csrf.js";
|
|
31
|
+
export { db, connection, setConnection, addConnection, setDefaultConnection, connectionNames, clearConnections, QueryBuilder, } from "./database.js";
|
|
32
|
+
export type { Connection, ConnectionHandle, WriteResult, Row, Dialect, Operator, Paginated } from "./database.js";
|
|
27
33
|
export { Model } from "./model.js";
|
|
28
34
|
export type { CastType, Casts } from "./casts.js";
|
|
29
35
|
export { Relation, HasOne, HasMany, BelongsTo, BelongsToMany } from "./relations.js";
|
|
@@ -63,10 +69,14 @@ export { validate, validateRequest, validated } from "./validation.js";
|
|
|
63
69
|
export type { Schema, RequestSchemas } from "./validation.js";
|
|
64
70
|
export { Session, session, sessionMiddleware } from "./session.js";
|
|
65
71
|
export type { SessionOptions } from "./session.js";
|
|
66
|
-
export { Auth, auth, authGuard, bearerAuth, setUserProvider } from "./auth.js";
|
|
67
|
-
export type { UserProvider } from "./auth.js";
|
|
68
|
-
export {
|
|
69
|
-
export type {
|
|
72
|
+
export { Auth, auth, authGuard, bearerAuth, basicAuth, tokenAuth, token, tokenCan, setUserProvider } from "./auth.js";
|
|
73
|
+
export type { UserProvider, BasicVerifier } from "./auth.js";
|
|
74
|
+
export { createToken, verifyToken, revokeToken, revokeTokens, listTokens, tokenAllows, tokenDenies, setTokensTable, } from "./tokens.js";
|
|
75
|
+
export type { AccessToken, CreateTokenOptions, IssuedToken } from "./tokens.js";
|
|
76
|
+
export { social, github, google, discord, oauthDriver, oauthState, OAuthDriver, OAuthError, twitter, oauth1Driver, oauth1Signature, OAuth1Driver, } from "./social.js";
|
|
77
|
+
export type { SocialUser, OAuthToken, OAuthConfig, ProviderSpec, RedirectOptions, OAuth1Config, OAuth1Token, OAuth1ProviderSpec, } from "./social.js";
|
|
78
|
+
export { define, policy, gateBefore, gateAfter, setUserResolver, clearAuthorization, can, cannot, canFor, authorize, authorizeFor, } from "./authorization.js";
|
|
79
|
+
export type { GateCallback, BeforeCallback, AfterCallback } from "./authorization.js";
|
|
70
80
|
export { Transformer } from "./transformer.js";
|
|
71
81
|
export type { Attributes, DocumentOptions } from "./transformer.js";
|
|
72
82
|
export { Broker, Service, LocalTransporter, ServiceNotFoundError, RequestTimeoutError, broker, setBroker, } from "./broker.js";
|
package/dist/core/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export { Container } from "./container.js";
|
|
3
3
|
export { Application } from "./application.js";
|
|
4
4
|
export { Config, env } from "./config.js";
|
|
5
|
-
export { app, config, view, bind, singleton, instance, make, bound, events, emit, listen, cache, logger, onReady, onShutdown, terminate, } from "./helpers.js";
|
|
5
|
+
export { app, config, view, bind, singleton, instance, make, bound, alias, swap, restore, events, emit, listen, cache, logger, onReady, onShutdown, terminate, } from "./helpers.js";
|
|
6
6
|
export { Logger } from "./logger.js";
|
|
7
7
|
export { requestLogger, requestLog } from "./request-logger.js";
|
|
8
8
|
export { Events } from "./events.js";
|
|
@@ -12,7 +12,10 @@ export { Storage, MemoryDisk, storage, setDisk } from "./storage.js";
|
|
|
12
12
|
export { dump, dd } from "./debug.js";
|
|
13
13
|
export { hash, encryption, jwt } from "./crypto.js";
|
|
14
14
|
export { rateLimiter } from "./rate-limit.js";
|
|
15
|
-
export {
|
|
15
|
+
export { cors } from "./cors.js";
|
|
16
|
+
export { securityHeaders } from "./shield.js";
|
|
17
|
+
export { csrf, csrfToken, csrfField } from "./csrf.js";
|
|
18
|
+
export { db, connection, setConnection, addConnection, setDefaultConnection, connectionNames, clearConnections, QueryBuilder, } from "./database.js";
|
|
16
19
|
export { Model } from "./model.js";
|
|
17
20
|
export { Relation, HasOne, HasMany, BelongsTo, BelongsToMany } from "./relations.js";
|
|
18
21
|
export { Faker, Factory as ModelFactory, factory, Seeder, seed } from "./factory.js";
|
|
@@ -35,8 +38,10 @@ export { TestClient, TestResponse, testClient } from "./testing.js";
|
|
|
35
38
|
export { HttpException, BadRequestException, UnauthorizedException, PaymentRequiredException, ForbiddenException, NotFoundException, MethodNotAllowedException, NotAcceptableException, RequestTimeoutException, ConflictException, LengthRequiredException, ValidationException, TooManyRequestsException, ServerErrorException, NotImplementedException, BadGatewayException, ServiceUnavailableException, createError, STATUS_TEXT, } from "./exceptions.js";
|
|
36
39
|
export { validate, validateRequest, validated } from "./validation.js";
|
|
37
40
|
export { Session, session, sessionMiddleware } from "./session.js";
|
|
38
|
-
export { Auth, auth, authGuard, bearerAuth, setUserProvider } from "./auth.js";
|
|
39
|
-
export {
|
|
41
|
+
export { Auth, auth, authGuard, bearerAuth, basicAuth, tokenAuth, token, tokenCan, setUserProvider } from "./auth.js";
|
|
42
|
+
export { createToken, verifyToken, revokeToken, revokeTokens, listTokens, tokenAllows, tokenDenies, setTokensTable, } from "./tokens.js";
|
|
43
|
+
export { social, github, google, discord, oauthDriver, oauthState, OAuthDriver, OAuthError, twitter, oauth1Driver, oauth1Signature, OAuth1Driver, } from "./social.js";
|
|
44
|
+
export { define, policy, gateBefore, gateAfter, setUserResolver, clearAuthorization, can, cannot, canFor, authorize, authorizeFor, } from "./authorization.js";
|
|
40
45
|
export { Transformer } from "./transformer.js";
|
|
41
46
|
export { Broker, Service, LocalTransporter, ServiceNotFoundError, RequestTimeoutError, broker, setBroker, } from "./broker.js";
|
|
42
47
|
export { Vite, viteTags, viteAsset, viteReactRefresh } from "./vite.js";
|
package/dist/core/model.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ type ModelClass<T extends Model> = (new (attributes?: Row) => T) & typeof Model;
|
|
|
22
22
|
export declare class Model {
|
|
23
23
|
static table: string;
|
|
24
24
|
static primaryKey: string;
|
|
25
|
+
/** Which registered connection this model uses; the default when unset. */
|
|
26
|
+
static connection: string | undefined;
|
|
25
27
|
/** Columns mass-assignable via `create`/`fill` (allowlist). */
|
|
26
28
|
static fillable: string[];
|
|
27
29
|
/** Columns NOT mass-assignable (denylist). Ignored when `fillable` is set. */
|
package/dist/core/model.js
CHANGED
|
@@ -34,6 +34,8 @@ function serialize(value) {
|
|
|
34
34
|
export class Model {
|
|
35
35
|
static table = "";
|
|
36
36
|
static primaryKey = "id";
|
|
37
|
+
/** Which registered connection this model uses; the default when unset. */
|
|
38
|
+
static connection = undefined;
|
|
37
39
|
/** Columns mass-assignable via `create`/`fill` (allowlist). */
|
|
38
40
|
static fillable = [];
|
|
39
41
|
/** Columns NOT mass-assignable (denylist). Ignored when `fillable` is set. */
|
|
@@ -51,7 +53,7 @@ export class Model {
|
|
|
51
53
|
/* ------------------------------ static -------------------------------- */
|
|
52
54
|
/** A raw query builder scoped to this model's table. */
|
|
53
55
|
static query() {
|
|
54
|
-
return db(this.table);
|
|
56
|
+
return db(this.table, this.connection);
|
|
55
57
|
}
|
|
56
58
|
/** Keep only the attributes mass-assignment allows (fillable / guarded). */
|
|
57
59
|
static filterFillable(attributes) {
|
|
@@ -86,11 +88,11 @@ export class Model {
|
|
|
86
88
|
return out;
|
|
87
89
|
}
|
|
88
90
|
static async all() {
|
|
89
|
-
const rows = await db(this.table).get();
|
|
91
|
+
const rows = await db(this.table, this.connection).get();
|
|
90
92
|
return rows.map((row) => new this(row));
|
|
91
93
|
}
|
|
92
94
|
static async find(id) {
|
|
93
|
-
const row = await db(this.table).where(this.primaryKey, id).first();
|
|
95
|
+
const row = await db(this.table, this.connection).where(this.primaryKey, id).first();
|
|
94
96
|
return row ? new this(row) : null;
|
|
95
97
|
}
|
|
96
98
|
static async findOrFail(id) {
|
|
@@ -100,18 +102,18 @@ export class Model {
|
|
|
100
102
|
return model;
|
|
101
103
|
}
|
|
102
104
|
static async first() {
|
|
103
|
-
const row = await db(this.table).first();
|
|
105
|
+
const row = await db(this.table, this.connection).first();
|
|
104
106
|
return row ? new this(row) : null;
|
|
105
107
|
}
|
|
106
108
|
/** Fetch models matching a simple equality condition. */
|
|
107
109
|
static async where(column, value) {
|
|
108
|
-
const rows = await db(this.table).where(column, value).get();
|
|
110
|
+
const rows = await db(this.table, this.connection).where(column, value).get();
|
|
109
111
|
return rows.map((row) => new this(row));
|
|
110
112
|
}
|
|
111
113
|
static async create(attributes) {
|
|
112
114
|
const filtered = this.filterFillable(attributes);
|
|
113
115
|
const write = this.stampTimestamps(this.toDatabase(filtered), true);
|
|
114
|
-
const id = await db(this.table).insertGetId(write);
|
|
116
|
+
const id = await db(this.table, this.connection).insertGetId(write);
|
|
115
117
|
const model = new this(filtered);
|
|
116
118
|
if (this.timestamps) {
|
|
117
119
|
model[this.createdAtColumn] = write[this.createdAtColumn];
|
|
@@ -123,7 +125,7 @@ export class Model {
|
|
|
123
125
|
}
|
|
124
126
|
/** A page of models plus pagination metadata. */
|
|
125
127
|
static async paginate(page = 1, perPage = 15) {
|
|
126
|
-
const result = await db(this.table).paginate(page, perPage);
|
|
128
|
+
const result = await db(this.table, this.connection).paginate(page, perPage);
|
|
127
129
|
return { ...result, data: result.data.map((row) => new this(row)) };
|
|
128
130
|
}
|
|
129
131
|
/** Find the first row matching `match`, or create one from `{ ...match, ...values }`. */
|
|
@@ -145,7 +147,7 @@ export class Model {
|
|
|
145
147
|
}
|
|
146
148
|
/** A query scoped to every column/value in `match`. */
|
|
147
149
|
static matching(match) {
|
|
148
|
-
let q = db(this.table);
|
|
150
|
+
let q = db(this.table, this.connection);
|
|
149
151
|
for (const [column, value] of Object.entries(match))
|
|
150
152
|
q = q.where(column, value);
|
|
151
153
|
return q;
|
|
@@ -209,7 +211,7 @@ export class Model {
|
|
|
209
211
|
/** Insert (no primary key) or update (has one). */
|
|
210
212
|
async save() {
|
|
211
213
|
const ctor = this.ctor();
|
|
212
|
-
const { table, primaryKey } = ctor;
|
|
214
|
+
const { table, primaryKey, connection } = ctor;
|
|
213
215
|
const idValue = this[primaryKey];
|
|
214
216
|
const forInsert = idValue == null;
|
|
215
217
|
const data = ctor.stampTimestamps(ctor.toDatabase({ ...this }), forInsert);
|
|
@@ -221,10 +223,10 @@ export class Model {
|
|
|
221
223
|
}
|
|
222
224
|
delete data[primaryKey];
|
|
223
225
|
if (!forInsert) {
|
|
224
|
-
await db(table).where(primaryKey, idValue).update(data);
|
|
226
|
+
await db(table, connection).where(primaryKey, idValue).update(data);
|
|
225
227
|
}
|
|
226
228
|
else {
|
|
227
|
-
const id = await db(table).insertGetId(data);
|
|
229
|
+
const id = await db(table, connection).insertGetId(data);
|
|
228
230
|
if (id != null)
|
|
229
231
|
this[primaryKey] = id;
|
|
230
232
|
}
|
|
@@ -237,14 +239,14 @@ export class Model {
|
|
|
237
239
|
/** Reload this model's columns from the database. */
|
|
238
240
|
async refresh() {
|
|
239
241
|
const ctor = this.ctor();
|
|
240
|
-
const row = await db(ctor.table).where(ctor.primaryKey, this[ctor.primaryKey]).first();
|
|
242
|
+
const row = await db(ctor.table, ctor.connection).where(ctor.primaryKey, this[ctor.primaryKey]).first();
|
|
241
243
|
if (row)
|
|
242
244
|
Object.assign(this, applyCasts(row, ctor.casts, castGet));
|
|
243
245
|
return this;
|
|
244
246
|
}
|
|
245
247
|
async delete() {
|
|
246
|
-
const { table, primaryKey } = this.ctor();
|
|
247
|
-
await db(table).where(primaryKey, this[primaryKey]).delete();
|
|
248
|
+
const { table, primaryKey, connection } = this.ctor();
|
|
249
|
+
await db(table, connection).where(primaryKey, this[primaryKey]).delete();
|
|
248
250
|
}
|
|
249
251
|
/** Merge mass-assignable attributes into the model (cast, not saved). */
|
|
250
252
|
fill(attributes) {
|
package/dist/core/provider.d.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* here — nothing is guaranteed to be registered yet.
|
|
7
7
|
* boot(): called after every provider has registered. Safe to resolve
|
|
8
8
|
* and wire things together here.
|
|
9
|
+
* ready(): called once the whole app has booted (after every provider's
|
|
10
|
+
* boot() and any onReady hooks). For work that needs a fully-live
|
|
11
|
+
* app — attaching to the running server, warming a cache.
|
|
12
|
+
* shutdown(): called during graceful termination, in reverse registration
|
|
13
|
+
* order (LIFO). Close connections, flush queues, cancel timers.
|
|
9
14
|
*
|
|
10
15
|
* Register with options to make a provider reusable:
|
|
11
16
|
*
|
|
@@ -21,5 +26,7 @@ export declare abstract class ServiceProvider<O = Record<string, unknown>> {
|
|
|
21
26
|
constructor(app: Application, options?: O);
|
|
22
27
|
register(): void | Promise<void>;
|
|
23
28
|
boot(): void | Promise<void>;
|
|
29
|
+
ready(): void | Promise<void>;
|
|
30
|
+
shutdown(): void | Promise<void>;
|
|
24
31
|
}
|
|
25
32
|
export type ProviderClass = new (app: Application, options?: any) => ServiceProvider;
|
package/dist/core/provider.js
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* here — nothing is guaranteed to be registered yet.
|
|
7
7
|
* boot(): called after every provider has registered. Safe to resolve
|
|
8
8
|
* and wire things together here.
|
|
9
|
+
* ready(): called once the whole app has booted (after every provider's
|
|
10
|
+
* boot() and any onReady hooks). For work that needs a fully-live
|
|
11
|
+
* app — attaching to the running server, warming a cache.
|
|
12
|
+
* shutdown(): called during graceful termination, in reverse registration
|
|
13
|
+
* order (LIFO). Close connections, flush queues, cancel timers.
|
|
9
14
|
*
|
|
10
15
|
* Register with options to make a provider reusable:
|
|
11
16
|
*
|
|
@@ -23,4 +28,6 @@ export class ServiceProvider {
|
|
|
23
28
|
}
|
|
24
29
|
register() { }
|
|
25
30
|
boot() { }
|
|
31
|
+
ready() { }
|
|
32
|
+
shutdown() { }
|
|
26
33
|
}
|
package/dist/core/rate-limit.js
CHANGED
|
@@ -35,10 +35,12 @@ export function rateLimiter(options = {}) {
|
|
|
35
35
|
}
|
|
36
36
|
bucket.count++;
|
|
37
37
|
const remaining = Math.max(0, max - bucket.count);
|
|
38
|
+
const resetSeconds = Math.ceil(bucket.reset / 1000);
|
|
38
39
|
if (bucket.count > max) {
|
|
39
40
|
return c.json({ error: options.message ?? "Too Many Requests", status: 429 }, 429, {
|
|
40
41
|
"X-RateLimit-Limit": String(max),
|
|
41
42
|
"X-RateLimit-Remaining": "0",
|
|
43
|
+
"X-RateLimit-Reset": String(resetSeconds),
|
|
42
44
|
"Retry-After": String(Math.ceil((bucket.reset - now) / 1000)),
|
|
43
45
|
});
|
|
44
46
|
}
|
|
@@ -46,5 +48,6 @@ export function rateLimiter(options = {}) {
|
|
|
46
48
|
// Set headers on the final response (survives any handler type).
|
|
47
49
|
c.header("X-RateLimit-Limit", String(max));
|
|
48
50
|
c.header("X-RateLimit-Remaining", String(remaining));
|
|
51
|
+
c.header("X-RateLimit-Reset", String(resetSeconds));
|
|
49
52
|
};
|
|
50
53
|
}
|