UNPKG

@adonisjs/lucid-slugify

Version:

Generate and persist unique slugs in your database via Lucid models

164 lines (158 loc) 6.41 kB
var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/errors.ts var errors_exports = {}; __export(errors_exports, { E_UNSUPPORTED_DB_DIALECT: () => E_UNSUPPORTED_DB_DIALECT }); import { createError } from "@adonisjs/core/exceptions"; var E_UNSUPPORTED_DB_DIALECT = createError( '%s dialect is not supported by the "dbIncrement" strategy', "E_UNSUPPORTED_DB_DIALECT" ); // src/strategies/short_id.ts import { nanoid } from "nanoid"; var ShortIdStrategy = class { #config; maxLengthBuffer = 11; constructor(config) { this.#config = { separator: "-", ...config }; } /** * Converts an existing slug to a unique slug by appending * nanoid to it */ async makeSlugUnique(_, __, slug) { return `${slug}${this.#config.separator}${nanoid(this.maxLengthBuffer - 1)}`; } }; // src/debug.ts import { debuglog } from "node:util"; var debug_default = debuglog("adonisjs:lucid-slugify"); // src/strategies/db_increment.ts var DbIncrementStrategy = class { #config; maxLengthBuffer = 3; constructor(config) { this.#config = { separator: "-", ...config }; } /** * Returns the counter from the rows fetched from the database by extracting * the counter from the slug value. We do it in JavaScript since certain * database dialects do not support Regular expressions. */ #getCounter(slug, columnName, rows) { const slugCounter = rows.reduce((result, row) => { const tokens = row[columnName].toLowerCase().split(`${slug}${this.#config.separator}`); if (tokens.length === 2) { const counter = Number(tokens[1]); if (!Number.isNaN(counter)) { result = result.concat(counter); } } return result; }, []); return slugCounter.length ? Math.max(...slugCounter) : 0; } /** * Makes the unique slug for the SQLite database */ async #getSlugForSqlite(model, client, columnName, slugValue) { const result = await client.modelQuery(model).select(columnName).whereRaw("lower(??) = ?", [columnName, slugValue]).orWhereRaw("lower(??) like ?", [columnName, `${slugValue}${this.#config.separator}%`]).pojo().exec(); debug_default('sqlite: rows match slug "%s": %O', slugValue, result); if (!result.length) { return slugValue; } const counter = this.#getCounter(slugValue, columnName, result); return `${slugValue}${this.#config.separator}${counter + 1}`; } /** * Returns a unique slug for MYSQL >= 8.0 (aka 5.7) */ async #getSlugForOldMysql(model, client, columnName, slugValue) { const result = await client.modelQuery(model).select(columnName).where(columnName, slugValue).orWhereRaw(`?? REGEXP ?`, [columnName, `^${slugValue}(${this.#config.separator}[0-9]*)?$`]).pojo().exec(); debug_default('mysql 5.7: rows match slug "%s": %O', slugValue, result); if (!result.length) { return slugValue; } const counter = this.#getCounter(slugValue, columnName, result); return `${slugValue}${this.#config.separator}${counter + 1}`; } /** * Returns a unique slug for MYSQL >= 8.0 */ async #getSlugForMysql(model, client, columnName, slugValue) { const result = await client.modelQuery(model).select( client.raw( `CAST(REGEXP_SUBSTR(${columnName}, '[0-9]+$') AS UNSIGNED) as lucid_slugify_counter` ) ).whereRaw(`?? REGEXP ?`, [columnName, `^${slugValue}(${this.#config.separator}[0-9]*)?$`]).orderBy("lucid_slugify_counter", "desc").pojo().exec(); debug_default('mysql: matching slug counters "%s": %O', slugValue, result); if (!result.length) { return slugValue; } const counter = result.find((row) => row.lucid_slugify_counter)?.lucid_slugify_counter ?? 0; return `${slugValue}${this.#config.separator}${counter + 1}`; } /** * Returns a unique slug for MSSQL. */ async #getSlugForMssql(model, client, columnName, slugValue) { const result = await client.modelQuery(model).select(columnName).where(columnName, slugValue).orWhere(columnName, "like", `${slugValue}${this.#config.separator}%`).pojo().exec(); debug_default('mssql 5.7: rows match slug "%s": %O', slugValue, result); if (!result.length) { return slugValue; } const counter = this.#getCounter(slugValue, columnName, result); return `${slugValue}${this.#config.separator}${counter + 1}`; } /** * Makes slug for PostgreSQL and RedShift both. Redshift is not tested and * assumed to be compatible with PostgreSQL. */ async #getSlugForPg(model, client, columnName, slugValue) { const result = await client.modelQuery(model).select( client.raw(`SUBSTRING(${columnName} from '[0-9]+$')::INTEGER as lucid_slugify_counter`) ).whereRaw(`?? ~* ?`, [columnName, `^${slugValue}(${this.#config.separator}[0-9]*)?$`]).orderBy("lucid_slugify_counter", "desc").limit(2).pojo().exec(); debug_default('pg: matching slug counters "%s": %O', slugValue, result); if (!result.length) { return slugValue; } const counter = result.find((row) => row.lucid_slugify_counter)?.lucid_slugify_counter ?? 0; return `${slugValue}${this.#config.separator}${counter + 1}`; } /** * Converts an existing slug to a unique slug by inspecting the database */ async makeSlugUnique(modelInstance, field, slug) { const model = modelInstance.constructor; const client = model.$adapter.modelClient(modelInstance); const columnName = model.$columnsDefinitions.get(field).columnName; const dialect = client.dialect; const dialectName = dialect.name; const dialectVersion = Number(dialect.version); switch (dialectName) { case "postgres": case "redshift": return this.#getSlugForPg(model, client, columnName, slug); case "sqlite3": case "better-sqlite3": return this.#getSlugForSqlite(model, client, columnName, slug); case "mysql": return dialectVersion < 8 ? this.#getSlugForOldMysql(model, client, columnName, slug) : this.#getSlugForMysql(model, client, columnName, slug); case "mssql": return this.#getSlugForMssql(model, client, columnName, slug); default: throw new E_UNSUPPORTED_DB_DIALECT([dialectName]); } } }; export { errors_exports, debug_default, ShortIdStrategy, DbIncrementStrategy };