@talqui-oss/template-builder 0.0.0-stage → 0.1.0-alpha.0

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.
@@ -0,0 +1,263 @@
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/adapters/talqui.ts
21
+ var talqui_exports = {};
22
+ __export(talqui_exports, {
23
+ talqui: () => talqui
24
+ });
25
+ module.exports = __toCommonJS(talqui_exports);
26
+
27
+ // src/validate/text.ts
28
+ var PLACEHOLDER = /\{\{(.*?)\}\}/g;
29
+ var VARIABLE_NAME = /^[a-z][a-z0-9_]*$/;
30
+ function placeholders(text) {
31
+ return [...text.matchAll(PLACEHOLDER)].map((match) => {
32
+ const name = match[1] ?? "";
33
+ return { raw: match[0], name, valid: VARIABLE_NAME.test(name) };
34
+ });
35
+ }
36
+
37
+ // src/variables.ts
38
+ var replacePlaceholders = (text, replace) => text.replace(/\{\{(.*?)\}\}/g, (_, name) => replace(name));
39
+
40
+ // src/adapters/types.ts
41
+ function defineAdapter(definition) {
42
+ return (...[options]) => ({
43
+ name: definition.name,
44
+ toRequests: (template) => definition.toRequests(template, options ?? {})
45
+ });
46
+ }
47
+ var UnsupportedByProviderError = class extends Error {
48
+ constructor(provider, path, message) {
49
+ super(`${provider}: ${message} (${path})`);
50
+ this.provider = provider;
51
+ this.path = path;
52
+ this.name = "UnsupportedByProviderError";
53
+ }
54
+ provider;
55
+ path;
56
+ code = "UNSUPPORTED_BY_PROVIDER";
57
+ };
58
+ function unsupported(provider, path, what) {
59
+ throw new UnsupportedByProviderError(
60
+ provider,
61
+ path,
62
+ `${what} is not supported by this provider.`
63
+ );
64
+ }
65
+
66
+ // src/adapters/talqui.ts
67
+ var NAME = "talqui";
68
+ var AUTHENTICATION_PREVIEW = "*123456* \xE9 o seu c\xF3digo de verifica\xE7\xE3o.";
69
+ var EMPTY_HEADER = {
70
+ type: null,
71
+ content: {},
72
+ preview: "",
73
+ text: "",
74
+ variables: []
75
+ };
76
+ var CHANNELS = {
77
+ "whatsapp-cloud": "urn:talqui:whatsapp-cloud:0",
78
+ "whatsapp-gup": "urn:talqui:whatsapp-gup:0"
79
+ };
80
+ var talqui = defineAdapter({
81
+ name: NAME,
82
+ toRequests(template, {
83
+ tenantId = "$TALQUI_TENANT_ID",
84
+ channel = "whatsapp-cloud",
85
+ channelId = "$TALQUI_CHANNEL_ID",
86
+ auth = "operator",
87
+ token = "$TALQUI_TOKEN"
88
+ }) {
89
+ if (template.language !== "pt_BR")
90
+ unsupported(NAME, "language", `The ${template.language} language`);
91
+ if (template.messageSendTtlSeconds !== void 0)
92
+ unsupported(NAME, "messageSendTtlSeconds", "A message TTL");
93
+ if (template.category === "AUTHENTICATION" && channel === "whatsapp-cloud")
94
+ unsupported(
95
+ NAME,
96
+ "category",
97
+ "Authentication on the whatsapp-cloud channel"
98
+ );
99
+ return [
100
+ {
101
+ method: "POST",
102
+ url: `https://services-api.talqui.chat/v1/tenants/${tenantId}/campaigns-models`,
103
+ headers: {
104
+ Authorization: `${auth === "operator" ? "Bearer" : "PluginConnection"} ${token}`,
105
+ "Content-Type": "application/json"
106
+ },
107
+ body: {
108
+ // Talqui slugifies the name (dropping `_`) and turns `-` back into `_` for Meta.
109
+ campaignModelName: template.name.replaceAll("_", "-"),
110
+ campaignModelType: template.category,
111
+ campaignModelChannel: CHANNELS[channel],
112
+ campaignModelChannelID: channelId,
113
+ campaignModelMeta: { allowedToStartSession: "denied" },
114
+ campaignModelGatewayMeta: template.category !== "AUTHENTICATION" && template.displayFormat ? { displayFormat: template.displayFormat } : {},
115
+ campaignModelSlots: template.category === "AUTHENTICATION" ? authentication(template) : standard(template)
116
+ }
117
+ }
118
+ ];
119
+ }
120
+ });
121
+ var spaced = (text) => replacePlaceholders(text, (name) => `{{ ${name} }}`);
122
+ var escapeHtml = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
123
+ var CHIP_COLORS = [
124
+ "ab7df6",
125
+ "7f8fa4",
126
+ "26c1c9",
127
+ "fd7b1f",
128
+ "e0102b",
129
+ "40b630",
130
+ "0079c4"
131
+ ];
132
+ var chipColor = (name) => CHIP_COLORS[[...name].reduce((sum, char) => sum + char.charCodeAt(0), 0) % CHIP_COLORS.length];
133
+ var MARKS = [
134
+ [/\*([^*\n]+)\*/g, "strong", "t-attribute-bold"],
135
+ [/_([^_\n]+)_/g, "em", "t-attribute-italic"],
136
+ [/~([^~\n]+)~/g, "s", "t-attribute-strike"]
137
+ ];
138
+ var editorHtml = (text, variables = {}) => {
139
+ const names = [];
140
+ let html = replacePlaceholders(
141
+ escapeHtml(text),
142
+ (name) => `\uE000${names.push(name) - 1}\uE000`
143
+ );
144
+ for (const [pattern, tag, className] of MARKS)
145
+ html = html.replace(pattern, `<${tag} class="${className}">$1</${tag}>`);
146
+ html = html.replace(/\uE000(\d+)\uE000/g, (_, index) => {
147
+ const name = names[Number(index)] ?? "";
148
+ return `<span class="variable" data-type="variable" data-id="${name}" data-key="${name}" data-label="${name}" data-default-value="${escapeHtml(variables[name]?.example ?? "")}" style="background-color: #${chipColor(name)};">${name}</span>`;
149
+ });
150
+ return `<p>${html.replace(/\n/g, "<br>")}</p>`;
151
+ };
152
+ var editorDoc = (text, variables = {}) => ({
153
+ type: "doc",
154
+ content: [
155
+ {
156
+ type: "paragraph",
157
+ content: text.split(/(\{\{.*?\}\})/).filter(Boolean).map((part) => {
158
+ const name = /^\{\{(.*?)\}\}$/.exec(part)?.[1];
159
+ return name ? {
160
+ type: "variable",
161
+ attrs: {
162
+ id: name,
163
+ key: name,
164
+ label: name,
165
+ defaultValue: variables[name]?.example ?? ""
166
+ }
167
+ } : { type: "text", text: part };
168
+ })
169
+ }
170
+ ]
171
+ });
172
+ var slotVariables = (text, variables = {}) => [...new Set(placeholders(text).map((p) => p.name))].map((name) => ({
173
+ id: name,
174
+ key: name,
175
+ type: "text",
176
+ fallback: variables[name]?.example
177
+ }));
178
+ function standard(t) {
179
+ if (t.limitedTimeOffer)
180
+ unsupported(NAME, "limitedTimeOffer", "A limited-time offer");
181
+ if (t.carousel) unsupported(NAME, "carousel", "A carousel");
182
+ if (t.callPermissionRequest)
183
+ unsupported(NAME, "callPermissionRequest", "A call permission request");
184
+ return {
185
+ header: header(t),
186
+ body: {
187
+ content: editorHtml(t.body.text, t.variables),
188
+ text: spaced(t.body.text),
189
+ variables: slotVariables(t.body.text, t.variables)
190
+ },
191
+ buttons: (t.buttons ?? []).map(
192
+ (b, index) => button(b, t.variables, `buttons.${index}`)
193
+ ),
194
+ footer: t.footer ? { text: t.footer.text } : {}
195
+ };
196
+ }
197
+ function header({ header: h, variables }) {
198
+ if (!h) return EMPTY_HEADER;
199
+ if (h.type === "TEXT")
200
+ return {
201
+ type: "text",
202
+ content: editorDoc(h.text, variables),
203
+ preview: editorHtml(h.text, variables),
204
+ text: spaced(h.text),
205
+ variables: slotVariables(h.text, variables)
206
+ };
207
+ if (h.type === "IMAGE" || h.type === "VIDEO" || h.type === "DOCUMENT") {
208
+ const type = h.type.toLowerCase();
209
+ return {
210
+ ...EMPTY_HEADER,
211
+ type,
212
+ variables: [{ id: "header", key: "header", type, fallback: h.url }]
213
+ };
214
+ }
215
+ return unsupported(NAME, "header", `A ${h.type} header`);
216
+ }
217
+ function button(b, variables = {}, path) {
218
+ switch (b.type) {
219
+ case "QUICK_REPLY":
220
+ case "ORDER_DETAILS":
221
+ return { type: b.type, text: b.text };
222
+ case "URL": {
223
+ const [param] = placeholders(b.url);
224
+ return {
225
+ type: b.type,
226
+ text: b.text,
227
+ url: param ? b.url.replace(param.raw, "{{1}}") : b.url,
228
+ example: param ? [variables[param.name]?.example ?? ""] : void 0
229
+ };
230
+ }
231
+ case "PHONE_NUMBER":
232
+ return { type: b.type, text: b.text, phone_number: b.phoneNumber };
233
+ case "VOICE_CALL":
234
+ return { type: b.type, text: b.text, ttl_minutes: b.ttlMinutes };
235
+ case "COPY_CODE":
236
+ return { type: b.type, example: [b.example] };
237
+ default:
238
+ return unsupported(NAME, path, `The ${b.type} button`);
239
+ }
240
+ }
241
+ function authentication(t) {
242
+ if (t.body?.addSecurityRecommendation !== void 0)
243
+ unsupported(NAME, "body.addSecurityRecommendation", "The security line");
244
+ if (t.footer?.codeExpirationMinutes !== void 0)
245
+ unsupported(NAME, "footer.codeExpirationMinutes", "The code expiration");
246
+ if (t.button.otpType !== "COPY_CODE")
247
+ unsupported(NAME, "button.otpType", `The ${t.button.otpType} OTP button`);
248
+ return {
249
+ header: EMPTY_HEADER,
250
+ // Meta writes the authentication body; Talqui only requires a non-empty text, shown in its preview.
251
+ body: {
252
+ content: editorHtml(AUTHENTICATION_PREVIEW),
253
+ text: AUTHENTICATION_PREVIEW,
254
+ variables: []
255
+ },
256
+ buttons: [{ type: "OTP", otp_type: "COPY_CODE" }],
257
+ footer: {}
258
+ };
259
+ }
260
+ // Annotate the CommonJS export names for ESM import in node:
261
+ 0 && (module.exports = {
262
+ talqui
263
+ });
@@ -0,0 +1,19 @@
1
+ import { Adapter } from '../index.cjs';
2
+
3
+ type TalquiOptions = {
4
+ /** Default: `$TALQUI_TENANT_ID`. */
5
+ tenantId?: string;
6
+ /** WhatsApp channel the template belongs to. Default: `whatsapp-cloud`. */
7
+ channel?: "whatsapp-cloud" | "whatsapp-gup";
8
+ /** Channel connection ID. Default: `$TALQUI_CHANNEL_ID`. */
9
+ channelId?: string;
10
+ /** `operator` sends `Bearer <token>`, `pluginConnection` sends `PluginConnection <token>`. Default: `operator`. */
11
+ auth?: "operator" | "pluginConnection";
12
+ /** Default: `$TALQUI_TOKEN`. */
13
+ token?: string;
14
+ };
15
+ /** Talqui: `POST /v1/tenants/{tenantID}/campaigns-models`. */
16
+ declare const talqui: (options?: TalquiOptions | undefined) => Adapter;
17
+
18
+ export { talqui };
19
+ export type { TalquiOptions };
@@ -0,0 +1,19 @@
1
+ import { Adapter } from '../index.js';
2
+
3
+ type TalquiOptions = {
4
+ /** Default: `$TALQUI_TENANT_ID`. */
5
+ tenantId?: string;
6
+ /** WhatsApp channel the template belongs to. Default: `whatsapp-cloud`. */
7
+ channel?: "whatsapp-cloud" | "whatsapp-gup";
8
+ /** Channel connection ID. Default: `$TALQUI_CHANNEL_ID`. */
9
+ channelId?: string;
10
+ /** `operator` sends `Bearer <token>`, `pluginConnection` sends `PluginConnection <token>`. Default: `operator`. */
11
+ auth?: "operator" | "pluginConnection";
12
+ /** Default: `$TALQUI_TOKEN`. */
13
+ token?: string;
14
+ };
15
+ /** Talqui: `POST /v1/tenants/{tenantID}/campaigns-models`. */
16
+ declare const talqui: (options?: TalquiOptions | undefined) => Adapter;
17
+
18
+ export { talqui };
19
+ export type { TalquiOptions };
@@ -0,0 +1,206 @@
1
+ import {
2
+ replacePlaceholders
3
+ } from "../chunk-SMN4WTO3.js";
4
+ import {
5
+ defineAdapter,
6
+ placeholders,
7
+ unsupported
8
+ } from "../chunk-ULTGMISN.js";
9
+
10
+ // src/adapters/talqui.ts
11
+ var NAME = "talqui";
12
+ var AUTHENTICATION_PREVIEW = "*123456* \xE9 o seu c\xF3digo de verifica\xE7\xE3o.";
13
+ var EMPTY_HEADER = {
14
+ type: null,
15
+ content: {},
16
+ preview: "",
17
+ text: "",
18
+ variables: []
19
+ };
20
+ var CHANNELS = {
21
+ "whatsapp-cloud": "urn:talqui:whatsapp-cloud:0",
22
+ "whatsapp-gup": "urn:talqui:whatsapp-gup:0"
23
+ };
24
+ var talqui = defineAdapter({
25
+ name: NAME,
26
+ toRequests(template, {
27
+ tenantId = "$TALQUI_TENANT_ID",
28
+ channel = "whatsapp-cloud",
29
+ channelId = "$TALQUI_CHANNEL_ID",
30
+ auth = "operator",
31
+ token = "$TALQUI_TOKEN"
32
+ }) {
33
+ if (template.language !== "pt_BR")
34
+ unsupported(NAME, "language", `The ${template.language} language`);
35
+ if (template.messageSendTtlSeconds !== void 0)
36
+ unsupported(NAME, "messageSendTtlSeconds", "A message TTL");
37
+ if (template.category === "AUTHENTICATION" && channel === "whatsapp-cloud")
38
+ unsupported(
39
+ NAME,
40
+ "category",
41
+ "Authentication on the whatsapp-cloud channel"
42
+ );
43
+ return [
44
+ {
45
+ method: "POST",
46
+ url: `https://services-api.talqui.chat/v1/tenants/${tenantId}/campaigns-models`,
47
+ headers: {
48
+ Authorization: `${auth === "operator" ? "Bearer" : "PluginConnection"} ${token}`,
49
+ "Content-Type": "application/json"
50
+ },
51
+ body: {
52
+ // Talqui slugifies the name (dropping `_`) and turns `-` back into `_` for Meta.
53
+ campaignModelName: template.name.replaceAll("_", "-"),
54
+ campaignModelType: template.category,
55
+ campaignModelChannel: CHANNELS[channel],
56
+ campaignModelChannelID: channelId,
57
+ campaignModelMeta: { allowedToStartSession: "denied" },
58
+ campaignModelGatewayMeta: template.category !== "AUTHENTICATION" && template.displayFormat ? { displayFormat: template.displayFormat } : {},
59
+ campaignModelSlots: template.category === "AUTHENTICATION" ? authentication(template) : standard(template)
60
+ }
61
+ }
62
+ ];
63
+ }
64
+ });
65
+ var spaced = (text) => replacePlaceholders(text, (name) => `{{ ${name} }}`);
66
+ var escapeHtml = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
67
+ var CHIP_COLORS = [
68
+ "ab7df6",
69
+ "7f8fa4",
70
+ "26c1c9",
71
+ "fd7b1f",
72
+ "e0102b",
73
+ "40b630",
74
+ "0079c4"
75
+ ];
76
+ var chipColor = (name) => CHIP_COLORS[[...name].reduce((sum, char) => sum + char.charCodeAt(0), 0) % CHIP_COLORS.length];
77
+ var MARKS = [
78
+ [/\*([^*\n]+)\*/g, "strong", "t-attribute-bold"],
79
+ [/_([^_\n]+)_/g, "em", "t-attribute-italic"],
80
+ [/~([^~\n]+)~/g, "s", "t-attribute-strike"]
81
+ ];
82
+ var editorHtml = (text, variables = {}) => {
83
+ const names = [];
84
+ let html = replacePlaceholders(
85
+ escapeHtml(text),
86
+ (name) => `\uE000${names.push(name) - 1}\uE000`
87
+ );
88
+ for (const [pattern, tag, className] of MARKS)
89
+ html = html.replace(pattern, `<${tag} class="${className}">$1</${tag}>`);
90
+ html = html.replace(/\uE000(\d+)\uE000/g, (_, index) => {
91
+ const name = names[Number(index)] ?? "";
92
+ return `<span class="variable" data-type="variable" data-id="${name}" data-key="${name}" data-label="${name}" data-default-value="${escapeHtml(variables[name]?.example ?? "")}" style="background-color: #${chipColor(name)};">${name}</span>`;
93
+ });
94
+ return `<p>${html.replace(/\n/g, "<br>")}</p>`;
95
+ };
96
+ var editorDoc = (text, variables = {}) => ({
97
+ type: "doc",
98
+ content: [
99
+ {
100
+ type: "paragraph",
101
+ content: text.split(/(\{\{.*?\}\})/).filter(Boolean).map((part) => {
102
+ const name = /^\{\{(.*?)\}\}$/.exec(part)?.[1];
103
+ return name ? {
104
+ type: "variable",
105
+ attrs: {
106
+ id: name,
107
+ key: name,
108
+ label: name,
109
+ defaultValue: variables[name]?.example ?? ""
110
+ }
111
+ } : { type: "text", text: part };
112
+ })
113
+ }
114
+ ]
115
+ });
116
+ var slotVariables = (text, variables = {}) => [...new Set(placeholders(text).map((p) => p.name))].map((name) => ({
117
+ id: name,
118
+ key: name,
119
+ type: "text",
120
+ fallback: variables[name]?.example
121
+ }));
122
+ function standard(t) {
123
+ if (t.limitedTimeOffer)
124
+ unsupported(NAME, "limitedTimeOffer", "A limited-time offer");
125
+ if (t.carousel) unsupported(NAME, "carousel", "A carousel");
126
+ if (t.callPermissionRequest)
127
+ unsupported(NAME, "callPermissionRequest", "A call permission request");
128
+ return {
129
+ header: header(t),
130
+ body: {
131
+ content: editorHtml(t.body.text, t.variables),
132
+ text: spaced(t.body.text),
133
+ variables: slotVariables(t.body.text, t.variables)
134
+ },
135
+ buttons: (t.buttons ?? []).map(
136
+ (b, index) => button(b, t.variables, `buttons.${index}`)
137
+ ),
138
+ footer: t.footer ? { text: t.footer.text } : {}
139
+ };
140
+ }
141
+ function header({ header: h, variables }) {
142
+ if (!h) return EMPTY_HEADER;
143
+ if (h.type === "TEXT")
144
+ return {
145
+ type: "text",
146
+ content: editorDoc(h.text, variables),
147
+ preview: editorHtml(h.text, variables),
148
+ text: spaced(h.text),
149
+ variables: slotVariables(h.text, variables)
150
+ };
151
+ if (h.type === "IMAGE" || h.type === "VIDEO" || h.type === "DOCUMENT") {
152
+ const type = h.type.toLowerCase();
153
+ return {
154
+ ...EMPTY_HEADER,
155
+ type,
156
+ variables: [{ id: "header", key: "header", type, fallback: h.url }]
157
+ };
158
+ }
159
+ return unsupported(NAME, "header", `A ${h.type} header`);
160
+ }
161
+ function button(b, variables = {}, path) {
162
+ switch (b.type) {
163
+ case "QUICK_REPLY":
164
+ case "ORDER_DETAILS":
165
+ return { type: b.type, text: b.text };
166
+ case "URL": {
167
+ const [param] = placeholders(b.url);
168
+ return {
169
+ type: b.type,
170
+ text: b.text,
171
+ url: param ? b.url.replace(param.raw, "{{1}}") : b.url,
172
+ example: param ? [variables[param.name]?.example ?? ""] : void 0
173
+ };
174
+ }
175
+ case "PHONE_NUMBER":
176
+ return { type: b.type, text: b.text, phone_number: b.phoneNumber };
177
+ case "VOICE_CALL":
178
+ return { type: b.type, text: b.text, ttl_minutes: b.ttlMinutes };
179
+ case "COPY_CODE":
180
+ return { type: b.type, example: [b.example] };
181
+ default:
182
+ return unsupported(NAME, path, `The ${b.type} button`);
183
+ }
184
+ }
185
+ function authentication(t) {
186
+ if (t.body?.addSecurityRecommendation !== void 0)
187
+ unsupported(NAME, "body.addSecurityRecommendation", "The security line");
188
+ if (t.footer?.codeExpirationMinutes !== void 0)
189
+ unsupported(NAME, "footer.codeExpirationMinutes", "The code expiration");
190
+ if (t.button.otpType !== "COPY_CODE")
191
+ unsupported(NAME, "button.otpType", `The ${t.button.otpType} OTP button`);
192
+ return {
193
+ header: EMPTY_HEADER,
194
+ // Meta writes the authentication body; Talqui only requires a non-empty text, shown in its preview.
195
+ body: {
196
+ content: editorHtml(AUTHENTICATION_PREVIEW),
197
+ text: AUTHENTICATION_PREVIEW,
198
+ variables: []
199
+ },
200
+ buttons: [{ type: "OTP", otp_type: "COPY_CODE" }],
201
+ footer: {}
202
+ };
203
+ }
204
+ export {
205
+ talqui
206
+ };