import postgres from "postgres";
import { types as nodeTypes } from "node:util";
import { createRequire } from "node:module";
import { eq as drizzleEq, getTableColumns, getTableName, isTable, sql as drizzleSql } from "drizzle-orm";
import { BetterSQLiteSession } from "drizzle-orm/better-sqlite3/session";
import { drizzle as drizzlePostgres } from "drizzle-orm/postgres-js";
import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core/db";
import { SQLiteSyncDialect } from "drizzle-orm/sqlite-core/dialect";
import { databaseConfig } from "../../tools/database-url.mjs";
import { loadNodeSqlite } from "../../tools/node-sqlite.mjs";
let cached = null;
let cachedDialect = null;
const tablePolicies = new WeakMap();
const predicatePolicies = new WeakMap();
const principalContexts = new WeakMap();
let principalAuthorityInstalled = false;
let scopedAccessAudit = null;
const DATE_PROTOTYPE = Date.prototype;
const DATE_GET_TIME = Date.prototype.getTime;
const DATE_GET_UTC_FULL_YEAR = Date.prototype.getUTCFullYear;
const POSTGRES_SMALLINT_MIN = -32_768;
const POSTGRES_SMALLINT_MAX = 32_767;
const POSTGRES_INTEGER_MIN = -2_147_483_648;
const POSTGRES_INTEGER_MAX = 2_147_483_647;
const POSTGRES_BIGINT_MIN = -9_223_372_036_854_775_808n;
const POSTGRES_BIGINT_MAX = 9_223_372_036_854_775_807n;
const POSTGRES_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const POSTGRES_DATE_PATTERN = /^(\d{4,7})-(\d{2})-(\d{2})( BC)?$/;
const POSTGRES_TIME_PATTERN = /^(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?(.*)$/;
const POSTGRES_TIMESTAMP_PATTERN = /^(\d{4,6})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?(.*)$/;
const POSTGRES_TIMEZONE_PATTERN = /^([+-])(\d{2})(?::(\d{2})(?::(\d{2}))?)?$/;
const POSTGRES_MACADDR_PATTERN = /^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$/i;
const POSTGRES_MACADDR8_PATTERN = /^(?:[0-9a-f]{2}:){7}[0-9a-f]{2}$/i;
const MYSQL_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
const MYSQL_DATE_TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?$/;
const MYSQL_TIME_PATTERN = /^(-?)(\d{1,3}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?$/;
const MYSQL_TEMPORAL_MIN_YEAR = 1000;
const MYSQL_TEMPORAL_MAX_YEAR = 9999;
const MYSQL_TIMESTAMP_MIN = "1970-01-01 00:00:01";
const MYSQL_TIMESTAMP_MAX = "2038-01-19 03:14:07";
const MYSQL_TIMESTAMP_MIN_MILLISECONDS = 1_000;
const MYSQL_TIMESTAMP_MAX_EXCLUSIVE_MILLISECONDS = 2_147_483_648_000;
const MYSQL_COLUMN_TYPES = new Set([
"MySqlBigInt53",
"MySqlBigInt64",
"MySqlBinary",
"MySqlBoolean",
"MySqlChar",
"MySqlDate",
"MySqlDateString",
"MySqlDateTime",
"MySqlDateTimeString",
"MySqlDecimal",
"MySqlDecimalBigInt",
"MySqlDecimalNumber",
"MySqlDouble",
"MySqlEnumColumn",
"MySqlEnumObjectColumn",
"MySqlFloat",
"MySqlInt",
"MySqlJson",
"MySqlMediumInt",
"MySqlReal",
"MySqlSerial",
"MySqlSmallInt",
"MySqlText",
"MySqlTime",
"MySqlTimestamp",
"MySqlTimestampString",
"MySqlTinyInt",
"MySqlVarBinary",
"MySqlVarChar",
"MySqlYear",
]);
const SQLITE_COLUMN_TYPES = new Set([
"SQLiteBigInt",
"SQLiteBlobBuffer",
"SQLiteBlobJson",
"SQLiteBoolean",
"SQLiteInteger",
"SQLiteNumeric",
"SQLiteNumericBigInt",
"SQLiteNumericNumber",
"SQLiteReal",
"SQLiteText",
"SQLiteTextJson",
"SQLiteTimestamp",
]);
class NodeSqliteStatement {
constructor(client, source) {
this.objectStatement = client.prepare(source);
this.arrayStatement = client.prepare(source);
this.arrayStatement.setReturnArrays(true);
}
run(...parameters) {
return this.objectStatement.run(...parameters);
}
all(...parameters) {
return this.objectStatement.all(...parameters);
}
get(...parameters) {
return this.objectStatement.get(...parameters);
}
raw() {
return Object.freeze({
all: (...parameters) => this.arrayStatement.all(...parameters),
get: (...parameters) => this.arrayStatement.get(...parameters),
});
}
}
class NodeSqliteClient {
constructor(filename) {
const { DatabaseSync } = loadNodeSqlite();
this.connection = new DatabaseSync(filename);
this.connection.exec("PRAGMA foreign_keys = ON");
this.connection.exec("PRAGMA busy_timeout = 10000");
}
prepare(source) {
return new NodeSqliteStatement(this.connection, source);
}
exec(source) {
return this.connection.exec(source);
}
transaction(action) {
const run = (behavior, ...parameters) => {
this.connection.exec(`BEGIN ${behavior}`);
try {
const result = action(...parameters);
this.connection.exec("COMMIT");
return result;
} catch (error) {
try {
this.connection.exec("ROLLBACK");
} catch {
}
throw error;
}
};
return Object.freeze({
deferred: (...parameters) => run("DEFERRED", ...parameters),
immediate: (...parameters) => run("IMMEDIATE", ...parameters),
exclusive: (...parameters) => run("EXCLUSIVE", ...parameters),
});
}
close() {
this.connection.close();
}
}
function drizzleNodeSqlite(client) {
const dialect = new SQLiteSyncDialect();
const session = new BetterSQLiteSession(client, dialect, undefined);
const db = new BaseSQLiteDatabase("sync", dialect, session, undefined);
db.$client = client;
return db;
}
export class DatabaseRowDriftError extends Error {
constructor({ table, column, rowIndex, expected, received }) {
super(
`error[DATABASE_ROW_DRIFT]: database row drift at ${table}.${column} (row ${rowIndex}): expected ${expected}, received ${received}`,
);
this.name = "DatabaseRowDriftError";
this.code = "DATABASE_ROW_DRIFT";
this.expose = true;
this.table = table;
this.column = column;
this.rowIndex = rowIndex;
this.expected = expected;
this.received = received;
}
}
export class DataScopeViolationError extends Error {
constructor(policy, reason) {
super(
`error[DATA_SCOPE_VIOLATION]: ${policy.table} requires ${policy.principalColumn} bound to the runtime request principal; ${reason}`,
);
this.name = "DataScopeViolationError";
this.code = "DATA_SCOPE_VIOLATION";
this.expose = true;
this.table = policy.table;
this.principalColumn = policy.principalColumn;
}
}
export function eq(left, right) {
const predicate = drizzleEq(left, right);
predicatePolicies.set(predicate, Object.freeze({ left, right }));
return predicate;
}
export function __installNoxidPrincipalAuthority(audit = null) {
if (principalAuthorityInstalled) {
throw new Error(
"error[DATA_PRINCIPAL_AUTHORITY_DUPLICATE]: the generated server runtime must be the sole principal authority",
);
}
if (audit !== null && typeof audit !== "function") {
throw new TypeError(
"error[DATA_PRINCIPAL_AUTHORITY_INVALID]: the generated data audit hook must be a function",
);
}
principalAuthorityInstalled = true;
scopedAccessAudit = audit;
return Object.freeze({
bind(context, principal) {
if (context === null || typeof context !== "object" || !Object.isFrozen(context)) {
throw new TypeError("error[DATA_PRINCIPAL_CONTEXT_INVALID]: principal contexts must be frozen runtime objects");
}
principalContexts.set(context, snapshotPrincipal(principal));
return context;
},
});
}
function declaredTable(table, helper) {
if (!isTable(table)) {
throw new TypeError(
`error[DATABASE_SCHEMA_REQUIRED]: ${helper} requires a declared Drizzle table`,
);
}
if (tablePolicies.has(table)) {
throw new TypeError(
`error[DATA_POLICY_DUPLICATE]: ${getTableName(table)} already has a data policy; keep exactly one scopedTable or unscopedTable declaration`,
);
}
return table;
}
export function scopedTable(table, principalColumn) {
declaredTable(table, "scopedTable");
if (typeof principalColumn !== "string" || principalColumn.length === 0) {
throw new TypeError(
"error[DATA_POLICY_INVALID]: scopedTable requires the physical principal column name",
);
}
const columnEntry = Object.entries(getTableColumns(table)).find(
([, candidate]) => candidate.name === principalColumn,
);
if (columnEntry === undefined) {
throw new TypeError(
`error[DATA_POLICY_INVALID]: ${getTableName(table)} has no declared physical column ${principalColumn}; pass the SQL column name used in the Drizzle declaration`,
);
}
tablePolicies.set(
table,
Object.freeze({
table: getTableName(table),
policy: "scoped",
principalColumn,
columnKey: columnEntry[0],
column: columnEntry[1],
}),
);
return table;
}
export function unscopedTable(table) {
declaredTable(table, "unscopedTable");
tablePolicies.set(
table,
Object.freeze({
table: getTableName(table),
policy: "unscoped",
principalColumn: null,
columnKey: null,
column: null,
}),
);
return table;
}
function receivedType(value) {
if (value === null) return "null";
if (value === undefined) return "undefined";
if (nodeTypes.isProxy(value)) return "Proxy";
if (Array.isArray(value)) return "array";
const dateValue = inspectDate(value);
if (dateValue.branded) {
return dateValue.valid ? "Date" : "invalid Date";
}
if (value instanceof Uint8Array) return "Uint8Array";
return typeof value;
}
function inspectDate(value) {
if (!nodeTypes.isDate(value)) {
return { branded: false, ordinary: false, valid: false };
}
try {
return {
branded: true,
ordinary: Object.getPrototypeOf(value) === DATE_PROTOTYPE,
valid: !Number.isNaN(DATE_GET_TIME.call(value)),
};
} catch {
return { branded: true, ordinary: false, valid: false };
}
}
function hasUntrustedInheritedProperty(value, property) {
let prototype = Object.getPrototypeOf(value);
while (prototype !== null) {
if (
nodeTypes.isProxy(prototype) ||
Object.getOwnPropertyDescriptor(prototype, property) !== undefined
) {
return true;
}
prototype = Object.getPrototypeOf(prototype);
}
return false;
}
function arrayAccepts(value, elementAccepts) {
if (
nodeTypes.isProxy(value) || !Array.isArray(value) ||
Object.getPrototypeOf(value) !== Array.prototype ||
hasUntrustedInheritedProperty(value, "toJSON")
) {
return false;
}
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
if (
lengthDescriptor === undefined || lengthDescriptor.get !== undefined ||
lengthDescriptor.set !== undefined || !Number.isSafeInteger(lengthDescriptor.value) ||
lengthDescriptor.value < 0
) {
return false;
}
const length = lengthDescriptor.value;
const ownKeys = Reflect.ownKeys(value);
if (ownKeys.length !== length + 1 || !ownKeys.includes("length")) return false;
for (let index = 0; index < length; index += 1) {
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
if (
descriptor === undefined || descriptor.enumerable !== true ||
descriptor.get !== undefined || descriptor.set !== undefined ||
!elementAccepts(descriptor.value)
) {
return false;
}
}
return true;
}
function isJsonValue(value, seen = new Set()) {
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
if (typeof value === "number") return Number.isFinite(value);
if (typeof value !== "object" || nodeTypes.isProxy(value) || seen.has(value)) return false;
seen.add(value);
if (Array.isArray(value)) {
const valid = arrayAccepts(value, (entry) => isJsonValue(entry, seen));
seen.delete(value);
return valid;
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== Object.prototype && prototype !== null) {
seen.delete(value);
return false;
}
if (hasUntrustedInheritedProperty(value, "toJSON")) {
seen.delete(value);
return false;
}
const keys = Reflect.ownKeys(value);
const valid = keys.every(
(key) => {
if (typeof key !== "string") return false;
const descriptor = Object.getOwnPropertyDescriptor(value, key);
return descriptor?.enumerable === true && descriptor.get === undefined && descriptor.set === undefined &&
isJsonValue(descriptor?.value, seen);
},
);
seen.delete(value);
return valid;
}
function mysqlUnsigned(column) {
return column.unsigned === true || column.config?.unsigned === true;
}
function mysqlColumnExpectation(column) {
const nullable = column.notNull ? "" : " or null";
const unsigned = mysqlUnsigned(column) ? " unsigned" : "";
switch (column.columnType) {
case "MySqlTinyInt":
return `${unsigned ? "unsigned 8-bit" : "signed 8-bit"} integer number${nullable}`;
case "MySqlSmallInt":
return `${unsigned ? "unsigned 16-bit" : "signed 16-bit"} integer number${nullable}`;
case "MySqlMediumInt":
return `${unsigned ? "unsigned 24-bit" : "signed 24-bit"} integer number${nullable}`;
case "MySqlInt":
return `${unsigned ? "unsigned 32-bit" : "signed 32-bit"} integer number${nullable}`;
case "MySqlBigInt53":
return `safe integer number in the MySQL bigint${unsigned} range${nullable}`;
case "MySqlBigInt64":
return `BigInt in the MySQL bigint${unsigned} range${nullable}`;
case "MySqlSerial":
return `safe non-negative integer number mapped from MySQL serial${nullable}`;
case "MySqlYear":
return `MySQL YEAR number (0 or 1901 through 2155)${nullable}`;
case "MySqlDateString":
return `a valid MySQL date string from 1000-01-01 through 9999-12-31${nullable}`;
case "MySqlDate":
return `a Date in the MySQL DATE range 1000-01-01 through 9999-12-31${nullable}`;
case "MySqlDateTimeString":
return `a valid MySQL datetime string from 1000-01-01 through 9999-12-31 at the declared fractional precision${nullable}`;
case "MySqlDateTime":
return `a Date in the MySQL DATETIME range 1000-01-01 through 9999-12-31${nullable}`;
case "MySqlTimestampString":
return `a valid MySQL timestamp string from 1970-01-01 00:00:01 UTC through 2038-01-19 03:14:07 UTC at the declared fractional precision${nullable}`;
case "MySqlTimestamp":
return `a Date in the MySQL TIMESTAMP range 1970-01-01 00:00:01 UTC through 2038-01-19 03:14:07 UTC${nullable}`;
case "MySqlTime":
return `a valid MySQL time string between -838:59:59 and 838:59:59${nullable}`;
case "MySqlDecimal":
case "MySqlDecimalNumber":
case "MySqlDecimalBigInt":
return `MySQL decimal mapped as ${column.dataType} with precision ${column.precision ?? 10} and scale ${column.scale ?? 0}${unsigned}${nullable}`;
case "MySqlBinary":
return `string mapped from MySQL binary${Number.isSafeInteger(column.length) ? `(${column.length})` : ""}${nullable}`;
case "MySqlVarBinary":
return `string mapped from MySQL varbinary${Number.isSafeInteger(column.length) ? `(${column.length})` : ""}${nullable}`;
default: {
const sqlType = typeof column.getSQLType === "function"
? column.getSQLType()
: column.columnType;
return `${sqlType} mapped as ${column.dataType}${nullable}`;
}
}
}
function sqliteColumnExpectation(column) {
const nullable = column.notNull ? "" : " or null";
switch (column.columnType) {
case "SQLiteInteger":
return `safe integer number mapped from SQLite INTEGER affinity${nullable}`;
case "SQLiteBoolean":
return `boolean mapped by Drizzle from SQLite INTEGER 0 or 1${nullable}`;
case "SQLiteTimestamp":
return `valid ordinary Date mapped from a SQLite INTEGER ${column.mode === "timestamp" ? "Unix-seconds" : "Unix-milliseconds"} value${nullable}`;
case "SQLiteReal":
return `finite number mapped from SQLite REAL affinity${nullable}`;
case "SQLiteNumeric":
return `finite decimal string mapped from SQLite NUMERIC affinity${nullable}`;
case "SQLiteNumericNumber":
return `finite number mapped from SQLite NUMERIC affinity${nullable}`;
case "SQLiteNumericBigInt":
return `BigInt mapped from an integral SQLite NUMERIC value${nullable}`;
case "SQLiteText":
return `string mapped from SQLite TEXT affinity${Number.isSafeInteger(column.length) ? ` with at most ${column.length} characters` : ""}${nullable}`;
case "SQLiteBigInt":
return `BigInt mapped from Drizzle's decimal-text SQLite BLOB representation${nullable}`;
case "SQLiteBlobBuffer":
return `Uint8Array mapped from SQLite BLOB affinity${nullable}`;
case "SQLiteBlobJson":
return `plain JSON value decoded from SQLite BLOB affinity${nullable}`;
case "SQLiteTextJson":
return `plain JSON value decoded from SQLite TEXT affinity${nullable}`;
default:
return `an explicitly mapped drizzle sqlite-core value${nullable}`;
}
}
function columnExpectation(column) {
if (MYSQL_COLUMN_TYPES.has(column.columnType)) {
return mysqlColumnExpectation(column);
}
if (SQLITE_COLUMN_TYPES.has(column.columnType)) {
return sqliteColumnExpectation(column);
}
let mappedType = column.dataType === "array"
? `array<${columnExpectation(column.baseColumn)}>`
: column.dataType;
const sqlType = typeof column.getSQLType === "function" ? column.getSQLType() : column.columnType;
if (column.dataType === "number") {
switch (sqlType) {
case "smallint":
case "smallserial":
mappedType = "number (signed 16-bit integer)";
break;
case "integer":
case "serial":
mappedType = "number (signed 32-bit integer)";
break;
case "bigint":
case "bigserial":
mappedType = "number (safe signed 64-bit integer)";
break;
default:
if (
sqlType.startsWith("numeric(") &&
Number.isSafeInteger(column.precision)
) {
mappedType = `number with precision ${column.precision} and scale ${column.scale ?? 0}`;
}
break;
}
} else if (
["string", "bigint"].includes(column.dataType) &&
sqlType.startsWith("numeric(") && Number.isSafeInteger(column.precision)
) {
mappedType = `${column.dataType === "bigint" ? "BigInt" : "string"} with precision ${column.precision} and scale ${column.scale ?? 0}`;
} else if (column.dataType === "string" && sqlType === "uuid") {
mappedType = "UUID string in 8-4-4-4-12 hexadecimal form";
} else if (
column.dataType === "string" && sqlType === "date" &&
column.columnType === "PgDateString"
) {
mappedType = "a lexically and semantically valid PostgreSQL date string";
} else if (
column.dataType === "string" && sqlType.startsWith("time") &&
column.columnType === "PgTime"
) {
mappedType = column.withTimezone
? "a valid PostgreSQL time string with a numeric time-zone offset"
: "a valid PostgreSQL time string without a time-zone offset";
} else if (
column.dataType === "string" && sqlType.startsWith("timestamp") &&
column.columnType === "PgTimestampString"
) {
mappedType = "a lexically and semantically valid PostgreSQL timestamp string";
} else if (
column.dataType === "string" && sqlType.startsWith("char(") &&
Number.isSafeInteger(column.length)
) {
mappedType = `string containing exactly ${column.length} characters`;
} else if (column.dataType === "string" && sqlType === "inet") {
mappedType = "a valid PostgreSQL IPv4 or IPv6 inet string";
} else if (column.dataType === "string" && sqlType === "cidr") {
mappedType = "a valid PostgreSQL IPv4 or IPv6 network string";
} else if (column.dataType === "string" && sqlType === "macaddr") {
mappedType = "a canonical six-octet PostgreSQL MAC address string";
} else if (column.dataType === "string" && sqlType === "macaddr8") {
mappedType = "a canonical eight-octet PostgreSQL MAC address string";
} else if (
column.dataType === "string" && column.columnType === "PgBinaryVector" &&
Number.isSafeInteger(column.dimensions)
) {
mappedType = `a PostgreSQL bit string containing exactly ${column.dimensions} binary digits`;
} else if (column.dataType === "string" && column.columnType === "PgInterval") {
mappedType = "an explicitly modeled PostgreSQL interval representation (currently unsupported)";
} else if (column.dataType === "bigint" && ["bigint", "bigserial"].includes(sqlType)) {
mappedType = "BigInt in the signed 64-bit range";
}
return `${sqlType} mapped as ${mappedType}${column.notNull ? "" : " or null"}`;
}
function decimalParts(value, expansionLimit) {
const match = /^(-?)(\d+)(?:\.(\d+))?(?:e([+-]?\d+))?$/.exec(String(value));
if (match === null) return null;
const exponent = match[4] === undefined ? 0 : Number(match[4]);
if (!Number.isSafeInteger(exponent)) return null;
const digits = `${match[2]}${match[3] ?? ""}`;
const decimalIndex = match[2].length + exponent;
if (
(decimalIndex <= 0 && -decimalIndex > expansionLimit) ||
(decimalIndex >= digits.length && decimalIndex - digits.length > expansionLimit)
) {
return null;
}
let integer;
let fraction;
if (decimalIndex <= 0) {
integer = "0";
fraction = `${"0".repeat(-decimalIndex)}${digits}`;
} else if (decimalIndex >= digits.length) {
integer = `${digits}${"0".repeat(decimalIndex - digits.length)}`;
fraction = "";
} else {
integer = digits.slice(0, decimalIndex);
fraction = digits.slice(decimalIndex);
}
return {
integer: integer.replace(/^0+/, ""),
fraction: fraction.replace(/0+$/, ""),
};
}
function numericColumnAccepts(column, value) {
const isMySqlDecimal = [
"MySqlDecimal",
"MySqlDecimalNumber",
"MySqlDecimalBigInt",
].includes(column.columnType);
const precision = isMySqlDecimal ? column.precision ?? 10 : column.precision;
const scale = isMySqlDecimal ? column.scale ?? 0 : column.scale ?? 0;
if (!Number.isSafeInteger(precision)) {
return true;
}
const parts = decimalParts(value, precision + Math.abs(scale) + 1);
if (parts === null) return false;
if (scale < 0) {
const roundedPlaces = -scale;
if (parts.fraction.length > 0 || parts.integer.length <= roundedPlaces) {
return parts.integer.length === 0;
}
return parts.integer.endsWith("0".repeat(roundedPlaces)) &&
parts.integer.length - roundedPlaces <= precision;
}
if (parts.fraction.length > scale) return false;
const integralCapacity = precision - scale;
if (integralCapacity >= 0) return parts.integer.length <= integralCapacity;
if (parts.integer.length > 0 || parts.fraction.length === 0) return parts.integer.length === 0;
const leadingFractionalZeros = parts.fraction.length - parts.fraction.replace(/^0+/, "").length;
return leadingFractionalZeros >= -integralCapacity;
}
function mysqlDateStringAccepts(value) {
const match = MYSQL_DATE_PATTERN.exec(value);
if (match === null) return false;
const year = Number(match[1]);
return year >= MYSQL_TEMPORAL_MIN_YEAR && year <= MYSQL_TEMPORAL_MAX_YEAR &&
calendarDateAccepts(year, Number(match[2]), Number(match[3]));
}
function mysqlFractionAccepts(column, fraction) {
const precision = column.fsp ?? 0;
return fraction === undefined || fraction.length <= precision;
}
function mysqlDateTimeStringAccepts(column, value, timestamp) {
const match = MYSQL_DATE_TIME_PATTERN.exec(value);
if (match === null) return false;
const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[3]);
const hour = Number(match[4]);
const minute = Number(match[5]);
const second = Number(match[6]);
if (
year < MYSQL_TEMPORAL_MIN_YEAR || year > MYSQL_TEMPORAL_MAX_YEAR ||
!calendarDateAccepts(year, month, day) ||
hour > 23 || minute > 59 || second > 59 ||
!mysqlFractionAccepts(column, match[7])
) {
return false;
}
if (!timestamp) return true;
const wholeSeconds = value.slice(0, 19);
return wholeSeconds >= MYSQL_TIMESTAMP_MIN &&
wholeSeconds <= MYSQL_TIMESTAMP_MAX;
}
function mysqlDateColumnAccepts(column, value) {
const dateValue = inspectDate(value);
if (!dateValue.branded || !dateValue.ordinary || !dateValue.valid) return false;
if (column.columnType === "MySqlTimestamp") {
const milliseconds = DATE_GET_TIME.call(value);
return milliseconds >= MYSQL_TIMESTAMP_MIN_MILLISECONDS &&
milliseconds < MYSQL_TIMESTAMP_MAX_EXCLUSIVE_MILLISECONDS;
}
if (["MySqlDate", "MySqlDateTime"].includes(column.columnType)) {
const year = DATE_GET_UTC_FULL_YEAR.call(value);
return year >= MYSQL_TEMPORAL_MIN_YEAR && year <= MYSQL_TEMPORAL_MAX_YEAR;
}
return false;
}
function mysqlTimeStringAccepts(column, value) {
const match = MYSQL_TIME_PATTERN.exec(value);
if (match === null) return false;
const hours = Number(match[2]);
const minutes = Number(match[3]);
const seconds = Number(match[4]);
return hours <= 838 && minutes <= 59 && seconds <= 59 &&
mysqlFractionAccepts(column, match[5]);
}
function mysqlNumberColumnAccepts(column, value) {
if (typeof value !== "number" || !Number.isFinite(value)) return false;
const unsigned = mysqlUnsigned(column);
const ranges = {
MySqlTinyInt: unsigned ? [0, 255] : [-128, 127],
MySqlSmallInt: unsigned ? [0, 65_535] : [-32_768, 32_767],
MySqlMediumInt: unsigned ? [0, 16_777_215] : [-8_388_608, 8_388_607],
MySqlInt: unsigned ? [0, 4_294_967_295] : [-2_147_483_648, 2_147_483_647],
};
const range = ranges[column.columnType];
if (range !== undefined) {
return Number.isInteger(value) && value >= range[0] && value <= range[1];
}
if (column.columnType === "MySqlBigInt53") {
return Number.isSafeInteger(value) && (unsigned ? value >= 0 : true);
}
if (column.columnType === "MySqlSerial") {
return Number.isSafeInteger(value) && value >= 0;
}
if (column.columnType === "MySqlYear") {
return Number.isInteger(value) && (value === 0 || (value >= 1901 && value <= 2155));
}
if (column.columnType === "MySqlDecimalNumber") {
return (!unsigned || value >= 0) && numericColumnAccepts(column, value);
}
if (["MySqlFloat", "MySqlDouble", "MySqlReal"].includes(column.columnType)) {
return !unsigned || value >= 0;
}
return false;
}
function isLeapYear(year) {
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
}
function calendarDateAccepts(year, month, day) {
if (month < 1 || month > 12) return false;
const daysInMonth = [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
return day >= 1 && day <= daysInMonth[month - 1];
}
function dateStringAccepts(value) {
if (value === "infinity" || value === "-infinity") return true;
const match = POSTGRES_DATE_PATTERN.exec(value);
if (match === null) return false;
const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[3]);
const isBc = match[4] !== undefined;
if (year < 1 || year > (isBc ? 4714 : 5_874_897)) return false;
if (isBc && year === 4714 && (month < 11 || (month === 11 && day < 24))) return false;
return calendarDateAccepts(isBc ? 1 - year : year, month, day);
}
function timezoneSuffixAccepts(suffix) {
const timezone = POSTGRES_TIMEZONE_PATTERN.exec(suffix);
if (timezone === null) return false;
const hour = Number(timezone[2]);
const minute = timezone[3] === undefined ? 0 : Number(timezone[3]);
const second = timezone[4] === undefined ? 0 : Number(timezone[4]);
return hour <= 15 && minute <= 59 && second <= 59;
}
function timeStringAccepts(column, value) {
const match = POSTGRES_TIME_PATTERN.exec(value);
if (match === null) return false;
const hour = Number(match[1]);
const minute = Number(match[2]);
const second = Number(match[3]);
const fraction = match[4];
const suffix = match[5];
if (hour > 24 || minute > 59 || second > 59) return false;
if (
hour === 24 &&
(minute !== 0 || second !== 0 || (fraction !== undefined && /[1-9]/.test(fraction)))
) {
return false;
}
const precision = column.precision === undefined ? 6 : column.precision;
if (fraction !== undefined && fraction.length > precision) return false;
return column.withTimezone ? timezoneSuffixAccepts(suffix) : suffix === "";
}
function ipv4Bytes(value) {
const parts = value.split(".");
if (parts.length !== 4) return null;
const bytes = [];
for (const part of parts) {
if (!/^(?:0|[1-9]\d{0,2})$/.test(part)) return null;
const byte = Number(part);
if (byte > 255) return null;
bytes.push(byte);
}
return bytes;
}
function ipv6Bytes(value) {
let expanded = value;
if (value.includes(".")) {
const lastColon = value.lastIndexOf(":");
if (lastColon < 0) return null;
const embedded = ipv4Bytes(value.slice(lastColon + 1));
if (embedded === null) return null;
const high = ((embedded[0] << 8) | embedded[1]).toString(16);
const low = ((embedded[2] << 8) | embedded[3]).toString(16);
expanded = `${value.slice(0, lastColon)}:${high}:${low}`;
}
const compressed = expanded.includes("::");
if (compressed && expanded.indexOf("::") !== expanded.lastIndexOf("::")) return null;
const halves = compressed ? expanded.split("::") : [expanded];
const left = halves[0] === "" ? [] : halves[0].split(":");
const right = !compressed || halves[1] === "" ? [] : halves[1].split(":");
const groups = [...left, ...right];
if (
groups.some((group) => !/^[0-9a-f]{1,4}$/i.test(group)) ||
(compressed ? groups.length >= 8 : groups.length !== 8)
) {
return null;
}
const zeroGroups = compressed ? 8 - groups.length : 0;
const words = [
...left.map((group) => Number.parseInt(group, 16)),
...Array(zeroGroups).fill(0),
...right.map((group) => Number.parseInt(group, 16)),
];
return words.flatMap((word) => [word >> 8, word & 0xff]);
}
function networkStringAccepts(value, isCidr) {
const firstSlash = value.indexOf("/");
if (firstSlash !== value.lastIndexOf("/")) return false;
const address = firstSlash < 0 ? value : value.slice(0, firstSlash);
const prefixText = firstSlash < 0 ? null : value.slice(firstSlash + 1);
if (isCidr && prefixText === null) return false;
const bytes = address.includes(":") ? ipv6Bytes(address) : ipv4Bytes(address);
if (bytes === null) return false;
if (prefixText === null) return true;
if (!/^(?:0|[1-9]\d{0,2})$/.test(prefixText)) return false;
const prefix = Number(prefixText);
const bitLength = bytes.length * 8;
if (prefix > bitLength) return false;
if (!isCidr) return true;
for (let bit = prefix; bit < bitLength; bit += 1) {
if ((bytes[Math.floor(bit / 8)] & (1 << (7 - (bit % 8)))) !== 0) return false;
}
return true;
}
function timestampStringAccepts(column, value) {
if (value === "infinity" || value === "-infinity") return true;
const match = POSTGRES_TIMESTAMP_PATTERN.exec(value);
if (match === null) return false;
const [, yearText, monthText, dayText, hourText, minuteText, secondText, fraction, suffix] = match;
const year = Number(yearText);
const month = Number(monthText);
const day = Number(dayText);
const hour = Number(hourText);
const minute = Number(minuteText);
const second = Number(secondText);
const timezonePattern = /^([+-])(\d{2})(?::(\d{2})(?::(\d{2}))?)?( BC)?$/;
const timezone = timezonePattern.exec(suffix);
const isBc = column.withTimezone ? timezone?.[5] !== undefined : suffix === " BC";
if (column.withTimezone ? timezone === null : suffix !== "" && suffix !== " BC") return false;
if (year < 1 || year > (isBc ? 4713 : 294_276)) return false;
if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59) return false;
const calendarYear = isBc ? 1 - year : year;
if (!calendarDateAccepts(calendarYear, month, day)) return false;
const precision = column.precision === undefined ? 6 : column.precision;
if (fraction !== undefined && fraction.length > precision) return false;
if (timezone !== null) {
const timezoneHour = Number(timezone[2]);
const timezoneMinute = timezone[3] === undefined ? 0 : Number(timezone[3]);
const timezoneSecond = timezone[4] === undefined ? 0 : Number(timezone[4]);
if (timezoneHour > 15 || timezoneMinute > 59 || timezoneSecond > 59) return false;
}
return true;
}
function numberColumnAccepts(column, value) {
if (MYSQL_COLUMN_TYPES.has(column.columnType)) {
return mysqlNumberColumnAccepts(column, value);
}
if (typeof value !== "number" || !Number.isFinite(value)) return false;
const sqlType = typeof column.getSQLType === "function" ? column.getSQLType() : column.columnType;
switch (sqlType) {
case "smallint":
case "smallserial":
return Number.isInteger(value) &&
value >= POSTGRES_SMALLINT_MIN && value <= POSTGRES_SMALLINT_MAX;
case "integer":
case "serial":
return Number.isInteger(value) &&
value >= POSTGRES_INTEGER_MIN && value <= POSTGRES_INTEGER_MAX;
case "bigint":
case "bigserial":
return Number.isSafeInteger(value) &&
BigInt(value) >= POSTGRES_BIGINT_MIN && BigInt(value) <= POSTGRES_BIGINT_MAX;
default:
if (sqlType.startsWith("numeric(")) return numericColumnAccepts(column, value);
return true;
}
}
function stringColumnAccepts(column, value) {
if (typeof value !== "string") return false;
if (
Array.isArray(column.enumValues) && column.enumValues.length > 0 &&
!column.enumValues.includes(value)
) {
return false;
}
if (MYSQL_COLUMN_TYPES.has(column.columnType)) {
switch (column.columnType) {
case "MySqlDateString":
return mysqlDateStringAccepts(value);
case "MySqlDateTimeString":
return mysqlDateTimeStringAccepts(column, value, false);
case "MySqlTimestampString":
return mysqlDateTimeStringAccepts(column, value, true);
case "MySqlTime":
return mysqlTimeStringAccepts(column, value);
case "MySqlDecimal":
return (!mysqlUnsigned(column) || !value.startsWith("-")) &&
numericColumnAccepts(column, value);
case "MySqlChar":
case "MySqlVarChar":
return !Number.isSafeInteger(column.length) ||
[...value].length <= column.length;
case "MySqlBinary":
return !Number.isSafeInteger(column.length) ||
Buffer.byteLength(value) === column.length;
case "MySqlVarBinary":
return !Number.isSafeInteger(column.length) ||
Buffer.byteLength(value) <= column.length;
case "MySqlEnumColumn":
case "MySqlEnumObjectColumn":
case "MySqlText":
return true;
default:
return false;
}
}
const sqlType = typeof column.getSQLType === "function" ? column.getSQLType() : column.columnType;
if (sqlType === "uuid") return POSTGRES_UUID_PATTERN.test(value);
if (sqlType === "date" && column.columnType === "PgDateString") {
return dateStringAccepts(value);
}
if (sqlType.startsWith("time") && column.columnType === "PgTime") {
return timeStringAccepts(column, value);
}
if (sqlType.startsWith("timestamp") && column.columnType === "PgTimestampString") {
return timestampStringAccepts(column, value);
}
if (sqlType.startsWith("varchar(") && Number.isSafeInteger(column.length)) {
return [...value].length <= column.length;
}
if (sqlType.startsWith("char(") && Number.isSafeInteger(column.length)) {
return [...value].length === column.length;
}
if (sqlType.startsWith("numeric(")) return numericColumnAccepts(column, value);
if (sqlType === "inet") return networkStringAccepts(value, false);
if (sqlType === "cidr") return networkStringAccepts(value, true);
if (sqlType === "macaddr") return POSTGRES_MACADDR_PATTERN.test(value);
if (sqlType === "macaddr8") return POSTGRES_MACADDR8_PATTERN.test(value);
if (column.columnType === "PgBinaryVector") {
return Number.isSafeInteger(column.dimensions) && column.dimensions > 0 &&
value.length === column.dimensions && /^[01]+$/.test(value);
}
if (column.columnType === "PgInterval") {
return false;
}
return true;
}
function bigintColumnAccepts(column, value) {
if (typeof value !== "bigint") return false;
if (column.columnType === "MySqlBigInt64") {
return mysqlUnsigned(column)
? value >= 0n && value <= 18_446_744_073_709_551_615n
: value >= POSTGRES_BIGINT_MIN && value <= POSTGRES_BIGINT_MAX;
}
if (column.columnType === "MySqlDecimalBigInt") {
return (!mysqlUnsigned(column) || value >= 0n) &&
numericColumnAccepts(column, value);
}
const sqlType = typeof column.getSQLType === "function" ? column.getSQLType() : column.columnType;
if (sqlType === "bigint" || sqlType === "bigserial") {
return value >= POSTGRES_BIGINT_MIN && value <= POSTGRES_BIGINT_MAX;
}
if (sqlType.startsWith("numeric(")) return numericColumnAccepts(column, value);
return true;
}
function columnAccepts(column, value) {
if (value === null) return !column.notNull;
if (nodeTypes.isProxy(value)) return false;
if (
typeof column.columnType === "string" &&
column.columnType.startsWith("MySql") &&
!MYSQL_COLUMN_TYPES.has(column.columnType)
) {
return false;
}
if (
typeof column.columnType === "string" &&
column.columnType.startsWith("SQLite") &&
!SQLITE_COLUMN_TYPES.has(column.columnType)
) {
return false;
}
if (SQLITE_COLUMN_TYPES.has(column.columnType)) {
switch (column.columnType) {
case "SQLiteInteger":
return typeof value === "number" && Number.isSafeInteger(value);
case "SQLiteBoolean":
return typeof value === "boolean";
case "SQLiteTimestamp": {
const dateValue = inspectDate(value);
return dateValue.branded && dateValue.ordinary && dateValue.valid;
}
case "SQLiteReal":
case "SQLiteNumericNumber":
return typeof value === "number" && Number.isFinite(value);
case "SQLiteNumeric":
return typeof value === "string" && decimalParts(value, 10_000) !== null;
case "SQLiteNumericBigInt":
case "SQLiteBigInt":
return typeof value === "bigint";
case "SQLiteText":
return typeof value === "string" &&
(!Array.isArray(column.enumValues) || column.enumValues.length === 0 ||
column.enumValues.includes(value)) &&
(!Number.isSafeInteger(column.length) || [...value].length <= column.length);
case "SQLiteBlobBuffer":
return value instanceof Uint8Array;
case "SQLiteBlobJson":
case "SQLiteTextJson":
return isJsonValue(value);
default:
return false;
}
}
switch (column.dataType) {
case "string":
return stringColumnAccepts(column, value);
case "number":
return numberColumnAccepts(column, value);
case "boolean":
return typeof value === "boolean";
case "bigint":
return bigintColumnAccepts(column, value);
case "date": {
if (MYSQL_COLUMN_TYPES.has(column.columnType)) {
return mysqlDateColumnAccepts(column, value);
}
const dateValue = inspectDate(value);
return dateValue.branded && dateValue.ordinary && dateValue.valid;
}
case "json":
return isJsonValue(value);
case "buffer":
return value instanceof Uint8Array;
case "array":
return column.baseColumn !== undefined &&
arrayAccepts(value, (entry) => columnAccepts(column.baseColumn, entry));
default:
return false;
}
}
function rowDrift(table, column, rowIndex, expected, value) {
throw new DatabaseRowDriftError({
table,
column,
rowIndex,
expected,
received: receivedType(value),
});
}
export function validatedRows(table, rows) {
if (!isTable(table)) {
throw new TypeError(
"error[DATABASE_SCHEMA_REQUIRED]: validatedRows requires a declared Drizzle table as its first argument",
);
}
const tableName = getTableName(table);
const columns = getTableColumns(table);
if (
nodeTypes.isProxy(rows) || !Array.isArray(rows) ||
Object.getPrototypeOf(rows) !== Array.prototype ||
hasUntrustedInheritedProperty(rows, "toJSON")
) {
rowDrift(tableName, "*", 0, "an ordinary array of database rows", rows);
}
const columnEntries = Object.entries(columns);
const declaredKeys = new Set(columnEntries.map(([key]) => key));
for (const key of Reflect.ownKeys(rows)) {
if (key === "length") continue;
const index = typeof key === "string" ? Number(key) : Number.NaN;
if (!Number.isInteger(index) || index < 0 || index >= rows.length || String(index) !== key) {
const descriptor = Object.getOwnPropertyDescriptor(rows, key);
rowDrift(
tableName,
"*",
0,
"a dense array containing only database rows",
descriptor?.value,
);
}
}
for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
const rowDescriptor = Object.getOwnPropertyDescriptor(rows, String(rowIndex));
if (
rowDescriptor === undefined || rowDescriptor.enumerable !== true ||
rowDescriptor.get !== undefined || rowDescriptor.set !== undefined
) {
rowDrift(tableName, "*", rowIndex, "a database row at every array position", undefined);
}
const row = rowDescriptor.value;
if (
row === null || typeof row !== "object" || nodeTypes.isProxy(row) ||
Array.isArray(row)
) {
rowDrift(tableName, "*", rowIndex, "a database row object", row);
}
const prototype = Object.getPrototypeOf(row);
if (prototype !== Object.prototype && prototype !== null) {
rowDrift(tableName, "*", rowIndex, "a plain database row object", row);
}
for (const key of Reflect.ownKeys(row)) {
if (typeof key !== "string") {
rowDrift(tableName, "*", rowIndex, "a row with string-named schema columns only", row);
}
if (!declaredKeys.has(key)) {
const descriptor = Object.getOwnPropertyDescriptor(row, key);
rowDrift(tableName, key, rowIndex, "a declared Drizzle schema column", descriptor?.value);
}
}
for (const [key, column] of columnEntries) {
if (!Object.prototype.hasOwnProperty.call(row, key)) {
rowDrift(tableName, column.name, rowIndex, columnExpectation(column), undefined);
}
const descriptor = Object.getOwnPropertyDescriptor(row, key);
if (descriptor?.enumerable !== true || descriptor.get !== undefined || descriptor.set !== undefined) {
rowDrift(tableName, column.name, rowIndex, columnExpectation(column), undefined);
}
if (!columnAccepts(column, descriptor.value)) {
rowDrift(tableName, column.name, rowIndex, columnExpectation(column), descriptor.value);
}
}
}
return rows;
}
let mysqlDriver = null;
function loadMysqlDriver() {
if (mysqlDriver !== null) return mysqlDriver;
const requireModule = createRequire(import.meta.url);
try {
const { createPool } = requireModule("mysql2/promise");
const { drizzle } = requireModule("drizzle-orm/mysql2");
mysqlDriver = Object.freeze({ createPool, drizzle });
} catch {
throw new Error(
"error[DATABASE_DRIVER_MISSING]: DATABASE_URL selects the mysql dialect but the vetted mysql2 package is not installed; run `pnpm add mysql2@3.23.4` (see plugins/mysql2/VETTING.md)",
);
}
return mysqlDriver;
}
function policyFor(table) {
if (!isTable(table)) {
throw new TypeError(
"error[DATABASE_SCHEMA_REQUIRED]: database operations require a declared Drizzle table",
);
}
const policy = tablePolicies.get(table);
if (policy === undefined) {
throw new TypeError(
`error[DATA_POLICY_UNDECLARED]: ${getTableName(table)} has no runtime data policy; wrap its declaration in scopedTable or unscopedTable`,
);
}
return policy;
}
function snapshotPrincipal(principal) {
if (
principal === null || typeof principal !== "object" || nodeTypes.isProxy(principal) ||
!Object.isFrozen(principal)
) return null;
const kind = Object.getOwnPropertyDescriptor(principal, "kind");
const canonical = Object.getOwnPropertyDescriptor(principal, "canonical");
const scope = Object.getOwnPropertyDescriptor(principal, "scope");
if (
kind?.get !== undefined || kind?.set !== undefined || typeof kind?.value !== "string" ||
canonical?.get !== undefined || canonical?.set !== undefined || typeof canonical?.value !== "string" ||
scope?.get !== undefined || scope?.set !== undefined ||
(scope?.value !== null && typeof scope?.value !== "string")
) return null;
return Object.freeze({
kind: kind.value,
canonical: canonical.value,
scope: scope.value,
});
}
function runtimePrincipal(context) {
if (context === null || typeof context !== "object") return null;
return principalContexts.get(context) ?? null;
}
function auditScopedAccess(policy, context) {
if (policy.policy !== "scoped" || scopedAccessAudit === null) return;
scopedAccessAudit(
context,
Object.freeze({ table: policy.table, principalColumn: policy.principalColumn }),
);
}
function requireScopedPrincipal(policy, principalScope) {
if (principalScope === null || principalScope.length === 0) {
throw new DataScopeViolationError(
policy,
"the supplied object is not a runtime-created user or acting-user context",
);
}
return principalScope;
}
function scopedPredicate(policy, predicate, principalScope) {
if (policy.policy !== "scoped") return predicate;
const principal = requireScopedPrincipal(policy, principalScope);
const declared = predicatePolicies.get(predicate);
if (declared === undefined) {
throw new DataScopeViolationError(
policy,
"the query is missing the adapter-owned equality predicate",
);
}
const columnOnLeft = declared.left === policy.column;
const columnOnRight = declared.right === policy.column;
const bound = columnOnLeft ? declared.right : columnOnRight ? declared.left : undefined;
if ((!columnOnLeft && !columnOnRight) || typeof bound !== "string" || bound !== principal) {
throw new DataScopeViolationError(
policy,
"the query predicate targets a different column or principal",
);
}
return drizzleEq(policy.column, principal);
}
function prepareScopedPredicate(policy, predicate, principalScope) {
try {
return Object.freeze({
executable: scopedPredicate(policy, predicate, principalScope),
violation: null,
});
} catch (violation) {
return Object.freeze({
executable: drizzleEq(
policy.column,
typeof principalScope === "string" ? principalScope : "",
),
violation,
});
}
}
function snapshotWriteRecord(policy, value, principalScope, phase, principalRequired) {
const principal = requireScopedPrincipal(policy, principalScope);
if (
value === null || typeof value !== "object" || Array.isArray(value) || nodeTypes.isProxy(value)
) {
throw new DataScopeViolationError(
policy,
`${phase} must use an ordinary row with ${policy.principalColumn} bound to the runtime principal`,
);
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== Object.prototype && prototype !== null) {
throw new DataScopeViolationError(policy, `${phase} must use an ordinary row object`);
}
const snapshot = Object.create(null);
for (const key of Reflect.ownKeys(value)) {
const descriptor = Object.getOwnPropertyDescriptor(value, key);
if (
typeof key !== "string" || descriptor?.enumerable !== true ||
descriptor.get !== undefined || descriptor.set !== undefined
) {
throw new DataScopeViolationError(
policy,
`${phase} must use enumerable data properties rather than accessors or symbols`,
);
}
snapshot[key] = descriptor.value;
}
const hasPrincipal = Object.hasOwn(snapshot, policy.columnKey);
if (
(principalRequired && !hasPrincipal) ||
(hasPrincipal &&
(typeof snapshot[policy.columnKey] !== "string" || snapshot[policy.columnKey] !== principal))
) {
throw new DataScopeViolationError(
policy,
`${phase} must set ${policy.principalColumn} to the runtime principal as a primitive string`,
);
}
return Object.freeze(snapshot);
}
function snapshotScopedValues(policy, values, principalScope, phase, principalRequired = true) {
if (policy.policy !== "scoped") return values;
if (nodeTypes.isProxy(values)) {
throw new DataScopeViolationError(policy, `${phase} must not use a Proxy value carrier`);
}
const rows = Array.isArray(values) ? values : [values];
if (rows.length === 0) {
throw new DataScopeViolationError(policy, `${phase} contains no principal-bound row`);
}
const snapshots = rows.map((row) =>
snapshotWriteRecord(policy, row, principalScope, phase, principalRequired)
);
return Array.isArray(values) ? Object.freeze(snapshots) : snapshots[0];
}
function executable(builder, verify, methods) {
const facade = Object.create(null);
for (const [name, next] of Object.entries(methods)) {
Object.defineProperty(facade, name, {
enumerable: true,
value: (...args) => next(builder, args),
});
}
Object.defineProperties(facade, {
all: {
value: (...args) => { verify(); return builder.all(...args); },
},
get: {
value: (...args) => { verify(); return builder.get(...args); },
},
run: {
value: (...args) => { verify(); return builder.run(...args); },
},
then: {
value: (resolve, reject) => {
try { verify(); } catch (error) { return Promise.reject(error).then(resolve, reject); }
return Promise.resolve(builder).then(resolve, reject);
},
},
catch: {
value: (reject) => facade.then(undefined, reject),
},
finally: {
value: (settle) => Promise.resolve(facade).finally(settle),
},
});
return Object.freeze(facade);
}
function selectable(
builder,
policy,
context,
principalScope,
predicate = null,
violation = null,
) {
const verify = () => {
if (violation !== null) throw violation;
if (policy.policy === "scoped" && predicate === null) {
scopedPredicate(policy, predicate, principalScope);
}
auditScopedAccess(policy, context);
};
const continueWith = (name) => (current, args) =>
selectable(current[name](...args), policy, context, principalScope, predicate, violation);
return executable(builder, verify, {
where: (current, args) => {
const prepared = prepareScopedPredicate(policy, args[0], principalScope);
return selectable(
current.where(prepared.executable),
policy,
context,
principalScope,
prepared.executable,
prepared.violation,
);
},
orderBy: continueWith("orderBy"),
limit: continueWith("limit"),
offset: continueWith("offset"),
});
}
function insertable(builder, policy, context, principalScope, dialect) {
const verify = () => auditScopedAccess(policy, context);
const continueWith = (name) => (current, args) =>
insertable(current[name](...args), policy, context, principalScope, dialect);
return executable(builder, verify, {
onConflictDoNothing: continueWith("onConflictDoNothing"),
onConflictDoUpdate: (current, args) => {
const configuration = args[0];
if (policy.policy !== "scoped") {
return insertable(
current.onConflictDoUpdate(...args),
policy,
context,
principalScope,
dialect,
);
}
if (
dialect !== "postgres" && dialect !== "sqlite" ||
configuration === null || typeof configuration !== "object" ||
Array.isArray(configuration) || nodeTypes.isProxy(configuration)
) {
throw new DataScopeViolationError(
policy,
"this dialect cannot constrain the conflict update to the runtime principal",
);
}
const descriptors = Object.getOwnPropertyDescriptors(configuration);
if (
Reflect.ownKeys(descriptors).some((key) =>
typeof key !== "string" || descriptors[key].get !== undefined ||
descriptors[key].set !== undefined || descriptors[key].enumerable !== true
) || descriptors.set === undefined ||
descriptors.where !== undefined || descriptors.setWhere !== undefined
) {
throw new DataScopeViolationError(
policy,
"conflict updates must use a plain configuration without caller-owned update predicates",
);
}
const safeConfiguration = Object.create(null);
for (const [key, descriptor] of Object.entries(descriptors)) {
safeConfiguration[key] = descriptor.value;
}
safeConfiguration.set = snapshotScopedValues(
policy,
descriptors.set.value,
principalScope,
"conflict update",
false,
);
safeConfiguration.setWhere = drizzleEq(
policy.column,
requireScopedPrincipal(policy, principalScope),
);
return insertable(
current.onConflictDoUpdate(Object.freeze(safeConfiguration)),
policy,
context,
principalScope,
dialect,
);
},
returning: continueWith("returning"),
});
}
function updateable(
builder,
policy,
context,
principalScope,
predicate = null,
violation = null,
) {
const verify = () => {
if (violation !== null) throw violation;
if (policy.policy === "scoped" && predicate === null) {
scopedPredicate(policy, predicate, principalScope);
}
auditScopedAccess(policy, context);
};
return executable(builder, verify, {
where: (current, args) => {
const prepared = prepareScopedPredicate(policy, args[0], principalScope);
return updateable(
current.where(prepared.executable),
policy,
context,
principalScope,
prepared.executable,
prepared.violation,
);
},
returning: (current, args) =>
updateable(
current.returning(...args),
policy,
context,
principalScope,
predicate,
violation,
),
});
}
function deletable(
builder,
policy,
context,
principalScope,
predicate = null,
violation = null,
) {
const verify = () => {
if (violation !== null) throw violation;
if (policy.policy === "scoped" && predicate === null) {
scopedPredicate(policy, predicate, principalScope);
}
auditScopedAccess(policy, context);
};
return executable(builder, verify, {
where: (current, args) => {
const prepared = prepareScopedPredicate(policy, args[0], principalScope);
return deletable(
current.where(prepared.executable),
policy,
context,
principalScope,
prepared.executable,
prepared.violation,
);
},
returning: (current, args) =>
deletable(
current.returning(...args),
policy,
context,
principalScope,
predicate,
violation,
),
});
}
function databaseFacade(raw, context, dialect, principalScope) {
return Object.freeze({
select(selection) {
const selectionBuilder = arguments.length === 0 ? raw.select() : raw.select(selection);
return Object.freeze({
from(table) {
const policy = policyFor(table);
return selectable(selectionBuilder.from(table), policy, context, principalScope);
},
});
},
insert(table) {
const policy = policyFor(table);
return Object.freeze({
values(values) {
const snapshots = snapshotScopedValues(policy, values, principalScope, "insert");
return insertable(
raw.insert(table).values(snapshots),
policy,
context,
principalScope,
dialect,
);
},
});
},
update(table) {
const policy = policyFor(table);
return Object.freeze({
set(values) {
const snapshot = snapshotScopedValues(
policy,
values,
principalScope,
"update",
false,
);
return updateable(raw.update(table).set(snapshot), policy, context, principalScope);
},
});
},
delete(table) {
const policy = policyFor(table);
return deletable(raw.delete(table), policy, context, principalScope);
},
transaction(action, configuration) {
if (typeof action !== "function") {
throw new TypeError("error[DATABASE_TRANSACTION_INVALID]: transaction requires a callback");
}
if (dialect !== "postgres") {
return raw.transaction(
(transaction) => action(databaseFacade(transaction, context, dialect, principalScope)),
configuration,
);
}
return raw.transaction(async (transaction) => {
if (principalScope !== null) {
const principal = Object.freeze({ scope: principalScope });
await transaction.execute(
drizzleSql`select set_config('noxid.principal', ${principal.scope}, true)`,
);
}
return action(databaseFacade(transaction, context, dialect, principalScope));
}, configuration);
},
rollback() {
if (typeof raw.rollback !== "function") {
throw new TypeError("error[DATABASE_TRANSACTION_INVALID]: rollback is available only inside a transaction");
}
return raw.rollback();
},
});
}
export function database(context) {
const environment = context?.environment ?? context;
const principalScope = runtimePrincipal(context)?.scope ?? null;
const poolSize = environment?.dbPool ?? 10;
if (!Number.isSafeInteger(poolSize) || poolSize <= 0) {
throw new Error(
"error[DATABASE_POOL_INVALID]: the compiler-owned database pool size must be a positive safe integer; set [server] db_pool to a positive integer in Noxid.toml",
);
}
if (cached) {
return Object.freeze({
db: databaseFacade(cached.db, context, cachedDialect, principalScope),
poolInfo: cached.poolInfo,
});
}
const url = environment?.secrets?.DATABASE_URL;
if (typeof url !== "string" || url.length === 0) {
throw new Error(
"error[DATABASE_URL_REQUIRED]: declare DATABASE_URL under [server] secrets in Noxid.toml and provide it in the server environment",
);
}
const config = databaseConfig(url, environment?.projectRoot ?? process.cwd());
const poolInfo = Object.freeze({ max: poolSize });
if (config.dialect === "postgres") {
const client = postgres(url, { max: poolSize });
cachedDialect = "postgres";
cached = Object.freeze({ client, db: drizzlePostgres(client), poolInfo });
} else if (config.dialect === "mysql") {
const { createPool, drizzle: drizzleMySql } = loadMysqlDriver();
const client = createPool({
uri: url,
connectionLimit: poolSize,
multipleStatements: false,
timezone: "Z",
});
cachedDialect = "mysql";
cached = Object.freeze({ client, db: drizzleMySql(client), poolInfo });
} else {
const client = new NodeSqliteClient(config.filename);
cachedDialect = "sqlite";
cached = Object.freeze({ client, db: drizzleNodeSqlite(client), poolInfo });
}
return Object.freeze({
db: databaseFacade(cached.db, context, cachedDialect, principalScope),
poolInfo: cached.poolInfo,
});
}
export async function healthCheck(environment) {
database(environment);
const { client } = cached;
if (cachedDialect === "mysql") {
const [rows] = await client.query("select 1 as ok");
return rows[0]?.ok === 1;
}
if (cachedDialect === "sqlite") {
return client.prepare("select 1 as ok").get()?.ok === 1;
}
const [row] = await client`select 1 as ok`;
return row?.ok === 1;
}
export async function closeDatabase() {
if (!cached) return;
if (cachedDialect === "mysql") {
await cached.client.end();
} else if (cachedDialect === "sqlite") {
cached.client.close();
} else {
await cached.client.end({ timeout: 5 });
}
cached = null;
cachedDialect = null;
}