UNPKG

@adonisjs/lucid-slugify

Version:

Generate and persist unique slugs in your database via Lucid models

262 lines (258 loc) 7.05 kB
import { DbIncrementStrategy, ShortIdStrategy, debug_default, errors_exports } from "./chunk-NUHZIELH.js"; // src/slugifier.ts import stringHelpers from "@adonisjs/core/helpers/string"; var Slugifier = class _Slugifier { /** * Convert a value to a URL-safe slug. Feel free to replace this * method with a custom implementation. */ static slugify(value, options) { return stringHelpers.slug(value, { strict: true, lower: options.lower, replacement: options.separator }); } #slugField; #separator; #model; #config; #strategy; constructor(model, slugField, strategy, config) { this.#model = model; this.#slugField = slugField; this.#config = config; this.#strategy = strategy; this.#separator = this.#config.separator ?? "-"; } /** * Transforms a given model attribute value to a string */ #transformToString(modelInstance, field, value) { if (typeof this.#config.transformer === "function") { return this.#config.transformer(modelInstance, field, value); } if (value === true) { return "1"; } if (value === false) { return "0"; } if (value instanceof Date) { return value.toJSON(); } return String(value); } /** * Returns the value to be used for creating a slug by concatenating * the values of the source fields. Null is returned when the value * of one or more source fields is null or undefined. */ #createSlugValue(modelInstance) { const slugValues = []; for (let field of this.#config.fields) { const value = modelInstance[field]; if (value === null || value === void 0) { debug_default('cannot create slug as "%s" property is set to %o', field, value); return null; } else { slugValues.push(this.#transformToString(modelInstance, field, value)); } } return slugValues.join(this.#separator); } /** * Transforms a string value to a URL-safe slug */ #makeSlug(value) { let slug = _Slugifier.slugify(value, { separator: this.#separator, lower: true }); if (this.#config.maxLength) { slug = stringHelpers.truncate(slug, this.#config.maxLength - this.#strategy.maxLengthBuffer, { completeWords: this.#config.completeWords, suffix: "" }); } return slug; } /** * Create a new unique slug using the provided strategy and sets * the value for the "forField" property on the model instance. * * @note * This method does not issue a database query, instead it is meant * to be used within a `beforeCreate` hook * * @example * ```ts * * const post = new Post() * post.title = 'Hello world' * * await slugifier.createSlug(post) * post.slug // hello-world * ``` */ async createSlug(row) { const modelName = this.#model.name; if (row[this.#slugField]) { debug_default( 'skipping slug generation since "%s.%s" property already has a value', modelName, this.#slugField ); return; } const slug = await this.makeSlug(row); if (slug) { debug_default('setting slug value for "%s.%s" property to "%s"', modelName, this.#slugField, slug); row[this.#slugField] = slug; } } /** * Updates the value for the existing slug property when its source * fields have been mutated. * * You can disable slug updates using the {@link SlugifierConfig.allowUpdates} * config option. * * @note * This method does not issue a database query, instead it is meant * to be used within a `beforeUpdate` hook. * * @example * ```ts * * const post = Post.findOrFail(1) * post.title = 'Updated title' * * await slugifier.updateSlug(post) * post.slug // updated-title * ``` */ async updateSlug(row) { const modelName = this.#model.name; let allowUpdates = typeof this.#config.allowUpdates === "function" ? this.#config.allowUpdates(row) : !!this.#config.allowUpdates; if (!allowUpdates) { debug_default('updating slugs is disabled for "%s.%s" property', modelName, this.#slugField); return; } if (row.isDirty(this.#slugField)) { debug_default( 'skipping slug updation since "%s.%s" property has been updated manually', modelName, this.#slugField ); return; } if (this.#config.fields.every((field) => !row.isDirty(field))) { return; } const slug = await this.makeSlug(row); if (slug) { debug_default( 'updating slug value for "%s.%s" property from "%s" to "%s"', modelName, this.#slugField, row[this.#slugField], slug ); row[this.#slugField] = slug; } } /** * Creates a slug for a given model instance using the values from * the source fields. * * A `null` value will be returned when one of the source fields * have `undefined` or `null` values. */ async makeSlug(modelInstance) { const slugValue = this.#createSlugValue(modelInstance); if (!slugValue) { return null; } return this.#strategy.makeSlugUnique( modelInstance, this.#slugField, this.#makeSlug(slugValue) ); } }; // src/strategies/simple.ts var SimpleStrategy = class { maxLengthBuffer = 0; /** * Returns the slug as it is */ async makeSlugUnique(_, __, slug) { return slug; } }; // src/decorators/slugify.ts function getStrategyBuilder(strategy) { let strategyFactory = typeof strategy === "function" ? strategy : void 0; if (!strategyFactory) { switch (strategy) { case "shortId": strategyFactory = (_, __, config) => new ShortIdStrategy(config); break; case "dbIncrement": strategyFactory = (_, __, config) => new DbIncrementStrategy(config); break; default: strategyFactory = () => new SimpleStrategy(); } } return strategyFactory; } function assignSlugifier(model, property, config) { if (!model.$slugifiers.has(property)) { debug_default('initiating slugifier for "%s.%s"', model.name, property); model.$slugifiers.set( property, new Slugifier( model, property, getStrategyBuilder(config.strategy)(model, property, config), config ) ); } return model.$slugifiers.get(property); } function slugify(config) { return function(target, property) { const model = target.constructor; model.boot(); model.$slugifiers = model.$slugifiers ?? /* @__PURE__ */ new Map(); model.before("create", async (row) => { const slugifier = assignSlugifier( model, property, config ); await slugifier.createSlug(row); }); model.before("update", async (row) => { const slugifier = assignSlugifier( model, property, config ); await slugifier.updateSlug(row); }); }; } export { Slugifier, errors_exports as errors, slugify };