Files
windmill/typescript-client/sqlUtils.d.ts
T
Diego ImbertandClaude Opus 5 22b3e69c07 feat(datatables): put a data table's connection under Postgres roles
A data table backed by the instance database resolved to exactly one Postgres connection,
`custom_instance_user`, for everyone who could reach it at all. There was no way to say
this job reads, that one writes, this one never sees the salaries table.

A data table role is now a real Postgres login on the cluster, defined once for the
instance by a superadmin and named exactly as they named it. A script that declares
`-- role analytics` connects as `analytics`, and Postgres decides what it may touch —
grants are ordinary SQL. Windmill answers only "may this caller ask for this role", from
the tenant lists on the data table entry: `u/alice`, `g/analysts`, `f/finance` or `*`.
A data table with no `permissions` block behaves exactly as before.

Everything that opens a connection on someone's behalf goes through one chokepoint,
`get_datatable_resource_from_db`, which takes the identity explicitly and fails closed when
there is none. The role logs in as itself — never `SET ROLE`, which a script could
`RESET ROLE` its way out of.

A fork's data table entry becomes a pointer at the workspace that governs it rather than a
copy of it. The settings clone used to hand a fork a byte-identical entry naming the
parent's database, which a fork admin could edit to grant themselves `admin` there; a
pointer has nothing local to edit, and its tenants are evaluated as a member of the
governing workspace, by email. `permissions` is stripped from the workspace export and
ignored on import: tenants name principals of one workspace, and a settings push is not
where an access decision should be made.

Operations that see the whole database whatever the roles grant stay with the governing
workspace's admins: editing the roles, a migration that declares none, and opening a
replication stream for a Postgres trigger or capture.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
2026-09-16 15:14:28 +02:00

108 lines
3.1 KiB
TypeScript

type ResultCollection =
| "last_statement_all_rows"
| "last_statement_first_row"
| "last_statement_all_rows_scalar"
| "last_statement_first_row_scalar"
| "all_statements_all_rows"
| "all_statements_first_row"
| "all_statements_all_rows_scalar"
| "all_statements_first_row_scalar"
| "legacy";
type FetchParams<ResultCollectionT extends ResultCollection> = {
resultCollection?: ResultCollectionT;
};
type SqlResult<
T,
ResultCollectionT extends ResultCollection
> = ResultCollectionT extends "last_statement_first_row"
? T | null
: ResultCollectionT extends "all_statements_first_row"
? T[]
: ResultCollectionT extends "last_statement_all_rows"
? T[]
: ResultCollectionT extends "all_statements_all_rows"
? T[][]
: ResultCollectionT extends "last_statement_all_rows_scalar"
? T[keyof T][]
: ResultCollectionT extends "all_statements_all_rows_scalar"
? T[keyof T][][]
: ResultCollectionT extends "last_statement_first_row_scalar"
? T[keyof T] | null
: ResultCollectionT extends "all_statements_first_row_scalar"
? T[keyof T][]
: unknown;
export type SqlStatement<T> = {
content: string;
args: Record<string, any>;
fetch<ResultCollectionT extends ResultCollection = "last_statement_all_rows">(
params?: FetchParams<ResultCollectionT | ResultCollection> // The union is for auto-completion
): Promise<SqlResult<T, ResultCollectionT>>;
fetchOne(
params?: Omit<FetchParams<"last_statement_first_row">, "resultCollection">
): Promise<SqlResult<T, "last_statement_first_row">>;
/**
* Execute the SQL query and return only the first row as a scalar value
* @param params - Optional parameters
* @returns First row of the query result
*/
fetchOneScalar(
params?: Omit<
FetchParams<"last_statement_first_row_scalar">,
"resultCollection"
>
): Promise<SqlResult<T, "last_statement_first_row_scalar">>;
/**
* Execute the SQL query without fetching rows
* @param params - Optional parameters
*/
execute(
params?: Omit<
FetchParams<"last_statement_first_row_scalar">,
"resultCollection"
>
): Promise<void>;
};
export declare class RawSql {
readonly __brand: "RawSql";
readonly value: string;
constructor(value: string);
}
export interface SqlTemplateFunction {
<T = any>(strings: TemplateStringsArray, ...values: any[]): SqlStatement<T>;
raw(value: string): RawSql;
}
export interface DatatableSqlTemplateFunction extends SqlTemplateFunction {
query<T = any>(sql: string, ...params: any[]): SqlStatement<T>;
}
export interface DatatableOptions {
role?: string;
}
export declare function datatable(name?: string, opts?: DatatableOptions): DatatableSqlTemplateFunction;
export declare function ducklake(name: string): SqlTemplateFunction;
export interface DucklakeMaterializeOptions {
ducklake?: string;
table: string;
selectSql: string;
partition?: string;
uniqueKey?: string;
partitionCol?: string;
}
export declare function upsertPartition(
opts: DucklakeMaterializeOptions,
): SqlStatement<any>;
export declare function appendPartition(
opts: Omit<DucklakeMaterializeOptions, "uniqueKey">,
): SqlStatement<any>;