@ecodrix/erix-api 1.1.9 → 1.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecodrix/erix-api",
3
- "version": "1.1.9",
3
+ "version": "1.2.1",
4
4
  "author": "ECODrIx Team <contact@ecodrix.com>",
5
5
  "license": "MIT",
6
6
  "description": "Official Isomorphic SDK for the ECODrIx platform. Native support for WhatsApp, CRM, Storage, and Meetings across TS, JS, Python, and Java.",
package/src/core.ts CHANGED
@@ -3,11 +3,12 @@ import axiosRetry from "axios-retry";
3
3
  import { type Socket, io } from "socket.io-client";
4
4
  import { APIError, AuthenticationError } from "./error";
5
5
  import { CRM } from "./resources/crm";
6
- import { EmailResource } from "./resources/email";
6
+ import { Email } from "./resources/email";
7
7
  import { EventsResource } from "./resources/events";
8
8
  import { Health } from "./resources/health";
9
+ import { Logs } from "./resources/logs";
9
10
  import { Marketing } from "./resources/marketing";
10
- import { MediaResource } from "./resources/media";
11
+ import { Media } from "./resources/media";
11
12
  import { Meetings } from "./resources/meet";
12
13
  import { Notifications } from "./resources/notifications";
13
14
  import { Queue } from "./resources/queue";
@@ -74,7 +75,13 @@ export interface EcodrixOptions {
74
75
  * ```
75
76
  */
76
77
  export class Ecodrix {
78
+ /**
79
+ * @internal Axios HTTP client for making API requests.
80
+ */
77
81
  private readonly client: AxiosInstance;
82
+ /**
83
+ * @internal Socket.io client for real-time events.
84
+ */
78
85
  private readonly socket: Socket;
79
86
 
80
87
  /** WhatsApp messaging and conversation management. */
@@ -84,7 +91,7 @@ export class Ecodrix {
84
91
  public readonly crm: CRM;
85
92
 
86
93
  /** Cloudflare R2-backed media storage. */
87
- public readonly media: MediaResource;
94
+ public readonly media: Media;
88
95
 
89
96
  /** Google Meet appointment scheduling. */
90
97
  public readonly meet: Meetings;
@@ -92,8 +99,11 @@ export class Ecodrix {
92
99
  /** Automation execution logs and provider webhook callbacks. */
93
100
  public readonly notifications: Notifications;
94
101
 
95
- /** Outbound email marketing engine. */
96
- public readonly email: EmailResource;
102
+ /** Outbound email marketing engine and template management. */
103
+ public readonly email: Email;
104
+
105
+ /** Platform-wide execution logs and audit trails. */
106
+ public readonly logs: Logs;
97
107
 
98
108
  /** Lead events and workflow automation triggers. */
99
109
  public readonly events: EventsResource;
@@ -172,10 +182,11 @@ export class Ecodrix {
172
182
  // Initialise resources
173
183
  this.whatsapp = new WhatsApp(this.client);
174
184
  this.crm = new CRM(this.client);
175
- this.media = new MediaResource(this.client);
185
+ this.media = new Media(this.client);
176
186
  this.meet = new Meetings(this.client);
177
187
  this.notifications = new Notifications(this.client);
178
- this.email = new EmailResource(this.client);
188
+ this.email = new Email(this.client);
189
+ this.logs = new Logs(this.client);
179
190
  this.events = new EventsResource(this.client);
180
191
  this.webhooks = new Webhooks();
181
192
  this.storage = new Storage(this.client);
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { Ecodrix, type EcodrixOptions } from "./core";
2
+ export * from "./types/metadata";
2
3
  export * from "./error";
3
4
  export * from "./resources/crm/activities";
4
5
  export * from "./resources/crm/analytics";
@@ -13,6 +14,7 @@ export * from "./resources/crm/sequences";
13
14
  export * from "./resources/email";
14
15
  export * from "./resources/events";
15
16
  export * from "./resources/health";
17
+ export * from "./resources/logs";
16
18
  export * from "./resources/marketing";
17
19
  export * from "./resources/media";
18
20
  export * from "./resources/meet";
@@ -1,4 +1,5 @@
1
1
  import { APIResource } from "../../resource";
2
+ import type { ResourceManifest } from "../../types/metadata";
2
3
 
3
4
  /**
4
5
  * Valid statuses for a CRM Lead.
@@ -102,6 +103,69 @@ export class Leads extends APIResource {
102
103
  return this.post<T>("/api/crm/leads", params);
103
104
  }
104
105
 
106
+ /**
107
+ * Introspect the Leads resource and provide a "Blueprint" for UI generation.
108
+ *
109
+ * This method returns a list of all fields (standard + custom), their types,
110
+ * labels, and suggested UI controls. Erix React uses this to render SmartForms
111
+ * and tables dynamically.
112
+ */
113
+ async describe(): Promise<ResourceManifest> {
114
+ // 1. Fetch custom fields from the tenant configuration
115
+ const customFieldsRes: any = await this.fields();
116
+ const customFields = Array.isArray(customFieldsRes.data) ? customFieldsRes.data : [];
117
+
118
+ // 2. Define the core system fields
119
+ const systemFields: any[] = [
120
+ {
121
+ key: "firstName",
122
+ label: "First Name",
123
+ type: "string",
124
+ required: true,
125
+ group: "Basic Info",
126
+ },
127
+ { key: "lastName", label: "Last Name", type: "string", required: false, group: "Basic Info" },
128
+ { key: "phone", label: "Phone Number", type: "phone", required: true, group: "Contact" },
129
+ { key: "email", label: "Email Address", type: "email", required: false, group: "Contact" },
130
+ {
131
+ key: "status",
132
+ label: "Status",
133
+ type: "select",
134
+ required: true,
135
+ options: [
136
+ { label: "New", value: "new" },
137
+ { label: "Contacted", value: "contacted" },
138
+ { label: "Qualified", value: "qualified" },
139
+ { label: "Won", value: "won" },
140
+ { label: "Lost", value: "lost" },
141
+ ],
142
+ },
143
+ { key: "value", label: "Lead Value", type: "currency", required: false },
144
+ { key: "source", label: "Source", type: "string", required: false },
145
+ ];
146
+
147
+ // 3. Map custom fields to FieldManifest shape
148
+ const mappedCustom = customFields.map((f: any) => ({
149
+ key: `metadata.extra.${f.name}`,
150
+ label: f.label || f.name,
151
+ type: f.type || "string",
152
+ required: !!f.required,
153
+ options: f.options,
154
+ group: "Custom Fields",
155
+ }));
156
+
157
+ return {
158
+ name: "Lead",
159
+ fields: [...systemFields, ...mappedCustom],
160
+ uiHints: {
161
+ icon: "User",
162
+ primaryColor: "#3b82f6",
163
+ defaultSort: { field: "createdAt", direction: "desc" },
164
+ summaryFields: ["firstName", "lastName", "phone", "status"],
165
+ },
166
+ };
167
+ }
168
+
105
169
  /**
106
170
  * Upsert a lead by phone number. If a lead with the same phone exists,
107
171
  * it will be updated; otherwise, a new lead is created.
@@ -1,4 +1,5 @@
1
1
  import { APIResource } from "../../resource";
2
+ import type { ResourceManifest } from "../../types/metadata";
2
3
 
3
4
  export interface CreatePipelineParams {
4
5
  name: string;
@@ -26,6 +27,39 @@ export class Pipelines extends APIResource {
26
27
  return this.get<T>("/api/crm/pipelines");
27
28
  }
28
29
 
30
+ /**
31
+ * Introspect a pipeline and provide a manifest of its stages.
32
+ *
33
+ * Returns metadata about stage colors, probabilities, and order.
34
+ * Erix React uses this to build kanban boards and stage pickers.
35
+ *
36
+ * @param pipelineId - The unique ID of the pipeline.
37
+ */
38
+ async getStageManifest(pipelineId: string): Promise<ResourceManifest> {
39
+ const pipeline: any = await this.retrieve(pipelineId);
40
+ const stages = Array.isArray(pipeline.data?.stages) ? pipeline.data.stages : [];
41
+
42
+ return {
43
+ name: `Pipeline: ${pipeline.data?.name || pipelineId}`,
44
+ fields: stages.map((s: any) => ({
45
+ key: s._id,
46
+ label: s.name,
47
+ type: "string", // Labels are strings
48
+ required: true,
49
+ options: [{ label: s.name, value: s._id }],
50
+ group: "Stages",
51
+ uiHints: {
52
+ color: s.color || "#cbd5e1",
53
+ probability: s.probability,
54
+ },
55
+ })) as any,
56
+ uiHints: {
57
+ icon: "Columns",
58
+ primaryColor: "#6366f1",
59
+ },
60
+ };
61
+ }
62
+
29
63
  /**
30
64
  * Create a new CRM sales or support pipeline.
31
65
  *
@@ -74,7 +108,9 @@ export class Pipelines extends APIResource {
74
108
  * Duplicate a pipeline with a new name.
75
109
  */
76
110
  async duplicate<T = any>(pipelineId: string, newName: string) {
77
- return this.post<T>(`/api/crm/pipelines/${pipelineId}/duplicate`, { newName });
111
+ return this.post<T>(`/api/crm/pipelines/${pipelineId}/duplicate`, {
112
+ newName,
113
+ });
78
114
  }
79
115
 
80
116
  /**
@@ -1,5 +1,3 @@
1
- import type { AxiosInstance } from "axios";
2
- import { APIError } from "../error";
3
1
  import { APIResource } from "../resource";
4
2
 
5
3
  /**
@@ -23,38 +21,122 @@ export interface CampaignResult {
23
21
  [key: string]: any;
24
22
  }
25
23
 
26
- export class EmailResource extends APIResource {
24
+ export interface EmailTemplate {
25
+ id: string;
26
+ name: string;
27
+ description?: string;
28
+ subject: string;
29
+ preheader?: string;
30
+ htmlBody: string;
31
+ textBody?: string;
32
+ category: "marketing" | "transactional" | "sequence";
33
+ status: "draft" | "published" | "archived";
34
+ type: "standard" | "layout";
35
+ layoutId?: string;
36
+ thumbnail?: string;
37
+ variableMapping?: any[];
38
+ createdAt: string;
39
+ updatedAt: string;
40
+ }
41
+
42
+ export interface CreateTemplateDTO {
43
+ name: string;
44
+ subject: string;
45
+ htmlBody: string;
46
+ description?: string;
47
+ category?: "marketing" | "transactional" | "sequence";
48
+ status?: "draft" | "published" | "archived";
49
+ variableMapping?: any[];
50
+ }
51
+
52
+ export interface TemplatePreviewResponse {
53
+ success: boolean;
54
+ data: {
55
+ subject: string;
56
+ html: string;
57
+ text?: string;
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Outbound email marketing engine and template management.
63
+ *
64
+ * Access via `ecod.email`.
65
+ */
66
+ export class Email extends APIResource {
27
67
  /**
28
68
  * Send an HTML email campaign to a list of recipients.
29
- *
30
- * @param payload - The campaign details (recipients, subject, html).
31
- * @returns The dispatch result indicating success or failure.
32
- * @example
33
- * ```typescript
34
- * await erixClient.email.sendEmailCampaign({
35
- * recipients: ["user1@example.com", "user2@example.com"],
36
- * subject: "Monthly Newsletter",
37
- * html: "<h1>Hello!</h1><p>Check out our latest updates...</p>"
38
- * });
39
- * ```
40
- */
41
- async sendEmailCampaign(payload: SendCampaignPayload): Promise<CampaignResult> {
69
+ */
70
+ async sendCampaign(payload: SendCampaignPayload): Promise<CampaignResult> {
42
71
  return this.post<CampaignResult>("/api/saas/emails/campaign", payload);
43
72
  }
44
73
 
45
74
  /**
46
75
  * Send a system test email to validate your current SMTP configuration.
47
- *
48
- * @param to - The recipient's email address.
49
- * @returns The dispatch result.
50
- * @example
51
- * ```typescript
52
- * await erixClient.email.sendTestEmail("admin@company.com");
53
- * ```
54
- */
55
- async sendTestEmail(to: string): Promise<CampaignResult> {
76
+ */
77
+ async sendTest(to: string): Promise<CampaignResult> {
56
78
  return this.post<CampaignResult>("/api/saas/emails/test", { to });
57
79
  }
58
80
 
59
- // WhatsApp moved to native wrapper
81
+ // --- Template Management ---
82
+
83
+ /**
84
+ * List all email templates.
85
+ */
86
+ async listTemplates(query?: any): Promise<{ success: boolean; data: EmailTemplate[] }> {
87
+ return this.get<{ success: boolean; data: EmailTemplate[] }>("/api/saas/mail/templates", {
88
+ params: query,
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Get a single email template by ID.
94
+ */
95
+ async getTemplate(id: string): Promise<{ success: boolean; data: EmailTemplate }> {
96
+ return this.get<{ success: boolean; data: EmailTemplate }>(`/api/saas/mail/templates/${id}`);
97
+ }
98
+
99
+ /**
100
+ * Create a new email template.
101
+ */
102
+ async createTemplate(
103
+ data: CreateTemplateDTO,
104
+ ): Promise<{ success: boolean; data: EmailTemplate }> {
105
+ return this.post<{ success: boolean; data: EmailTemplate }>("/api/saas/mail/templates", data);
106
+ }
107
+
108
+ /**
109
+ * Update an existing email template.
110
+ */
111
+ async updateTemplate(
112
+ id: string,
113
+ data: Partial<CreateTemplateDTO>,
114
+ ): Promise<{ success: boolean; data: EmailTemplate }> {
115
+ return this.put<{ success: boolean; data: EmailTemplate }>(
116
+ `/api/saas/mail/templates/${id}`,
117
+ data,
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Delete an email template.
123
+ */
124
+ async deleteTemplate(id: string, force = false): Promise<{ success: boolean }> {
125
+ return this.deleteRequest<{ success: boolean }>(`/api/saas/mail/templates/${id}`, {
126
+ params: { force },
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Preview a template with dynamic variable resolution.
132
+ */
133
+ async previewTemplate(
134
+ id: string,
135
+ params: { leadId?: string; variables?: Record<string, any> },
136
+ ): Promise<TemplatePreviewResponse> {
137
+ return this.post<TemplatePreviewResponse>(`/api/saas/mail/templates/${id}/preview`, params);
138
+ }
60
139
  }
140
+
141
+ /** @deprecated Use Email instead */
142
+ export class EmailResource extends Email {}
@@ -34,48 +34,51 @@ export interface JobStatus {
34
34
  createdAt: string;
35
35
  }
36
36
 
37
+ /**
38
+ * Platform and tenant-specific health diagnostics.
39
+ *
40
+ * Access via `ecod.health`.
41
+ */
37
42
  export class Health extends APIResource {
38
43
  /**
39
44
  * Perform a global platform health check.
40
- * Verify that the API, database, and background workers are operational.
41
- *
42
- * @returns System health summary (status, version, uptime).
43
- * @example
44
- * ```typescript
45
- * const health = await erixClient.health.system();
46
- * console.log(`API Status: ${health.status}`);
47
- * ```
45
+ * Verify that the core API, database, and background workers are operational.
48
46
  */
49
47
  async system(): Promise<SystemHealth> {
50
- const res = await this.get<{ data: SystemHealth }>("/api/saas/health", {
51
- headers: { accept: "application/json" },
52
- });
48
+ const res = await this.get<{ success: boolean; data: SystemHealth }>("/api/saas/health");
53
49
  return res.data;
54
50
  }
55
51
 
56
52
  /**
57
- * Tenant-specific health check. Identifies configured services.
53
+ * Tenant-specific health check.
54
+ *
55
+ * Identifies correctly configured third-party integrations (WhatsApp, SMTP, Google Meet)
56
+ * and reports active automation counts.
58
57
  */
59
58
  async clientHealth(): Promise<ClientHealth> {
60
- const res = await this.get<{ data: ClientHealth }>("/api/saas/health/client");
59
+ const res = await this.get<{ success: boolean; data: ClientHealth }>("/api/saas/health/client");
61
60
  return res.data;
62
61
  }
63
62
 
63
+ /**
64
+ * Alias for clientHealth to match diagnostic terminology.
65
+ */
66
+ async getDiagnosticReport(): Promise<ClientHealth> {
67
+ return this.clientHealth();
68
+ }
69
+
64
70
  /**
65
71
  * Lookup the execution status of a background job.
66
72
  *
73
+ * Use this to poll for completion of long-running tasks like bulk exports
74
+ * or campaign dispatches.
75
+ *
67
76
  * @param jobId - The unique ID of the background job.
68
- * @returns Job status report (attempts, error trace, completion time).
69
- * @example
70
- * ```typescript
71
- * const status = await erixClient.health.jobStatus("job_123");
72
- * if (status.status === "completed") {
73
- * console.log("Job finished successfully!");
74
- * }
75
- * ```
76
77
  */
77
78
  async jobStatus(jobId: string): Promise<JobStatus> {
78
- const res = await this.get<{ data: JobStatus }>(`/api/saas/jobs/status/${jobId}`);
79
+ const res = await this.get<{ success: boolean; data: JobStatus }>(
80
+ `/api/saas/jobs/status/${jobId}`,
81
+ );
79
82
  return res.data;
80
83
  }
81
84
  }
@@ -0,0 +1,97 @@
1
+ import { APIResource } from "../resource";
2
+
3
+ export interface LogPaginationQuery {
4
+ page?: number;
5
+ limit?: number;
6
+ trigger?: string;
7
+ status?: "received" | "processing" | "completed" | "partial" | "failed";
8
+ from?: string;
9
+ to?: string;
10
+ }
11
+
12
+ export interface EventLog {
13
+ id: string;
14
+ trigger: string;
15
+ phone?: string;
16
+ email?: string;
17
+ status: "received" | "processing" | "completed" | "partial" | "failed";
18
+ rulesMatched: number;
19
+ jobsCreated: number;
20
+ meetLink?: string;
21
+ callbackUrl?: string;
22
+ callbackStatus: "not_required" | "sent" | "failed";
23
+ payload: any;
24
+ error?: string;
25
+ idempotencyKey?: string;
26
+ processedAt?: string;
27
+ createdAt: string;
28
+ updatedAt: string;
29
+ }
30
+
31
+ export interface CallbackLog {
32
+ id: string;
33
+ callbackUrl: string;
34
+ method: string;
35
+ payload: any;
36
+ jobId?: string;
37
+ enrollmentId?: string;
38
+ responseStatus: number;
39
+ responseBody: string;
40
+ status: "sent" | "failed" | "pending_retry";
41
+ attempts: number;
42
+ lastAttemptAt?: string;
43
+ signature?: string;
44
+ createdAt: string;
45
+ }
46
+
47
+ export interface LogResponse<T> {
48
+ success: boolean;
49
+ data: T[];
50
+ pagination: {
51
+ total: number;
52
+ page: number;
53
+ limit: number;
54
+ pages: number;
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Audit and execution logs for platform events and webhooks.
60
+ *
61
+ * Access via `ecod.logs`.
62
+ */
63
+ export class Logs extends APIResource {
64
+ /**
65
+ * List event execution logs with optional filtering.
66
+ *
67
+ * Use this to audit automation triggers and debug workflow execution.
68
+ */
69
+ async listEventLogs(query?: LogPaginationQuery): Promise<LogResponse<EventLog>> {
70
+ return this.get<LogResponse<EventLog>>("/api/saas/events/logs", {
71
+ params: query,
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Get detailed information for a single event execution.
77
+ */
78
+ async getEventLog(id: string): Promise<{ success: boolean; data: EventLog }> {
79
+ return this.get<{ success: boolean; data: EventLog }>(`/api/saas/events/logs/${id}`);
80
+ }
81
+
82
+ /**
83
+ * List webhook callback logs (outbound responses from ECODrIx to your system).
84
+ */
85
+ async listCallbackLogs(query?: LogPaginationQuery): Promise<LogResponse<CallbackLog>> {
86
+ return this.get<LogResponse<CallbackLog>>("/api/saas/callbacks/logs", {
87
+ params: query,
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Get high-level execution statistics for the tenant.
93
+ */
94
+ async getStats(): Promise<{ success: boolean; data: any }> {
95
+ return this.get<{ success: boolean; data: any }>("/api/saas/events/stats");
96
+ }
97
+ }
@@ -41,7 +41,7 @@ export interface UploadOptions {
41
41
  * console.log(data.url); // → https://cdn.ecodrix.com/invoices/invoice-001.pdf
42
42
  * ```
43
43
  */
44
- export class MediaResource extends APIResource {
44
+ export class Media extends APIResource {
45
45
  /**
46
46
  * Get current storage usage metrics for the tenant.
47
47
  *
@@ -83,10 +83,7 @@ export class MediaResource extends APIResource {
83
83
  * const { data: files } = await ecod.media.list("invoices", { q: "contract" });
84
84
  * ```
85
85
  */
86
- async list(
87
- folder: string,
88
- params?: { year?: string; month?: string; q?: string },
89
- ) {
86
+ async list(folder: string, params?: { year?: string; month?: string; q?: string }) {
90
87
  return this.get(`/api/saas/storage/files/${folder}`, { params } as any);
91
88
  }
92
89
 
@@ -158,11 +155,8 @@ export class MediaResource extends APIResource {
158
155
  */
159
156
  async upload(file: any, options: UploadOptions): Promise<any> {
160
157
  // Step 1: Get presigned URL
161
- const { data: presignedData } = await this.client.post(
162
- "/api/saas/storage/upload-url",
163
- options,
164
- );
165
- const { uploadUrl, key } = presignedData;
158
+ const response = await this.post<any>("/api/saas/storage/upload-url", options);
159
+ const { uploadUrl, key } = response.data;
166
160
 
167
161
  // Step 2: Upload directly to R2 (bypasses the API server for performance)
168
162
  await axios.put(uploadUrl, file, {
@@ -175,3 +169,6 @@ export class MediaResource extends APIResource {
175
169
  return this.post("/api/saas/storage/confirm-upload", { key, sizeBytes });
176
170
  }
177
171
  }
172
+
173
+ /** @deprecated Use Media instead */
174
+ export class MediaResource extends Media {}
@@ -1,4 +1,5 @@
1
1
  import { APIResource } from "../../resource";
2
+ import type { ResourceManifest } from "../../types/metadata";
2
3
 
3
4
  export interface TemplateMapping {
4
5
  mappings: any[];
@@ -164,6 +165,50 @@ export class Templates extends APIResource {
164
165
  });
165
166
  }
166
167
 
168
+ /**
169
+ * Introspect a template and provide a manifest of its required variables.
170
+ *
171
+ * This is used by Erix React to build dynamic "Mapping Forms" where users
172
+ * can connect CRM fields to WhatsApp variables.
173
+ *
174
+ * @param templateIdentifier - The name or ID of the template.
175
+ */
176
+ async getVariableManifest(templateIdentifier: string): Promise<ResourceManifest> {
177
+ const template: any = await this.retrieve(templateIdentifier);
178
+ const components = Array.isArray(template.data?.components) ? template.data.components : [];
179
+
180
+ const fields: any[] = [];
181
+
182
+ for (const comp of components) {
183
+ // Look for {{n}} pattern in text components
184
+ if (comp.text) {
185
+ const vars = comp.text.match(/{{(\d+)}}/g);
186
+ if (vars) {
187
+ for (const v of vars) {
188
+ const index = v.replace(/{{|}}/g, "");
189
+ fields.push({
190
+ key: `var_${index}`,
191
+ label: `${comp.type} Var {{${index}}}`,
192
+ type: "string",
193
+ required: true,
194
+ group: comp.type,
195
+ description: `Variable placeholder in template ${comp.type}`,
196
+ });
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ return {
203
+ name: `Template variables: ${template.data?.name || templateIdentifier}`,
204
+ fields,
205
+ uiHints: {
206
+ icon: "Variables",
207
+ primaryColor: "#059669",
208
+ },
209
+ };
210
+ }
211
+
167
212
  /**
168
213
  * Check which automations or sequences are currently using this template.
169
214
  */