@variablesoftware/mock-d1
Version:
🎛️🗂️🧠 Mock D1 Database implementation for testing Cloudflare Workers
98 lines (97 loc) • 4.12 kB
JavaScript
/**
* @file engine/tableUtils/schemaUtils.ts
* @description Centralized schema validation and normalization for D1 mock.
*/
import { d1Error } from '../errors.js';
import { log } from '@variablesoftware/logface';
/**
* Validates a row against a schema (D1-compatible).
* Throws if extra columns are present or required columns are missing.
* @param columns - The schema columns array.
* @param row - The row to validate.
* @returns Result object.
*/
export function validateRowAgainstSchema(columns, row) {
if (process.env.DEBUG || process.env.MOCK_D1_DEBUG) {
log.debug('[validateRowAgainstSchema] called', { columns, row });
}
const rowCols = Object.keys(row);
log.debug('[validateRowAgainstSchema] rowCols', { rowCols });
log.debug('[validateRowAgainstSchema] columns', { columns });
const extra = rowCols.filter(k => {
let isQuoted = /^".*"$/.test(k);
let match = false;
if (isQuoted) {
// Only match quoted columns with exact name
const keyName = k.slice(1, -1);
log.debug('[validateRowAgainstSchema] quoted keyName', { k, keyName });
match = columns.some(c => c.quoted && c.name === keyName);
log.debug('[validateRowAgainstSchema] quoted key match', { key: k, keyName, match, columns });
}
else {
// Only match unquoted columns (case-insensitive)
const keyName = k.toLowerCase();
log.debug('[validateRowAgainstSchema] unquoted keyName', { k, keyName });
match = columns.some(c => {
if (!c.quoted) {
const colLower = c.name.toLowerCase();
log.debug('[validateRowAgainstSchema] compare', { keyName, colLower, eq: colLower === keyName, c });
return colLower === keyName;
}
return false;
});
log.debug('[validateRowAgainstSchema] unquoted key match', { key: k, keyName, match, columns });
}
log.debug('[validateRowAgainstSchema] key result', { k, isQuoted, match });
return !match;
});
if (extra.length > 0) {
log.debug('[validateRowAgainstSchema] extra columns', { extra, columns, row });
throw d1Error('EXTRA_COLUMNS', extra.join(', '));
}
if (process.env.DEBUG || process.env.MOCK_D1_DEBUG) {
log.debug('[validateRowAgainstSchema] validated OK', { columns, row });
}
return { result: true };
}
/**
* Normalizes a row to match the schema (D1-compatible).
* Fills missing columns with null.
* @param columns - The schema columns array.
* @param row - The row to normalize.
* @returns Normalized row.
*/
export function normalizeRowToSchema(columns, row) {
if (process.env.DEBUG || process.env.MOCK_D1_DEBUG) {
log.debug('[normalizeRowToSchema] called', { columns, row });
}
const normalized = {};
for (const col of columns) {
let value = null;
if (col.quoted) {
// Look for quoted key
const quotedKey = '"' + col.name + '"';
if (Object.prototype.hasOwnProperty.call(row, quotedKey)) {
value = row[quotedKey];
log.debug('[normalizeRowToSchema] found quoted key', { quotedKey, value });
}
else if (Object.prototype.hasOwnProperty.call(row, col.name)) {
value = row[col.name];
log.debug('[normalizeRowToSchema] found unquoted key for quoted col', { key: col.name, value });
}
}
else {
// Look for unquoted key (case-insensitive)
const matchKey = Object.keys(row).find(k => k.toLowerCase() === col.name.toLowerCase());
if (matchKey) {
value = row[matchKey];
log.debug('[normalizeRowToSchema] found unquoted key', { matchKey, value });
}
}
normalized[col.name] = value;
}
if (process.env.DEBUG || process.env.MOCK_D1_DEBUG) {
log.debug('[normalizeRowToSchema] normalized', { normalized });
}
return normalized;
}