type DatabaseBinding = { fetch: typeof fetch; }; type SQLSuccess = { results?: Array; lastRowId: number | null; // doesn't work yet CFSQL-51 changes: NonNullable | 0; // still sends null CFSQL-54 duration: NonNullable | 0; }; type SQLError = { error: string; }; export class Database { private readonly binding: DatabaseBinding; constructor(binding: DatabaseBinding) { this.binding = binding; } prepare(query: string): PreparedStatement { return new PreparedStatement(this, query); } async dump(): Promise { const response = await this.binding.fetch("/dump", { method: "POST", headers: { "content-type": "application/json", }, }); if (response.status !== 200) { const err = (await response.json()) as SQLError; throw new Error("D1_DUMP_ERROR", { cause: new Error(err.error), }); } return await response.arrayBuffer(); } async batch(statements: Array): Promise>> { const exec = await this._send( "/query", statements.map((s: PreparedStatement) => s.statement), statements.map((s: PreparedStatement) => s.params) ); return exec as Array>; } async exec(query: string): Promise { // should be /execute - see CFSQL-52 const lines = query.trim().split("\n"); const exec = await this._send("/query", lines, []); const error = exec .map((r: any) => { return r.error ? 1 : 0; }) .indexOf(1); if (error !== -1) { throw new Error("D1_EXEC_ERROR", { cause: new Error(`Error in line ${ error + 1 }: ${ lines[error] }: ${ exec[error].error }`), }); } else { return { count: exec.length, duration: exec.reduce((p: SQLSuccess, c: SQLSuccess) => { return p.duration + c.duration; }), }; } } async _send(endpoint: string, query: any, params: Array): Promise { /* this needs work - we currently only support ordered ?n params */ const body = JSON.stringify( typeof query == "object" ? (query as Array).map((s: string, index: number) => { return { sql: s, params: params[index] }; }) : { sql: query, params: params, } ); const response = await this.binding.fetch(endpoint, { method: "POST", headers: { "content-type": "application/json", }, body, }); if (response.status !== 200) { const err = (await response.json()) as SQLError; throw new Error("D1_ERROR", { cause: new Error(err.error) }); } const answer = await response.json(); return Array.isArray(answer) ? (answer as Array>) : (answer as SQLSuccess); } } class PreparedStatement { readonly statement: string; private readonly database: Database; params: Array; constructor(database: Database, statement: string, values?: any) { this.database = database; this.statement = statement; this.params = values || []; } bind(...values: any) { // CFSQL-55 - Validate value types return new PreparedStatement(this.database, this.statement, values); } async first(colName?: string): Promise { const info = await this.database._send>("/query", this.statement, this.params); const results = info.results; if (results.length < 1) { throw new Error("D1_NORESULTS", { cause: new Error("No results") }); } const result = results[0]; if (colName !== undefined) { if (result[colName] === undefined) { throw new Error("D1_COLUMN_NOTFOUND", { cause: new Error(`Column not found`), }); } return result[colName]; } else { return result; } } async run(): Promise> { return this.database._send("/execute", this.statement, this.params); } async all(): Promise>> { return await this.database._send>("/query", this.statement, this.params); } async raw(): Promise> { const s = await this.database._send>("/query", this.statement, this.params); const raw = []; for (var r in s.results) { const entry = Object.keys(s.results[r]).map((k) => { return s.results[r][k]; }); raw.push(entry); } return raw; } }