@protobase/server 0.37.1 → 0.38.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/assistant/query-runner/query-runner.d.ts +21 -0
- package/dist/assistant/query-runner/query-runner.js +79 -0
- package/dist/assistant/query-runner/runner-plan.js +30 -0
- package/dist/assistant/query-runner/runner-sql.js +68 -0
- package/dist/assistant/query-tools.d.ts +2 -1
- package/dist/assistant/query-tools.js +3 -2
- package/dist/assistant/run-query.d.ts +3 -1
- package/dist/assistant/run-query.js +21 -7
- package/dist/better-auth/auth-schema.js +1 -1
- package/dist/better-auth/auth-tables.d.ts +8 -0
- package/dist/better-auth/auth-tables.js +11 -0
- package/dist/better-auth/session-token.js +8 -3
- package/dist/create-admin.d.ts +3 -2
- package/dist/functions/define-function.d.ts +5 -3
- package/dist/functions/function-socket.d.ts +6 -0
- package/dist/functions/function-socket.js +6 -2
- package/dist/functions/routes.js +16 -9
- package/dist/functions/socket-recheck.js +19 -7
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/package.json +4 -4
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ResourceModel } from "@protobase/schema";
|
|
2
|
+
import { Db } from "@protobase/query";
|
|
3
|
+
//#region src/assistant/query-runner/query-runner.d.ts
|
|
4
|
+
/** A table as config names it: the search path decides when it names no schema. */
|
|
5
|
+
export type TableName = {
|
|
6
|
+
schema?: string;
|
|
7
|
+
name: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The SQL that makes the role the assistant's read-only queries run as, and its runner, and grants it exactly the
|
|
11
|
+
* resources' tables, never an auth table; `undefined` when the database is current.
|
|
12
|
+
*/
|
|
13
|
+
export declare const queryRunnerMigration: (db: Db, models: readonly ResourceModel[], authTables: readonly TableName[]) => Promise<string | undefined>;
|
|
14
|
+
/**
|
|
15
|
+
* Refuses to go on while the runner of the assistant's read-only queries is missing or out of date, or runs as a role
|
|
16
|
+
* that is not weaker than the app's: the app's role itself or one it is a member of, a superuser, a login role, or one
|
|
17
|
+
* that can read an auth table, or while the role may call a security-definer function that runs as a role able to
|
|
18
|
+
* read one. It checks the owner's privileges, not its name, so a database cloned under another name still passes. A resource added since the last migration is not checked: Postgres refuses to read its table until then.
|
|
19
|
+
*/
|
|
20
|
+
export declare const checkQueryRunner: (db: Db, authTables: readonly TableName[]) => Promise<void>;
|
|
21
|
+
//#endregion
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { modelVariables } from "../model-settings.js";
|
|
2
|
+
import { migrationCommand } from "../../jobs/schema.js";
|
|
3
|
+
import { queryRoleSql, queryRunnerSignature, queryRunnerSql, runnerBody } from "./runner-sql.js";
|
|
4
|
+
import { plannedGrants } from "./runner-plan.js";
|
|
5
|
+
import { sql } from "kysely";
|
|
6
|
+
//#region src/assistant/query-runner/query-runner.ts
|
|
7
|
+
const quoted = (name) => `"${name.replaceAll("\"", "\"\"")}"`;
|
|
8
|
+
const regclassText = ({ schema, name }) => schema ? `${quoted(schema)}.${quoted(name)}` : quoted(name);
|
|
9
|
+
const roleSql = sql.raw(queryRoleSql);
|
|
10
|
+
const rowsOf = async (db, query) => (await query.execute(db)).rows;
|
|
11
|
+
const resolved = (db, tables) => rowsOf(db, sql`select n.nspname as schema, c.relname as name from unnest(${tables.map(regclassText)}::text[]) with ordinality as t(name, n)
|
|
12
|
+
join pg_class c on c.oid = to_regclass(t.name) join pg_namespace n on n.oid = c.relnamespace order by t.n`);
|
|
13
|
+
const readRunner = async (db) => {
|
|
14
|
+
const [runner] = await rowsOf(db, sql`select p.prosrc as source, p.prosecdef as definer, pg_get_userbyid(p.proowner) as owner,
|
|
15
|
+
(p.proacl is null or exists (select from aclexplode(p.proacl) a where a.grantee = 0 and a.privilege_type = 'EXECUTE')) as executable
|
|
16
|
+
from pg_proc p where p.oid = to_regprocedure(${queryRunnerSignature})`);
|
|
17
|
+
return runner;
|
|
18
|
+
};
|
|
19
|
+
/** What the database holds of the query role and its runner, and the tables the role should read and must not. Only reads the catalogue. */
|
|
20
|
+
const readQueryRunner = async (db, models, authTables) => {
|
|
21
|
+
const [role] = await rowsOf(db, sql`select ${roleSql} as name, exists (select from pg_roles where rolname = ${roleSql}) as exists`);
|
|
22
|
+
const tables = await rowsOf(db, sql`select n.nspname as schema, c.relname as name from pg_class c join pg_namespace n on n.oid = c.relnamespace, aclexplode(c.relacl) a
|
|
23
|
+
where a.grantee = (select oid from pg_roles where rolname = ${roleSql}) and a.privilege_type = 'SELECT' order by 1, 2`);
|
|
24
|
+
const schemas = await rowsOf(db, sql`select n.nspname as schema from pg_namespace n, aclexplode(n.nspacl) a
|
|
25
|
+
where a.grantee = (select oid from pg_roles where rolname = ${roleSql}) and a.privilege_type = 'USAGE' order by 1`);
|
|
26
|
+
return {
|
|
27
|
+
role,
|
|
28
|
+
runner: await readRunner(db),
|
|
29
|
+
resources: await resolved(db, models.map((model) => model.table)),
|
|
30
|
+
authTables: await resolved(db, authTables),
|
|
31
|
+
granted: {
|
|
32
|
+
tables,
|
|
33
|
+
schemas: schemas.map((found) => found.schema)
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The SQL that makes the role the assistant's read-only queries run as, and its runner, and grants it exactly the
|
|
39
|
+
* resources' tables, never an auth table; `undefined` when the database is current.
|
|
40
|
+
*/
|
|
41
|
+
const queryRunnerMigration = async (db, models, authTables) => {
|
|
42
|
+
const { role, runner, resources, authTables: auth, granted } = await readQueryRunner(db, models, authTables);
|
|
43
|
+
const plan = plannedGrants({
|
|
44
|
+
resources,
|
|
45
|
+
authTables: auth,
|
|
46
|
+
granted
|
|
47
|
+
});
|
|
48
|
+
return role.exists && runner?.source === runnerBody && runner.definer && runner.owner === role.name && !runner.executable && plan.grants.length === 0 && plan.revokes.length === 0 ? void 0 : queryRunnerSql(plan);
|
|
49
|
+
};
|
|
50
|
+
const refusal = (state) => /* @__PURE__ */ new Error(`The query role of the assistant's read-only queries ${state}. Write it as a migration with \`${migrationCommand}\`, run with the built-in assistant's key set (${modelVariables.apiKey} or options.assistant.apiKey), apply it with the project's migrations, then start again.`);
|
|
51
|
+
const leakingFunctions = (db, authTables) => rowsOf(db, sql`with runner as (select proowner as role from pg_proc where oid = to_regprocedure(${queryRunnerSignature}))
|
|
52
|
+
select format('%I.%I(%s)', n.nspname, p.proname, pg_get_function_identity_arguments(p.oid)) as name
|
|
53
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace, runner
|
|
54
|
+
where p.prosecdef and p.prorettype <> 'trigger'::regtype and p.proowner <> runner.role
|
|
55
|
+
and has_schema_privilege(runner.role, p.pronamespace, 'USAGE') and has_function_privilege(runner.role, p.oid, 'EXECUTE')
|
|
56
|
+
and exists (select from unnest(${authTables.map(regclassText)}::text[]) as t(name) join pg_class c on c.oid = to_regclass(t.name)
|
|
57
|
+
where has_schema_privilege(p.proowner, c.relnamespace, 'USAGE') and has_any_column_privilege(p.proowner, c.oid, 'SELECT'))
|
|
58
|
+
order by 1`);
|
|
59
|
+
/**
|
|
60
|
+
* Refuses to go on while the runner of the assistant's read-only queries is missing or out of date, or runs as a role
|
|
61
|
+
* that is not weaker than the app's: the app's role itself or one it is a member of, a superuser, a login role, or one
|
|
62
|
+
* that can read an auth table, or while the role may call a security-definer function that runs as a role able to
|
|
63
|
+
* read one. It checks the owner's privileges, not its name, so a database cloned under another name still passes. A resource added since the last migration is not checked: Postgres refuses to read its table until then.
|
|
64
|
+
*/
|
|
65
|
+
const checkQueryRunner = async (db, authTables) => {
|
|
66
|
+
const [runner] = await rowsOf(db, sql`select p.prosrc as source, p.prosecdef as definer,
|
|
67
|
+
(o.rolsuper or o.rolcanlogin or pg_has_role(p.proowner, current_user, 'MEMBER')) as strong,
|
|
68
|
+
exists (select from unnest(${authTables.map(regclassText)}::text[]) as t(name) join pg_class c on c.oid = to_regclass(t.name)
|
|
69
|
+
where has_schema_privilege(p.proowner, c.relnamespace, 'USAGE') and has_any_column_privilege(p.proowner, c.oid, 'SELECT')) as reads_auth
|
|
70
|
+
from pg_proc p join pg_roles o on o.oid = p.proowner where p.oid = to_regprocedure(${queryRunnerSignature})`);
|
|
71
|
+
if (!runner || !runner.definer || runner.source !== runnerBody) throw refusal("is missing");
|
|
72
|
+
if (runner.strong) throw refusal("is not weaker than the app's database role");
|
|
73
|
+
if (runner.reads_auth) throw refusal("can read the auth schema");
|
|
74
|
+
const leaking = await leakingFunctions(db, authTables);
|
|
75
|
+
if (leaking.length === 0) return;
|
|
76
|
+
throw new Error(`The query role of the assistant's read-only queries can read the auth schema through the security-definer functions ${leaking.map((found) => found.name).join(", ")}. Revoke execute on them from PUBLIC, or move them to a schema without a resource, then start again.`);
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
export { checkQueryRunner, queryRunnerMigration, readQueryRunner };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/assistant/query-runner/runner-plan.ts
|
|
2
|
+
const same = (a, b) => a.schema === b.schema && a.name === b.name;
|
|
3
|
+
const unique = (items, equal) => items.filter((item, index) => items.findIndex((other) => equal(item, other)) === index);
|
|
4
|
+
/**
|
|
5
|
+
* What the query role must be given and what taken back so that it reads exactly the resources' tables, never an auth
|
|
6
|
+
* table, with usage on the schemas those are in. `resources` and `authTables` are the tables that exist, `granted`
|
|
7
|
+
* what the role holds now.
|
|
8
|
+
*/
|
|
9
|
+
const plannedGrants = ({ resources, authTables, granted }) => {
|
|
10
|
+
const tables = unique(resources.filter((table) => !authTables.some((auth) => same(table, auth))), same);
|
|
11
|
+
const schemas = [...new Set(tables.map((table) => table.schema))];
|
|
12
|
+
return {
|
|
13
|
+
grants: [...schemas.filter((schema) => !granted.schemas.includes(schema)).map((schema) => ({
|
|
14
|
+
on: "schema",
|
|
15
|
+
schema
|
|
16
|
+
})), ...tables.filter((table) => !granted.tables.some((found) => same(found, table))).map((table) => ({
|
|
17
|
+
on: "table",
|
|
18
|
+
...table
|
|
19
|
+
}))],
|
|
20
|
+
revokes: [...granted.tables.filter((found) => !tables.some((table) => same(table, found))).map((table) => ({
|
|
21
|
+
on: "table",
|
|
22
|
+
...table
|
|
23
|
+
})), ...granted.schemas.filter((schema) => !schemas.includes(schema)).map((schema) => ({
|
|
24
|
+
on: "schema",
|
|
25
|
+
schema
|
|
26
|
+
}))]
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { plannedGrants };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//#region src/assistant/query-runner/runner-sql.ts
|
|
2
|
+
/** The schema the read-only query runner lives in. */
|
|
3
|
+
const queryRunnerSchema = "query";
|
|
4
|
+
/** The function every read-only query runs through, as the query role. */
|
|
5
|
+
const queryRunnerFunction = `${queryRunnerSchema}.run_read_only`;
|
|
6
|
+
/** Its signature, as `to_regprocedure` and `revoke` name it. */
|
|
7
|
+
const queryRunnerSignature = `${queryRunnerFunction}(text, integer)`;
|
|
8
|
+
/**
|
|
9
|
+
* The query role's name, computed by Postgres when the SQL runs, so one migration file serves every environment's
|
|
10
|
+
* database. Roles belong to the whole cluster, so the name carries the database's, cut to leave room for the suffix.
|
|
11
|
+
*/
|
|
12
|
+
const queryRoleSql = "left(current_database(), 57) || '_query'";
|
|
13
|
+
/**
|
|
14
|
+
* The runner's plpgsql body. A cursor runs the statement, which Postgres refuses when the text holds more than one
|
|
15
|
+
* query. Each row comes back as JSON, a number that is not a plain integer of at most 15 digits as its exact text,
|
|
16
|
+
* which `JSON.parse` would otherwise round or reformat. The newlines keep a trailing `--` comment from swallowing the
|
|
17
|
+
* wrapper.
|
|
18
|
+
*/
|
|
19
|
+
const runnerBody = `
|
|
20
|
+
declare
|
|
21
|
+
rows refcursor;
|
|
22
|
+
row json;
|
|
23
|
+
begin
|
|
24
|
+
open rows no scroll for execute 'select (select json_object_agg(e.key, case when json_typeof(e.value) = ''number'' and e.value::text !~ ''^-?[0-9]{1,15}$'' then to_json(e.value::text) else e.value end order by e.n) from json_each(to_json(r)) with ordinality as e(key, value, n)) from (' || E'\\n' || statement || E'\\n' || ') r limit ' || (row_limit + 1);
|
|
25
|
+
loop
|
|
26
|
+
fetch rows into row;
|
|
27
|
+
exit when not found;
|
|
28
|
+
return next row;
|
|
29
|
+
end loop;
|
|
30
|
+
close rows;
|
|
31
|
+
end
|
|
32
|
+
`;
|
|
33
|
+
const literal = (text) => `'${text.replaceAll("'", "''")}'`;
|
|
34
|
+
const grantSql = ({ grants, revokes }) => [...grants.map((grant) => grant.on === "schema" ? ` execute format('grant usage on schema %I to %I', ${literal(grant.schema)}, query_role);` : ` execute format('grant select on table %I.%I to %I', ${literal(grant.schema)}, ${literal(grant.name)}, query_role);`), ...revokes.map((revoke) => revoke.on === "schema" ? ` execute format('revoke usage on schema %I from %I', ${literal(revoke.schema)}, query_role);` : ` execute format('revoke select on table %I.%I from %I', ${literal(revoke.schema)}, ${literal(revoke.name)}, query_role);`)];
|
|
35
|
+
/**
|
|
36
|
+
* The SQL that makes the query role and the runner, and gives the role exactly `grants`, taking back `revokes`. It
|
|
37
|
+
* applies again on a database that has them. The migrating role becomes a member of the role, which Postgres 16 needs
|
|
38
|
+
* to hand it the function, and so may execute the runner; PUBLIC may not. The role bypasses row-level security only
|
|
39
|
+
* when the migrating role may give it that: a superuser, or a role with BYPASSRLS on Postgres 16 or later.
|
|
40
|
+
*/
|
|
41
|
+
const queryRunnerSql = (plan) => [
|
|
42
|
+
`create schema if not exists ${queryRunnerSchema};`,
|
|
43
|
+
[
|
|
44
|
+
"do $$",
|
|
45
|
+
"declare",
|
|
46
|
+
` query_role text := ${queryRoleSql};`,
|
|
47
|
+
"begin",
|
|
48
|
+
" if not exists (select from pg_roles where rolname = query_role) then execute format('create role %I nologin', query_role); end if;",
|
|
49
|
+
" execute format('grant %I to current_user', query_role);",
|
|
50
|
+
" if (select rolsuper or (rolbypassrls and current_setting('server_version_num')::int >= 160000) from pg_roles where rolname = current_user) then execute format('alter role %I bypassrls', query_role); end if;",
|
|
51
|
+
...grantSql(plan),
|
|
52
|
+
"end $$;"
|
|
53
|
+
].join("\n"),
|
|
54
|
+
`create or replace function ${queryRunnerFunction}(statement text, row_limit integer) returns setof json language plpgsql security definer as $f$${runnerBody}$f$;`,
|
|
55
|
+
[
|
|
56
|
+
"do $$",
|
|
57
|
+
"declare",
|
|
58
|
+
` query_role text := ${queryRoleSql};`,
|
|
59
|
+
"begin",
|
|
60
|
+
` execute format('grant create on schema ${queryRunnerSchema} to %I', query_role);`,
|
|
61
|
+
` execute format('alter function ${queryRunnerSignature} owner to %I', query_role);`,
|
|
62
|
+
` execute format('revoke create on schema ${queryRunnerSchema} from %I', query_role);`,
|
|
63
|
+
"end $$;"
|
|
64
|
+
].join("\n"),
|
|
65
|
+
`revoke execute on function ${queryRunnerSignature} from public;`
|
|
66
|
+
].join("\n\n");
|
|
67
|
+
//#endregion
|
|
68
|
+
export { queryRoleSql, queryRunnerFunction, queryRunnerSchema, queryRunnerSignature, queryRunnerSql, runnerBody };
|
|
@@ -4,7 +4,8 @@ import { Db } from "@protobase/query";
|
|
|
4
4
|
//#region src/assistant/query-tools.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Lets the model read the app's database: one SELECT per call, in a read-only transaction (see `runReadOnlyQuery`).
|
|
7
|
-
* The rows show as a table in the chat. It runs as the
|
|
7
|
+
* The rows show as a table in the chat. It runs as the query role, outside Protobase's access rules: it reads the
|
|
8
|
+
* resources' tables only, never the auth schema.
|
|
8
9
|
*/
|
|
9
10
|
export declare const readOnlyQueryTool: (db: Db, options?: QueryOptions) => AssistantTool;
|
|
10
11
|
/**
|
|
@@ -26,11 +26,12 @@ const shown = (result, statement, context) => {
|
|
|
26
26
|
const statementOf = (args) => typeof args.sql === "string" && args.sql.trim() ? args.sql : void 0;
|
|
27
27
|
/**
|
|
28
28
|
* Lets the model read the app's database: one SELECT per call, in a read-only transaction (see `runReadOnlyQuery`).
|
|
29
|
-
* The rows show as a table in the chat. It runs as the
|
|
29
|
+
* The rows show as a table in the chat. It runs as the query role, outside Protobase's access rules: it reads the
|
|
30
|
+
* resources' tables only, never the auth schema.
|
|
30
31
|
*/
|
|
31
32
|
const readOnlyQueryTool = (db, options = {}) => defineTool({
|
|
32
33
|
name: "run_read_only_query",
|
|
33
|
-
description: `Runs one PostgreSQL SELECT on the app's database in a read-only transaction and returns up to ${options.rowLimit ?? 200} rows as JSON ({ columns, rows, truncated }). The user sees the rows as a table. Use it to answer questions about the data. It cannot change anything.`,
|
|
34
|
+
description: `Runs one PostgreSQL SELECT on the app's database in a read-only transaction and returns up to ${options.rowLimit ?? 200} rows as JSON ({ columns, rows, truncated }). The user sees the rows as a table. Use it to answer questions about the data. It reads the app's resource tables only. It cannot change anything.`,
|
|
34
35
|
parameters: {
|
|
35
36
|
type: "object",
|
|
36
37
|
properties: { sql: sqlParameter("One SELECT statement, without a trailing semicolon.") },
|
|
@@ -13,7 +13,9 @@ export type QueryResult = {
|
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
15
|
* Runs one SELECT (or VALUES, or a WITH query) in a READ ONLY transaction that is always rolled back, so nothing it
|
|
16
|
-
* does, settings included, outlives it. Postgres refuses any write in it, and the statement cannot leave it.
|
|
16
|
+
* does, settings included, outlives it. Postgres refuses any write in it, and the statement cannot leave it. It runs
|
|
17
|
+
* through the runner `protobase migration` makes, as the query role, which reads only the resources' tables and never
|
|
18
|
+
* the auth schema, and cannot switch back to the app's role; its cursor runs one statement only.
|
|
17
19
|
*/
|
|
18
20
|
export declare const runReadOnlyQuery: (db: Db, statement: string, options?: QueryOptions) => Promise<QueryResult>;
|
|
19
21
|
/**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { queryRunnerFunction } from "./query-runner/runner-sql.js";
|
|
1
2
|
import { sql } from "kysely";
|
|
2
3
|
//#region src/assistant/run-query.ts
|
|
3
|
-
const
|
|
4
|
+
const withoutSemicolon = (statement) => statement.trim().replace(/;\s*$/, "");
|
|
5
|
+
const wrapped = (statement, rowLimit) => sql`with result as (\n${sql.raw(withoutSemicolon(statement))}\n) select * from result limit ${rowLimit + 1}`;
|
|
4
6
|
const cell = (value) => {
|
|
5
7
|
if (typeof value === "bigint") return value.toString();
|
|
6
8
|
if (value instanceof Date) return value.toISOString();
|
|
@@ -12,28 +14,40 @@ const resultOf = (rows, rowLimit) => ({
|
|
|
12
14
|
rows: rows.slice(0, rowLimit).map((row) => Object.values(row).map(cell)),
|
|
13
15
|
truncated: rows.length > rowLimit
|
|
14
16
|
});
|
|
15
|
-
const run = (db,
|
|
17
|
+
const run = (db, query, options, begin, end) => db.connection().execute(async (connection) => {
|
|
16
18
|
const rowLimit = options.rowLimit ?? 200;
|
|
17
19
|
await sql.raw(begin).execute(connection);
|
|
18
20
|
let succeeded = false;
|
|
19
21
|
try {
|
|
20
22
|
await sql`select set_config('statement_timeout', ${String(options.timeoutMs ?? 5e3)}, true)`.execute(connection);
|
|
21
|
-
const { rows } = await
|
|
23
|
+
const { rows } = await query(rowLimit).execute(connection);
|
|
22
24
|
succeeded = true;
|
|
23
|
-
return
|
|
25
|
+
return {
|
|
26
|
+
rows,
|
|
27
|
+
rowLimit
|
|
28
|
+
};
|
|
24
29
|
} finally {
|
|
25
30
|
await sql.raw(end(succeeded)).execute(connection);
|
|
26
31
|
}
|
|
27
32
|
});
|
|
33
|
+
const throughRunner = (statement) => (rowLimit) => sql`select row::text as row from ${sql.raw(queryRunnerFunction)}(${withoutSemicolon(statement)}, ${rowLimit}) as row`;
|
|
28
34
|
/**
|
|
29
35
|
* Runs one SELECT (or VALUES, or a WITH query) in a READ ONLY transaction that is always rolled back, so nothing it
|
|
30
|
-
* does, settings included, outlives it. Postgres refuses any write in it, and the statement cannot leave it.
|
|
36
|
+
* does, settings included, outlives it. Postgres refuses any write in it, and the statement cannot leave it. It runs
|
|
37
|
+
* through the runner `protobase migration` makes, as the query role, which reads only the resources' tables and never
|
|
38
|
+
* the auth schema, and cannot switch back to the app's role; its cursor runs one statement only.
|
|
31
39
|
*/
|
|
32
|
-
const runReadOnlyQuery = (db, statement, options = {}) =>
|
|
40
|
+
const runReadOnlyQuery = async (db, statement, options = {}) => {
|
|
41
|
+
const { rows, rowLimit } = await run(db, throughRunner(statement), options, "begin transaction read only", () => "rollback");
|
|
42
|
+
return resultOf(rows.map(({ row }) => JSON.parse(row ?? "{}")), rowLimit);
|
|
43
|
+
};
|
|
33
44
|
/**
|
|
34
45
|
* Runs one INSERT, UPDATE or DELETE with a RETURNING clause (or a query) in a READ WRITE transaction, committed when it
|
|
35
46
|
* succeeds. Only call it for a statement the user approved.
|
|
36
47
|
*/
|
|
37
|
-
const runReadWriteQuery = (db, statement, options = {}) =>
|
|
48
|
+
const runReadWriteQuery = async (db, statement, options = {}) => {
|
|
49
|
+
const { rows, rowLimit } = await run(db, (limit) => wrapped(statement, limit), options, "begin transaction read write", (succeeded) => succeeded ? "commit" : "rollback");
|
|
50
|
+
return resultOf(rows, rowLimit);
|
|
51
|
+
};
|
|
38
52
|
//#endregion
|
|
39
53
|
export { runReadOnlyQuery, runReadWriteQuery };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { storeSchema } from "./auth-tables.js";
|
|
1
2
|
import { dropMcpTables, leftoverMcpTables, mcpTableNames, withMcpTables } from "./mcp/mcp-tables.js";
|
|
2
3
|
import { snakeCaseMigration } from "./snake-case-migration.js";
|
|
3
4
|
import { getMigrations } from "better-auth/db/migration";
|
|
@@ -15,7 +16,6 @@ const skipExisting = (sql) => sql.split(";\n\n").map((statement) => statement.re
|
|
|
15
16
|
* the old ones.
|
|
16
17
|
*/
|
|
17
18
|
const hasCamelCaseNames = ({ toBeAdded }) => toBeAdded.some(({ table, fields }) => table === "user" && "email_verified" in fields);
|
|
18
|
-
const storeSchema = ({ options: { database } }) => database && "schemaName" in database ? database.schemaName : void 0;
|
|
19
19
|
/**
|
|
20
20
|
* The SQL that brings the admin store up to what this version of Protobase and the project's Better Auth plugins need:
|
|
21
21
|
* for a store with the camelCase names of Protobase 0.6, their rename to snake_case (`snakeCaseMigration`); otherwise
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AdminAuth } from "./create-auth.js";
|
|
2
|
+
//#region src/better-auth/auth-tables.d.ts
|
|
3
|
+
/** The admin store's tables with every plugin the project's auth has, which the assistant's queries must never read. */
|
|
4
|
+
export declare const authStoreTables: (auth: AdminAuth) => {
|
|
5
|
+
schema: string | undefined;
|
|
6
|
+
name: string;
|
|
7
|
+
}[];
|
|
8
|
+
//#endregion
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { getSchema } from "better-auth/db";
|
|
2
|
+
//#region src/better-auth/auth-tables.ts
|
|
3
|
+
/** The schema the admin store lives in; the search path decides when its options name none. */
|
|
4
|
+
const storeSchema = ({ options: { database } }) => database && "schemaName" in database ? database.schemaName : void 0;
|
|
5
|
+
/** The admin store's tables with every plugin the project's auth has, which the assistant's queries must never read. */
|
|
6
|
+
const authStoreTables = (auth) => Object.keys(getSchema(auth.options)).map((name) => ({
|
|
7
|
+
schema: storeSchema(auth),
|
|
8
|
+
name
|
|
9
|
+
}));
|
|
10
|
+
//#endregion
|
|
11
|
+
export { authStoreTables, storeSchema };
|
|
@@ -7,11 +7,16 @@ const messageOf = (body) => {
|
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
9
9
|
* The bearer token of the session a request's cookie carries, minted as `GET /api/auth/token` would, with the same
|
|
10
|
-
* plugin hooks (the sign-in policy's hold-back included)
|
|
11
|
-
* signature
|
|
12
|
-
*
|
|
10
|
+
* plugin hooks (the sign-in policy's hold-back included). The direct call has no rate limit, so nothing before the
|
|
11
|
+
* signature check may read the store: the session is read first, which refuses a cookie that is missing, forged or for
|
|
12
|
+
* no session before any store read, and the policy is read only for a session that exists. No session is a 401
|
|
13
|
+
* problem, a refusal a 403 with Better Auth's message; any other error, such as a store failure, stays as it is.
|
|
13
14
|
*/
|
|
14
15
|
const betterAuthSessionToken = (auth) => async (request) => {
|
|
16
|
+
if (await auth.api.getSession({
|
|
17
|
+
headers: request.headers,
|
|
18
|
+
query: { disableRefresh: true }
|
|
19
|
+
}) === null) throw unauthorized("Sign in first");
|
|
15
20
|
try {
|
|
16
21
|
const { token } = await auth.api.getToken({ headers: request.headers });
|
|
17
22
|
return token;
|
package/dist/create-admin.d.ts
CHANGED
|
@@ -75,8 +75,9 @@ export type CreateAdminInput = {
|
|
|
75
75
|
/** @internal Verifies a user's token on `/api/sb/rest/v1`, `/api/sb/realtime/v1` and `/api/sb/storage/v1` and gives its claims, which tests fake; those `/api/sb/auth/v1` signs otherwise. */
|
|
76
76
|
verifyToken?: (token: string) => Promise<JWTPayload>;
|
|
77
77
|
/**
|
|
78
|
-
* The host's WebSocket upgrade,
|
|
79
|
-
*
|
|
78
|
+
* The host's WebSocket upgrade, `@hono/node-server`'s `upgradeWebSocket`, the only one supported: function sockets use
|
|
79
|
+
* its `ws` socket's send callback and `bufferedAmount`. With it, functions accept sockets, and an app with keys and
|
|
80
|
+
* live resources serves supabase-js's Realtime at `/api/sb/realtime/v1/websocket`; without it, the app serves none.
|
|
80
81
|
*/
|
|
81
82
|
upgradeWebSocket?: UpgradeWebSocket;
|
|
82
83
|
};
|
|
@@ -14,9 +14,11 @@ export type PublicFunctionContext = {
|
|
|
14
14
|
/** The caller, through the app's own `authenticate`: rejects with a 401 problem when the request carries no valid token. */
|
|
15
15
|
caller: () => Promise<FunctionCaller>;
|
|
16
16
|
/**
|
|
17
|
-
* Answers a WebSocket upgrade with a socket: return its response. A request that is no upgrade is a 426 problem.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Answers a WebSocket upgrade with a socket: return its response. A request that is no upgrade is a 426 problem. While
|
|
18
|
+
* the socket is open, the caller of a protected function, and of a public one once `caller()` resolved, is checked
|
|
19
|
+
* again every minute: their credential, the function's roles, and the same user, tenant, organization and roles. The
|
|
20
|
+
* socket closes with 1008 once they would be refused. The function's own checks, such as access to one record, are
|
|
21
|
+
* not repeated unless it passes them as `recheck`.
|
|
20
22
|
*/
|
|
21
23
|
acceptWebSocket: (events: SocketEvents) => Response;
|
|
22
24
|
};
|
|
@@ -14,5 +14,11 @@ export type SocketEvents = {
|
|
|
14
14
|
/** Text arrives as a string, binary as an ArrayBuffer. The next message waits until a returned promise settles. */
|
|
15
15
|
onMessage?: (data: string | ArrayBuffer, socket: FunctionSocket) => void | Promise<void>;
|
|
16
16
|
onClose?: (code: number, reason: string) => void | Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Checked every minute after the framework's own check, while the socket is open; what it returns is ignored. Throw
|
|
19
|
+
* or reject with a 401, 403 or 404 problem to close the socket with 1008; any other error is reported and closes it
|
|
20
|
+
* with 1011.
|
|
21
|
+
*/
|
|
22
|
+
recheck?: () => unknown;
|
|
17
23
|
};
|
|
18
24
|
//#endregion
|
|
@@ -61,8 +61,12 @@ const socketEvents = (events, { report, recheck }) => {
|
|
|
61
61
|
return raw.bufferedAmount;
|
|
62
62
|
}
|
|
63
63
|
};
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
const check = async () => {
|
|
65
|
+
await recheck?.();
|
|
66
|
+
await events.recheck?.();
|
|
67
|
+
};
|
|
68
|
+
if (recheck || events.recheck) stopRecheck = recheckWhileOpen({
|
|
69
|
+
check,
|
|
66
70
|
close: end,
|
|
67
71
|
report
|
|
68
72
|
});
|
package/dist/functions/routes.js
CHANGED
|
@@ -18,8 +18,8 @@ const isUpgrade = (request) => request.headers.get("upgrade")?.toLowerCase() ===
|
|
|
18
18
|
* a call without a valid token is the authenticator's 401 problem, and one its gate refuses a 403; a public one gets the caller only when it asks. Each
|
|
19
19
|
* call runs as background work, so calls that hold the database for long leave a connection to page requests. Once a
|
|
20
20
|
* call ends, failed or not, `wake` runs, so what it wrote reaches open pages without waiting for the dispatcher's poll.
|
|
21
|
-
* A function may answer an upgrade with a socket, only once it returned
|
|
22
|
-
* every minute
|
|
21
|
+
* A function may answer an upgrade with a socket, only once it returned. While it is open, its caller is checked again
|
|
22
|
+
* every minute: a protected function's always, a public one's once `caller()` resolved.
|
|
23
23
|
*/
|
|
24
24
|
const functionRoutes = ({ basePath, functions, authenticate, access, db, records, onError, wake, upgradeWebSocket, upgradeAuthenticate = authenticate, report }) => {
|
|
25
25
|
const app = new Hono().basePath(basePath);
|
|
@@ -33,6 +33,11 @@ const functionRoutes = ({ basePath, functions, authenticate, access, db, records
|
|
|
33
33
|
};
|
|
34
34
|
};
|
|
35
35
|
const call = (work) => work().finally(wake);
|
|
36
|
+
const recheckCaller = async (name, gate, request, first) => {
|
|
37
|
+
const again = await upgradeAuthenticate(request);
|
|
38
|
+
requireGate(name, gate, again);
|
|
39
|
+
if (!sameCaller(first, again)) throw forbidden("caller-changed", "Your access changed: open the socket again");
|
|
40
|
+
};
|
|
36
41
|
for (const [name, fn] of Object.entries(functions)) {
|
|
37
42
|
const prefix = `${basePath}/${name}`;
|
|
38
43
|
const { options } = fn;
|
|
@@ -42,26 +47,28 @@ const functionRoutes = ({ basePath, functions, authenticate, access, db, records
|
|
|
42
47
|
const request = relativeRequest(c.req.raw, prefix);
|
|
43
48
|
if (options.public) {
|
|
44
49
|
let caller;
|
|
50
|
+
let admitted;
|
|
51
|
+
const resolveCaller = () => caller ??= callerOf(c.req.raw).then((resolved) => (admitted = resolved.session, resolved));
|
|
52
|
+
const recheck = async () => {
|
|
53
|
+
if (admitted) await recheckCaller(name, gate, c.req.raw, admitted);
|
|
54
|
+
};
|
|
45
55
|
const acceptor = socketAcceptor({
|
|
46
56
|
c,
|
|
47
57
|
upgradeWebSocket,
|
|
48
|
-
report
|
|
58
|
+
report,
|
|
59
|
+
recheck
|
|
49
60
|
});
|
|
50
61
|
const context = {
|
|
51
62
|
name,
|
|
52
63
|
db,
|
|
53
|
-
caller:
|
|
64
|
+
caller: resolveCaller,
|
|
54
65
|
acceptWebSocket: acceptor.accept
|
|
55
66
|
};
|
|
56
67
|
return acceptor.answer(name, await invokeFunction(name, fn, request, context));
|
|
57
68
|
}
|
|
58
69
|
const caller = await callerOf(c.req.raw);
|
|
59
70
|
requireGate(name, gate, caller.session);
|
|
60
|
-
const recheck =
|
|
61
|
-
const again = await upgradeAuthenticate(c.req.raw);
|
|
62
|
-
requireGate(name, gate, again);
|
|
63
|
-
if (!sameCaller(caller.session, again)) throw forbidden("caller-changed", "Your access changed: open the socket again");
|
|
64
|
-
};
|
|
71
|
+
const recheck = () => recheckCaller(name, gate, c.req.raw, caller.session);
|
|
65
72
|
const acceptor = socketAcceptor({
|
|
66
73
|
c,
|
|
67
74
|
upgradeWebSocket,
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { HttpProblem } from "../problem.js";
|
|
2
2
|
//#region src/functions/socket-recheck.ts
|
|
3
|
-
const
|
|
3
|
+
const refusals = /* @__PURE__ */ new Set([
|
|
4
|
+
401,
|
|
5
|
+
403,
|
|
6
|
+
404
|
|
7
|
+
]);
|
|
8
|
+
const isRefusal = (error) => error instanceof HttpProblem && refusals.has(error.status);
|
|
4
9
|
/**
|
|
5
|
-
* Runs `check` every `everyMs` while a socket is open, one check after the other. A refusal
|
|
6
|
-
* (Policy Violation) and the problem's title; any other error is
|
|
7
|
-
* stops the check ends the access too.
|
|
10
|
+
* Runs `check` every `everyMs` while a socket is open, one check after the other. A refusal, such as a record the caller
|
|
11
|
+
* can no longer see (404), closes the socket with 1008 (Policy Violation) and the problem's title; any other error is
|
|
12
|
+
* reported and closes it with 1011, so an outage that stops the check ends the access too. A check still pending after
|
|
13
|
+
* another `everyMs` counts as such an error, so a hung store never leaves a socket open unchecked. Returns the function
|
|
14
|
+
* that stops it.
|
|
8
15
|
*/
|
|
9
16
|
const recheckWhileOpen = ({ check, close, report, everyMs = 6e4 }) => {
|
|
10
17
|
let stopped = false;
|
|
@@ -16,9 +23,14 @@ const recheckWhileOpen = ({ check, close, report, everyMs = 6e4 }) => {
|
|
|
16
23
|
report(error);
|
|
17
24
|
close(1011, "Internal Error");
|
|
18
25
|
};
|
|
19
|
-
const run = () =>
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
const run = () => {
|
|
27
|
+
timer = setTimeout(() => end(/* @__PURE__ */ new Error(`A socket's check did not finish within ${everyMs} ms`)), everyMs);
|
|
28
|
+
timer.unref?.();
|
|
29
|
+
check().then(() => {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
if (!stopped) schedule();
|
|
32
|
+
}, end);
|
|
33
|
+
};
|
|
22
34
|
const schedule = () => {
|
|
23
35
|
timer = setTimeout(run, everyMs);
|
|
24
36
|
timer.unref?.();
|
package/dist/index.d.ts
CHANGED
|
@@ -94,6 +94,8 @@ import { McpGateInput, mcpGate, sessionOf } from "./mcp/mcp-gate.js";
|
|
|
94
94
|
import { McpHandlerInput, mcpHandler } from "./mcp/mcp-handler.js";
|
|
95
95
|
import { McpRoutesInput, mcpRoutes } from "./mcp/mcp-routes.js";
|
|
96
96
|
import { mcpToolContext } from "./mcp/mcp-context.js";
|
|
97
|
+
import { checkQueryRunner, queryRunnerMigration } from "./assistant/query-runner/query-runner.js";
|
|
98
|
+
import { authStoreTables } from "./better-auth/auth-tables.js";
|
|
97
99
|
import { ErrorReporter } from "./error-reporting/sentry-reporter.js";
|
|
98
100
|
import { startErrorReporting } from "./error-reporting/error-reporter.js";
|
|
99
101
|
import { FunctionEnv, PublicFunctionEnv, functionApp } from "./functions/function-app.js";
|
|
@@ -120,4 +122,4 @@ import { isSchedule } from "./jobs/schedule.js";
|
|
|
120
122
|
import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/runtime.js";
|
|
121
123
|
import { jobsDrainApp } from "./jobs/drain-route.js";
|
|
122
124
|
import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
|
|
123
|
-
export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApiKeys, type ApiKeysOptions, type AppIcons, type AppIdentity, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type Caller, type CallerOptions, type CapturedRow, type CascadingKey, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentUsers, type Completion, type CompletionOptions, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldStatus, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSocket, type FunctionSource, type HistoryOptions, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type MemberInput, MissingCredentials, type MobileConfig, ModelError, type ModelSettings, type NewOrganization, type NewUser, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type QueryOptions, type QueryResult, type ReadableResources, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type User, type UserCreatedHook, type ViewSource, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, addAppRoleToOwners, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, backgroundSettled, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, betterAuthTokens, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createUser, defaultAppName, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, localFiles, mapSettings, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mcpGate, mcpHandler, mcpRoutes, mcpServer, mcpToolContext, mcpTools, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readWriteQueryTool, readableResources, realtimeLimits, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, sessionOf, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, waitUntil, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
|
125
|
+
export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApiKeys, type ApiKeysOptions, type AppIcons, type AppIdentity, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type Caller, type CallerOptions, type CapturedRow, type CascadingKey, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentUsers, type Completion, type CompletionOptions, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldStatus, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSocket, type FunctionSource, type HistoryOptions, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type MemberInput, MissingCredentials, type MobileConfig, ModelError, type ModelSettings, type NewOrganization, type NewUser, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type QueryOptions, type QueryResult, type ReadableResources, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type User, type UserCreatedHook, type ViewSource, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, addAppRoleToOwners, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, betterAuthTokens, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkQueryRunner, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createUser, defaultAppName, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, localFiles, mapSettings, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mcpGate, mcpHandler, mcpRoutes, mcpServer, mcpToolContext, mcpTools, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, processor, queryRunnerMigration, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readWriteQueryTool, readableResources, realtimeLimits, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, sessionOf, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, waitUntil, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
package/dist/index.js
CHANGED
|
@@ -72,12 +72,14 @@ import { defineConfig, mergeConfig } from "./project-config.js";
|
|
|
72
72
|
import { withTrustedApps } from "./better-auth/app-tokens.js";
|
|
73
73
|
import { betterAuthAuthenticator, betterAuthTokens } from "./better-auth/authenticator.js";
|
|
74
74
|
import { issueToken, maxTokenTtlSeconds } from "./better-auth/tokens.js";
|
|
75
|
+
import { authStoreTables } from "./better-auth/auth-tables.js";
|
|
75
76
|
import { authSchemaMigration, checkAuthSchema } from "./better-auth/auth-schema.js";
|
|
76
77
|
import { checkComputed, computedMigration } from "./computed/computed-schema.js";
|
|
77
78
|
import { webhooks } from "./webhooks/table/plugin.js";
|
|
78
79
|
import { checkWebhooksSchema, webhooksMigration } from "./webhooks/table/schema.js";
|
|
79
80
|
import { checkAi } from "./ai/check-ai.js";
|
|
80
81
|
import { maxWidgetPropsBytes, widget } from "./assistant/widget.js";
|
|
82
|
+
import { checkQueryRunner, queryRunnerMigration } from "./assistant/query-runner/query-runner.js";
|
|
81
83
|
import { startErrorReporting } from "./error-reporting/error-reporter.js";
|
|
82
84
|
import { functionApp } from "./functions/function-app.js";
|
|
83
85
|
import { readJson } from "./functions/read-json.js";
|
|
@@ -91,4 +93,4 @@ import { aspectRatio, fileSize, fileType, imageSize, processor } from "./files/p
|
|
|
91
93
|
import { defineEmail } from "./mail/define-email.js";
|
|
92
94
|
import { startJobs } from "./jobs/runtime.js";
|
|
93
95
|
import { jobsDrainApp } from "./jobs/drain-route.js";
|
|
94
|
-
export { FatalJobError, HttpProblem, MissingCredentials, ModelError, addAppRoleToOwners, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, backgroundSettled, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, betterAuthTokens, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createUser, defaultAppName, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, localFiles, mapSettings, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mcpGate, mcpHandler, mcpRoutes, mcpServer, mcpToolContext, mcpTools, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readWriteQueryTool, readableResources, realtimeLimits, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, sessionOf, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, waitUntil, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
|
96
|
+
export { FatalJobError, HttpProblem, MissingCredentials, ModelError, addAppRoleToOwners, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, betterAuthTokens, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkQueryRunner, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createUser, defaultAppName, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, localFiles, mapSettings, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mcpGate, mcpHandler, mcpRoutes, mcpServer, mcpToolContext, mcpTools, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, processor, queryRunnerMigration, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readWriteQueryTool, readableResources, realtimeLimits, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, sessionOf, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, waitUntil, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"@better-auth/passkey": "^1.7.12",
|
|
37
37
|
"@better-auth/sso": "^1.7.12",
|
|
38
38
|
"@modelcontextprotocol/server": "^2.3.1",
|
|
39
|
-
"@protobase/layout": "0.
|
|
40
|
-
"@protobase/query": "0.
|
|
41
|
-
"@protobase/schema": "0.
|
|
39
|
+
"@protobase/layout": "0.38.0",
|
|
40
|
+
"@protobase/query": "0.38.0",
|
|
41
|
+
"@protobase/schema": "0.38.0",
|
|
42
42
|
"@react-email/render": "^2.1.0",
|
|
43
43
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
44
44
|
"@sentry/core": "^11.7.0",
|