@ackplus/nest-dynamic-templates 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/package.json +2 -2
  2. package/src/{index.ts → index.d.ts} +2 -14
  3. package/src/index.d.ts.map +1 -0
  4. package/src/lib/constant.d.ts +3 -0
  5. package/src/lib/constant.d.ts.map +1 -0
  6. package/src/lib/dto/create-template-layout.dto.d.ts +16 -0
  7. package/src/lib/dto/create-template-layout.dto.d.ts.map +1 -0
  8. package/src/lib/dto/create-template.dto.d.ts +18 -0
  9. package/src/lib/dto/create-template.dto.d.ts.map +1 -0
  10. package/src/lib/dto/render-content-template-layout.dto.d.ts +8 -0
  11. package/src/lib/dto/render-content-template-layout.dto.d.ts.map +1 -0
  12. package/src/lib/dto/render-content-template.dto.d.ts +9 -0
  13. package/src/lib/dto/render-content-template.dto.d.ts.map +1 -0
  14. package/src/lib/dto/render-template-layout.dto.d.ts +11 -0
  15. package/src/lib/dto/render-template-layout.dto.d.ts.map +1 -0
  16. package/src/lib/dto/render-template.dto.d.ts +15 -0
  17. package/src/lib/dto/render-template.dto.d.ts.map +1 -0
  18. package/src/lib/dto/template-filter.dto.d.ts +9 -0
  19. package/src/lib/dto/template-filter.dto.d.ts.map +1 -0
  20. package/src/lib/dto/template-layout-filter.dto.d.ts +8 -0
  21. package/src/lib/dto/template-layout-filter.dto.d.ts.map +1 -0
  22. package/src/lib/engines/language/html.engine.d.ts +10 -0
  23. package/src/lib/engines/language/html.engine.d.ts.map +1 -0
  24. package/src/lib/engines/language/{index.ts → index.d.ts} +1 -0
  25. package/src/lib/engines/language/index.d.ts.map +1 -0
  26. package/src/lib/engines/language/markdown.engine.d.ts +10 -0
  27. package/src/lib/engines/language/markdown.engine.d.ts.map +1 -0
  28. package/src/lib/engines/language/mjml.engine.d.ts +10 -0
  29. package/src/lib/engines/language/mjml.engine.d.ts.map +1 -0
  30. package/src/lib/engines/language/text.engine.d.ts +8 -0
  31. package/src/lib/engines/language/text.engine.d.ts.map +1 -0
  32. package/src/lib/engines/{language-engine.ts → language-engine.d.ts} +2 -7
  33. package/src/lib/engines/language-engine.d.ts.map +1 -0
  34. package/src/lib/engines/template/ejs.engine.d.ts +11 -0
  35. package/src/lib/engines/template/ejs.engine.d.ts.map +1 -0
  36. package/src/lib/engines/template/handlebars.engine.d.ts +11 -0
  37. package/src/lib/engines/template/handlebars.engine.d.ts.map +1 -0
  38. package/src/lib/engines/template/{index.ts → index.d.ts} +1 -0
  39. package/src/lib/engines/template/index.d.ts.map +1 -0
  40. package/src/lib/engines/template/nunjucks.engine.d.ts +17 -0
  41. package/src/lib/engines/template/nunjucks.engine.d.ts.map +1 -0
  42. package/src/lib/engines/template/pug.engine.d.ts +13 -0
  43. package/src/lib/engines/template/pug.engine.d.ts.map +1 -0
  44. package/src/lib/engines/{template-engine.ts → template-engine.d.ts} +2 -6
  45. package/src/lib/engines/template-engine.d.ts.map +1 -0
  46. package/src/lib/entities/template-layout.entity.d.ts +21 -0
  47. package/src/lib/entities/template-layout.entity.d.ts.map +1 -0
  48. package/src/lib/entities/template.entity.d.ts +22 -0
  49. package/src/lib/entities/template.entity.d.ts.map +1 -0
  50. package/src/lib/errors/template.errors.d.ts +20 -0
  51. package/src/lib/errors/template.errors.d.ts.map +1 -0
  52. package/src/lib/interfaces/{module-config.interface.ts → module-config.interface.d.ts} +2 -10
  53. package/src/lib/interfaces/module-config.interface.d.ts.map +1 -0
  54. package/src/lib/interfaces/template.types.d.ts +23 -0
  55. package/src/lib/interfaces/template.types.d.ts.map +1 -0
  56. package/src/lib/nest-dynamic-templates.module.d.ts +11 -0
  57. package/src/lib/nest-dynamic-templates.module.d.ts.map +1 -0
  58. package/src/lib/services/template-config.service.d.ts +55 -0
  59. package/src/lib/services/template-config.service.d.ts.map +1 -0
  60. package/src/lib/services/template-engine.registry.d.ts +22 -0
  61. package/src/lib/services/template-engine.registry.d.ts.map +1 -0
  62. package/src/lib/services/template-layout.service.d.ts +37 -0
  63. package/src/lib/services/template-layout.service.d.ts.map +1 -0
  64. package/src/lib/services/template.service.d.ts +39 -0
  65. package/src/lib/services/template.service.d.ts.map +1 -0
  66. package/src/test/helpers.d.ts +5 -0
  67. package/src/test/helpers.d.ts.map +1 -0
  68. package/src/test/test-database.config.d.ts +5 -0
  69. package/src/test/test-database.config.d.ts.map +1 -0
  70. package/src/test/test.setup.d.ts +4 -0
  71. package/src/test/test.setup.d.ts.map +1 -0
  72. package/eslint.config.mjs +0 -22
  73. package/jest.config.ts +0 -10
  74. package/project.json +0 -38
  75. package/src/lib/constant.ts +0 -2
  76. package/src/lib/dto/create-template-layout.dto.ts +0 -65
  77. package/src/lib/dto/create-template.dto.ts +0 -80
  78. package/src/lib/dto/render-content-template-layout.dto.ts +0 -32
  79. package/src/lib/dto/render-content-template.dto.ts +0 -37
  80. package/src/lib/dto/render-template-layout.dto.ts +0 -55
  81. package/src/lib/dto/render-template.dto.ts +0 -74
  82. package/src/lib/dto/template-filter.dto.ts +0 -52
  83. package/src/lib/dto/template-layout-filter.dto.ts +0 -51
  84. package/src/lib/engines/language/html.engine.ts +0 -49
  85. package/src/lib/engines/language/markdown.engine.ts +0 -37
  86. package/src/lib/engines/language/mjml.engine.ts +0 -44
  87. package/src/lib/engines/language/text.engine.ts +0 -15
  88. package/src/lib/engines/template/ejs.engine.ts +0 -33
  89. package/src/lib/engines/template/handlebars.engine.ts +0 -35
  90. package/src/lib/engines/template/nunjucks.engine.ts +0 -70
  91. package/src/lib/engines/template/pug.engine.ts +0 -43
  92. package/src/lib/entities/template-layout.entity.ts +0 -99
  93. package/src/lib/entities/template.entity.ts +0 -105
  94. package/src/lib/errors/template.errors.ts +0 -73
  95. package/src/lib/interfaces/template.types.ts +0 -25
  96. package/src/lib/nest-dynamic-templates.module.ts +0 -143
  97. package/src/lib/services/template-config.service.ts +0 -112
  98. package/src/lib/services/template-engine.registry.ts +0 -109
  99. package/src/lib/services/template-layout.service.ts +0 -407
  100. package/src/lib/services/template.service.ts +0 -476
  101. package/src/test/helpers.ts +0 -31
  102. package/src/test/nunjucks.service.spec.ts +0 -157
  103. package/src/test/pug.service.spec-temp +0 -254
  104. package/src/test/template-layout.service.spec.ts +0 -422
  105. package/src/test/template.service.spec.ts +0 -862
  106. package/src/test/test-database.config.ts +0 -24
  107. package/src/test/test-database.d.ts +0 -6
  108. package/src/test/test.setup.ts +0 -34
  109. package/src/types/ioredis.d.ts +0 -6
  110. package/src/types/mjml.d.ts +0 -5
  111. package/tsconfig.json +0 -17
  112. package/tsconfig.lib.json +0 -14
  113. package/tsconfig.spec.json +0 -15
@@ -1,407 +0,0 @@
1
- import { BadRequestException, ConflictException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
2
- import { InjectRepository } from '@nestjs/typeorm';
3
- import { Equal, In, IsNull, Not, Repository } from 'typeorm';
4
- import { NestDynamicTemplateLayout } from '../entities/template-layout.entity';
5
- import { TemplateEngineEnum, TemplateLanguageEnum, TemplateTypeEnum } from '../interfaces/template.types';
6
- import { TemplateLayoutFilterDto } from '../dto/template-layout-filter.dto';
7
- import { TemplateEngineRegistryService } from './template-engine.registry';
8
- import { RenderTemplateLayoutDto, RenderTemplateLayoutOutput } from '../dto/render-template-layout.dto';
9
- import { CreateTemplateLayoutDto } from '../dto/create-template-layout.dto';
10
- import { RenderContentTemplateLayoutDto } from '../dto/render-content-template-layout.dto';
11
- import {
12
- TemplateRenderError,
13
- TemplateEngineError,
14
- TemplateLanguageError,
15
- } from '../errors/template.errors';
16
-
17
- @Injectable()
18
- export class TemplateLayoutService {
19
- constructor(
20
- @InjectRepository(NestDynamicTemplateLayout)
21
- private readonly layoutRepository: Repository<NestDynamicTemplateLayout>,
22
- private readonly engineRegistry: TemplateEngineRegistryService
23
- ) { }
24
-
25
-
26
- async render(renderDto: RenderTemplateLayoutDto): Promise<RenderTemplateLayoutOutput> {
27
- const { name, scope, scopeId, locale, context } = renderDto;
28
-
29
- try {
30
- // Find template with fallback
31
- const templateLayout = await this.findTemplateLayout(name, scope, scopeId, locale);
32
- if (!templateLayout) {
33
- throw new NotFoundException(`Template layout not found: ${name} in scope ${scope || 'system'}`);
34
- }
35
-
36
- // Validate content exists
37
- if (!templateLayout.content) {
38
- throw new BadRequestException(`Template layout '${name}' has no content to render`);
39
- }
40
-
41
- let content = templateLayout.content;
42
-
43
- // Render content by template engine
44
- if (templateLayout.engine) {
45
- try {
46
- content = await this.renderEngine(templateLayout.engine, content, context || {});
47
- } catch (error) {
48
- throw new TemplateEngineError(templateLayout.engine, error as Error);
49
- }
50
- }
51
-
52
- // If template has language format, process with language engine
53
- if (templateLayout.language) {
54
- try {
55
- content = await this.renderLanguage(templateLayout.language, content, context || {});
56
- } catch (error) {
57
- throw new TemplateLanguageError(templateLayout.language, error as Error);
58
- }
59
- }
60
-
61
- return {
62
- content
63
- };
64
- } catch (error) {
65
- // Re-throw known template errors
66
- if (error instanceof TemplateEngineError ||
67
- error instanceof TemplateLanguageError ||
68
- error instanceof NotFoundException ||
69
- error instanceof BadRequestException) {
70
- throw error;
71
- }
72
-
73
- // Wrap unknown errors
74
- throw new TemplateRenderError('template layout rendering', error as Error, name);
75
- }
76
- }
77
-
78
- async renderContent(input: RenderContentTemplateLayoutDto): Promise<string> {
79
- const { content, language, engine, context } = input;
80
-
81
- try {
82
- if (!content) {
83
- throw new BadRequestException('Content is required for template layout rendering');
84
- }
85
-
86
- let renderContent = content;
87
-
88
- // Render content by template engine
89
- if (engine) {
90
- try {
91
- renderContent = await this.renderEngine(engine, content, context || {});
92
- } catch (error) {
93
- throw new TemplateEngineError(engine, error as Error);
94
- }
95
- }
96
-
97
- // Render content by language engine
98
- if (language) {
99
- try {
100
- renderContent = await this.renderLanguage(language, renderContent, context || {});
101
- } catch (error) {
102
- throw new TemplateLanguageError(language, error as Error);
103
- }
104
- }
105
-
106
- return renderContent;
107
- } catch (error) {
108
- // Re-throw known template errors
109
- if (error instanceof TemplateEngineError ||
110
- error instanceof TemplateLanguageError ||
111
- error instanceof BadRequestException) {
112
- throw error;
113
- }
114
-
115
- // Wrap unknown errors
116
- throw new TemplateRenderError('template layout content rendering', error as Error);
117
- }
118
- }
119
-
120
- async renderLanguage(language: TemplateLanguageEnum, content: string, context: Record<string, any>): Promise<string> {
121
- try {
122
- if (!content) {
123
- throw new BadRequestException('Content is required for language rendering');
124
- }
125
-
126
- const languageEngine = this.engineRegistry.getLanguageEngine(language);
127
- if (!languageEngine) {
128
- throw new BadRequestException(`Language engine not found for: ${language}`);
129
- }
130
-
131
- return await languageEngine.render(content, context);
132
- } catch (error) {
133
- if (error instanceof BadRequestException) {
134
- throw error;
135
- }
136
- throw new TemplateLanguageError(language, error as Error);
137
- }
138
- }
139
-
140
- async renderEngine(engine: TemplateEngineEnum, content: string, context: Record<string, any>): Promise<string> {
141
- try {
142
- if (!content) {
143
- throw new BadRequestException('Content is required for engine rendering');
144
- }
145
-
146
- const templateEngine = this.engineRegistry.getTemplateEngine(engine);
147
- if (!templateEngine) {
148
- throw new BadRequestException(`Template engine not found for: ${engine}`);
149
- }
150
-
151
- return await templateEngine.render(content, context);
152
- } catch (error) {
153
- if (error instanceof BadRequestException) {
154
- throw error;
155
- }
156
- throw new TemplateEngineError(engine, error as Error);
157
- }
158
- }
159
-
160
- /**
161
- * Get all templates, with scoped templates taking precedence over system templates
162
- */
163
- async getTemplateLayouts(filter: TemplateLayoutFilterDto = {}): Promise<NestDynamicTemplateLayout[]> {
164
- const {
165
- scope,
166
- scopeId,
167
- type,
168
- locale,
169
- excludeNames = [],
170
- } = filter;
171
-
172
-
173
- // Build the where clause
174
- const where: any = {};
175
- if (type) where.type = type;
176
- if (locale) where.locale = locale;
177
- if (excludeNames.length > 0) where.name = Not(In(excludeNames));
178
-
179
- const systemTemplates = await this.layoutRepository.find({
180
- where: {
181
- ...where,
182
- scope: 'system',
183
- scopeId: IsNull(),
184
- },
185
- });
186
-
187
- if (scope === 'system') {
188
- return systemTemplates;
189
- }
190
-
191
- // First get all templates matching the filters
192
- const templates = await this.layoutRepository.find({
193
- where: {
194
- ...where,
195
- scope: Equal(scope),
196
- scopeId: scopeId as any,
197
- },
198
- order: {
199
- createdAt: 'DESC',
200
- },
201
- });
202
-
203
- // Create a map to store unique templates by name+type
204
- const templateMap = new Map<string, NestDynamicTemplateLayout>();
205
-
206
- for (const template of systemTemplates) {
207
- const key = `${template.type}/${template.name}/${template.locale}`;
208
- templateMap.set(key, template);
209
- }
210
-
211
- for (const template of templates) {
212
- const key = `${template.type}/${template.name}/${template.locale}`;
213
- templateMap.set(key, template);
214
- }
215
-
216
- // Convert map values back to array
217
- return Array.from(templateMap.values());
218
- }
219
-
220
- async getTemplateLayoutById(id: string): Promise<NestDynamicTemplateLayout | null> {
221
- return this.layoutRepository.findOne({
222
- where: { id },
223
- });
224
- }
225
-
226
- async findTemplateLayout(
227
- name: string,
228
- scope?: string,
229
- scopeId?: string,
230
- locale?: string
231
- ): Promise<NestDynamicTemplateLayout | null> {
232
- // Try to find template in the following order:
233
- // 1. Scoped template with locale
234
- // 2. Scoped template without locale
235
- // 3. System template with locale
236
- // 4. System template without locale
237
-
238
- const locales = (locale ? [locale, 'en'] : ['en']).filter(Boolean);
239
-
240
- // First try to find in the specified scope
241
- for (const currentLocale of locales) {
242
- const template = await this.layoutRepository.findOne({
243
- where: {
244
- name,
245
- scope,
246
- scopeId: scope === 'system' ? IsNull() : Equal(scopeId),
247
- locale: currentLocale,
248
- }
249
- });
250
-
251
- if (template) {
252
- return template;
253
- }
254
- }
255
-
256
- // If not found and not already in system scope, try system scope
257
- if (scope !== 'system') {
258
- for (const currentLocale of locales) {
259
- const template = await this.layoutRepository.findOne({
260
- where: {
261
- name,
262
- scope: 'system',
263
- scopeId: IsNull(),
264
- locale: currentLocale,
265
- }
266
- });
267
-
268
- if (template) {
269
- return template;
270
- }
271
- }
272
- }
273
-
274
- return null;
275
- }
276
-
277
- /**
278
- * Create a system template. Only system templates can be created directly.
279
- */
280
- async createTemplateLayout(data: CreateTemplateLayoutDto): Promise<NestDynamicTemplateLayout> {
281
- // Ensure this is a system template
282
- if (data.scope !== 'system') {
283
- throw new ForbiddenException('Only system templates can be created directly');
284
- }
285
-
286
- // Check if template already exists
287
- const existingTemplate = await this.layoutRepository.findOne({
288
- where: {
289
- name: data.name,
290
- scope: 'system',
291
- scopeId: IsNull(),
292
- locale: data.locale,
293
- },
294
- });
295
-
296
- if (existingTemplate) {
297
- throw new ConflictException(`System template already exists`);
298
- }
299
-
300
- const template = this.layoutRepository.create({
301
- ...data,
302
- scopeId: undefined, // Ensure system templates have no scopeId
303
- });
304
-
305
- return this.layoutRepository.save(template);
306
- }
307
-
308
- async overwriteSystemTemplateLayout(templateId: string, updates: Partial<CreateTemplateLayoutDto>): Promise<NestDynamicTemplateLayout> {
309
- let template = await this.layoutRepository.findOne({
310
- where: { id: templateId },
311
- });
312
-
313
- if (!template) {
314
- throw new NotFoundException(`Template not found: ${templateId}`);
315
- }
316
-
317
- if (template.scope === 'system') {
318
- if (!updates.scope) {
319
- throw new BadRequestException('Scope is required when overwriting system template');
320
- }
321
-
322
- // Check if template already exists in target scope
323
- const existingTemplate = await this.layoutRepository.findOne({
324
- where: {
325
- name: template.name,
326
- locale: template.locale,
327
- scope: updates.scope,
328
- scopeId: updates.scopeId as any,
329
- },
330
- });
331
-
332
- if (existingTemplate) {
333
- // Update existing template in target scope
334
- template = existingTemplate;
335
- } else {
336
- // Create new template in target scope
337
- const newTemplate = this.layoutRepository.create({
338
- ...template,
339
- id: undefined,
340
- createdAt: undefined,
341
- updatedAt: undefined,
342
- scope: updates.scope,
343
- scopeId: updates.scopeId,
344
- });
345
- template = newTemplate;
346
- }
347
- }
348
-
349
- template = this.layoutRepository.merge(template, updates);
350
- await this.layoutRepository.save(template);
351
- return template;
352
- }
353
-
354
-
355
- /**
356
- * Update a template
357
- */
358
- async updateTemplateLayout(
359
- id: string,
360
- updates: Partial<CreateTemplateLayoutDto>,
361
- canUpdateSystemTemplate: boolean = false,
362
- ): Promise<NestDynamicTemplateLayout> {
363
- // Find the template
364
- let template = await this.layoutRepository.findOne({
365
- where: { id },
366
- });
367
-
368
- if (!template) {
369
- throw new NotFoundException(`Template not found: ${id}`);
370
- }
371
-
372
- // If it's a system template and we can't update it, try to overwrite it
373
- if (template.scope === 'system' && !canUpdateSystemTemplate) {
374
-
375
- if (updates.scope) {
376
- // Otherwise, allow overwriting to custom scope
377
- return this.overwriteSystemTemplateLayout(id, updates);
378
- } else {
379
- throw new ForbiddenException('Cannot update system templates');
380
- }
381
- }
382
-
383
- // For regular updates
384
- template = this.layoutRepository.merge(template, updates);
385
- return this.layoutRepository.save(template);
386
- }
387
-
388
- /**
389
- * Delete a scoped template
390
- */
391
- async deleteTemplateLayout(id: string, canDeleteSystemTemplate: boolean = false): Promise<void> {
392
- const template = await this.layoutRepository.findOne({
393
- where: { id },
394
- });
395
-
396
- if (!template) {
397
- throw new Error(`Template not found: ${id}`);
398
- }
399
-
400
- // Prevent deleting system templates
401
- if (template.scope === 'system' && !canDeleteSystemTemplate) {
402
- throw new ForbiddenException('Cannot delete system templates');
403
- }
404
-
405
- await this.layoutRepository.remove(template);
406
- }
407
- }