@odatnurd/d1-query
Version:
Simple D1 query wrapper
130 lines (102 loc) • 4.45 kB
JavaScript
/******************************************************************************/
import { readFileSync } from 'fs';
import {
processSQLString,
SQLStatement,
SQLBindError,
mapBinds
} from '../lib/index.js';
/******************************************************************************/
// Define a unique ID for the virtual module that holds the preparation code for
// the imported SQL. Per the documentation on plugins, the convention is to lead
// with a null byte to make sure other plugins don't try to process it.
const HELPER_MODULE_ID = '\0d1-sql-helpers';
const HELPER_MODULE_PATH = new URL('./rollup-plugin-helper.js', import.meta.url).pathname;
/******************************************************************************/
/**
* A Rollup plugin that allows for importing a SQL file directly in code.
*
* At build time, it reads the SQL file, processes it using the same logic as
* the main library (except allowing for multiple statements), and embeds the
* rewritten SQL and its bind metadata into the generated module.
*
* The result of the import is a module with a default export of a function.
* When you call this function with a D1 database instance, it returns either a
* single prepared SQLStatement or an array of them, ready for execution.
*
* This function can also take optional binds, which will be applied to the
* statements. There needs to be either exactly 1 bind for each statement that
* requires them (and they wil be applied in order), or exactly one bindable
* statement and any number of binds, and you will get back as many bound
* copies of the statement as needed.
*/
export default function d1sql() {
const sqlRegex = /\.sql$/;
return {
name: 'd1-sql-import',
// Hook into the module resolver to see if someone is trying to resolve our
// particular module or not. Using our virtual ID, we signal the module name
// back if it's our module, so that we get invoked to load it.
resolveId(id) {
if (id === HELPER_MODULE_ID) {
return id;
}
// Defer to other resolvers.
return null;
},
load(id) {
// Produce the source code for our virtual module, if someone tries to
// load it.
if (id === HELPER_MODULE_ID) {
return readFileSync(HELPER_MODULE_PATH, 'utf-8');
}
// Skip non-SQL files
if (sqlRegex.test(id) === false) {
return null;
}
// Read the entire file content as a single string, then process it into
// as many statements as the file content happens to contain. This gets
// us an array of objects that contain the modified SQL and the metadata
// on their binds (if any).
const sqlContent = readFileSync(id, 'utf8');
const statements = processSQLString(sqlContent, true);
// Determine which of the statements in the processed statement array take
// bind arguments.
const bindableIndices = statements
.map((info, index) => info.bindMetadata.argCount > 0 ? index : -1)
.filter(index => index !== -1);
// Generate module output for the file; this uses the helper module to
// do the heavy lifting.
return {
code: `
import { prepare } from '${HELPER_MODULE_ID}';
import { dbFetch, dbFetchOne, dbFetchFirst } from '@odatnurd/d1-query';
const sqlInfo = ${JSON.stringify(statements)};
const bindables = ${JSON.stringify(bindableIndices)};
export function statements(db, ...binds) {
return prepare(db, sqlInfo, bindables, ...binds);
}
export async function fetch(db, action, ...binds) {
const prepared = statements(db, ...binds);
return dbFetch(db, action, ...(Array.isArray(prepared) ? prepared : [prepared]));
}
export async function fetchOne(db, action, ...binds) {
const prepared = statements(db, ...binds);
return dbFetchOne(db, action, ...(Array.isArray(prepared) ? prepared : [prepared]));
}
export async function fetchFirst(db, action, ...binds) {
const prepared = statements(db, ...binds);
return dbFetchFirst(db, action, ...(Array.isArray(prepared) ? prepared : [prepared]));
}
export async function execute(db, action, ...binds) {
const prepared = statements(db, ...binds);
await dbFetch(db, action, ...(Array.isArray(prepared) ? prepared : [prepared]));
}
export default statements;
`,
map: null
};
}
};
}
/******************************************************************************/