@ikatec/digisac-api-sdk 4.10.0 → 4.12.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.
Files changed (104) hide show
  1. package/dist/apis/categories/types.d.ts +1 -0
  2. package/dist/apis/categories/types.d.ts.map +1 -1
  3. package/dist/apis/contactBlockLists/ContactBlockListsApi.cjs +28 -0
  4. package/dist/apis/contactBlockLists/ContactBlockListsApi.cjs.map +1 -1
  5. package/dist/apis/contactBlockLists/ContactBlockListsApi.d.ts +24 -0
  6. package/dist/apis/contactBlockLists/ContactBlockListsApi.d.ts.map +1 -1
  7. package/dist/apis/contactBlockLists/ContactBlockListsApi.mjs +28 -0
  8. package/dist/apis/contactBlockLists/ContactBlockListsApi.mjs.map +1 -1
  9. package/dist/apis/contactBlockLists/types.d.ts +6 -1
  10. package/dist/apis/contactBlockLists/types.d.ts.map +1 -1
  11. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.cjs +31 -1
  12. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.cjs.map +1 -1
  13. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.d.ts +28 -2
  14. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.d.ts.map +1 -1
  15. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.mjs +31 -1
  16. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.mjs.map +1 -1
  17. package/dist/apis/contactBlockListsControls/types.d.ts +7 -0
  18. package/dist/apis/contactBlockListsControls/types.d.ts.map +1 -1
  19. package/dist/apis/distribution/DistributionApi.cjs +25 -0
  20. package/dist/apis/distribution/DistributionApi.cjs.map +1 -1
  21. package/dist/apis/distribution/DistributionApi.d.ts +20 -1
  22. package/dist/apis/distribution/DistributionApi.d.ts.map +1 -1
  23. package/dist/apis/distribution/DistributionApi.mjs +25 -0
  24. package/dist/apis/distribution/DistributionApi.mjs.map +1 -1
  25. package/dist/apis/distribution/types.d.ts +18 -1
  26. package/dist/apis/distribution/types.d.ts.map +1 -1
  27. package/dist/apis/integrations/IntegrationsApi.cjs +17 -0
  28. package/dist/apis/integrations/IntegrationsApi.cjs.map +1 -1
  29. package/dist/apis/integrations/IntegrationsApi.d.ts +15 -2
  30. package/dist/apis/integrations/IntegrationsApi.d.ts.map +1 -1
  31. package/dist/apis/integrations/IntegrationsApi.mjs +17 -0
  32. package/dist/apis/integrations/IntegrationsApi.mjs.map +1 -1
  33. package/dist/apis/integrations/types.d.ts +15 -6
  34. package/dist/apis/integrations/types.d.ts.map +1 -1
  35. package/dist/apis/interactiveMessages/InteractiveMessagesApi.cjs +15 -0
  36. package/dist/apis/interactiveMessages/InteractiveMessagesApi.cjs.map +1 -1
  37. package/dist/apis/interactiveMessages/InteractiveMessagesApi.d.ts +13 -0
  38. package/dist/apis/interactiveMessages/InteractiveMessagesApi.d.ts.map +1 -1
  39. package/dist/apis/interactiveMessages/InteractiveMessagesApi.mjs +15 -0
  40. package/dist/apis/interactiveMessages/InteractiveMessagesApi.mjs.map +1 -1
  41. package/dist/apis/interactiveMessages/types.d.ts +8 -2
  42. package/dist/apis/interactiveMessages/types.d.ts.map +1 -1
  43. package/dist/apis/me/MeApi.cjs +36 -29
  44. package/dist/apis/me/MeApi.cjs.map +1 -1
  45. package/dist/apis/me/MeApi.d.ts +33 -22
  46. package/dist/apis/me/MeApi.d.ts.map +1 -1
  47. package/dist/apis/me/MeApi.mjs +36 -29
  48. package/dist/apis/me/MeApi.mjs.map +1 -1
  49. package/dist/apis/me/types.d.ts +67 -4
  50. package/dist/apis/me/types.d.ts.map +1 -1
  51. package/dist/apis/transcripts/TranscriptsApi.cjs +12 -2
  52. package/dist/apis/transcripts/TranscriptsApi.cjs.map +1 -1
  53. package/dist/apis/transcripts/TranscriptsApi.d.ts +11 -5
  54. package/dist/apis/transcripts/TranscriptsApi.d.ts.map +1 -1
  55. package/dist/apis/transcripts/TranscriptsApi.mjs +12 -2
  56. package/dist/apis/transcripts/TranscriptsApi.mjs.map +1 -1
  57. package/dist/apis/transcripts/types.d.ts +4 -0
  58. package/dist/apis/transcripts/types.d.ts.map +1 -1
  59. package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.cjs +52 -0
  60. package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.cjs.map +1 -1
  61. package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.d.ts +45 -2
  62. package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.d.ts.map +1 -1
  63. package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.mjs +52 -0
  64. package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.mjs.map +1 -1
  65. package/dist/apis/whatsappBusinessTemplates/types.d.ts +34 -1
  66. package/dist/apis/whatsappBusinessTemplates/types.d.ts.map +1 -1
  67. package/dist/core/BaseCrudApi.cjs +3 -1
  68. package/dist/core/BaseCrudApi.cjs.map +1 -1
  69. package/dist/core/BaseCrudApi.d.ts +6 -4
  70. package/dist/core/BaseCrudApi.d.ts.map +1 -1
  71. package/dist/core/BaseCrudApi.mjs +3 -1
  72. package/dist/core/BaseCrudApi.mjs.map +1 -1
  73. package/dist/core/types.d.ts +1 -1
  74. package/dist/core/types.d.ts.map +1 -1
  75. package/package.json +1 -1
  76. package/src/apis/categories/types.ts +1 -0
  77. package/src/apis/contactBlockLists/ContactBlockListsApi.test.ts +57 -0
  78. package/src/apis/contactBlockLists/ContactBlockListsApi.ts +44 -0
  79. package/src/apis/contactBlockLists/types.ts +6 -1
  80. package/src/apis/contactBlockListsControls/ContactBlockListsControlsApi.test.ts +48 -0
  81. package/src/apis/contactBlockListsControls/ContactBlockListsControlsApi.ts +49 -2
  82. package/src/apis/contactBlockListsControls/types.ts +7 -0
  83. package/src/apis/distribution/DistributionApi.test.ts +65 -0
  84. package/src/apis/distribution/DistributionApi.ts +39 -1
  85. package/src/apis/distribution/types.ts +17 -1
  86. package/src/apis/integrations/IntegrationsApi.test.ts +42 -0
  87. package/src/apis/integrations/IntegrationsApi.ts +35 -2
  88. package/src/apis/integrations/types.ts +25 -6
  89. package/src/apis/interactiveMessages/InteractiveMessagesApi.test.ts +59 -0
  90. package/src/apis/interactiveMessages/InteractiveMessagesApi.ts +26 -0
  91. package/src/apis/interactiveMessages/types.ts +8 -2
  92. package/src/apis/me/MeApi.test.ts +44 -0
  93. package/src/apis/me/MeApi.ts +83 -28
  94. package/src/apis/me/types.ts +75 -4
  95. package/src/apis/payloads.test-d.ts +94 -2
  96. package/src/apis/transcripts/TranscriptsApi.test.ts +14 -21
  97. package/src/apis/transcripts/TranscriptsApi.ts +15 -6
  98. package/src/apis/transcripts/types.ts +4 -0
  99. package/src/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.test.ts +86 -0
  100. package/src/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.ts +75 -1
  101. package/src/apis/whatsappBusinessTemplates/types.ts +38 -1
  102. package/src/core/BaseCrudApi.test-d.ts +11 -1
  103. package/src/core/BaseCrudApi.ts +15 -6
  104. package/src/core/types.ts +4 -2
@@ -18,6 +18,10 @@ import type {
18
18
  import { CardsApi } from './cards/CardsApi'
19
19
  import { ContactsApi } from './contacts/ContactsApi'
20
20
  import { InteractiveMessagesApi } from './interactiveMessages/InteractiveMessagesApi'
21
+ import { MeApi } from './me/MeApi'
22
+ import type { AccountAmounts, MeStatusResponse } from './me/types'
23
+ import { TranscriptsApi } from './transcripts/TranscriptsApi'
24
+ import { WhatsappBusinessTemplatesApi } from './whatsappBusinessTemplates/WhatsappBusinessTemplatesApi'
21
25
  import type {
22
26
  ChannelRedirect,
23
27
  CtaUrlInteractiveMessageContent,
@@ -54,6 +58,8 @@ import type { SummarizeTicketResult } from './contacts/types'
54
58
  import { CustomFieldsApi } from './customFields/CustomFieldsApi'
55
59
  import { DepartmentsApi } from './departments/DepartmentsApi'
56
60
  import type { Department } from './departments/types'
61
+ import type { Distribution } from './distribution/types'
62
+ import type { ContactBlockList } from './contactBlockLists/types'
57
63
  import { DistributionApi } from './distribution/DistributionApi'
58
64
  import { FilesApi } from './files/FilesApi'
59
65
  import type { CreateToUploadResult, DeleteFileResult, File } from './files/types'
@@ -68,7 +74,7 @@ import { NotificationsApi } from './notifications/NotificationsApi'
68
74
  import { OrganizationsApi } from './organizations/OrganizationsApi'
69
75
  import { PeopleApi } from './people/PeopleApi'
70
76
  import { ServicesApi } from './services/ServicesApi'
71
- import type { UpdateUserPayload } from './users/types'
77
+ import type { UpdateUserPayload, User } from './users/types'
72
78
  import { WebhooksApi } from './webhooks/WebhooksApi'
73
79
 
74
80
  declare const client: ApiClient
@@ -208,7 +214,10 @@ expectError(
208
214
  )
209
215
 
210
216
  declare const interactiveMessage: InteractiveMessage
211
- expectType<'list' | 'button' | 'cta_url' | 'request_contact_info'>(interactiveMessage.type)
217
+ // `type` is not a field of the backend (deprecated, optional): the type is `interactive.type`.
218
+ expectType<'list' | 'button' | 'cta_url' | 'request_contact_info' | undefined>(
219
+ interactiveMessage.type,
220
+ )
212
221
  if (interactiveMessage.interactive.type === 'cta_url') {
213
222
  expectType<CtaUrlInteractiveMessageContent>(interactiveMessage.interactive)
214
223
  expectType<string>(interactiveMessage.interactive.action.parameters.url)
@@ -545,6 +554,89 @@ expectType<Promise<'OK'>>(new ContactBlockListsControlsApi(client).deleteById('c
545
554
  expectType<Promise<boolean>>(new RolesApi(client).deleteById('r-1'))
546
555
  expectType<Promise<void>>(new AcceptanceTermsApi(client).deleteById('t-1'))
547
556
  expectType<Promise<void>>(new DistributionApi(client).deleteById('d-1'))
557
+
558
+ // ── QARC-1009: /me, transcripts, interactive messages, templates ─────
559
+
560
+ expectType<Promise<'OK'>>(new MeApi(client).activateOtpAuth({ 'x-digisac-otp': '123456' }))
561
+ expectType<Promise<MeStatusResponse>>(
562
+ new MeApi(client).updateStatus({ status: 'offline', client: 'web' }),
563
+ )
564
+ expectType<Promise<AccountAmounts>>(
565
+ new MeApi(client).getAccountAmounts({ filter: ['users.amounts'] }),
566
+ )
567
+ expectType<Promise<User>>(new MeApi(client).getMe({ include: ['account'] }))
568
+ expectError(new MeApi(client).updateStatus({ status: 'offline', client: 'desktop' }))
569
+ expectType<Promise<void>>(new TranscriptsApi(client).transcribe('m-1'))
570
+ expectType<Promise<{ count: number }>>(new InteractiveMessagesApi(client).count())
571
+ expectType<Promise<{ count: number }>>(new WhatsappBusinessTemplatesApi(client).count())
572
+ expectType<Promise<'OK'>>(new WhatsappBusinessTemplatesApi(client).refreshTemplates())
573
+ expectError(
574
+ new WhatsappBusinessTemplatesApi(client).create({
575
+ name: 'qa',
576
+ internalName: 'QA',
577
+ components: [],
578
+ serviceId: 's-1',
579
+ messageType: 'text_only',
580
+ language: 'pt_BR',
581
+ category: 'UTILITY',
582
+ }),
583
+ )
584
+
585
+ // ── create/updateById: what each route really answers (QARC-1008) ─────
586
+
587
+ expectType<Promise<Distribution | undefined>>(
588
+ new DistributionApi(client).create({
589
+ name: 'd',
590
+ maxNum: 1,
591
+ redistributeAll: false,
592
+ departments: [{ id: 'dep-1' }],
593
+ }),
594
+ )
595
+ expectType<Promise<Distribution | Department[]>>(
596
+ new DistributionApi(client).updateById('d-1', {
597
+ name: 'd',
598
+ roles: [{ id: 'r-1' }],
599
+ departments: [],
600
+ }),
601
+ )
602
+ expectType<Promise<Record<string, never>>>(
603
+ new IntegrationsApi(client).create({ type: 'menu_button', text: 'x' }),
604
+ )
605
+ expectType<Promise<Record<string, never>>>(
606
+ new IntegrationsApi(client).updateById('i-1', { text: 'x' }),
607
+ )
608
+ expectType<Promise<void>>(
609
+ new ContactBlockListsControlsApi(client).create({
610
+ reason: 'r',
611
+ servicesIds: ['s-1'],
612
+ contactBlockListsIds: ['l-1'],
613
+ action: 'block',
614
+ revertExceptions: false,
615
+ }),
616
+ )
617
+ expectType<Promise<{ count: number }>>(
618
+ new ContactBlockListsApi(client).count({ where: { name: 'x' } }),
619
+ )
620
+ expectType<Promise<{ count: number }>>(new ContactBlockListsControlsApi(client).count())
621
+ expectType<Promise<Paginated<ContactBlockList>>>(
622
+ new ContactBlockListsApi(client).getMany({ include: ['importFile'] }),
623
+ )
624
+ expectError(
625
+ new DistributionApi(client).create({
626
+ name: 'd',
627
+ maxNum: 1,
628
+ redistributeAll: false,
629
+ departments: ['dep-1'],
630
+ }),
631
+ )
632
+ expectError(
633
+ new ContactBlockListsControlsApi(client).create({
634
+ reason: 'r',
635
+ servicesIds: [],
636
+ contactBlockListsIds: [],
637
+ action: 'remove',
638
+ }),
639
+ )
548
640
  expectType<Promise<false>>(new KnowledgeBaseApi(client).deleteById('k-1'))
549
641
  expectType<Promise<false>>(new KnowledgeBaseItemApi(client).deleteById('k-1'))
550
642
  expectType<Promise<DeleteFileResult>>(new FilesApi(client).deleteById('f-1'))
@@ -1,7 +1,6 @@
1
1
  import { beforeEach, describe, expect, it, vi } from 'vitest'
2
2
  import { TranscriptsApi } from './TranscriptsApi'
3
3
  import type { ApiClient } from '../../core/ApiClient'
4
- import type { TranscriptResponse } from './types'
5
4
 
6
5
  // ── Mock client factory ──────────────────────────────────────────────
7
6
 
@@ -28,36 +27,30 @@ describe('TranscriptsApi', () => {
28
27
  api = new TranscriptsApi(client)
29
28
  })
30
29
 
31
- // ── getById ──────────────────────────────────────────────────────
32
-
33
- describe('getById', () => {
34
- it('calls client.get with /transcripts/:id', async () => {
35
- const response: TranscriptResponse = {
36
- id: 'msg-1',
37
- isTranscribing: true,
38
- transcribeError: false,
39
- }
40
- vi.mocked(client.get).mockResolvedValue(response)
41
- const result = await api.getById('msg-1')
30
+ describe('transcribe', () => {
31
+ it('calls client.get with /transcripts/:messageId and resolves the empty answer', async () => {
32
+ vi.mocked(client.get).mockResolvedValue(undefined)
33
+ await expect(api.transcribe('msg-1')).resolves.toBeUndefined()
42
34
  expect(client.get).toHaveBeenCalledWith('/transcripts/msg-1', undefined)
43
- expect(result).toEqual(response)
44
35
  })
45
36
 
46
37
  it('forwards custom headers', async () => {
47
- const response: TranscriptResponse = {
48
- id: 'msg-2',
49
- isTranscribing: false,
50
- transcribeError: true,
51
- }
52
- vi.mocked(client.get).mockResolvedValue(response)
53
38
  const headers = { 'X-Custom': 'h' }
54
- await api.getById('msg-2', headers)
39
+ await api.transcribe('msg-2', headers)
55
40
  expect(client.get).toHaveBeenCalledWith('/transcripts/msg-2', headers)
56
41
  })
57
42
 
58
43
  it('propagates errors from client.get', async () => {
59
44
  vi.mocked(client.get).mockRejectedValue(new Error('Not found'))
60
- await expect(api.getById('missing')).rejects.toThrow('Not found')
45
+ await expect(api.transcribe('missing')).rejects.toThrow('Not found')
46
+ })
47
+ })
48
+
49
+ describe('getById (deprecated)', () => {
50
+ it('starts the transcription through the same route', async () => {
51
+ vi.mocked(client.get).mockResolvedValue(undefined)
52
+ await expect(api.getById('msg-3')).resolves.toBeUndefined()
53
+ expect(client.get).toHaveBeenCalledWith('/transcripts/msg-3', undefined)
61
54
  })
62
55
  })
63
56
  })
@@ -1,12 +1,21 @@
1
1
  import { BaseApi } from '../../core/BaseApi'
2
- import type { HasGetById } from '../../core/types'
3
- import type { TranscriptResponse } from './types'
4
2
 
5
- export class TranscriptsApi extends BaseApi implements HasGetById<TranscriptResponse> {
3
+ export class TranscriptsApi extends BaseApi {
6
4
  private readonly basePath = '/transcripts'
7
5
 
8
- /** Triggers audio transcription for a message by dispatching an async job, and marks it as transcribing. */
9
- getById(id: string, headers?: Record<string, string>): Promise<TranscriptResponse> {
10
- return this.client.get<TranscriptResponse>(`${this.basePath}/${id}`, headers)
6
+ /**
7
+ * Starts the transcription of the audio of a message: marks the message as transcribing and dispatches an async
8
+ * job, which calls the transcription service (AI) and debits the AI credits of the account. Resolves with no body;
9
+ * the result shows on the message. An unknown message answers 500.
10
+ */
11
+ transcribe(messageId: string, headers?: Record<string, string>): Promise<void> {
12
+ return this.client.get<void>(`${this.basePath}/${messageId}`, headers)
13
+ }
14
+
15
+ /**
16
+ * @deprecated Not a read: it starts the transcription of the message. Use {@link transcribe}.
17
+ */
18
+ getById(id: string, headers?: Record<string, string>): Promise<void> {
19
+ return this.transcribe(id, headers)
11
20
  }
12
21
  }
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @deprecated The route answers no body: `TranscriptsApi.transcribe` (and `getById`) resolve `undefined`. The
3
+ * transcription status shows on the message (`isTranscribing`, `transcribeError`).
4
+ */
1
5
  export type TranscriptResponse = {
2
6
  id: string
3
7
  isTranscribing: boolean
@@ -0,0 +1,86 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { WhatsappBusinessTemplatesApi } from './WhatsappBusinessTemplatesApi'
3
+ import type { ApiClient } from '../../core/ApiClient'
4
+
5
+ // ── Mock client factory ──────────────────────────────────────────────
6
+
7
+ function createMockClient(): ApiClient {
8
+ return {
9
+ setAccessToken: vi.fn().mockReturnThis(),
10
+ request: vi.fn(),
11
+ get: vi.fn(),
12
+ post: vi.fn(),
13
+ put: vi.fn(),
14
+ patch: vi.fn(),
15
+ delete: vi.fn(),
16
+ }
17
+ }
18
+
19
+ // ── Tests ────────────────────────────────────────────────────────────
20
+
21
+ describe('WhatsappBusinessTemplatesApi', () => {
22
+ let client: ReturnType<typeof createMockClient>
23
+ let api: WhatsappBusinessTemplatesApi
24
+
25
+ beforeEach(() => {
26
+ client = createMockClient()
27
+ api = new WhatsappBusinessTemplatesApi(client)
28
+ })
29
+
30
+ it('count calls client.get with /whatsapp-business-templates/count', async () => {
31
+ vi.mocked(client.get).mockResolvedValue({ count: 2 })
32
+ await expect(api.count()).resolves.toEqual({ count: 2 })
33
+ expect(client.get).toHaveBeenCalledWith('/whatsapp-business-templates/count', undefined)
34
+ })
35
+
36
+ it('getHsmLimit calls client.get with /whatsapp-business-templates/:accountId/hsmLimit', async () => {
37
+ const limit = { hsmLimit: 10, hsmUsedLimit: 3, hasLimit: 7 }
38
+ vi.mocked(client.get).mockResolvedValue(limit)
39
+ await expect(api.getHsmLimit('acc-1')).resolves.toEqual(limit)
40
+ expect(client.get).toHaveBeenCalledWith(
41
+ '/whatsapp-business-templates/acc-1/hsmLimit',
42
+ undefined,
43
+ )
44
+ })
45
+
46
+ it('sendToReview posts to /whatsapp-business-templates/:id/send-to-review', async () => {
47
+ await api.sendToReview('t-1')
48
+ expect(client.post).toHaveBeenCalledWith(
49
+ '/whatsapp-business-templates/t-1/send-to-review',
50
+ undefined,
51
+ undefined,
52
+ )
53
+ })
54
+
55
+ it('refreshTemplates posts to /whatsapp-business-templates/refresh-templates and resolves OK', async () => {
56
+ vi.mocked(client.post).mockResolvedValue('OK')
57
+ await expect(api.refreshTemplates()).resolves.toBe('OK')
58
+ expect(client.post).toHaveBeenCalledWith(
59
+ '/whatsapp-business-templates/refresh-templates',
60
+ undefined,
61
+ undefined,
62
+ )
63
+ })
64
+
65
+ it('resume calls client.get with /whatsapp-business-templates/resume', async () => {
66
+ vi.mocked(client.get).mockResolvedValue([])
67
+ await expect(api.resume()).resolves.toEqual([])
68
+ expect(client.get).toHaveBeenCalledWith('/whatsapp-business-templates/resume', undefined)
69
+ })
70
+
71
+ it('create posts the draft with status and namespace', async () => {
72
+ const body = {
73
+ name: 'qa_template',
74
+ internalName: 'QA',
75
+ status: '',
76
+ namespace: '',
77
+ components: [{ type: 'BODY' as const, text: 'Olá' }],
78
+ serviceId: 's-1',
79
+ messageType: 'text_only' as const,
80
+ language: 'pt_BR' as const,
81
+ category: 'UTILITY' as const,
82
+ }
83
+ await api.create(body)
84
+ expect(client.post).toHaveBeenCalledWith('/whatsapp-business-templates', body, undefined)
85
+ })
86
+ })
@@ -1,17 +1,91 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
+ import type { ListQuery } from '../../core/types'
2
3
  import { BaseCrudApi } from '../../core/BaseCrudApi'
3
4
  import type {
4
5
  WhatsappBusinessTemplate,
5
6
  CreateWhatsappBusinessTemplatePayload,
6
7
  UpdateWhatsappBusinessTemplatePayload,
8
+ WhatsappBusinessTemplateHsmLimit,
7
9
  } from './types'
8
10
 
11
+ /**
12
+ * WhatsApp Business templates. Create, update, reads and count are local; `sendToReview`, `refreshTemplates`,
13
+ * `resume` and the delete of a template of a `whatsapp-business` connection call the provider (Meta, 360Dialog).
14
+ * `getById` answers `null` for an unknown id.
15
+ */
9
16
  export class WhatsappBusinessTemplatesApi extends BaseCrudApi<
10
17
  WhatsappBusinessTemplate,
11
18
  CreateWhatsappBusinessTemplatePayload,
12
- UpdateWhatsappBusinessTemplatePayload
19
+ UpdateWhatsappBusinessTemplatePayload,
20
+ ListQuery<WhatsappBusinessTemplate>,
21
+ unknown
13
22
  > {
14
23
  constructor(client: ApiClient) {
15
24
  super(client, '/whatsapp-business-templates')
16
25
  }
26
+
27
+ /**
28
+ * Counts the templates matching the query.
29
+ * @permissions hsm.view
30
+ */
31
+ count(
32
+ query?: ListQuery<WhatsappBusinessTemplate>,
33
+ headers?: Record<string, string>,
34
+ ): Promise<{ count: number }> {
35
+ return this.client.get<{ count: number }>(
36
+ `${this.basePath}/count${this.toQueryString(query)}`,
37
+ headers,
38
+ )
39
+ }
40
+
41
+ /**
42
+ * Reads the template limit of the plan of the account. Send the `accountId` of the current user: the route is
43
+ * outside the error handler of the backend, so an unknown id gets no answer (the request waits for the timeout).
44
+ * @permissions hsm.view
45
+ */
46
+ getHsmLimit(
47
+ accountId: string,
48
+ headers?: Record<string, string>,
49
+ ): Promise<WhatsappBusinessTemplateHsmLimit> {
50
+ return this.client.get<WhatsappBusinessTemplateHsmLimit>(
51
+ `${this.basePath}/${accountId}/hsmLimit`,
52
+ headers,
53
+ )
54
+ }
55
+
56
+ /**
57
+ * Deletes a template. On a `whatsapp-business` connection it goes to the provider (and answers what it answers);
58
+ * a draft of another connection type is deleted locally (answers the stored template). An unknown id answers 500.
59
+ * @permissions hsm.view OR hsm.destroy
60
+ */
61
+ override deleteById(id: string, headers?: Record<string, string>): Promise<unknown> {
62
+ return super.deleteById(id, headers)
63
+ }
64
+
65
+ /**
66
+ * SENDS the template to Meta for approval (through the provider of the connection). An unknown or archived
67
+ * template answers 500 before anything is sent.
68
+ * @permissions hsm.view OR hsm.send OR hsm.send.before.expire
69
+ */
70
+ sendToReview(id: string, headers?: Record<string, string>): Promise<unknown> {
71
+ return this.client.post<unknown>(`${this.basePath}/${id}/send-to-review`, undefined, headers)
72
+ }
73
+
74
+ /**
75
+ * Reads the templates of the connected `whatsapp-business` connections from the provider and syncs them (it may
76
+ * archive the local templates the provider no longer has). Resolves the text `'OK'`.
77
+ * @permissions hsm.view
78
+ */
79
+ refreshTemplates(headers?: Record<string, string>): Promise<'OK'> {
80
+ return this.client.post<'OK'>(`${this.basePath}/refresh-templates`, undefined, headers)
81
+ }
82
+
83
+ /**
84
+ * Reads the template balance from 360Dialog. The backend looks for the 360Dialog connection on the whole platform,
85
+ * not only in the account, and calls 360Dialog when it finds one.
86
+ * @permissions hsm.view
87
+ */
88
+ resume(headers?: Record<string, string>): Promise<Record<string, unknown>[]> {
89
+ return this.client.get<Record<string, unknown>[]>(`${this.basePath}/resume`, headers)
90
+ }
17
91
  }
@@ -10,7 +10,8 @@ export type WhatsappBusinessTemplateHeaderFormat = 'TEXT' | 'IMAGE' | 'VIDEO' |
10
10
 
11
11
  export type WhatsappBusinessTemplateButton = {
12
12
  type: string
13
- text: string
13
+ /** Overwritten by the backend on `REQUEST_CONTACT_INFO` buttons. */
14
+ text?: string
14
15
  url?: string
15
16
  params?: string[]
16
17
  phone_number?: string
@@ -44,11 +45,40 @@ export type WhatsappBusinessTemplate = {
44
45
  createdAt: string
45
46
  updatedAt: string
46
47
  deletedAt: string | null
48
+ /** Only the listing includes it by default; `getById` answers it with `include: ['fileExample']`. */
49
+ fileExample?: WhatsappBusinessTemplateFileExample | null
50
+ // relationships
51
+ service?: Record<string, unknown>
52
+ account?: Record<string, unknown>
53
+ }
54
+
55
+ /** The media example of a template with a media header. */
56
+ export type WhatsappBusinessTemplateFileExample = Record<string, unknown>
57
+
58
+ /** The media example sent on create/update (`base64Url` as a data URL). */
59
+ export type WhatsappBusinessTemplateFileExampleInput = {
60
+ fileName: string
61
+ base64Url: string
62
+ mimetype: string
47
63
  }
48
64
 
65
+ export type WhatsappBusinessTemplateHsmLimit = {
66
+ hsmLimit: number
67
+ hsmUsedLimit: number
68
+ /** The balance left (a number), or `true` when the account has no limit. */
69
+ hasLimit: number | boolean
70
+ }
71
+
72
+ /**
73
+ * Stored locally (nothing goes to Meta until `sendToReview`). `status` is `''` for a draft and `namespace` is `''`
74
+ * until Meta answers — both are required by the database (500 without them).
75
+ */
49
76
  export type CreateWhatsappBusinessTemplatePayload = {
50
77
  name: string
51
78
  internalName: string
79
+ status: string
80
+ namespace: string
81
+ fileExample?: WhatsappBusinessTemplateFileExampleInput
52
82
  components: WhatsappBusinessTemplateComponent[]
53
83
  serviceId: string
54
84
  messageType: WhatsappBusinessTemplateMessageType
@@ -56,7 +86,14 @@ export type CreateWhatsappBusinessTemplatePayload = {
56
86
  category: WhatsappBusinessTemplateCategory
57
87
  }
58
88
 
89
+ /**
90
+ * The backend validates the same 7 fields of the create on the update (`name`, `internalName`, `components`,
91
+ * `serviceId`, `messageType`, `language`, `category`), but ignores `name`.
92
+ */
59
93
  export type UpdateWhatsappBusinessTemplatePayload = {
94
+ status?: string
95
+ namespace?: string
96
+ fileExample?: WhatsappBusinessTemplateFileExampleInput
60
97
  name?: string
61
98
  internalName?: string
62
99
  components?: WhatsappBusinessTemplateComponent[]
@@ -1,7 +1,7 @@
1
1
  import { expectError, expectType } from 'tsd'
2
2
  import type { ApiClient } from './ApiClient'
3
3
  import { BaseCrudApi } from './BaseCrudApi'
4
- import type { Paginated } from './types'
4
+ import type { ListQuery, Paginated } from './types'
5
5
 
6
6
  // ── Test model ──────────────────────────────────────────────────────
7
7
 
@@ -40,6 +40,16 @@ expectType<Promise<Item>>(api.create({ name: 'x' }))
40
40
  expectType<Promise<Item>>(api.updateById('1', {}))
41
41
  expectType<Promise<Item>>(api.deleteById('1'))
42
42
 
43
+ // Routes that do not answer the resource on create/update (TCreateResponse, TUpdateResponse)
44
+ const writes = new BaseCrudApi<Item, CreateItem, UpdateItem, ListQuery<Item>, 'OK', void, Item[]>(
45
+ client,
46
+ '/items',
47
+ )
48
+ expectType<Promise<void>>(writes.create({ name: 'x' }))
49
+ expectType<Promise<Item[]>>(writes.updateById('1', {}))
50
+ expectType<Promise<'OK'>>(writes.deleteById('1'))
51
+ expectType<Promise<Item>>(writes.getById('1'))
52
+
43
53
  expectError(api.create({}))
44
54
  expectError(api.create({ name: 1 }))
45
55
  expectError(api.updateById('1', { name: 1 }))
@@ -16,7 +16,9 @@ export type {
16
16
 
17
17
  /**
18
18
  * Generic CRUD resource: the reads of `BaseReadApi` plus `create`, `updateById` and `deleteById`.
19
- * `TDeleteResponse` is what `deleteById` resolves to (most routes answer the text `'OK'`).
19
+ * `TDeleteResponse` is what `deleteById` resolves to (most routes answer the text `'OK'`);
20
+ * `TCreateResponse` and `TUpdateResponse` are what `create` and `updateById` resolve to, for the routes
21
+ * that do not answer the resource (an empty body, `{}`, another model).
20
22
  */
21
23
  export class BaseCrudApi<
22
24
  TResponse,
@@ -24,24 +26,31 @@ export class BaseCrudApi<
24
26
  TUpdate = TCreate,
25
27
  TQuery extends ListQuery<TResponse> = ListQuery<TResponse>,
26
28
  TDeleteResponse = TResponse,
29
+ TCreateResponse = TResponse,
30
+ TUpdateResponse = TResponse,
27
31
  >
28
32
  extends BaseReadApi<TResponse, TQuery>
29
- implements CrudApi<TResponse, TCreate, TUpdate, TQuery, TDeleteResponse>
33
+ implements
34
+ CrudApi<TResponse, TCreate, TUpdate, TQuery, TDeleteResponse, TCreateResponse, TUpdateResponse>
30
35
  {
31
36
  /**
32
37
  * Creates a new resource.
33
38
  * @permissions $resourceName.create
34
39
  */
35
- create(body: TCreate, headers?: Record<string, string>): Promise<TResponse> {
36
- return this.client.post<TResponse, TCreate>(this.basePath, body, headers)
40
+ create(body: TCreate, headers?: Record<string, string>): Promise<TCreateResponse> {
41
+ return this.client.post<TCreateResponse, TCreate>(this.basePath, body, headers)
37
42
  }
38
43
 
39
44
  /**
40
45
  * Updates an existing resource by ID.
41
46
  * @permissions $resourceName.update
42
47
  */
43
- updateById(id: string, body: TUpdate, headers?: Record<string, string>): Promise<TResponse> {
44
- return this.client.put<TResponse, TUpdate>(`${this.basePath}/${id}`, body, headers)
48
+ updateById(
49
+ id: string,
50
+ body: TUpdate,
51
+ headers?: Record<string, string>,
52
+ ): Promise<TUpdateResponse> {
53
+ return this.client.put<TUpdateResponse, TUpdate>(`${this.basePath}/${id}`, body, headers)
45
54
  }
46
55
 
47
56
  /**
package/src/core/types.ts CHANGED
@@ -160,8 +160,10 @@ export type CrudApi<
160
160
  TUpdate = TCreate,
161
161
  TQuery extends ListQuery<TResponse> = ListQuery<TResponse>,
162
162
  TDeleteResponse = TResponse,
163
+ TCreateResponse = TResponse,
164
+ TUpdateResponse = TResponse,
163
165
  > = HasGetMany<TResponse, TQuery> &
164
166
  HasGetById<TResponse> &
165
- HasCreate<TResponse, TCreate> &
166
- HasUpdateById<TResponse, TUpdate> &
167
+ HasCreate<TCreateResponse, TCreate> &
168
+ HasUpdateById<TUpdateResponse, TUpdate> &
167
169
  HasDeleteById<TDeleteResponse>