UNPKG

@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.

281 lines 12.7 kB
"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.TemplateService = void 0; const common_1 = require("@nestjs/common"); const typeorm_1 = require("@nestjs/typeorm"); const typeorm_2 = require("typeorm"); const template_entity_1 = require("../entities/template.entity"); const template_types_1 = require("../interfaces/template.types"); const template_layout_service_1 = require("./template-layout.service"); const template_engine_registry_1 = require("./template-engine.registry"); const lodash_1 = require("lodash"); const template_errors_1 = require("../errors/template.errors"); const diagnose_1 = require("../errors/diagnose"); let TemplateService = class TemplateService { templateRepository; engineRegistry; templateLayoutService; constructor(templateRepository, engineRegistry, templateLayoutService) { this.templateRepository = templateRepository; this.engineRegistry = engineRegistry; this.templateLayoutService = templateLayoutService; } async render(renderDto) { const { name, scopeId, locale, context } = renderDto; const scope = renderDto.scope || 'system'; if (!name) { throw new template_errors_1.TemplateInputError('`name` is required to render a stored template.', 'To render a raw string instead, use renderContent().'); } const template = await this.findTemplate(name, scope, scopeId, locale); if (!template) { throw new template_errors_1.TemplateNotFoundError({ templateName: name, scope, scopeId, locale }); } const ctx = context || {}; const meta = (stage) => ({ stage, engine: template.engine, language: template.language, templateName: name, scope, scopeId, locale, }); let subject = template.subject ?? null; if (template.subject && template.engine) { try { subject = await this.engineRegistry .getTemplateEngine(template.engine) .render(template.subject, ctx); } catch (error) { throw this.asRenderError(error, template.subject, ctx, meta('subject')); } } let content = template.content; if (template.engine) { try { content = await this.engineRegistry.getTemplateEngine(template.engine).render(content, ctx); } catch (error) { throw this.asRenderError(error, template.content, ctx, meta('template-engine')); } } if (template.templateLayoutName) { const layout = await this.templateLayoutService.render({ name: template.templateLayoutName, scope, scopeId, locale, context: { ...ctx, content }, }); return { subject, content: layout.content }; } if (template.language) { try { content = await this.engineRegistry.getLanguageEngine(template.language).render(content, ctx); } catch (error) { throw this.asRenderError(error, content, ctx, meta('language-engine')); } } return { subject, content }; } async renderContent(input) { const { content, language, templateLayoutId } = input; const engine = input.engine || template_types_1.TemplateEngineEnum.NUNJUCKS; const ctx = input.context || {}; if (!content) { throw new template_errors_1.TemplateInputError('`content` is required for renderContent().'); } let rendered; try { rendered = await this.engineRegistry.getTemplateEngine(engine).render(content, ctx); } catch (error) { throw this.asRenderError(error, content, ctx, { stage: 'template-engine', engine, language }); } if (templateLayoutId) { const layout = await this.templateLayoutService.getTemplateLayoutById(templateLayoutId); if (!layout) { throw new template_errors_1.TemplateNotFoundError({ templateName: templateLayoutId, kind: 'layout' }); } return this.templateLayoutService.renderContent({ content: layout.content, language, engine, context: { ...ctx, content: rendered }, }); } 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 getTemplates(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.templateRepository.find({ where: { ...where, scope: 'system', scopeId: (0, typeorm_2.IsNull)() }, }); if (scope === 'system') { return systemTemplates; } const templates = await this.templateRepository.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 getTemplateById(id) { return this.templateRepository.findOne({ where: { id } }); } async findTemplate(name, scope, scopeId, locale) { const locales = (locale ? [locale, 'en'] : ['en']).filter(Boolean); for (const currentLocale of locales) { const template = await this.templateRepository.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.templateRepository.findOne({ where: { name, scope: 'system', scopeId: (0, typeorm_2.IsNull)(), locale: currentLocale, isActive: true, }, }); if (template) return template; } } return null; } async createTemplate(data) { if (data.scope !== 'system') { throw new template_errors_1.TemplateForbiddenError('Only system templates can be created directly.', 'To create a scoped override, call overwriteSystemTemplate() or updateTemplate() with a non-system scope.'); } const existingTemplate = await this.templateRepository.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 template "${data.name}" already exists for locale "${data.locale ?? 'en'}".`); } const template = this.templateRepository.create({ ...data, scopeId: undefined }); return this.templateRepository.save(template); } async overwriteSystemTemplate(templateId, updates) { let template = await this.templateRepository.findOne({ where: { id: templateId } }); if (!template) { throw new template_errors_1.TemplateNotFoundError({ templateName: templateId }); } if (template.scope === 'system') { if (!updates.scope) { throw new template_errors_1.TemplateInputError('`scope` is required when overwriting a system template.'); } const existingTemplate = await this.templateRepository.findOne({ where: { name: template.name, locale: template.locale, scope: updates.scope, scopeId: updates.scopeId, }, }); if (existingTemplate) { template = existingTemplate; } else { template = this.templateRepository.create({ ...template, id: undefined, createdAt: undefined, updatedAt: undefined, scope: updates.scope, scopeId: updates.scopeId, }); } } updates = (0, lodash_1.omit)(updates, ['name', 'id', 'createdAt', 'updatedAt']); template = this.templateRepository.merge(template, updates); await this.templateRepository.save(template); return template; } async updateTemplate(id, updates, canUpdateSystemTemplate = false) { let template = await this.templateRepository.findOne({ where: { id } }); if (!template) { throw new template_errors_1.TemplateNotFoundError({ templateName: id }); } if (template.scope === 'system' && !canUpdateSystemTemplate) { if (updates.scope) { return this.overwriteSystemTemplate(id, updates); } throw new template_errors_1.TemplateForbiddenError('Cannot update a system template directly.', 'Pass a non-system `scope` to create a scoped override, or call updateTemplate(id, updates, true).'); } template = this.templateRepository.merge(template, updates); return this.templateRepository.save(template); } async deleteTemplate(id, canDeleteSystemTemplate = false) { const template = await this.templateRepository.findOne({ where: { id } }); if (!template) { throw new template_errors_1.TemplateNotFoundError({ templateName: id }); } if (template.scope === 'system' && !canDeleteSystemTemplate) { throw new template_errors_1.TemplateForbiddenError('Cannot delete a system template.'); } await this.templateRepository.remove(template); } }; exports.TemplateService = TemplateService; exports.TemplateService = TemplateService = __decorate([ (0, common_1.Injectable)(), __param(0, (0, typeorm_1.InjectRepository)(template_entity_1.NestDynamicTemplate)), __metadata("design:paramtypes", [typeorm_2.Repository, template_engine_registry_1.TemplateEngineRegistryService, template_layout_service_1.TemplateLayoutService]) ], TemplateService); //# sourceMappingURL=template.service.js.map