@ackplus/nest-dynamic-templates
Version:
A powerful and flexible dynamic template rendering library for NestJS applications with support for Nunjucks, Handlebars, EJS, Pug, MJML, Markdown, and more.
242 lines • 10.7 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TemplateLayoutService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const template_layout_entity_1 = require("../entities/template-layout.entity");
const template_engine_registry_1 = require("./template-engine.registry");
const template_errors_1 = require("../errors/template.errors");
const diagnose_1 = require("../errors/diagnose");
let TemplateLayoutService = class TemplateLayoutService {
layoutRepository;
engineRegistry;
constructor(layoutRepository, engineRegistry) {
this.layoutRepository = layoutRepository;
this.engineRegistry = engineRegistry;
}
async render(renderDto) {
const { name, scopeId, locale, context } = renderDto;
const scope = renderDto.scope || 'system';
const layout = await this.findTemplateLayout(name, scope, scopeId, locale);
if (!layout) {
throw new template_errors_1.TemplateNotFoundError({ templateName: name, scope, scopeId, locale, kind: 'layout' });
}
if (!layout.content) {
throw new template_errors_1.TemplateInputError(`Layout "${name}" has no content to render.`);
}
const ctx = context || {};
const meta = (stage) => ({
stage,
engine: layout.engine,
language: layout.language,
templateName: name,
scope,
scopeId,
locale,
});
let content = layout.content;
if (layout.engine) {
try {
content = await this.engineRegistry.getTemplateEngine(layout.engine).render(content, ctx);
}
catch (error) {
throw this.asRenderError(error, layout.content, ctx, meta('template-engine'));
}
}
if (layout.language) {
try {
content = await this.engineRegistry.getLanguageEngine(layout.language).render(content, ctx);
}
catch (error) {
throw this.asRenderError(error, content, ctx, meta('language-engine'));
}
}
return { content };
}
async renderContent(input) {
const { content, language, engine, context } = input;
const ctx = context || {};
if (!content) {
throw new template_errors_1.TemplateInputError('`content` is required for layout rendering.');
}
let rendered = content;
if (engine) {
try {
rendered = await this.engineRegistry.getTemplateEngine(engine).render(content, ctx);
}
catch (error) {
throw this.asRenderError(error, content, ctx, { stage: 'template-engine', engine, language });
}
}
if (language) {
try {
rendered = await this.engineRegistry.getLanguageEngine(language).render(rendered, ctx);
}
catch (error) {
throw this.asRenderError(error, rendered, ctx, { stage: 'language-engine', engine, language });
}
}
return rendered;
}
asRenderError(error, source, context, meta) {
if ((0, template_errors_1.isTemplateError)(error))
return error;
return (0, diagnose_1.diagnoseRenderError)(error, source, context, meta);
}
async getTemplateLayouts(filter = {}) {
const { scope, scopeId, type, locale, excludeNames = [] } = filter;
const where = {};
if (type)
where.type = type;
if (locale)
where.locale = locale;
if (excludeNames.length > 0)
where.name = (0, typeorm_2.Not)((0, typeorm_2.In)(excludeNames));
const systemTemplates = await this.layoutRepository.find({
where: { ...where, scope: 'system', scopeId: (0, typeorm_2.IsNull)() },
});
if (scope === 'system') {
return systemTemplates;
}
const templates = await this.layoutRepository.find({
where: { ...where, scope: (0, typeorm_2.Equal)(scope), scopeId: scopeId },
order: { createdAt: 'DESC' },
});
const templateMap = new Map();
for (const template of systemTemplates) {
templateMap.set(`${template.type}/${template.name}/${template.locale}`, template);
}
for (const template of templates) {
templateMap.set(`${template.type}/${template.name}/${template.locale}`, template);
}
return Array.from(templateMap.values());
}
async getTemplateLayoutById(id) {
return this.layoutRepository.findOne({ where: { id } });
}
async findTemplateLayout(name, scope, scopeId, locale) {
const locales = (locale ? [locale, 'en'] : ['en']).filter(Boolean);
for (const currentLocale of locales) {
const template = await this.layoutRepository.findOne({
where: {
name,
scope,
scopeId: scope === 'system' ? (0, typeorm_2.IsNull)() : (0, typeorm_2.Equal)(scopeId),
locale: currentLocale,
isActive: true,
},
});
if (template)
return template;
}
if (scope !== 'system') {
for (const currentLocale of locales) {
const template = await this.layoutRepository.findOne({
where: {
name,
scope: 'system',
scopeId: (0, typeorm_2.IsNull)(),
locale: currentLocale,
isActive: true,
},
});
if (template)
return template;
}
}
return null;
}
async createTemplateLayout(data) {
if (data.scope !== 'system') {
throw new template_errors_1.TemplateForbiddenError('Only system layouts can be created directly.');
}
const existingTemplate = await this.layoutRepository.findOne({
where: { name: data.name, scope: 'system', scopeId: (0, typeorm_2.IsNull)(), locale: data.locale },
});
if (existingTemplate) {
throw new template_errors_1.TemplateConflictError(`A system layout "${data.name}" already exists for locale "${data.locale ?? 'en'}".`);
}
const template = this.layoutRepository.create({ ...data, scopeId: undefined });
return this.layoutRepository.save(template);
}
async overwriteSystemTemplateLayout(templateId, updates) {
let template = await this.layoutRepository.findOne({ where: { id: templateId } });
if (!template) {
throw new template_errors_1.TemplateNotFoundError({ templateName: templateId, kind: 'layout' });
}
if (template.scope === 'system') {
if (!updates.scope) {
throw new template_errors_1.TemplateInputError('`scope` is required when overwriting a system layout.');
}
const existingTemplate = await this.layoutRepository.findOne({
where: {
name: template.name,
locale: template.locale,
scope: updates.scope,
scopeId: updates.scopeId,
},
});
if (existingTemplate) {
template = existingTemplate;
}
else {
template = this.layoutRepository.create({
...template,
id: undefined,
createdAt: undefined,
updatedAt: undefined,
scope: updates.scope,
scopeId: updates.scopeId,
});
}
}
template = this.layoutRepository.merge(template, updates);
await this.layoutRepository.save(template);
return template;
}
async updateTemplateLayout(id, updates, canUpdateSystemTemplate = false) {
let template = await this.layoutRepository.findOne({ where: { id } });
if (!template) {
throw new template_errors_1.TemplateNotFoundError({ templateName: id, kind: 'layout' });
}
if (template.scope === 'system' && !canUpdateSystemTemplate) {
if (updates.scope) {
return this.overwriteSystemTemplateLayout(id, updates);
}
throw new template_errors_1.TemplateForbiddenError('Cannot update a system layout directly.');
}
template = this.layoutRepository.merge(template, updates);
return this.layoutRepository.save(template);
}
async deleteTemplateLayout(id, canDeleteSystemTemplate = false) {
const template = await this.layoutRepository.findOne({ where: { id } });
if (!template) {
throw new template_errors_1.TemplateNotFoundError({ templateName: id, kind: 'layout' });
}
if (template.scope === 'system' && !canDeleteSystemTemplate) {
throw new template_errors_1.TemplateForbiddenError('Cannot delete a system layout.');
}
await this.layoutRepository.remove(template);
}
};
exports.TemplateLayoutService = TemplateLayoutService;
exports.TemplateLayoutService = TemplateLayoutService = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(template_layout_entity_1.NestDynamicTemplateLayout)),
__metadata("design:paramtypes", [typeorm_2.Repository,
template_engine_registry_1.TemplateEngineRegistryService])
], TemplateLayoutService);
//# sourceMappingURL=template-layout.service.js.map