@k-msg/template 0.1.1 → 0.1.3

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.
package/dist/index.cjs DELETED
@@ -1,1315 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- ButtonParser: () => ButtonParser,
24
- TemplateBuilder: () => TemplateBuilder,
25
- TemplateBuilders: () => TemplateBuilders,
26
- TemplateCategory: () => TemplateCategory,
27
- TemplateRegistry: () => TemplateRegistry,
28
- TemplateService: () => TemplateService,
29
- TemplateStatus: () => TemplateStatus,
30
- TemplateType: () => TemplateType,
31
- TemplateValidator: () => TemplateValidator,
32
- VariableParser: () => VariableParser
33
- });
34
- module.exports = __toCommonJS(index_exports);
35
-
36
- // src/parser/variable.parser.ts
37
- var VariableParser = class {
38
- static VARIABLE_PATTERN = /#{([^}]+)}/g;
39
- /**
40
- * 템플릿 내용에서 변수를 추출합니다
41
- */
42
- static extractVariables(content) {
43
- const variables = [];
44
- const matches = content.matchAll(this.VARIABLE_PATTERN);
45
- for (const match of matches) {
46
- const variableName = match[1].trim();
47
- if (variableName && !variables.includes(variableName)) {
48
- variables.push(variableName);
49
- }
50
- }
51
- return variables;
52
- }
53
- /**
54
- * 템플릿 내용의 변수를 실제 값으로 치환합니다
55
- */
56
- static replaceVariables(content, variables) {
57
- return content.replace(this.VARIABLE_PATTERN, (match, variableName) => {
58
- const value = variables[variableName.trim()];
59
- if (value === void 0 || value === null) {
60
- return match;
61
- }
62
- if (value instanceof Date) {
63
- return value.toISOString();
64
- }
65
- return String(value);
66
- });
67
- }
68
- /**
69
- * 변수 정의와 실제 제공된 값을 검증합니다
70
- */
71
- static validateVariables(variableDefinitions, providedVariables) {
72
- const errors = [];
73
- for (const definition of variableDefinitions) {
74
- const value = providedVariables[definition.name];
75
- if (definition.required && (value === void 0 || value === null)) {
76
- errors.push(`Required variable '${definition.name}' is missing`);
77
- continue;
78
- }
79
- if (value === void 0 || value === null) {
80
- continue;
81
- }
82
- if (!this.validateVariableType(value, definition.type)) {
83
- errors.push(`Variable '${definition.name}' has invalid type. Expected: ${definition.type}`);
84
- }
85
- if (definition.maxLength && String(value).length > definition.maxLength) {
86
- errors.push(`Variable '${definition.name}' exceeds maximum length of ${definition.maxLength}`);
87
- }
88
- if (definition.format && !new RegExp(definition.format).test(String(value))) {
89
- errors.push(`Variable '${definition.name}' does not match required format: ${definition.format}`);
90
- }
91
- }
92
- return {
93
- isValid: errors.length === 0,
94
- errors
95
- };
96
- }
97
- static validateVariableType(value, expectedType) {
98
- switch (expectedType) {
99
- case "string":
100
- return typeof value === "string";
101
- case "number":
102
- return typeof value === "number" && !isNaN(value);
103
- case "date":
104
- return value instanceof Date || !isNaN(Date.parse(value));
105
- case "custom":
106
- return true;
107
- // 커스텀 타입은 별도 검증 로직 필요
108
- default:
109
- return false;
110
- }
111
- }
112
- /**
113
- * 템플릿에서 사용된 변수와 정의된 변수의 일치성을 검사합니다
114
- */
115
- static validateTemplateVariables(content, variableDefinitions) {
116
- const usedVariables = this.extractVariables(content);
117
- const definedVariables = variableDefinitions.map((v) => v.name);
118
- const errors = [];
119
- for (const usedVar of usedVariables) {
120
- if (!definedVariables.includes(usedVar)) {
121
- errors.push(`Variable '${usedVar}' is used in template but not defined`);
122
- }
123
- }
124
- for (const definition of variableDefinitions) {
125
- if (definition.required && !usedVariables.includes(definition.name)) {
126
- errors.push(`Required variable '${definition.name}' is defined but not used in template`);
127
- }
128
- }
129
- return {
130
- isValid: errors.length === 0,
131
- errors
132
- };
133
- }
134
- };
135
-
136
- // src/types/template.types.ts
137
- var import_zod = require("zod");
138
- var TemplateType = /* @__PURE__ */ ((TemplateType2) => {
139
- TemplateType2["ALIMTALK"] = "ALIMTALK";
140
- TemplateType2["SMS"] = "SMS";
141
- TemplateType2["LMS"] = "LMS";
142
- TemplateType2["MMS"] = "MMS";
143
- TemplateType2["RCS"] = "RCS";
144
- return TemplateType2;
145
- })(TemplateType || {});
146
- var TemplateCategory = /* @__PURE__ */ ((TemplateCategory2) => {
147
- TemplateCategory2["AUTHENTICATION"] = "AUTHENTICATION";
148
- TemplateCategory2["NOTIFICATION"] = "NOTIFICATION";
149
- TemplateCategory2["PROMOTION"] = "PROMOTION";
150
- TemplateCategory2["INFORMATION"] = "INFORMATION";
151
- TemplateCategory2["RESERVATION"] = "RESERVATION";
152
- TemplateCategory2["SHIPPING"] = "SHIPPING";
153
- TemplateCategory2["PAYMENT"] = "PAYMENT";
154
- return TemplateCategory2;
155
- })(TemplateCategory || {});
156
- var TemplateStatus = /* @__PURE__ */ ((TemplateStatus2) => {
157
- TemplateStatus2["DRAFT"] = "DRAFT";
158
- TemplateStatus2["PENDING"] = "PENDING";
159
- TemplateStatus2["APPROVED"] = "APPROVED";
160
- TemplateStatus2["REJECTED"] = "REJECTED";
161
- TemplateStatus2["DISABLED"] = "DISABLED";
162
- return TemplateStatus2;
163
- })(TemplateStatus || {});
164
- var TemplateVariableSchema = import_zod.z.object({
165
- name: import_zod.z.string().min(1),
166
- type: import_zod.z.enum(["string", "number", "date", "custom"]),
167
- required: import_zod.z.boolean(),
168
- maxLength: import_zod.z.number().optional(),
169
- format: import_zod.z.string().optional(),
170
- description: import_zod.z.string().optional(),
171
- example: import_zod.z.string().optional()
172
- });
173
- var TemplateButtonSchema = import_zod.z.object({
174
- type: import_zod.z.enum(["WL", "AL", "DS", "BK", "MD"]),
175
- name: import_zod.z.string().min(1),
176
- linkMobile: import_zod.z.string().url().optional(),
177
- linkPc: import_zod.z.string().url().optional(),
178
- linkIos: import_zod.z.string().url().optional(),
179
- linkAndroid: import_zod.z.string().url().optional(),
180
- schemeIos: import_zod.z.string().optional(),
181
- schemeAndroid: import_zod.z.string().optional()
182
- });
183
- var AlimTalkTemplateSchema = import_zod.z.object({
184
- id: import_zod.z.string(),
185
- code: import_zod.z.string(),
186
- name: import_zod.z.string().min(1),
187
- content: import_zod.z.string().min(1),
188
- variables: import_zod.z.array(TemplateVariableSchema),
189
- buttons: import_zod.z.array(TemplateButtonSchema).optional(),
190
- category: import_zod.z.nativeEnum(TemplateCategory),
191
- status: import_zod.z.nativeEnum(TemplateStatus),
192
- provider: import_zod.z.string(),
193
- metadata: import_zod.z.object({
194
- createdAt: import_zod.z.date(),
195
- updatedAt: import_zod.z.date(),
196
- approvedAt: import_zod.z.date().optional(),
197
- rejectedAt: import_zod.z.date().optional(),
198
- rejectionReason: import_zod.z.string().optional(),
199
- usage: import_zod.z.object({
200
- sent: import_zod.z.number().min(0),
201
- delivered: import_zod.z.number().min(0),
202
- failed: import_zod.z.number().min(0)
203
- })
204
- })
205
- });
206
-
207
- // src/parser/button.parser.ts
208
- var ButtonParser = class {
209
- /**
210
- * 버튼 설정의 유효성을 검증합니다
211
- */
212
- static validateButtons(buttons) {
213
- const errors = [];
214
- if (buttons.length > 5) {
215
- errors.push("Maximum 5 buttons are allowed");
216
- }
217
- for (let i = 0; i < buttons.length; i++) {
218
- const button = buttons[i];
219
- const buttonIndex = i + 1;
220
- if (!button.name || button.name.trim().length === 0) {
221
- errors.push(`Button ${buttonIndex}: name is required`);
222
- } else if (button.name.length > 14) {
223
- errors.push(`Button ${buttonIndex}: name cannot exceed 14 characters`);
224
- }
225
- this.validateButtonByType(button, buttonIndex, errors);
226
- }
227
- return {
228
- isValid: errors.length === 0,
229
- errors
230
- };
231
- }
232
- static validateButtonByType(button, buttonIndex, errors) {
233
- switch (button.type) {
234
- case "WL":
235
- this.validateWebLinkButton(button, buttonIndex, errors);
236
- break;
237
- case "AL":
238
- this.validateAppLinkButton(button, buttonIndex, errors);
239
- break;
240
- case "DS":
241
- this.validateDeliveryButton(button, buttonIndex, errors);
242
- break;
243
- case "BK":
244
- this.validateBotKeywordButton(button, buttonIndex, errors);
245
- break;
246
- case "MD":
247
- this.validateMessageDeliveryButton(button, buttonIndex, errors);
248
- break;
249
- default:
250
- errors.push(`Button ${buttonIndex}: invalid button type '${button.type}'`);
251
- }
252
- }
253
- static validateWebLinkButton(button, buttonIndex, errors) {
254
- if (!button.linkMobile && !button.linkPc) {
255
- errors.push(`Button ${buttonIndex}: web link button must have at least mobile or PC link`);
256
- }
257
- if (button.linkMobile && !this.isValidUrl(button.linkMobile)) {
258
- errors.push(`Button ${buttonIndex}: invalid mobile link URL`);
259
- }
260
- if (button.linkPc && !this.isValidUrl(button.linkPc)) {
261
- errors.push(`Button ${buttonIndex}: invalid PC link URL`);
262
- }
263
- }
264
- static validateAppLinkButton(button, buttonIndex, errors) {
265
- const hasAnyLink = button.linkIos || button.linkAndroid || button.schemeIos || button.schemeAndroid;
266
- if (!hasAnyLink) {
267
- errors.push(`Button ${buttonIndex}: app link button must have at least one app link or scheme`);
268
- }
269
- if (button.linkIos && !this.isValidUrl(button.linkIos)) {
270
- errors.push(`Button ${buttonIndex}: invalid iOS link URL`);
271
- }
272
- if (button.linkAndroid && !this.isValidUrl(button.linkAndroid)) {
273
- errors.push(`Button ${buttonIndex}: invalid Android link URL`);
274
- }
275
- }
276
- static validateDeliveryButton(button, buttonIndex, errors) {
277
- }
278
- static validateBotKeywordButton(button, buttonIndex, errors) {
279
- }
280
- static validateMessageDeliveryButton(button, buttonIndex, errors) {
281
- }
282
- static isValidUrl(url) {
283
- try {
284
- new URL(url);
285
- return true;
286
- } catch {
287
- return false;
288
- }
289
- }
290
- /**
291
- * 버튼을 JSON 문자열로 직렬화합니다 (카카오 API 형식)
292
- */
293
- static serializeButtons(buttons) {
294
- const serializedButtons = buttons.map((button) => ({
295
- name: button.name,
296
- type: button.type,
297
- url_mobile: button.linkMobile,
298
- url_pc: button.linkPc,
299
- scheme_ios: button.schemeIos,
300
- scheme_android: button.schemeAndroid
301
- }));
302
- return JSON.stringify(serializedButtons);
303
- }
304
- /**
305
- * JSON 문자열에서 버튼 배열로 역직렬화합니다
306
- */
307
- static deserializeButtons(buttonsJson) {
308
- try {
309
- const parsed = JSON.parse(buttonsJson);
310
- return parsed.map((button) => ({
311
- name: button.name,
312
- type: button.type,
313
- linkMobile: button.url_mobile,
314
- linkPc: button.url_pc,
315
- linkIos: button.url_ios,
316
- linkAndroid: button.url_android,
317
- schemeIos: button.scheme_ios,
318
- schemeAndroid: button.scheme_android
319
- }));
320
- } catch (error) {
321
- throw new Error(`Failed to parse buttons JSON: ${error}`);
322
- }
323
- }
324
- };
325
-
326
- // src/parser/validator.ts
327
- var TemplateValidator = class {
328
- /**
329
- * 알림톡 템플릿의 전체적인 유효성을 검증합니다
330
- */
331
- static validate(template) {
332
- const errors = [];
333
- const warnings = [];
334
- this.validateBasicFields(template, errors);
335
- this.validateContent(template, errors, warnings);
336
- const variableValidation = VariableParser.validateTemplateVariables(
337
- template.content,
338
- template.variables
339
- );
340
- errors.push(...variableValidation.errors);
341
- if (template.buttons && template.buttons.length > 0) {
342
- const buttonValidation = ButtonParser.validateButtons(template.buttons);
343
- errors.push(...buttonValidation.errors);
344
- }
345
- this.validateByCategory(template, errors, warnings);
346
- return {
347
- isValid: errors.length === 0,
348
- errors,
349
- warnings
350
- };
351
- }
352
- static validateBasicFields(template, errors) {
353
- if (!template.name || template.name.trim().length === 0) {
354
- errors.push("Template name is required");
355
- }
356
- if (!template.code || template.code.trim().length === 0) {
357
- errors.push("Template code is required");
358
- }
359
- if (!template.provider || template.provider.trim().length === 0) {
360
- errors.push("Provider is required");
361
- }
362
- }
363
- static validateContent(template, errors, warnings) {
364
- if (!template.content || template.content.trim().length === 0) {
365
- errors.push("Template content is required");
366
- return;
367
- }
368
- const content = template.content;
369
- if (content.length > 1e3) {
370
- errors.push("Template content cannot exceed 1000 characters");
371
- }
372
- if (this.containsProhibitedCharacters(content)) {
373
- errors.push("Template content contains prohibited characters");
374
- }
375
- const lineBreaks = (content.match(/\n/g) || []).length;
376
- if (lineBreaks > 20) {
377
- warnings.push("Template content has many line breaks, which may affect readability");
378
- }
379
- this.validateUrlsInContent(content, warnings);
380
- }
381
- static containsProhibitedCharacters(content) {
382
- const prohibitedChars = /[<>]/;
383
- return prohibitedChars.test(content);
384
- }
385
- static validateUrlsInContent(content, warnings) {
386
- const urlPattern = /https?:\/\/[^\s]+/g;
387
- const urls = content.match(urlPattern) || [];
388
- for (const url of urls) {
389
- try {
390
- new URL(url);
391
- } catch {
392
- warnings.push(`Invalid URL found in content: ${url}`);
393
- }
394
- }
395
- }
396
- static validateByCategory(template, errors, warnings) {
397
- switch (template.category) {
398
- case "AUTHENTICATION" /* AUTHENTICATION */:
399
- this.validateAuthenticationTemplate(template, errors, warnings);
400
- break;
401
- case "PROMOTION" /* PROMOTION */:
402
- this.validatePromotionTemplate(template, errors, warnings);
403
- break;
404
- case "PAYMENT" /* PAYMENT */:
405
- this.validatePaymentTemplate(template, errors, warnings);
406
- break;
407
- }
408
- }
409
- static validateAuthenticationTemplate(template, errors, warnings) {
410
- const hasAuthCode = template.variables.some(
411
- (v) => v.name.includes("\uC778\uC99D") || v.name.includes("\uCF54\uB4DC") || v.name.includes("\uBC88\uD638")
412
- );
413
- if (!hasAuthCode) {
414
- warnings.push("Authentication template should include an authentication code variable");
415
- }
416
- if (template.buttons && template.buttons.length > 0) {
417
- warnings.push("Authentication templates typically should not have buttons");
418
- }
419
- }
420
- static validatePromotionTemplate(template, errors, warnings) {
421
- if (!template.buttons || template.buttons.length === 0) {
422
- warnings.push("Promotion templates typically should have action buttons");
423
- }
424
- }
425
- static validatePaymentTemplate(template, errors, warnings) {
426
- const hasAmountVariable = template.variables.some(
427
- (v) => v.name.includes("\uAE08\uC561") || v.name.includes("\uAC00\uACA9") || v.name.includes("\uC6D0")
428
- );
429
- if (!hasAmountVariable) {
430
- warnings.push("Payment template should include an amount variable");
431
- }
432
- }
433
- /**
434
- * 빠른 검증 - 기본적인 필수 필드만 검사
435
- */
436
- static quickValidate(template) {
437
- const errors = [];
438
- if (!template.name) errors.push("Name is required");
439
- if (!template.content) errors.push("Content is required");
440
- if (!template.category) errors.push("Category is required");
441
- if (!template.provider) errors.push("Provider is required");
442
- return {
443
- isValid: errors.length === 0,
444
- errors,
445
- warnings: []
446
- };
447
- }
448
- };
449
-
450
- // src/services/template.service.ts
451
- var TemplateService = class {
452
- templates = /* @__PURE__ */ new Map();
453
- async createTemplate(template) {
454
- const variables = VariableParser.extractVariables(template.content);
455
- const newTemplate = {
456
- ...template,
457
- id: this.generateTemplateId(),
458
- variables: variables.map((name) => ({
459
- name,
460
- type: "string",
461
- required: true
462
- })),
463
- metadata: {
464
- createdAt: /* @__PURE__ */ new Date(),
465
- updatedAt: /* @__PURE__ */ new Date(),
466
- usage: { sent: 0, delivered: 0, failed: 0 }
467
- }
468
- };
469
- const validation = TemplateValidator.validate(newTemplate);
470
- if (!validation.isValid) {
471
- throw new Error(`Template validation failed: ${validation.errors.join(", ")}`);
472
- }
473
- this.templates.set(newTemplate.id, newTemplate);
474
- return newTemplate;
475
- }
476
- async getTemplate(templateId) {
477
- return this.templates.get(templateId) || null;
478
- }
479
- async updateTemplate(templateId, updates) {
480
- const template = this.templates.get(templateId);
481
- if (!template) {
482
- throw new Error(`Template ${templateId} not found`);
483
- }
484
- const updatedTemplate = {
485
- ...template,
486
- ...updates,
487
- metadata: {
488
- ...template.metadata,
489
- updatedAt: /* @__PURE__ */ new Date()
490
- }
491
- };
492
- this.templates.set(templateId, updatedTemplate);
493
- return updatedTemplate;
494
- }
495
- async deleteTemplate(templateId) {
496
- this.templates.delete(templateId);
497
- }
498
- async renderTemplate(templateId, variables) {
499
- const template = this.templates.get(templateId);
500
- if (!template) {
501
- throw new Error(`Template ${templateId} not found`);
502
- }
503
- const validation = VariableParser.validateVariables(template.variables, variables);
504
- if (!validation.isValid) {
505
- throw new Error(`Variable validation failed: ${validation.errors.join(", ")}`);
506
- }
507
- return VariableParser.replaceVariables(template.content, variables);
508
- }
509
- generateTemplateId() {
510
- return `tpl_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
511
- }
512
- };
513
-
514
- // src/builder/template.builder.ts
515
- var TemplateBuilder = class _TemplateBuilder {
516
- template = {
517
- variables: [],
518
- buttons: [],
519
- status: "DRAFT" /* DRAFT */,
520
- metadata: {
521
- createdAt: /* @__PURE__ */ new Date(),
522
- updatedAt: /* @__PURE__ */ new Date(),
523
- usage: { sent: 0, delivered: 0, failed: 0 }
524
- }
525
- };
526
- /**
527
- * Set template name
528
- */
529
- name(name) {
530
- this.template.name = name;
531
- return this;
532
- }
533
- /**
534
- * Set template code (provider specific)
535
- */
536
- code(code) {
537
- this.template.code = code;
538
- return this;
539
- }
540
- /**
541
- * Set template content with variables
542
- */
543
- content(content) {
544
- this.template.content = content;
545
- const extractedVariables = VariableParser.extractVariables(content);
546
- const existingVariableNames = (this.template.variables || []).map((v) => v.name);
547
- for (const varName of extractedVariables) {
548
- if (!existingVariableNames.includes(varName)) {
549
- this.variable(varName, "string", true);
550
- }
551
- }
552
- return this;
553
- }
554
- /**
555
- * Set template category
556
- */
557
- category(category) {
558
- this.template.category = category;
559
- return this;
560
- }
561
- /**
562
- * Set template provider
563
- */
564
- provider(provider) {
565
- this.template.provider = provider;
566
- return this;
567
- }
568
- /**
569
- * Set template status
570
- */
571
- status(status) {
572
- this.template.status = status;
573
- return this;
574
- }
575
- /**
576
- * Add a variable definition
577
- */
578
- variable(name, type = "string", required = true, options = {}) {
579
- if (!this.template.variables) {
580
- this.template.variables = [];
581
- }
582
- this.template.variables = this.template.variables.filter((v) => v.name !== name);
583
- const variable = {
584
- name,
585
- type,
586
- required,
587
- ...options
588
- };
589
- this.template.variables.push(variable);
590
- return this;
591
- }
592
- /**
593
- * Add multiple variables at once
594
- */
595
- variables(variables) {
596
- for (const variable of variables) {
597
- this.variable(
598
- variable.name,
599
- variable.type || "string",
600
- variable.required ?? true,
601
- {
602
- maxLength: variable.maxLength,
603
- format: variable.format,
604
- description: variable.description,
605
- example: variable.example
606
- }
607
- );
608
- }
609
- return this;
610
- }
611
- /**
612
- * Add a web link button
613
- */
614
- webLinkButton(name, mobileUrl, pcUrl) {
615
- return this.button({
616
- type: "WL",
617
- name,
618
- linkMobile: mobileUrl,
619
- linkPc: pcUrl
620
- });
621
- }
622
- /**
623
- * Add an app link button
624
- */
625
- appLinkButton(name, options) {
626
- return this.button({
627
- type: "AL",
628
- name,
629
- linkIos: options.iosUrl,
630
- linkAndroid: options.androidUrl,
631
- schemeIos: options.iosScheme,
632
- schemeAndroid: options.androidScheme
633
- });
634
- }
635
- /**
636
- * Add a delivery tracking button
637
- */
638
- deliveryButton(name) {
639
- return this.button({
640
- type: "DS",
641
- name
642
- });
643
- }
644
- /**
645
- * Add a bot keyword button
646
- */
647
- botKeywordButton(name) {
648
- return this.button({
649
- type: "BK",
650
- name
651
- });
652
- }
653
- /**
654
- * Add a message delivery button
655
- */
656
- messageDeliveryButton(name) {
657
- return this.button({
658
- type: "MD",
659
- name
660
- });
661
- }
662
- /**
663
- * Add a custom button
664
- */
665
- button(button) {
666
- if (!this.template.buttons) {
667
- this.template.buttons = [];
668
- }
669
- if (this.template.buttons.length >= 5) {
670
- throw new Error("Maximum 5 buttons are allowed per template");
671
- }
672
- this.template.buttons.push(button);
673
- return this;
674
- }
675
- /**
676
- * Clear all buttons
677
- */
678
- clearButtons() {
679
- this.template.buttons = [];
680
- return this;
681
- }
682
- /**
683
- * Set template metadata
684
- */
685
- metadata(metadata) {
686
- this.template.metadata = {
687
- ...this.template.metadata,
688
- ...metadata,
689
- updatedAt: /* @__PURE__ */ new Date()
690
- };
691
- return this;
692
- }
693
- /**
694
- * Validate the current template
695
- */
696
- validate() {
697
- const quickValidation = TemplateValidator.quickValidate(this.template);
698
- if (!quickValidation.isValid) {
699
- return quickValidation;
700
- }
701
- try {
702
- const fullTemplate = this.build();
703
- return TemplateValidator.validate(fullTemplate);
704
- } catch (error) {
705
- return {
706
- isValid: false,
707
- errors: [error instanceof Error ? error.message : "Unknown validation error"],
708
- warnings: []
709
- };
710
- }
711
- }
712
- /**
713
- * Preview the template with sample variables
714
- */
715
- preview(sampleVariables = {}) {
716
- if (!this.template.content) {
717
- throw new Error("Template content is required for preview");
718
- }
719
- const variables = { ...this.generateSampleVariables(), ...sampleVariables };
720
- return VariableParser.replaceVariables(this.template.content, variables);
721
- }
722
- /**
723
- * Generate sample variables based on variable definitions
724
- */
725
- generateSampleVariables() {
726
- const samples = {};
727
- for (const variable of this.template.variables || []) {
728
- if (variable.example) {
729
- samples[variable.name] = variable.example;
730
- } else {
731
- switch (variable.type) {
732
- case "string":
733
- samples[variable.name] = variable.name.includes("name") || variable.name.includes("\uC774\uB984") ? "\uD64D\uAE38\uB3D9" : variable.name.includes("code") || variable.name.includes("\uCF54\uB4DC") ? "123456" : `\uC0D8\uD50C${variable.name}`;
734
- break;
735
- case "number":
736
- samples[variable.name] = variable.name.includes("amount") || variable.name.includes("\uAE08\uC561") ? 1e4 : variable.name.includes("count") || variable.name.includes("\uAC1C\uC218") ? 1 : 123;
737
- break;
738
- case "date":
739
- samples[variable.name] = /* @__PURE__ */ new Date();
740
- break;
741
- default:
742
- samples[variable.name] = `\uC0D8\uD50C\uAC12`;
743
- }
744
- }
745
- }
746
- return samples;
747
- }
748
- /**
749
- * Clone the current builder
750
- */
751
- clone() {
752
- const cloned = new _TemplateBuilder();
753
- cloned.template = JSON.parse(JSON.stringify(this.template));
754
- return cloned;
755
- }
756
- /**
757
- * Reset the builder to start fresh
758
- */
759
- reset() {
760
- this.template = {
761
- variables: [],
762
- buttons: [],
763
- status: "DRAFT" /* DRAFT */,
764
- metadata: {
765
- createdAt: /* @__PURE__ */ new Date(),
766
- updatedAt: /* @__PURE__ */ new Date(),
767
- usage: { sent: 0, delivered: 0, failed: 0 }
768
- }
769
- };
770
- return this;
771
- }
772
- /**
773
- * Build the final template
774
- */
775
- build() {
776
- if (!this.template.name) throw new Error("Template name is required");
777
- if (!this.template.code) throw new Error("Template code is required");
778
- if (!this.template.content) throw new Error("Template content is required");
779
- if (!this.template.category) throw new Error("Template category is required");
780
- if (!this.template.provider) throw new Error("Template provider is required");
781
- const template = {
782
- id: this.template.id || this.generateTemplateId(),
783
- name: this.template.name,
784
- code: this.template.code,
785
- content: this.template.content,
786
- variables: this.template.variables || [],
787
- buttons: this.template.buttons,
788
- category: this.template.category,
789
- status: this.template.status || "DRAFT" /* DRAFT */,
790
- provider: this.template.provider,
791
- metadata: {
792
- ...this.template.metadata,
793
- updatedAt: /* @__PURE__ */ new Date()
794
- }
795
- };
796
- const validation = TemplateValidator.validate(template);
797
- if (!validation.isValid) {
798
- throw new Error(`Template validation failed: ${validation.errors.join(", ")}`);
799
- }
800
- return template;
801
- }
802
- generateTemplateId() {
803
- return `tpl_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
804
- }
805
- };
806
- var TemplateBuilders = {
807
- /**
808
- * Create an authentication template builder
809
- */
810
- authentication(name, provider) {
811
- return new TemplateBuilder().name(name).category("AUTHENTICATION" /* AUTHENTICATION */).provider(provider).variable("code", "string", true, {
812
- maxLength: 10,
813
- description: "Authentication code",
814
- example: "123456"
815
- });
816
- },
817
- /**
818
- * Create a notification template builder
819
- */
820
- notification(name, provider) {
821
- return new TemplateBuilder().name(name).category("NOTIFICATION" /* NOTIFICATION */).provider(provider).variable("name", "string", true, {
822
- description: "Recipient name",
823
- example: "\uD64D\uAE38\uB3D9"
824
- });
825
- },
826
- /**
827
- * Create a promotion template builder
828
- */
829
- promotion(name, provider) {
830
- return new TemplateBuilder().name(name).category("PROMOTION" /* PROMOTION */).provider(provider).variable("name", "string", true, {
831
- description: "Customer name",
832
- example: "\uD64D\uAE38\uB3D9"
833
- }).variable("discount", "number", false, {
834
- description: "Discount percentage",
835
- example: "20"
836
- });
837
- },
838
- /**
839
- * Create a payment template builder
840
- */
841
- payment(name, provider) {
842
- return new TemplateBuilder().name(name).category("PAYMENT" /* PAYMENT */).provider(provider).variable("name", "string", true, {
843
- description: "Customer name",
844
- example: "\uD64D\uAE38\uB3D9"
845
- }).variable("amount", "number", true, {
846
- description: "Payment amount",
847
- example: "10000"
848
- });
849
- }
850
- };
851
-
852
- // src/registry/template.registry.ts
853
- var import_events = require("events");
854
- var TemplateRegistry = class extends import_events.EventEmitter {
855
- constructor(options = {}) {
856
- super();
857
- this.options = options;
858
- this.options = { ...this.defaultOptions, ...options };
859
- if (this.options.enableAutoBackup) {
860
- this.startAutoBackup();
861
- }
862
- }
863
- templates = /* @__PURE__ */ new Map();
864
- templatesByCode = /* @__PURE__ */ new Map();
865
- templatesByProvider = /* @__PURE__ */ new Map();
866
- templatesByCategory = /* @__PURE__ */ new Map();
867
- templateHistories = /* @__PURE__ */ new Map();
868
- usageStats = /* @__PURE__ */ new Map();
869
- backupTimer;
870
- defaultOptions = {
871
- enableVersioning: true,
872
- maxVersionsPerTemplate: 10,
873
- enableUsageTracking: true,
874
- enableAutoBackup: false,
875
- backupInterval: 36e5,
876
- // 1 hour
877
- enableValidationOnRegister: true
878
- };
879
- /**
880
- * Register a new template
881
- */
882
- async register(template) {
883
- if (this.options.enableValidationOnRegister) {
884
- const validation = TemplateValidator.validate(template);
885
- if (!validation.isValid) {
886
- throw new Error(`Template validation failed: ${validation.errors.join(", ")}`);
887
- }
888
- }
889
- const existingTemplate = this.getByCode(template.code, template.provider);
890
- if (existingTemplate && existingTemplate.id !== template.id) {
891
- throw new Error(`Template with code '${template.code}' already exists for provider '${template.provider}'`);
892
- }
893
- this.templates.set(template.id, template);
894
- this.templatesByCode.set(`${template.provider}:${template.code}`, template);
895
- this.updateIndexes(template);
896
- if (this.options.enableVersioning) {
897
- this.initializeVersionHistory(template);
898
- }
899
- if (this.options.enableUsageTracking) {
900
- this.initializeUsageStats(template);
901
- }
902
- this.emit("template:registered", { template });
903
- }
904
- /**
905
- * Update an existing template
906
- */
907
- async update(templateId, updates) {
908
- const existing = this.templates.get(templateId);
909
- if (!existing) {
910
- throw new Error(`Template ${templateId} not found`);
911
- }
912
- const updatedTemplate = {
913
- ...existing,
914
- ...updates,
915
- id: templateId,
916
- // Ensure ID doesn't change
917
- metadata: {
918
- ...existing.metadata,
919
- ...updates.metadata,
920
- updatedAt: /* @__PURE__ */ new Date()
921
- }
922
- };
923
- if (this.options.enableValidationOnRegister) {
924
- const validation = TemplateValidator.validate(updatedTemplate);
925
- if (!validation.isValid) {
926
- throw new Error(`Template validation failed: ${validation.errors.join(", ")}`);
927
- }
928
- }
929
- if (this.options.enableVersioning) {
930
- this.addVersionToHistory(existing, updatedTemplate);
931
- }
932
- this.templates.set(templateId, updatedTemplate);
933
- this.templatesByCode.set(`${updatedTemplate.provider}:${updatedTemplate.code}`, updatedTemplate);
934
- this.updateIndexes(updatedTemplate);
935
- this.emit("template:updated", {
936
- oldTemplate: existing,
937
- newTemplate: updatedTemplate
938
- });
939
- return updatedTemplate;
940
- }
941
- /**
942
- * Get template by ID
943
- */
944
- get(templateId) {
945
- return this.templates.get(templateId) || null;
946
- }
947
- /**
948
- * Get template by code and provider
949
- */
950
- getByCode(code, provider) {
951
- return this.templatesByCode.get(`${provider}:${code}`) || null;
952
- }
953
- /**
954
- * Search templates with filters and pagination
955
- */
956
- search(filters = {}, options = {}) {
957
- let templates = Array.from(this.templates.values());
958
- if (filters.provider) {
959
- templates = templates.filter((t) => t.provider === filters.provider);
960
- }
961
- if (filters.category) {
962
- templates = templates.filter((t) => t.category === filters.category);
963
- }
964
- if (filters.status) {
965
- templates = templates.filter((t) => t.status === filters.status);
966
- }
967
- if (filters.nameContains) {
968
- const searchTerm = filters.nameContains.toLowerCase();
969
- templates = templates.filter((t) => t.name.toLowerCase().includes(searchTerm));
970
- }
971
- if (filters.codeContains) {
972
- const searchTerm = filters.codeContains.toLowerCase();
973
- templates = templates.filter((t) => t.code.toLowerCase().includes(searchTerm));
974
- }
975
- if (filters.createdAfter) {
976
- templates = templates.filter((t) => t.metadata.createdAt >= filters.createdAfter);
977
- }
978
- if (filters.createdBefore) {
979
- templates = templates.filter((t) => t.metadata.createdAt <= filters.createdBefore);
980
- }
981
- if (filters.usageMin !== void 0) {
982
- templates = templates.filter((t) => t.metadata.usage.sent >= filters.usageMin);
983
- }
984
- if (filters.usageMax !== void 0) {
985
- templates = templates.filter((t) => t.metadata.usage.sent <= filters.usageMax);
986
- }
987
- const sortBy = options.sortBy || "createdAt";
988
- const sortOrder = options.sortOrder || "desc";
989
- templates.sort((a, b) => {
990
- let aValue, bValue;
991
- switch (sortBy) {
992
- case "name":
993
- aValue = a.name;
994
- bValue = b.name;
995
- break;
996
- case "code":
997
- aValue = a.code;
998
- bValue = b.code;
999
- break;
1000
- case "createdAt":
1001
- aValue = a.metadata.createdAt.getTime();
1002
- bValue = b.metadata.createdAt.getTime();
1003
- break;
1004
- case "updatedAt":
1005
- aValue = a.metadata.updatedAt.getTime();
1006
- bValue = b.metadata.updatedAt.getTime();
1007
- break;
1008
- case "usage":
1009
- aValue = a.metadata.usage.sent;
1010
- bValue = b.metadata.usage.sent;
1011
- break;
1012
- default:
1013
- aValue = a.metadata.createdAt.getTime();
1014
- bValue = b.metadata.createdAt.getTime();
1015
- }
1016
- if (sortOrder === "asc") {
1017
- return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
1018
- } else {
1019
- return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
1020
- }
1021
- });
1022
- const page = options.page || 1;
1023
- const limit = options.limit || 20;
1024
- const start = (page - 1) * limit;
1025
- const end = start + limit;
1026
- const paginatedTemplates = templates.slice(start, end);
1027
- return {
1028
- templates: paginatedTemplates,
1029
- total: templates.length,
1030
- page,
1031
- limit,
1032
- hasMore: end < templates.length
1033
- };
1034
- }
1035
- /**
1036
- * Get templates by provider
1037
- */
1038
- getByProvider(provider) {
1039
- const templateIds = this.templatesByProvider.get(provider) || /* @__PURE__ */ new Set();
1040
- return Array.from(templateIds).map((id) => this.templates.get(id)).filter((template) => template !== void 0);
1041
- }
1042
- /**
1043
- * Get templates by category
1044
- */
1045
- getByCategory(category) {
1046
- const templateIds = this.templatesByCategory.get(category) || /* @__PURE__ */ new Set();
1047
- return Array.from(templateIds).map((id) => this.templates.get(id)).filter((template) => template !== void 0);
1048
- }
1049
- /**
1050
- * Delete template
1051
- */
1052
- async delete(templateId) {
1053
- const template = this.templates.get(templateId);
1054
- if (!template) {
1055
- return false;
1056
- }
1057
- this.templates.delete(templateId);
1058
- this.templatesByCode.delete(`${template.provider}:${template.code}`);
1059
- const providerSet = this.templatesByProvider.get(template.provider);
1060
- if (providerSet) {
1061
- providerSet.delete(templateId);
1062
- if (providerSet.size === 0) {
1063
- this.templatesByProvider.delete(template.provider);
1064
- }
1065
- }
1066
- const categorySet = this.templatesByCategory.get(template.category);
1067
- if (categorySet) {
1068
- categorySet.delete(templateId);
1069
- if (categorySet.size === 0) {
1070
- this.templatesByCategory.delete(template.category);
1071
- }
1072
- }
1073
- this.templateHistories.delete(templateId);
1074
- this.usageStats.delete(templateId);
1075
- this.emit("template:deleted", { template });
1076
- return true;
1077
- }
1078
- /**
1079
- * Get template version history
1080
- */
1081
- getHistory(templateId) {
1082
- return this.templateHistories.get(templateId) || null;
1083
- }
1084
- /**
1085
- * Get specific template version
1086
- */
1087
- getVersion(templateId, version) {
1088
- const history = this.templateHistories.get(templateId);
1089
- if (!history) return null;
1090
- const versionEntry = history.versions.find((v) => v.version === version);
1091
- return versionEntry ? versionEntry.template : null;
1092
- }
1093
- /**
1094
- * Restore template to a specific version
1095
- */
1096
- async restoreVersion(templateId, version) {
1097
- const versionTemplate = this.getVersion(templateId, version);
1098
- if (!versionTemplate) {
1099
- throw new Error(`Version ${version} not found for template ${templateId}`);
1100
- }
1101
- return this.update(templateId, {
1102
- ...versionTemplate,
1103
- metadata: {
1104
- ...versionTemplate.metadata,
1105
- updatedAt: /* @__PURE__ */ new Date()
1106
- }
1107
- });
1108
- }
1109
- /**
1110
- * Get template usage statistics
1111
- */
1112
- getUsageStats(templateId) {
1113
- return this.usageStats.get(templateId) || null;
1114
- }
1115
- /**
1116
- * Update template usage statistics
1117
- */
1118
- updateUsageStats(templateId, stats) {
1119
- const template = this.templates.get(templateId);
1120
- if (!template) return;
1121
- if (stats.sent) template.metadata.usage.sent += stats.sent;
1122
- if (stats.delivered) template.metadata.usage.delivered += stats.delivered;
1123
- if (stats.failed) template.metadata.usage.failed += stats.failed;
1124
- if (this.options.enableUsageTracking) {
1125
- const usageStats = this.usageStats.get(templateId);
1126
- if (usageStats) {
1127
- if (stats.sent) usageStats.totalSent += stats.sent;
1128
- if (stats.delivered) usageStats.totalDelivered += stats.delivered;
1129
- if (stats.failed) usageStats.totalFailed += stats.failed;
1130
- usageStats.deliveryRate = usageStats.totalSent > 0 ? usageStats.totalDelivered / usageStats.totalSent * 100 : 0;
1131
- usageStats.failureRate = usageStats.totalSent > 0 ? usageStats.totalFailed / usageStats.totalSent * 100 : 0;
1132
- usageStats.lastUsed = /* @__PURE__ */ new Date();
1133
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1134
- let todayStats = usageStats.usageByDay.find((d) => d.date === today);
1135
- if (!todayStats) {
1136
- todayStats = { date: today, sent: 0, delivered: 0, failed: 0 };
1137
- usageStats.usageByDay.push(todayStats);
1138
- usageStats.usageByDay = usageStats.usageByDay.sort((a, b) => b.date.localeCompare(a.date)).slice(0, 30);
1139
- }
1140
- if (stats.sent) todayStats.sent += stats.sent;
1141
- if (stats.delivered) todayStats.delivered += stats.delivered;
1142
- if (stats.failed) todayStats.failed += stats.failed;
1143
- }
1144
- }
1145
- this.emit("usage:updated", { templateId, stats });
1146
- }
1147
- /**
1148
- * Get registry statistics
1149
- */
1150
- getStats() {
1151
- const templates = Array.from(this.templates.values());
1152
- const byProvider = {};
1153
- const byCategory = {};
1154
- const byStatus = {};
1155
- for (const template of templates) {
1156
- byProvider[template.provider] = (byProvider[template.provider] || 0) + 1;
1157
- byCategory[template.category] = (byCategory[template.category] || 0) + 1;
1158
- byStatus[template.status] = (byStatus[template.status] || 0) + 1;
1159
- }
1160
- return {
1161
- totalTemplates: templates.length,
1162
- byProvider,
1163
- byCategory,
1164
- byStatus
1165
- };
1166
- }
1167
- /**
1168
- * Export templates to JSON
1169
- */
1170
- export(filters) {
1171
- const result = this.search(filters, { limit: 1e4 });
1172
- return JSON.stringify({
1173
- templates: result.templates,
1174
- exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1175
- total: result.total
1176
- }, null, 2);
1177
- }
1178
- /**
1179
- * Import templates from JSON
1180
- */
1181
- async import(jsonData, options = {}) {
1182
- const result = { imported: 0, skipped: 0, errors: [] };
1183
- try {
1184
- const data = JSON.parse(jsonData);
1185
- const templates = data.templates || [];
1186
- for (const templateData of templates) {
1187
- try {
1188
- const existingTemplate = this.get(templateData.id);
1189
- if (existingTemplate && !options.overwrite) {
1190
- result.skipped++;
1191
- continue;
1192
- }
1193
- await this.register(templateData);
1194
- result.imported++;
1195
- } catch (error) {
1196
- result.errors.push(`Template ${templateData.id}: ${error instanceof Error ? error.message : "Unknown error"}`);
1197
- }
1198
- }
1199
- } catch (error) {
1200
- result.errors.push(`Invalid JSON format: ${error instanceof Error ? error.message : "Unknown error"}`);
1201
- }
1202
- return result;
1203
- }
1204
- /**
1205
- * Clear all templates (use with caution!)
1206
- */
1207
- clear() {
1208
- this.templates.clear();
1209
- this.templatesByCode.clear();
1210
- this.templatesByProvider.clear();
1211
- this.templatesByCategory.clear();
1212
- this.templateHistories.clear();
1213
- this.usageStats.clear();
1214
- this.emit("registry:cleared");
1215
- }
1216
- /**
1217
- * Stop the registry and cleanup
1218
- */
1219
- destroy() {
1220
- if (this.backupTimer) {
1221
- clearInterval(this.backupTimer);
1222
- this.backupTimer = void 0;
1223
- }
1224
- this.removeAllListeners();
1225
- this.emit("registry:destroyed");
1226
- }
1227
- updateIndexes(template) {
1228
- if (!this.templatesByProvider.has(template.provider)) {
1229
- this.templatesByProvider.set(template.provider, /* @__PURE__ */ new Set());
1230
- }
1231
- this.templatesByProvider.get(template.provider).add(template.id);
1232
- if (!this.templatesByCategory.has(template.category)) {
1233
- this.templatesByCategory.set(template.category, /* @__PURE__ */ new Set());
1234
- }
1235
- this.templatesByCategory.get(template.category).add(template.id);
1236
- }
1237
- initializeVersionHistory(template) {
1238
- const history = {
1239
- templateId: template.id,
1240
- versions: [{
1241
- version: 1,
1242
- template: JSON.parse(JSON.stringify(template)),
1243
- changes: ["Initial version"],
1244
- createdAt: /* @__PURE__ */ new Date()
1245
- }],
1246
- currentVersion: 1
1247
- };
1248
- this.templateHistories.set(template.id, history);
1249
- }
1250
- addVersionToHistory(oldTemplate, newTemplate) {
1251
- const history = this.templateHistories.get(newTemplate.id);
1252
- if (!history) return;
1253
- const changes = [];
1254
- if (oldTemplate.name !== newTemplate.name) changes.push("Name changed");
1255
- if (oldTemplate.content !== newTemplate.content) changes.push("Content changed");
1256
- if (oldTemplate.category !== newTemplate.category) changes.push("Category changed");
1257
- if (oldTemplate.status !== newTemplate.status) changes.push("Status changed");
1258
- if (JSON.stringify(oldTemplate.variables) !== JSON.stringify(newTemplate.variables)) {
1259
- changes.push("Variables changed");
1260
- }
1261
- if (JSON.stringify(oldTemplate.buttons) !== JSON.stringify(newTemplate.buttons)) {
1262
- changes.push("Buttons changed");
1263
- }
1264
- if (changes.length === 0) return;
1265
- const newVersion = {
1266
- version: history.currentVersion + 1,
1267
- template: JSON.parse(JSON.stringify(newTemplate)),
1268
- changes,
1269
- createdAt: /* @__PURE__ */ new Date()
1270
- };
1271
- history.versions.push(newVersion);
1272
- history.currentVersion = newVersion.version;
1273
- if (history.versions.length > this.options.maxVersionsPerTemplate) {
1274
- history.versions = history.versions.slice(-this.options.maxVersionsPerTemplate);
1275
- }
1276
- }
1277
- initializeUsageStats(template) {
1278
- const stats = {
1279
- templateId: template.id,
1280
- totalSent: template.metadata.usage.sent,
1281
- totalDelivered: template.metadata.usage.delivered,
1282
- totalFailed: template.metadata.usage.failed,
1283
- deliveryRate: 0,
1284
- failureRate: 0,
1285
- usageByDay: []
1286
- };
1287
- if (stats.totalSent > 0) {
1288
- stats.deliveryRate = stats.totalDelivered / stats.totalSent * 100;
1289
- stats.failureRate = stats.totalFailed / stats.totalSent * 100;
1290
- }
1291
- this.usageStats.set(template.id, stats);
1292
- }
1293
- startAutoBackup() {
1294
- this.backupTimer = setInterval(() => {
1295
- this.emit("backup:requested", {
1296
- data: this.export(),
1297
- timestamp: /* @__PURE__ */ new Date()
1298
- });
1299
- }, this.options.backupInterval);
1300
- }
1301
- };
1302
- // Annotate the CommonJS export names for ESM import in node:
1303
- 0 && (module.exports = {
1304
- ButtonParser,
1305
- TemplateBuilder,
1306
- TemplateBuilders,
1307
- TemplateCategory,
1308
- TemplateRegistry,
1309
- TemplateService,
1310
- TemplateStatus,
1311
- TemplateType,
1312
- TemplateValidator,
1313
- VariableParser
1314
- });
1315
- //# sourceMappingURL=index.cjs.map