@lucidcms/postgres-adapter
Version:
The official Postgres adapter for Lucid CMS
204 lines (198 loc) • 7.42 kB
JavaScript
import { DatabaseAdapter } from "@lucidcms/core";
import { ParseJSONResultsPlugin, sql } from "kysely";
import { PostgresJSDialect } from "kysely-postgres-js";
import postgres from "postgres";
import { jsonArrayFrom } from "kysely/helpers/postgres";
//#region src/utils/format-default-value.ts
const formatDefaultValue = (type, defaultValue) => {
if (defaultValue === null) return null;
const withoutTypeCast = defaultValue.split("::")[0];
if (withoutTypeCast === void 0) return null;
if (withoutTypeCast.toLowerCase() === "now()") return "NOW()";
if (type === "jsonb") try {
if (withoutTypeCast.startsWith("'") && withoutTypeCast.endsWith("'")) return JSON.parse(withoutTypeCast.slice(1, -1));
return JSON.parse(withoutTypeCast);
} catch (e) {
return null;
}
if (withoutTypeCast.startsWith("'") && withoutTypeCast.endsWith("'")) return withoutTypeCast.slice(1, -1);
if (withoutTypeCast.includes("nextval(")) return null;
if (withoutTypeCast.toLowerCase() === "true") return true;
if (withoutTypeCast.toLowerCase() === "false") return false;
if (/^-?\d+(\.\d+)?$/.test(withoutTypeCast)) return type === "integer" || type === "bigint" ? Number.parseInt(withoutTypeCast, 10) : Number.parseFloat(withoutTypeCast);
return defaultValue;
};
var format_default_value_default = formatDefaultValue;
//#endregion
//#region src/utils/format-on-delete.ts
const formatOnDelete = (value) => {
return value?.toLowerCase() ?? "no action";
};
var format_on_delete_default = formatOnDelete;
//#endregion
//#region src/utils/format-on-update.ts
const formatOnUpdate = (value) => {
return value?.toLowerCase() ?? "no action";
};
var format_on_update_default = formatOnUpdate;
//#endregion
//#region src/utils/format-type.ts
const formatType = (type, defaultValue) => {
if (type.includes("timestamp")) return "timestamp";
if (type.includes("character")) return "text";
if (type === "integer" && defaultValue?.includes("nextval(")) return "serial";
return type;
};
var format_type_default = formatType;
//#endregion
//#region src/index.ts
var PostgresAdapter = class extends DatabaseAdapter {
constructor(url, options) {
super({
adapter: "postgres",
dialect: new PostgresJSDialect({ postgres: postgres(url, options) }),
plugins: [new ParseJSONResultsPlugin()]
});
}
async initialise() {
await sql`CREATE EXTENSION IF NOT EXISTS pg_trgm`.execute(this.client);
await sql`SET timezone = 'UTC'`.execute(this.client);
}
get jsonArrayFrom() {
return jsonArrayFrom;
}
get config() {
return {
support: {
alterColumn: true,
multipleAlterTables: true,
autoIncrement: false,
boolean: true
},
dataTypes: {
primary: "serial",
integer: "integer",
boolean: "boolean",
json: "jsonb",
text: "text",
timestamp: "timestamp",
char: (length) => `char(${length})`,
varchar: (length) => length ? `varchar(${length})` : "varchar"
},
defaults: {
timestamp: { now: "NOW()" },
boolean: {
true: true,
false: false
}
},
fuzzOperator: "%"
};
}
async inferSchema(tx) {
const res = await sql`
WITH table_columns AS (
SELECT
c.table_name,
c.column_name AS name,
c.data_type AS type,
c.is_nullable = 'NO' AS notnull,
c.column_default AS dflt_value,
CASE WHEN pk.constraint_name IS NOT NULL THEN true ELSE false END AS pk,
CASE WHEN uc.constraint_name IS NOT NULL THEN true ELSE false END AS is_unique
FROM information_schema.columns c
LEFT JOIN (
SELECT
kcu.table_name,
kcu.column_name,
tc.constraint_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
WHERE tc.constraint_type = 'PRIMARY KEY'
) pk ON
c.table_name = pk.table_name AND
c.column_name = pk.column_name
LEFT JOIN (
SELECT
kcu.table_name,
kcu.column_name,
tc.constraint_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
WHERE tc.constraint_type = 'UNIQUE'
) uc ON
c.table_name = uc.table_name AND
c.column_name = uc.column_name
WHERE c.table_name LIKE 'lucid_%'
),
foreign_keys AS (
SELECT
kcu.table_name,
kcu.column_name,
ccu.table_name AS referenced_table,
ccu.column_name AS referenced_column,
rc.update_rule AS on_update,
rc.delete_rule AS on_delete
FROM information_schema.key_column_usage kcu
JOIN information_schema.referential_constraints rc
ON kcu.constraint_name = rc.constraint_name
JOIN information_schema.constraint_column_usage ccu
ON rc.unique_constraint_name = ccu.constraint_name
WHERE kcu.table_name LIKE 'lucid_%'
)
SELECT
tc.*,
fk.referenced_table AS fk_table,
fk.referenced_column AS fk_column,
fk.on_update AS fk_on_update,
fk.on_delete AS fk_on_delete
FROM table_columns tc
LEFT JOIN foreign_keys fk ON
tc.table_name = fk.table_name AND
tc.name = fk.column_name
`.execute(tx ?? this.client);
const tableMap = /* @__PURE__ */ new Map();
for (const row of res.rows) {
let table = tableMap.get(row.table_name);
if (!table) {
table = {
name: row.table_name,
columns: []
};
tableMap.set(row.table_name, table);
}
table.columns.push({
name: row.name,
type: format_type_default(row.type, row.dflt_value),
nullable: !row.notnull,
default: format_default_value_default(format_type_default(row.type, row.dflt_value), row.dflt_value),
primary: row.pk,
unique: row.is_unique,
foreignKey: row.fk_table && row.fk_column ? {
table: row.fk_table,
column: row.fk_column,
onUpdate: format_on_update_default(row.fk_on_update),
onDelete: format_on_delete_default(row.fk_on_delete)
} : void 0
});
}
return Array.from(tableMap.values());
}
formatDefaultValue(type, value) {
if (type === "text" && typeof value === "string") return sql.raw(`'${value}'`);
if (type === "timestamp" && typeof value === "string") return sql.raw(value);
if ((type === "json" || type === "jsonb") && typeof value === "object" && value !== null) return sql.raw(`'${JSON.stringify(value)}'`);
if (type === "boolean") {
if (value) return true;
return false;
}
if (typeof value === "number") return value;
return value;
}
};
var src_default = PostgresAdapter;
//#endregion
export { src_default as default };
//# sourceMappingURL=index.js.map