@adonisjs/lucid-slugify
Version:
Generate and persist unique slugs in your database via Lucid models
262 lines (258 loc) • 7.05 kB
JavaScript
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
};