@bernierllc/email 1.0.1 → 1.1.1

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 (65) hide show
  1. package/README.md +76 -217
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +28 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/simple-email-service.d.ts +58 -0
  7. package/dist/simple-email-service.d.ts.map +1 -0
  8. package/dist/simple-email-service.js +416 -0
  9. package/dist/simple-email-service.js.map +1 -0
  10. package/dist/types.d.ts +311 -0
  11. package/dist/types.d.ts.map +1 -0
  12. package/dist/types.js +33 -0
  13. package/dist/types.js.map +1 -0
  14. package/package.json +57 -22
  15. package/.eslintrc.json +0 -112
  16. package/.flake8 +0 -18
  17. package/.github/workflows/ci.yml +0 -300
  18. package/EXTRACTION_SUMMARY.md +0 -265
  19. package/IMPLEMENTATION_STATUS.md +0 -159
  20. package/OPEN_SOURCE_SETUP.md +0 -420
  21. package/PACKAGE_USAGE.md +0 -471
  22. package/examples/fastapi-example/main.py +0 -257
  23. package/examples/nextjs-example/next-env.d.ts +0 -13
  24. package/examples/nextjs-example/package.json +0 -26
  25. package/examples/nextjs-example/pages/admin/templates.tsx +0 -157
  26. package/examples/nextjs-example/tsconfig.json +0 -28
  27. package/packages/core/package.json +0 -70
  28. package/packages/core/rollup.config.js +0 -37
  29. package/packages/core/specification.md +0 -416
  30. package/packages/core/src/adapters/supabase.ts +0 -291
  31. package/packages/core/src/core/scheduler.ts +0 -356
  32. package/packages/core/src/core/template-manager.ts +0 -388
  33. package/packages/core/src/index.ts +0 -30
  34. package/packages/core/src/providers/base.ts +0 -104
  35. package/packages/core/src/providers/sendgrid.ts +0 -368
  36. package/packages/core/src/types/provider.ts +0 -91
  37. package/packages/core/src/types/scheduled.ts +0 -78
  38. package/packages/core/src/types/template.ts +0 -97
  39. package/packages/core/tsconfig.json +0 -23
  40. package/packages/python/README.md +0 -106
  41. package/packages/python/email_template_manager/__init__.py +0 -66
  42. package/packages/python/email_template_manager/config.py +0 -98
  43. package/packages/python/email_template_manager/core/magic_links.py +0 -245
  44. package/packages/python/email_template_manager/core/manager.py +0 -344
  45. package/packages/python/email_template_manager/core/scheduler.py +0 -473
  46. package/packages/python/email_template_manager/exceptions.py +0 -67
  47. package/packages/python/email_template_manager/models/magic_link.py +0 -59
  48. package/packages/python/email_template_manager/models/scheduled.py +0 -78
  49. package/packages/python/email_template_manager/models/template.py +0 -90
  50. package/packages/python/email_template_manager/providers/aws_ses.py +0 -44
  51. package/packages/python/email_template_manager/providers/base.py +0 -94
  52. package/packages/python/email_template_manager/providers/sendgrid.py +0 -325
  53. package/packages/python/email_template_manager/providers/smtp.py +0 -44
  54. package/packages/python/pyproject.toml +0 -133
  55. package/packages/python/setup.py +0 -93
  56. package/packages/python/specification.md +0 -930
  57. package/packages/react/README.md +0 -13
  58. package/packages/react/package.json +0 -105
  59. package/packages/react/rollup.config.js +0 -37
  60. package/packages/react/specification.md +0 -569
  61. package/packages/react/src/index.ts +0 -20
  62. package/packages/react/tsconfig.json +0 -24
  63. package/plans/email-template-manager_app-admin.md +0 -590
  64. package/src/index.js +0 -1
  65. package/test_package.py +0 -125
@@ -1,416 +0,0 @@
1
- # @email-template-manager/core - Core Library Specification
2
-
3
- ## Overview
4
-
5
- The core library provides the foundational functionality for email template management, scheduling, and delivery. This package is framework-agnostic and can be used in any JavaScript/TypeScript environment.
6
-
7
- ## Core Data Models
8
-
9
- ### EmailTemplate
10
-
11
- ```typescript
12
- interface EmailTemplate {
13
- id: string;
14
- name: string;
15
- subject: string;
16
- htmlBody: string;
17
- textBody: string;
18
- variables: TemplateVariable[];
19
- categoryId?: string;
20
- tags: string[];
21
- isActive: boolean;
22
- version: number;
23
- createdAt: Date;
24
- updatedAt: Date;
25
- createdBy: string;
26
- }
27
-
28
- interface TemplateVariable {
29
- name: string;
30
- type: "text" | "number" | "date" | "boolean" | "url";
31
- description?: string;
32
- defaultValue?: any;
33
- required: boolean;
34
- }
35
- ```
36
-
37
- ### ScheduledEmail
38
-
39
- ```typescript
40
- interface ScheduledEmail {
41
- id: string;
42
- templateId: string;
43
- recipientEmail: string;
44
- recipientName?: string;
45
- scheduledFor: Date;
46
- triggerType: "immediate" | "relative" | "absolute";
47
- triggerOffset?: number; // days before/after reference date
48
- referenceDate?: Date;
49
- variables: Record<string, any>;
50
- status: "pending" | "sent" | "failed" | "cancelled" | "paused";
51
- sentAt?: Date;
52
- errorMessage?: string;
53
- retryCount: number;
54
- maxRetries: number;
55
- metadata: Record<string, any>;
56
- }
57
- ```
58
-
59
- ### MagicLink
60
-
61
- ```typescript
62
- interface MagicLink {
63
- id: string;
64
- token: string;
65
- email: string;
66
- type: string;
67
- payload: Record<string, any>;
68
- expiresAt: Date;
69
- usedAt?: Date;
70
- isUsed: boolean;
71
- metadata: Record<string, any>;
72
- }
73
- ```
74
-
75
- ### EmailProvider Configuration
76
-
77
- ```typescript
78
- interface EmailProviderConfig {
79
- provider: "sendgrid" | "ses" | "mailgun" | "postmark" | "smtp";
80
- apiKey?: string;
81
- region?: string;
82
- endpoint?: string;
83
- fromEmail: string;
84
- fromName: string;
85
- replyTo?: string;
86
- webhookUrl?: string;
87
- webhookSecret?: string;
88
- }
89
- ```
90
-
91
- ## Core Classes
92
-
93
- ### EmailTemplateManager
94
-
95
- ```typescript
96
- class EmailTemplateManager {
97
- constructor(config: EmailManagerConfig);
98
-
99
- // Template CRUD operations
100
- async createTemplate(
101
- template: Partial<EmailTemplate>
102
- ): Promise<EmailTemplate>;
103
- async getTemplate(id: string): Promise<EmailTemplate | null>;
104
- async updateTemplate(
105
- id: string,
106
- updates: Partial<EmailTemplate>
107
- ): Promise<EmailTemplate>;
108
- async deleteTemplate(id: string): Promise<boolean>;
109
- async listTemplates(filters?: TemplateFilters): Promise<EmailTemplate[]>;
110
-
111
- // Template rendering
112
- async renderTemplate(
113
- templateId: string,
114
- variables: Record<string, any>
115
- ): Promise<RenderedTemplate>;
116
- async previewTemplate(
117
- template: EmailTemplate,
118
- variables: Record<string, any>
119
- ): Promise<RenderedTemplate>;
120
-
121
- // Variable validation
122
- validateTemplateVariables(
123
- template: EmailTemplate,
124
- variables: Record<string, any>
125
- ): ValidationResult;
126
- }
127
- ```
128
-
129
- ### EmailScheduler
130
-
131
- ```typescript
132
- class EmailScheduler {
133
- constructor(
134
- templateManager: EmailTemplateManager,
135
- emailProvider: EmailProvider
136
- );
137
-
138
- // Scheduling operations
139
- async scheduleEmail(email: Partial<ScheduledEmail>): Promise<ScheduledEmail>;
140
- async scheduleBatch(
141
- emails: Partial<ScheduledEmail>[]
142
- ): Promise<ScheduledEmail[]>;
143
- async cancelScheduledEmail(id: string): Promise<boolean>;
144
- async pauseScheduledEmail(id: string): Promise<boolean>;
145
- async resumeScheduledEmail(id: string): Promise<boolean>;
146
-
147
- // Processing
148
- async processPendingEmails(): Promise<ProcessingResult>;
149
- async retryFailedEmails(): Promise<ProcessingResult>;
150
-
151
- // Status tracking
152
- async getScheduledEmail(id: string): Promise<ScheduledEmail | null>;
153
- async listScheduledEmails(
154
- filters?: SchedulingFilters
155
- ): Promise<ScheduledEmail[]>;
156
- }
157
- ```
158
-
159
- ### MagicLinkManager
160
-
161
- ```typescript
162
- class MagicLinkManager {
163
- constructor(config: MagicLinkConfig);
164
-
165
- // Magic link operations
166
- async generateMagicLink(
167
- email: string,
168
- type: string,
169
- payload: Record<string, any>,
170
- expiresIn?: number
171
- ): Promise<MagicLink>;
172
- async validateMagicLink(token: string): Promise<MagicLink | null>;
173
- async useMagicLink(token: string): Promise<MagicLink>;
174
- async revokeMagicLink(token: string): Promise<boolean>;
175
-
176
- // Cleanup
177
- async cleanupExpiredLinks(): Promise<number>;
178
- }
179
- ```
180
-
181
- ### EmailProvider (Abstract Base)
182
-
183
- ```typescript
184
- abstract class EmailProvider {
185
- constructor(config: EmailProviderConfig);
186
-
187
- abstract async sendEmail(email: EmailMessage): Promise<SendResult>;
188
- abstract async sendBatch(emails: EmailMessage[]): Promise<SendResult[]>;
189
- abstract async getDeliveryStatus(messageId: string): Promise<DeliveryStatus>;
190
- abstract async processWebhook(
191
- payload: any,
192
- signature: string
193
- ): Promise<WebhookEvent[]>;
194
- }
195
- ```
196
-
197
- ## Utility Functions
198
-
199
- ### Template Rendering
200
-
201
- ```typescript
202
- // Liquid template engine integration
203
- function renderLiquidTemplate(
204
- template: string,
205
- variables: Record<string, any>
206
- ): string;
207
-
208
- // HTML sanitization
209
- function sanitizeHtml(html: string): string;
210
-
211
- // Text extraction from HTML
212
- function extractTextFromHtml(html: string): string;
213
- ```
214
-
215
- ### Validation
216
-
217
- ```typescript
218
- function validateEmailAddress(email: string): boolean;
219
- function validateTemplateVariables(
220
- template: EmailTemplate,
221
- variables: Record<string, any>
222
- ): ValidationResult;
223
- function validateCronExpression(expression: string): boolean;
224
- ```
225
-
226
- ### Encryption/Security
227
-
228
- ```typescript
229
- function generateSecureToken(length?: number): string;
230
- function hashToken(token: string): string;
231
- function verifyTokenHash(token: string, hash: string): boolean;
232
- function encryptPayload(payload: Record<string, any>, key: string): string;
233
- function decryptPayload(encrypted: string, key: string): Record<string, any>;
234
- ```
235
-
236
- ## Configuration
237
-
238
- ### EmailManagerConfig
239
-
240
- ```typescript
241
- interface EmailManagerConfig {
242
- // Database connection
243
- database: {
244
- type: "postgresql" | "mysql" | "sqlite" | "mongodb";
245
- connectionString: string;
246
- ssl?: boolean;
247
- pool?: {
248
- min: number;
249
- max: number;
250
- idleTimeoutMillis: number;
251
- };
252
- };
253
-
254
- // Email provider
255
- emailProvider: EmailProviderConfig;
256
-
257
- // Template settings
258
- templates: {
259
- defaultFromEmail: string;
260
- defaultFromName: string;
261
- liquidEngine: {
262
- strictVariables: boolean;
263
- customFilters?: Record<string, Function>;
264
- };
265
- };
266
-
267
- // Scheduling
268
- scheduling: {
269
- defaultMaxRetries: number;
270
- retryDelayMinutes: number[];
271
- batchSize: number;
272
- processingIntervalMinutes: number;
273
- };
274
-
275
- // Security
276
- security: {
277
- encryptionKey: string;
278
- tokenLength: number;
279
- defaultExpiryHours: number;
280
- };
281
-
282
- // Logging
283
- logging: {
284
- level: "debug" | "info" | "warn" | "error";
285
- includePayload: boolean;
286
- };
287
- }
288
- ```
289
-
290
- ## Event System
291
-
292
- ### EmailEvents
293
-
294
- ```typescript
295
- interface EmailTemplateEvent {
296
- type: "template.created" | "template.updated" | "template.deleted";
297
- templateId: string;
298
- timestamp: Date;
299
- metadata: Record<string, any>;
300
- }
301
-
302
- interface EmailSchedulingEvent {
303
- type: "email.scheduled" | "email.sent" | "email.failed" | "email.cancelled";
304
- scheduledEmailId: string;
305
- timestamp: Date;
306
- metadata: Record<string, any>;
307
- }
308
-
309
- interface MagicLinkEvent {
310
- type: "link.generated" | "link.used" | "link.expired" | "link.revoked";
311
- linkId: string;
312
- timestamp: Date;
313
- metadata: Record<string, any>;
314
- }
315
- ```
316
-
317
- ### Event Emitter
318
-
319
- ```typescript
320
- class EmailEventEmitter extends EventEmitter {
321
- onTemplateEvent(handler: (event: EmailTemplateEvent) => void): void;
322
- onSchedulingEvent(handler: (event: EmailSchedulingEvent) => void): void;
323
- onMagicLinkEvent(handler: (event: MagicLinkEvent) => void): void;
324
- }
325
- ```
326
-
327
- ## Error Handling
328
-
329
- ### Custom Errors
330
-
331
- ```typescript
332
- class EmailTemplateError extends Error {
333
- code: string;
334
- templateId?: string;
335
- }
336
-
337
- class EmailSchedulingError extends Error {
338
- code: string;
339
- scheduledEmailId?: string;
340
- }
341
-
342
- class MagicLinkError extends Error {
343
- code: string;
344
- token?: string;
345
- }
346
-
347
- class EmailProviderError extends Error {
348
- code: string;
349
- provider: string;
350
- originalError?: Error;
351
- }
352
- ```
353
-
354
- ## Testing Utilities
355
-
356
- ### Mock Providers
357
-
358
- ```typescript
359
- class MockEmailProvider extends EmailProvider {
360
- // Implementation for testing
361
- }
362
-
363
- class InMemoryStorage implements StorageAdapter {
364
- // Implementation for testing
365
- }
366
- ```
367
-
368
- ### Test Helpers
369
-
370
- ```typescript
371
- function createTestTemplate(overrides?: Partial<EmailTemplate>): EmailTemplate;
372
- function createTestScheduledEmail(
373
- overrides?: Partial<ScheduledEmail>
374
- ): ScheduledEmail;
375
- function createTestMagicLink(overrides?: Partial<MagicLink>): MagicLink;
376
- ```
377
-
378
- ## Export Structure
379
-
380
- ```typescript
381
- // Main classes
382
- export { EmailTemplateManager, EmailScheduler, MagicLinkManager };
383
-
384
- // Email providers
385
- export {
386
- EmailProvider,
387
- SendGridProvider,
388
- SESProvider,
389
- MailgunProvider,
390
- PostmarkProvider,
391
- SMTPProvider,
392
- };
393
-
394
- // Data models
395
- export type { EmailTemplate, ScheduledEmail, MagicLink, EmailProviderConfig };
396
-
397
- // Utilities
398
- export { renderLiquidTemplate, validateEmailAddress, generateSecureToken };
399
-
400
- // Events
401
- export { EmailEventEmitter };
402
- export type { EmailTemplateEvent, EmailSchedulingEvent, MagicLinkEvent };
403
-
404
- // Errors
405
- export {
406
- EmailTemplateError,
407
- EmailSchedulingError,
408
- MagicLinkError,
409
- EmailProviderError,
410
- };
411
-
412
- // Testing
413
- export { MockEmailProvider, InMemoryStorage, createTestTemplate };
414
- ```
415
-
416
- This core library provides all the essential functionality needed for email template management while remaining framework-agnostic and easily extensible.
@@ -1,291 +0,0 @@
1
- /*
2
- Copyright (c) 2025 Bernier LLC
3
-
4
- This file is licensed to the client under a limited-use license.
5
- The client may use and modify this code *only within the scope of the project it was delivered for*.
6
- Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
- */
8
-
9
- /**
10
- * Supabase adapter for EmailTemplateManager
11
- */
12
-
13
- import { createClient, SupabaseClient } from '@supabase/supabase-js';
14
- import { EmailTemplate, TemplateFilters } from '../types/template';
15
-
16
- export interface SupabaseConfig {
17
- url: string;
18
- key: string;
19
- templatesTable?: string;
20
- logsTable?: string;
21
- }
22
-
23
- export class SupabaseEmailAdapter {
24
- private supabase: SupabaseClient;
25
- private templatesTable: string;
26
- private logsTable: string;
27
-
28
- constructor(config: SupabaseConfig) {
29
- this.supabase = createClient(config.url, config.key);
30
- this.templatesTable = config.templatesTable || 'email_templates';
31
- this.logsTable = config.logsTable || 'email_delivery_logs';
32
- }
33
-
34
- /**
35
- * Save template to Supabase
36
- */
37
- async saveTemplate(template: EmailTemplate): Promise<EmailTemplate> {
38
- const { data, error } = await this.supabase
39
- .from(this.templatesTable)
40
- .upsert([
41
- {
42
- id: template.id,
43
- name: template.name,
44
- subject: template.subject,
45
- html_body: template.htmlBody,
46
- text_body: template.textBody,
47
- variables: template.variables,
48
- category_id: template.categoryId,
49
- tags: template.tags,
50
- is_active: template.isActive,
51
- version: template.version,
52
- created_at: template.createdAt?.toISOString(),
53
- updated_at: template.updatedAt?.toISOString(),
54
- created_by: template.createdBy,
55
- metadata: template.metadata,
56
- },
57
- ])
58
- .select()
59
- .single();
60
-
61
- if (error) {
62
- throw new Error(`Failed to save template: ${error.message}`);
63
- }
64
-
65
- return this.mapFromDatabase(data);
66
- }
67
-
68
- /**
69
- * Load template by ID
70
- */
71
- async loadTemplate(id: string): Promise<EmailTemplate | null> {
72
- const { data, error } = await this.supabase
73
- .from(this.templatesTable)
74
- .select('*')
75
- .eq('id', id)
76
- .single();
77
-
78
- if (error) {
79
- if (error.code === 'PGRST116') {
80
- return null; // Not found
81
- }
82
- throw new Error(`Failed to load template: ${error.message}`);
83
- }
84
-
85
- return this.mapFromDatabase(data);
86
- }
87
-
88
- /**
89
- * Load template by name
90
- */
91
- async loadTemplateByName(name: string): Promise<EmailTemplate | null> {
92
- const { data, error } = await this.supabase
93
- .from(this.templatesTable)
94
- .select('*')
95
- .eq('name', name)
96
- .eq('is_active', true)
97
- .single();
98
-
99
- if (error) {
100
- if (error.code === 'PGRST116') {
101
- return null; // Not found
102
- }
103
- throw new Error(`Failed to load template by name: ${error.message}`);
104
- }
105
-
106
- return this.mapFromDatabase(data);
107
- }
108
-
109
- /**
110
- * Load templates with filters
111
- */
112
- async loadTemplates(filters?: TemplateFilters): Promise<EmailTemplate[]> {
113
- let query = this.supabase.from(this.templatesTable).select('*');
114
-
115
- if (filters) {
116
- if (filters.categoryId) {
117
- query = query.eq('category_id', filters.categoryId);
118
- }
119
-
120
- if (filters.isActive !== undefined) {
121
- query = query.eq('is_active', filters.isActive);
122
- }
123
-
124
- if (filters.createdBy) {
125
- query = query.eq('created_by', filters.createdBy);
126
- }
127
-
128
- if (filters.createdAfter) {
129
- query = query.gte('created_at', filters.createdAfter.toISOString());
130
- }
131
-
132
- if (filters.createdBefore) {
133
- query = query.lte('created_at', filters.createdBefore.toISOString());
134
- }
135
-
136
- if (filters.search) {
137
- query = query.or(
138
- `name.ilike.%${filters.search}%,subject.ilike.%${filters.search}%`
139
- );
140
- }
141
-
142
- if (filters.tags && filters.tags.length > 0) {
143
- query = query.overlaps('tags', filters.tags);
144
- }
145
- }
146
-
147
- const { data, error } = await query.order('created_at', { ascending: false });
148
-
149
- if (error) {
150
- throw new Error(`Failed to load templates: ${error.message}`);
151
- }
152
-
153
- return data?.map(item => this.mapFromDatabase(item)) || [];
154
- }
155
-
156
- /**
157
- * Delete template
158
- */
159
- async deleteTemplate(id: string): Promise<boolean> {
160
- const { error } = await this.supabase
161
- .from(this.templatesTable)
162
- .delete()
163
- .eq('id', id);
164
-
165
- if (error) {
166
- throw new Error(`Failed to delete template: ${error.message}`);
167
- }
168
-
169
- return true;
170
- }
171
-
172
- /**
173
- * Log email delivery
174
- */
175
- async logEmailDelivery(log: {
176
- templateName: string;
177
- recipientEmail: string;
178
- messageId?: string;
179
- success: boolean;
180
- error?: string;
181
- metadata?: any;
182
- }): Promise<void> {
183
- const { error } = await this.supabase
184
- .from(this.logsTable)
185
- .insert([
186
- {
187
- template_name: log.templateName,
188
- recipient_email: log.recipientEmail,
189
- message_id: log.messageId,
190
- success: log.success,
191
- error_message: log.error,
192
- sent_at: new Date().toISOString(),
193
- metadata: log.metadata,
194
- },
195
- ]);
196
-
197
- if (error) {
198
- console.error('Failed to log email delivery:', error);
199
- // Don't throw error for logging failures
200
- }
201
- }
202
-
203
- /**
204
- * Update email delivery status from webhook
205
- */
206
- async updateDeliveryStatus(
207
- messageId: string,
208
- status: string,
209
- deliveredAt?: Date,
210
- webhookData?: any
211
- ): Promise<void> {
212
- const { error } = await this.supabase
213
- .from(this.logsTable)
214
- .update({
215
- delivery_status: status,
216
- delivered_at: deliveredAt?.toISOString(),
217
- webhook_data: webhookData,
218
- })
219
- .eq('message_id', messageId);
220
-
221
- if (error) {
222
- console.error('Failed to update delivery status:', error);
223
- // Don't throw error for logging failures
224
- }
225
- }
226
-
227
- /**
228
- * Get email delivery logs
229
- */
230
- async getDeliveryLogs(filters?: {
231
- templateName?: string;
232
- recipientEmail?: string;
233
- success?: boolean;
234
- limit?: number;
235
- offset?: number;
236
- }): Promise<any[]> {
237
- let query = this.supabase.from(this.logsTable).select('*');
238
-
239
- if (filters) {
240
- if (filters.templateName) {
241
- query = query.eq('template_name', filters.templateName);
242
- }
243
-
244
- if (filters.recipientEmail) {
245
- query = query.eq('recipient_email', filters.recipientEmail);
246
- }
247
-
248
- if (filters.success !== undefined) {
249
- query = query.eq('success', filters.success);
250
- }
251
-
252
- if (filters.limit) {
253
- query = query.limit(filters.limit);
254
- }
255
-
256
- if (filters.offset) {
257
- query = query.range(filters.offset, filters.offset + (filters.limit || 50) - 1);
258
- }
259
- }
260
-
261
- const { data, error } = await query.order('sent_at', { ascending: false });
262
-
263
- if (error) {
264
- throw new Error(`Failed to get delivery logs: ${error.message}`);
265
- }
266
-
267
- return data || [];
268
- }
269
-
270
- /**
271
- * Map database row to EmailTemplate
272
- */
273
- private mapFromDatabase(data: any): EmailTemplate {
274
- return {
275
- id: data.id,
276
- name: data.name,
277
- subject: data.subject,
278
- htmlBody: data.html_body,
279
- textBody: data.text_body,
280
- variables: data.variables || [],
281
- categoryId: data.category_id,
282
- tags: data.tags || [],
283
- isActive: data.is_active,
284
- version: data.version,
285
- createdAt: data.created_at ? new Date(data.created_at) : undefined,
286
- updatedAt: data.updated_at ? new Date(data.updated_at) : undefined,
287
- createdBy: data.created_by,
288
- metadata: data.metadata,
289
- };
290
- }
291
- }