d1-sql-tag
Version:
A template literal for working with Cloudflare D1 database
147 lines (146 loc) • 4.68 kB
JavaScript
// src/sql-tag.ts
var batchId = 0;
var rowTypeSymbol = Symbol("rowType");
function createD1SqlTag(db, options) {
const sqlTag = (strings, ...values) => {
const fragment = {
build() {
return buildPreparedStatement(db, options, strings, values);
},
all() {
return buildPreparedStatement(db, options, strings, values).all();
},
run() {
return buildPreparedStatement(db, options, strings, values).run();
},
templateStrings: strings,
templateValues: values
};
return fragment;
};
sqlTag.batch = async (statements) => {
const queries = statements.map((it) => it.query);
const id = makeBatchId();
options?.beforeQuery?.(id, queries);
const start = Date.now();
const result = await db.batch(statements.map((it) => makeNativeStatement(db, it)));
const duration = Date.now() - start;
options?.afterQuery?.(id, queries, result, duration);
for (let i = 0; i < result.length; i++) {
const statement = statements[i];
const statementResult = result[i];
if ("mapper" in statement) {
statementResult.results = statementResult.results.map(statement.mapper);
}
}
return result;
};
return sqlTag;
}
function buildPreparedStatement(db, options, templateStrings, templateValues) {
const { query, values } = expandTemplate(templateStrings, templateValues);
const statement = {
all() {
return executeAll(db, options, statement, null);
},
run() {
return executeRun(db, options, statement);
},
map(mapper) {
const mappedStatement = {
all() {
return executeAll(db, options, mappedStatement, mapper);
},
run() {
return executeRun(db, options, mappedStatement);
},
query,
values,
mapper,
[rowTypeSymbol]: null
};
return mappedStatement;
},
query,
values,
[rowTypeSymbol]: null
};
return statement;
}
function expandTemplate(rootTemplateStrings, rootTemplateValues) {
let query = "";
const values = [];
function expand(templateStrings, templateValues) {
for (let i = 0; i < templateStrings.length; i++) {
if (i > 0) {
const value = templateValues[i - 1];
const valueIsFragment = value && typeof value === "object" && "templateStrings" in value && "templateValues" in value;
if (valueIsFragment) {
expand(value.templateStrings, value.templateValues);
} else {
let valueIndex = values.indexOf(value);
if (valueIndex === -1) {
valueIndex = values.push(value) - 1;
}
query += `?${valueIndex + 1}`;
}
}
query += templateStrings[i];
}
}
expand(rootTemplateStrings, rootTemplateValues);
return { query, values };
}
async function executeAll(db, options, statement, mapper) {
const batchId2 = makeBatchId();
options?.beforeQuery?.(batchId2, [statement.query]);
const start = Date.now();
const result = await makeNativeStatement(db, statement).all();
const duration = Date.now() - start;
options?.afterQuery?.(batchId2, [statement.query], [result], duration);
if (mapper) {
result.results = result.results.map(mapper);
}
return result;
}
async function executeRun(db, options, statement) {
const batchId2 = makeBatchId();
options?.beforeQuery?.(batchId2, [statement.query]);
const start = Date.now();
const result = await makeNativeStatement(db, statement).run();
const duration = Date.now() - start;
options?.afterQuery?.(batchId2, [statement.query], [result], duration);
return result;
}
function makeNativeStatement(db, statement) {
let stmt = db.prepare(statement.query);
if (statement.values.length > 0) {
stmt = stmt.bind(...statement.values);
}
return stmt;
}
function makeBatchId() {
batchId += 1;
return batchId;
}
// src/logger.ts
function logQueryResults(queries, results, duration) {
console.log(
`D1 batch: ${typeof duration === "number" ? `${duration}ms \xB7 ` : ""}${queries.length} queries`
);
for (let i = 0; i < queries.length; i++) {
const query = queries[i];
const result = results[i];
console.log(`${i + 1}: ${cleanupSqlQuery(query)}`);
const logSuffix = "rows_read" in result.meta ? ` \xB7 ${result.meta.rows_read} read \xB7 ${result.meta.rows_written} written` : "";
console.log(` \u21B3 ${result.meta.duration}ms \xB7 ${result.meta.changes} changed` + logSuffix);
}
}
function cleanupSqlQuery(query) {
return query.replace(/\n/g, " ").replace(/\s+/g, " ");
}
export {
createD1SqlTag,
logQueryResults
};
//# sourceMappingURL=index.js.map