@ikatec/digisac-api-sdk 4.15.0 → 4.17.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.
- package/README.md +3 -1
- package/dist/apis/absence/AbsenceApi.cjs +2 -2
- package/dist/apis/absence/AbsenceApi.cjs.map +1 -1
- package/dist/apis/absence/AbsenceApi.d.ts +2 -2
- package/dist/apis/absence/AbsenceApi.mjs +2 -2
- package/dist/apis/absence/AbsenceApi.mjs.map +1 -1
- package/dist/apis/absence/types.d.ts +1 -1
- package/dist/apis/answers/AnswersApi.cjs +23 -2
- package/dist/apis/answers/AnswersApi.cjs.map +1 -1
- package/dist/apis/answers/AnswersApi.d.ts +16 -2
- package/dist/apis/answers/AnswersApi.d.ts.map +1 -1
- package/dist/apis/answers/AnswersApi.mjs +23 -2
- package/dist/apis/answers/AnswersApi.mjs.map +1 -1
- package/dist/apis/answers/types.d.ts +1 -2
- package/dist/apis/answers/types.d.ts.map +1 -1
- package/dist/apis/bots/BotsApi.cjs +16 -7
- package/dist/apis/bots/BotsApi.cjs.map +1 -1
- package/dist/apis/bots/BotsApi.d.ts +13 -7
- package/dist/apis/bots/BotsApi.d.ts.map +1 -1
- package/dist/apis/bots/BotsApi.mjs +16 -7
- package/dist/apis/bots/BotsApi.mjs.map +1 -1
- package/dist/apis/bots/types.d.ts +12 -2
- package/dist/apis/bots/types.d.ts.map +1 -1
- package/dist/apis/campaigns/CampaignsApi.cjs +25 -1
- package/dist/apis/campaigns/CampaignsApi.cjs.map +1 -1
- package/dist/apis/campaigns/CampaignsApi.d.ts +16 -1
- package/dist/apis/campaigns/CampaignsApi.d.ts.map +1 -1
- package/dist/apis/campaigns/CampaignsApi.mjs +25 -1
- package/dist/apis/campaigns/CampaignsApi.mjs.map +1 -1
- package/dist/apis/campaigns/types.d.ts +98 -6
- package/dist/apis/campaigns/types.d.ts.map +1 -1
- package/dist/apis/cards/types.d.ts +5 -5
- package/dist/apis/categories/CategoriesApi.cjs +1 -2
- package/dist/apis/categories/CategoriesApi.cjs.map +1 -1
- package/dist/apis/categories/CategoriesApi.d.ts +1 -2
- package/dist/apis/categories/CategoriesApi.d.ts.map +1 -1
- package/dist/apis/categories/CategoriesApi.mjs +1 -2
- package/dist/apis/categories/CategoriesApi.mjs.map +1 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.cjs +2 -3
- package/dist/apis/clientFeedback/ClientFeedbackApi.cjs.map +1 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.d.ts +2 -3
- package/dist/apis/clientFeedback/ClientFeedbackApi.d.ts.map +1 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.mjs +2 -3
- package/dist/apis/clientFeedback/ClientFeedbackApi.mjs.map +1 -1
- package/dist/apis/contacts/ContactsApi.cjs +10 -6
- package/dist/apis/contacts/ContactsApi.cjs.map +1 -1
- package/dist/apis/contacts/ContactsApi.d.ts +19 -3
- package/dist/apis/contacts/ContactsApi.d.ts.map +1 -1
- package/dist/apis/contacts/ContactsApi.mjs +10 -6
- package/dist/apis/contacts/ContactsApi.mjs.map +1 -1
- package/dist/apis/contacts/types.d.ts +25 -0
- package/dist/apis/contacts/types.d.ts.map +1 -1
- package/dist/apis/customFields/types.d.ts +4 -4
- package/dist/apis/dashboard/DashboardApi.cjs +3 -3
- package/dist/apis/dashboard/DashboardApi.cjs.map +1 -1
- package/dist/apis/dashboard/DashboardApi.d.ts +3 -3
- package/dist/apis/dashboard/DashboardApi.mjs +3 -3
- package/dist/apis/dashboard/DashboardApi.mjs.map +1 -1
- package/dist/apis/dashboard/types.d.ts +11 -4
- package/dist/apis/dashboard/types.d.ts.map +1 -1
- package/dist/apis/departments/DepartmentsApi.cjs +5 -5
- package/dist/apis/departments/DepartmentsApi.cjs.map +1 -1
- package/dist/apis/departments/DepartmentsApi.d.ts +5 -5
- package/dist/apis/departments/DepartmentsApi.mjs +5 -5
- package/dist/apis/departments/DepartmentsApi.mjs.map +1 -1
- package/dist/apis/departments/types.d.ts +6 -0
- package/dist/apis/departments/types.d.ts.map +1 -1
- package/dist/apis/distribution/DistributionApi.cjs +3 -4
- package/dist/apis/distribution/DistributionApi.cjs.map +1 -1
- package/dist/apis/distribution/DistributionApi.d.ts +3 -4
- package/dist/apis/distribution/DistributionApi.d.ts.map +1 -1
- package/dist/apis/distribution/DistributionApi.mjs +3 -4
- package/dist/apis/distribution/DistributionApi.mjs.map +1 -1
- package/dist/apis/kanbanCards/KanbanCardsApi.cjs +32 -0
- package/dist/apis/kanbanCards/KanbanCardsApi.cjs.map +1 -1
- package/dist/apis/kanbanCards/KanbanCardsApi.d.ts +27 -1
- package/dist/apis/kanbanCards/KanbanCardsApi.d.ts.map +1 -1
- package/dist/apis/kanbanCards/KanbanCardsApi.mjs +32 -0
- package/dist/apis/kanbanCards/KanbanCardsApi.mjs.map +1 -1
- package/dist/apis/kanbanCards/types.d.ts +1 -1
- package/dist/apis/kanbanCards/types.d.ts.map +1 -1
- package/dist/apis/kanbanColumns/KanbanColumnsApi.cjs +19 -0
- package/dist/apis/kanbanColumns/KanbanColumnsApi.cjs.map +1 -1
- package/dist/apis/kanbanColumns/KanbanColumnsApi.d.ts +16 -1
- package/dist/apis/kanbanColumns/KanbanColumnsApi.d.ts.map +1 -1
- package/dist/apis/kanbanColumns/KanbanColumnsApi.mjs +19 -0
- package/dist/apis/kanbanColumns/KanbanColumnsApi.mjs.map +1 -1
- package/dist/apis/kanbanColumns/types.d.ts +4 -0
- package/dist/apis/kanbanColumns/types.d.ts.map +1 -1
- package/dist/apis/messages/MessagesApi.cjs +13 -4
- package/dist/apis/messages/MessagesApi.cjs.map +1 -1
- package/dist/apis/messages/MessagesApi.d.ts +13 -4
- package/dist/apis/messages/MessagesApi.d.ts.map +1 -1
- package/dist/apis/messages/MessagesApi.mjs +13 -4
- package/dist/apis/messages/MessagesApi.mjs.map +1 -1
- package/dist/apis/messages/types.d.ts +30 -0
- package/dist/apis/messages/types.d.ts.map +1 -1
- package/dist/apis/pipeline/types.d.ts +7 -8
- package/dist/apis/pipeline/types.d.ts.map +1 -1
- package/dist/apis/quickReplies/types.d.ts +4 -1
- package/dist/apis/quickReplies/types.d.ts.map +1 -1
- package/dist/apis/schedule/types.d.ts +41 -10
- package/dist/apis/schedule/types.d.ts.map +1 -1
- package/dist/apis/terms/TermsApi.cjs +2 -2
- package/dist/apis/terms/TermsApi.cjs.map +1 -1
- package/dist/apis/terms/TermsApi.d.ts +2 -2
- package/dist/apis/terms/TermsApi.mjs +2 -2
- package/dist/apis/terms/TermsApi.mjs.map +1 -1
- package/dist/apis/tickets/TicketsApi.cjs +18 -8
- package/dist/apis/tickets/TicketsApi.cjs.map +1 -1
- package/dist/apis/tickets/TicketsApi.d.ts +15 -8
- package/dist/apis/tickets/TicketsApi.d.ts.map +1 -1
- package/dist/apis/tickets/TicketsApi.mjs +18 -8
- package/dist/apis/tickets/TicketsApi.mjs.map +1 -1
- package/dist/apis/tickets/types.d.ts +4 -1
- package/dist/apis/tickets/types.d.ts.map +1 -1
- package/dist/apis/timetable/TimetableApi.cjs +6 -0
- package/dist/apis/timetable/TimetableApi.cjs.map +1 -1
- package/dist/apis/timetable/TimetableApi.d.ts +6 -0
- package/dist/apis/timetable/TimetableApi.d.ts.map +1 -1
- package/dist/apis/timetable/TimetableApi.mjs +6 -0
- package/dist/apis/timetable/TimetableApi.mjs.map +1 -1
- package/dist/apis/timetable/types.d.ts +8 -0
- package/dist/apis/timetable/types.d.ts.map +1 -1
- package/dist/apis/users/UsersApi.cjs +11 -3
- package/dist/apis/users/UsersApi.cjs.map +1 -1
- package/dist/apis/users/UsersApi.d.ts +10 -4
- package/dist/apis/users/UsersApi.d.ts.map +1 -1
- package/dist/apis/users/UsersApi.mjs +11 -3
- package/dist/apis/users/UsersApi.mjs.map +1 -1
- package/dist/apis/users/types.d.ts +26 -0
- package/dist/apis/users/types.d.ts.map +1 -1
- package/dist/apis/webhooks/types.d.ts +51 -6
- package/dist/apis/webhooks/types.d.ts.map +1 -1
- package/dist/core/errors.cjs +4 -1
- package/dist/core/errors.cjs.map +1 -1
- package/dist/core/errors.d.ts +4 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.mjs +4 -1
- package/dist/core/errors.mjs.map +1 -1
- package/package.json +1 -1
- package/src/apis/absence/AbsenceApi.ts +2 -2
- package/src/apis/absence/types.ts +1 -1
- package/src/apis/answers/AnswersApi.test.ts +32 -0
- package/src/apis/answers/AnswersApi.ts +38 -2
- package/src/apis/answers/types.ts +1 -2
- package/src/apis/bots/BotsApi.test.ts +16 -0
- package/src/apis/bots/BotsApi.ts +25 -7
- package/src/apis/bots/types.ts +12 -2
- package/src/apis/campaigns/CampaignsApi.test.ts +48 -0
- package/src/apis/campaigns/CampaignsApi.ts +31 -2
- package/src/apis/campaigns/types.ts +101 -3
- package/src/apis/cards/types.ts +5 -5
- package/src/apis/categories/CategoriesApi.ts +1 -2
- package/src/apis/clientFeedback/ClientFeedbackApi.ts +2 -3
- package/src/apis/contacts/ContactsApi.test.ts +36 -2
- package/src/apis/contacts/ContactsApi.ts +41 -3
- package/src/apis/contacts/types.ts +28 -0
- package/src/apis/creditMovements/CreditMovementsApi.test.ts +1 -1
- package/src/apis/customFields/types.ts +4 -4
- package/src/apis/dashboard/DashboardApi.test-d.ts +9 -0
- package/src/apis/dashboard/DashboardApi.ts +3 -3
- package/src/apis/dashboard/types.ts +11 -4
- package/src/apis/departments/DepartmentsApi.ts +5 -5
- package/src/apis/departments/types.ts +13 -0
- package/src/apis/distribution/DistributionApi.ts +3 -4
- package/src/apis/kanbanCards/KanbanCardsApi.test.ts +21 -0
- package/src/apis/kanbanCards/KanbanCardsApi.ts +47 -1
- package/src/apis/kanbanCards/types.ts +1 -1
- package/src/apis/kanbanColumns/KanbanColumnsApi.test.ts +26 -0
- package/src/apis/kanbanColumns/KanbanColumnsApi.ts +31 -0
- package/src/apis/kanbanColumns/types.ts +5 -0
- package/src/apis/messages/MessagesApi.ts +13 -4
- package/src/apis/messages/types.ts +31 -0
- package/src/apis/payloads.test-d.ts +217 -4
- package/src/apis/pipeline/types.ts +7 -8
- package/src/apis/queryFormat.test.ts +13 -0
- package/src/apis/quickReplies/types.ts +4 -1
- package/src/apis/schedule/types.ts +42 -10
- package/src/apis/terms/TermsApi.ts +2 -2
- package/src/apis/tickets/TicketsApi.ts +28 -8
- package/src/apis/tickets/types.ts +4 -1
- package/src/apis/timetable/TimetableApi.ts +6 -0
- package/src/apis/timetable/types.ts +9 -0
- package/src/apis/users/UsersApi.test.ts +17 -0
- package/src/apis/users/UsersApi.ts +21 -3
- package/src/apis/users/types.ts +28 -0
- package/src/apis/webhooks/types.ts +53 -6
- package/src/core/errors.ts +4 -1
|
@@ -1,13 +1,43 @@
|
|
|
1
1
|
import type { Service } from '../services/types';
|
|
2
|
+
/**
|
|
3
|
+
* `by_service` and `whatsapp_low_level` only get the events of the connections in `services`.
|
|
4
|
+
* Events not tied to a connection (users, departments, tags...) reach `by_service` too. A webhook
|
|
5
|
+
* with `services`, whatever its type, only gets the events of those connections (events not tied
|
|
6
|
+
* to a connection reach it too); a `general` webhook is meant to have none.
|
|
7
|
+
*/
|
|
8
|
+
export type WebhookType = 'general' | 'by_service' | 'whatsapp_low_level';
|
|
9
|
+
/**
|
|
10
|
+
* The categories of message the `message.created` event delivers. Only `false` leaves a category
|
|
11
|
+
* out (an absent key keeps it); `null`, or no filter, delivers every message. The other events are
|
|
12
|
+
* not filtered. A key outside these answers 400.
|
|
13
|
+
*/
|
|
14
|
+
export type WebhookMessageFilters = {
|
|
15
|
+
/** Messages of the contact (not their reactions). */
|
|
16
|
+
received?: boolean;
|
|
17
|
+
/** Messages sent through the connection, except the bot's (and the reactions). */
|
|
18
|
+
sent?: boolean;
|
|
19
|
+
/** Messages sent by the bot. */
|
|
20
|
+
bot?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Internal comments, ticket events, reactions, summaries, schedules, AI evaluations, the notices
|
|
23
|
+
* of the message block rule and the message types the platform does not know.
|
|
24
|
+
*/
|
|
25
|
+
system?: boolean;
|
|
26
|
+
};
|
|
2
27
|
export type Webhook = {
|
|
3
28
|
id: string;
|
|
4
29
|
name: string;
|
|
5
30
|
url: string;
|
|
6
|
-
type
|
|
31
|
+
/** A webhook created before the type was validated may hold another value. */
|
|
32
|
+
type: WebhookType;
|
|
7
33
|
active: boolean;
|
|
8
34
|
events: string[];
|
|
35
|
+
/** `null` when `events` has no `message.created` (the backend clears it on every write). */
|
|
36
|
+
messageFilters?: WebhookMessageFilters | null;
|
|
9
37
|
errorAt: string | null;
|
|
10
38
|
errorNotifiedAt: string | null;
|
|
39
|
+
/** The average delivery latency, in ms (shown in the list of webhooks); `null` until measured. */
|
|
40
|
+
avgLatencyMs?: number | null;
|
|
11
41
|
userId: string;
|
|
12
42
|
accountId: string;
|
|
13
43
|
createdAt: string;
|
|
@@ -19,15 +49,30 @@ export type WebhookRelationships = 'services';
|
|
|
19
49
|
export type CreateWebhookPayload = {
|
|
20
50
|
name: string;
|
|
21
51
|
url: string;
|
|
22
|
-
type:
|
|
52
|
+
type: WebhookType;
|
|
23
53
|
events: string[];
|
|
24
54
|
active?: boolean;
|
|
55
|
+
/** The connections of a `by_service` or `whatsapp_low_level` webhook (none for `general`). */
|
|
56
|
+
services?: string[];
|
|
57
|
+
/** Only kept with the `message.created` event. */
|
|
58
|
+
messageFilters?: WebhookMessageFilters | null;
|
|
25
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* `updateById` sends a `PUT`: `name`, `url`, `type` and `events` are required (400 without them),
|
|
62
|
+
* as in the create.
|
|
63
|
+
*/
|
|
26
64
|
export type UpdateWebhookPayload = {
|
|
27
|
-
name
|
|
28
|
-
url
|
|
29
|
-
type
|
|
30
|
-
events
|
|
65
|
+
name: string;
|
|
66
|
+
url: string;
|
|
67
|
+
type: WebhookType;
|
|
68
|
+
events: string[];
|
|
31
69
|
active?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Replaces the connections of the webhook; omitted keeps them. Send `[]` when the type becomes
|
|
72
|
+
* `general`: the connections kept also filter a `general` webhook.
|
|
73
|
+
*/
|
|
74
|
+
services?: string[];
|
|
75
|
+
/** Only kept with the `message.created` event. */
|
|
76
|
+
messageFilters?: WebhookMessageFilters | null;
|
|
32
77
|
};
|
|
33
78
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/apis/webhooks/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAEhD,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/apis/webhooks/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAEhD;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,oBAAoB,CAAA;AAEzE;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,qDAAqD;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,kFAAkF;IAClF,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,gCAAgC;IAChC,GAAG,CAAC,EAAE,OAAO,CAAA;IACb;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,8EAA8E;IAC9E,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,4FAA4F;IAC5F,cAAc,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAA;IAC7C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,kGAAkG;IAClG,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IAExB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAA;AAE7C,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,kDAAkD;IAClD,cAAc,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAA;CAC9C,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,kDAAkD;IAClD,cAAc,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAA;CAC9C,CAAA"}
|
package/dist/core/errors.cjs
CHANGED
|
@@ -107,7 +107,10 @@ var AuthenticationError = class extends ApiError {
|
|
|
107
107
|
var PaymentRequiredError = class extends ApiError {
|
|
108
108
|
name = "PaymentRequiredError";
|
|
109
109
|
};
|
|
110
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* 403 — the user lacks the permission for this action, or the action is refused (e.g. out of
|
|
112
|
+
* office hours, see `OutOfOfficeHoursErrorExtra`).
|
|
113
|
+
*/
|
|
111
114
|
var PermissionDeniedError = class extends ApiError {
|
|
112
115
|
name = "PermissionDeniedError";
|
|
113
116
|
};
|
package/dist/core/errors.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.cjs","names":[],"sources":["../../src/core/errors.ts"],"sourcesContent":["/**\n * A field-level error from the backend `validate` middleware — one entry per failed rule.\n */\nexport interface FieldError {\n /** Where the field was read from: `body`, `params`, `query`, `headers` or `cookies`. */\n location: string\n /** The field name, e.g. `name`. */\n field: string\n /** `<location>.<field>`, e.g. `body.name`. */\n path: string\n message: string\n /** The failed rule, e.g. `required` or `hasLengthLesserThanOrEqual`. */\n code: string\n}\n\n/**\n * The backend error name sent as `error` in the body (`ApiError.errorClass`). Known names are listed\n * for autocomplete; any other string is still accepted. `HttpError` is the generic one: most 400s\n * and every unexpected 500.\n */\nexport type ErrorClassName =\n | 'HttpError'\n | 'ValidationError'\n | 'BadRequestHttpError'\n | 'UnauthorizedHttpError'\n | 'PaymentRequired'\n | 'ForbiddenHttpError'\n | 'NotFoundHttpError'\n | 'TooManyRequestsHttpError'\n | (string & {})\n\nexport interface ApiErrorOptions {\n /** Response headers. */\n headers?: Headers\n /** The parsed JSON error body, or the raw text when it is not JSON. */\n body?: unknown\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * Flattens the backend `errors` object — `{ <location>: { <field>: { messages: string[], types: string[] } } }`\n * — into one {@link FieldError} per message. Returns `null` when there are no field errors.\n */\nexport function parseFieldErrors(errors: unknown): FieldError[] | null {\n if (!isRecord(errors)) {\n return null\n }\n\n const parsed: FieldError[] = []\n for (const [location, fields] of Object.entries(errors)) {\n if (!isRecord(fields)) {\n continue\n }\n for (const [field, detail] of Object.entries(fields)) {\n if (!isRecord(detail)) {\n continue\n }\n const messages = Array.isArray(detail.messages) ? detail.messages.map(String) : []\n const types = Array.isArray(detail.types) ? detail.types.map(String) : []\n messages.forEach((message, index) => {\n parsed.push({\n location,\n field,\n path: `${location}.${field}`,\n message,\n code: types[index] ?? types[0] ?? '',\n })\n })\n }\n }\n\n return parsed.length ? parsed : null\n}\n\n/** Base class of every error thrown by the SDK. */\nexport class DigisacError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'DigisacError'\n }\n}\n\n/** The request never got an HTTP response (network failure, DNS, connection refused, ...). */\nexport class ApiConnectionError extends DigisacError {\n constructor(message = 'Connection error.', options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'ApiConnectionError'\n }\n}\n\n/**\n * The API answered with a non-2xx status. Thrown as the subclass matching the status\n * (`NotFoundError`, `BadRequestError`, ...) — or `ValidationError` for a 400 field validation\n * failure — so callers can narrow with `instanceof`.\n */\nexport class ApiError extends DigisacError {\n /** The backend error name (`error` in the body), e.g. `ValidationError` or `PaymentRequired`. */\n public readonly errorClass?: ErrorClassName | undefined\n public readonly status?: number | undefined\n /** Field errors of a 400 `ValidationError`; `null` when the response carries none. */\n public readonly validationErrors: FieldError[] | null\n public readonly headers?: Headers | undefined\n /** The parsed JSON error body (`{ error, message, status, errors?, extra? }`), or the raw text. */\n public readonly body?: unknown\n\n constructor(\n message: string,\n errorClass?: ErrorClassName,\n status?: number,\n validationErrors: FieldError[] | null = null,\n options: ApiErrorOptions = {},\n ) {\n super(message)\n this.name = 'ApiError'\n this.errorClass = errorClass\n this.status = status\n this.validationErrors = validationErrors\n this.headers = options.headers\n this.body = options.body\n }\n\n /**\n * Builds the `ApiError` subclass for a response and its (already read) body: `ValidationError`\n * for a 400 `ValidationError`, else the class of the status.\n */\n static fromResponse(\n status: number,\n statusText: string,\n headers: Headers,\n body: unknown,\n ): ApiError {\n let message = `HTTP Error ${status}: ${statusText}`\n let errorClass: string | undefined\n let validationErrors: FieldError[] | null = null\n\n if (isRecord(body)) {\n const apiMessage = typeof body.message === 'string' ? body.message : undefined\n errorClass = typeof body.error === 'string' ? body.error : undefined\n message = apiMessage || errorClass || message\n validationErrors = parseFieldErrors(body.errors)\n }\n\n const ErrorClass = errorClassFor(status, errorClass)\n return new ErrorClass(message, errorClass, status, validationErrors, { headers, body })\n }\n}\n\n/** 400 (see {@link ValidationError} for field validation failures). */\nexport class BadRequestError extends ApiError {\n override name = 'BadRequestError'\n}\n\n/**\n * 400 `ValidationError` — the request failed field validation. `validationErrors` is always an\n * array here (empty when the backend sent no field details).\n */\nexport class ValidationError extends BadRequestError {\n override name = 'ValidationError'\n declare public readonly validationErrors: FieldError[]\n\n constructor(\n message: string,\n errorClass?: ErrorClassName,\n status?: number,\n validationErrors: FieldError[] | null = null,\n options: ApiErrorOptions = {},\n ) {\n super(message, errorClass, status, validationErrors ?? [], options)\n }\n}\n\n/** 401 — missing or invalid access token. */\nexport class AuthenticationError extends ApiError {\n override name = 'AuthenticationError'\n}\n\n/** 402 — the account is out of credits or over a plan limit. */\nexport class PaymentRequiredError extends ApiError {\n override name = 'PaymentRequiredError'\n}\n\n/** 403 — the user lacks the permission for this action. */\nexport class PermissionDeniedError extends ApiError {\n override name = 'PermissionDeniedError'\n}\n\n/** 404 */\nexport class NotFoundError extends ApiError {\n override name = 'NotFoundError'\n}\n\n/** 409 */\nexport class ConflictError extends ApiError {\n override name = 'ConflictError'\n}\n\n/** 422 */\nexport class UnprocessableEntityError extends ApiError {\n override name = 'UnprocessableEntityError'\n}\n\n/** 429 */\nexport class RateLimitError extends ApiError {\n override name = 'RateLimitError'\n\n /**\n * Seconds to wait before retrying, or `null` when the response does not say.\n *\n * Reads the standard `Retry-After` header (delay seconds or HTTP date) first, then the\n * `X-RateLimit-Reset` header the Digisac backend sends (Unix time in seconds).\n */\n get retryAfter(): number | null {\n return (\n parseRetryAfter(this.headers?.get('Retry-After')) ??\n parseRateLimitReset(this.headers?.get('X-RateLimit-Reset'))\n )\n }\n}\n\nconst secondsUntil = (epochMs: number): number =>\n Math.max(0, Math.ceil((epochMs - Date.now()) / 1000))\n\n/** `Retry-After`: delay seconds or an HTTP date. */\nfunction parseRetryAfter(value: string | null | undefined): number | null {\n if (!value?.trim()) {\n return null\n }\n const seconds = Number(value)\n if (Number.isFinite(seconds)) {\n return Math.max(0, seconds)\n }\n const date = Date.parse(value)\n return Number.isNaN(date) ? null : secondsUntil(date)\n}\n\n/**\n * `X-RateLimit-Reset`: Unix time in seconds (what the Digisac backend sends). Small values are\n * read as delay seconds instead, the other convention in use for this header.\n */\nfunction parseRateLimitReset(value: string | null | undefined): number | null {\n if (!value?.trim()) {\n return null\n }\n const reset = Number(value)\n if (!Number.isFinite(reset)) {\n return null\n }\n // Anything before 2001-09-09 (1e9 s) can't be a reset time, so it is a delay.\n return reset < 1e9 ? Math.max(0, reset) : secondsUntil(reset * 1000)\n}\n\n/** 5xx */\nexport class InternalServerError extends ApiError {\n override name = 'InternalServerError'\n}\n\nfunction errorClassFor(status: number, errorClass: string | undefined): typeof ApiError {\n if (status === 400 && errorClass === 'ValidationError') {\n return ValidationError\n }\n\n switch (status) {\n case 400:\n return BadRequestError\n case 401:\n return AuthenticationError\n case 402:\n return PaymentRequiredError\n case 403:\n return PermissionDeniedError\n case 404:\n return NotFoundError\n case 409:\n return ConflictError\n case 422:\n return UnprocessableEntityError\n case 429:\n return RateLimitError\n default:\n return status >= 500 ? InternalServerError : ApiError\n }\n}\n"],"mappings":";;;AAsCA,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;;;;AAMtE,SAAgB,iBAAiB,QAAsC;AACrE,KAAI,CAAC,SAAS,OAAO,CACnB,QAAO;CAGT,MAAM,SAAuB,EAAE;AAC/B,MAAK,MAAM,CAAC,UAAU,WAAW,OAAO,QAAQ,OAAO,EAAE;AACvD,MAAI,CAAC,SAAS,OAAO,CACnB;AAEF,OAAK,MAAM,CAAC,OAAO,WAAW,OAAO,QAAQ,OAAO,EAAE;AACpD,OAAI,CAAC,SAAS,OAAO,CACnB;GAEF,MAAM,WAAW,MAAM,QAAQ,OAAO,SAAS,GAAG,OAAO,SAAS,IAAI,OAAO,GAAG,EAAE;GAClF,MAAM,QAAQ,MAAM,QAAQ,OAAO,MAAM,GAAG,OAAO,MAAM,IAAI,OAAO,GAAG,EAAE;AACzE,YAAS,SAAS,SAAS,UAAU;AACnC,WAAO,KAAK;KACV;KACA;KACA,MAAM,GAAG,SAAS,GAAG;KACrB;KACA,MAAM,MAAM,UAAU,MAAM,MAAM;KACnC,CAAC;KACF;;;AAIN,QAAO,OAAO,SAAS,SAAS;;;AAIlC,IAAa,eAAb,cAAkC,MAAM;CACtC,YAAY,SAAiB,SAA+B;AAC1D,QAAM,SAAS,QAAQ;AACvB,OAAK,OAAO;;;;AAKhB,IAAa,qBAAb,cAAwC,aAAa;CACnD,YAAY,UAAU,qBAAqB,SAA+B;AACxE,QAAM,SAAS,QAAQ;AACvB,OAAK,OAAO;;;;;;;;AAShB,IAAa,WAAb,cAA8B,aAAa;;CAEzC,AAAgB;CAChB,AAAgB;;CAEhB,AAAgB;CAChB,AAAgB;;CAEhB,AAAgB;CAEhB,YACE,SACA,YACA,QACA,mBAAwC,MACxC,UAA2B,EAAE,EAC7B;AACA,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,aAAa;AAClB,OAAK,SAAS;AACd,OAAK,mBAAmB;AACxB,OAAK,UAAU,QAAQ;AACvB,OAAK,OAAO,QAAQ;;;;;;CAOtB,OAAO,aACL,QACA,YACA,SACA,MACU;EACV,IAAI,UAAU,cAAc,OAAO,IAAI;EACvC,IAAI;EACJ,IAAI,mBAAwC;AAE5C,MAAI,SAAS,KAAK,EAAE;GAClB,MAAM,aAAa,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AACrE,gBAAa,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAC3D,aAAU,cAAc,cAAc;AACtC,sBAAmB,iBAAiB,KAAK,OAAO;;AAIlD,SAAO,KADY,cAAc,QAAQ,WAAW,EAC9B,SAAS,YAAY,QAAQ,kBAAkB;GAAE;GAAS;GAAM,CAAC;;;;AAK3F,IAAa,kBAAb,cAAqC,SAAS;CAC5C,AAAS,OAAO;;;;;;AAOlB,IAAa,kBAAb,cAAqC,gBAAgB;CACnD,AAAS,OAAO;CAGhB,YACE,SACA,YACA,QACA,mBAAwC,MACxC,UAA2B,EAAE,EAC7B;AACA,QAAM,SAAS,YAAY,QAAQ,oBAAoB,EAAE,EAAE,QAAQ;;;;AAKvE,IAAa,sBAAb,cAAyC,SAAS;CAChD,AAAS,OAAO;;;AAIlB,IAAa,uBAAb,cAA0C,SAAS;CACjD,AAAS,OAAO;;;AAIlB,IAAa,wBAAb,cAA2C,SAAS;CAClD,AAAS,OAAO;;;AAIlB,IAAa,gBAAb,cAAmC,SAAS;CAC1C,AAAS,OAAO;;;AAIlB,IAAa,gBAAb,cAAmC,SAAS;CAC1C,AAAS,OAAO;;;AAIlB,IAAa,2BAAb,cAA8C,SAAS;CACrD,AAAS,OAAO;;;AAIlB,IAAa,iBAAb,cAAoC,SAAS;CAC3C,AAAS,OAAO;;;;;;;CAQhB,IAAI,aAA4B;AAC9B,SACE,gBAAgB,KAAK,SAAS,IAAI,cAAc,CAAC,IACjD,oBAAoB,KAAK,SAAS,IAAI,oBAAoB,CAAC;;;AAKjE,MAAM,gBAAgB,YACpB,KAAK,IAAI,GAAG,KAAK,MAAM,UAAU,KAAK,KAAK,IAAI,IAAK,CAAC;;AAGvD,SAAS,gBAAgB,OAAiD;AACxE,KAAI,CAAC,OAAO,MAAM,CAChB,QAAO;CAET,MAAM,UAAU,OAAO,MAAM;AAC7B,KAAI,OAAO,SAAS,QAAQ,CAC1B,QAAO,KAAK,IAAI,GAAG,QAAQ;CAE7B,MAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,QAAO,OAAO,MAAM,KAAK,GAAG,OAAO,aAAa,KAAK;;;;;;AAOvD,SAAS,oBAAoB,OAAiD;AAC5E,KAAI,CAAC,OAAO,MAAM,CAChB,QAAO;CAET,MAAM,QAAQ,OAAO,MAAM;AAC3B,KAAI,CAAC,OAAO,SAAS,MAAM,CACzB,QAAO;AAGT,QAAO,QAAQ,MAAM,KAAK,IAAI,GAAG,MAAM,GAAG,aAAa,QAAQ,IAAK;;;AAItE,IAAa,sBAAb,cAAyC,SAAS;CAChD,AAAS,OAAO;;AAGlB,SAAS,cAAc,QAAgB,YAAiD;AACtF,KAAI,WAAW,OAAO,eAAe,kBACnC,QAAO;AAGT,SAAQ,QAAR;EACE,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,QACE,QAAO,UAAU,MAAM,sBAAsB"}
|
|
1
|
+
{"version":3,"file":"errors.cjs","names":[],"sources":["../../src/core/errors.ts"],"sourcesContent":["/**\n * A field-level error from the backend `validate` middleware — one entry per failed rule.\n */\nexport interface FieldError {\n /** Where the field was read from: `body`, `params`, `query`, `headers` or `cookies`. */\n location: string\n /** The field name, e.g. `name`. */\n field: string\n /** `<location>.<field>`, e.g. `body.name`. */\n path: string\n message: string\n /** The failed rule, e.g. `required` or `hasLengthLesserThanOrEqual`. */\n code: string\n}\n\n/**\n * The backend error name sent as `error` in the body (`ApiError.errorClass`). Known names are listed\n * for autocomplete; any other string is still accepted. `HttpError` is the generic one: most 400s\n * and every unexpected 500.\n */\nexport type ErrorClassName =\n | 'HttpError'\n | 'ValidationError'\n | 'BadRequestHttpError'\n | 'UnauthorizedHttpError'\n | 'PaymentRequired'\n | 'ForbiddenHttpError'\n | 'NotFoundHttpError'\n | 'TooManyRequestsHttpError'\n | (string & {})\n\nexport interface ApiErrorOptions {\n /** Response headers. */\n headers?: Headers\n /** The parsed JSON error body, or the raw text when it is not JSON. */\n body?: unknown\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * Flattens the backend `errors` object — `{ <location>: { <field>: { messages: string[], types: string[] } } }`\n * — into one {@link FieldError} per message. Returns `null` when there are no field errors.\n */\nexport function parseFieldErrors(errors: unknown): FieldError[] | null {\n if (!isRecord(errors)) {\n return null\n }\n\n const parsed: FieldError[] = []\n for (const [location, fields] of Object.entries(errors)) {\n if (!isRecord(fields)) {\n continue\n }\n for (const [field, detail] of Object.entries(fields)) {\n if (!isRecord(detail)) {\n continue\n }\n const messages = Array.isArray(detail.messages) ? detail.messages.map(String) : []\n const types = Array.isArray(detail.types) ? detail.types.map(String) : []\n messages.forEach((message, index) => {\n parsed.push({\n location,\n field,\n path: `${location}.${field}`,\n message,\n code: types[index] ?? types[0] ?? '',\n })\n })\n }\n }\n\n return parsed.length ? parsed : null\n}\n\n/** Base class of every error thrown by the SDK. */\nexport class DigisacError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'DigisacError'\n }\n}\n\n/** The request never got an HTTP response (network failure, DNS, connection refused, ...). */\nexport class ApiConnectionError extends DigisacError {\n constructor(message = 'Connection error.', options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'ApiConnectionError'\n }\n}\n\n/**\n * The API answered with a non-2xx status. Thrown as the subclass matching the status\n * (`NotFoundError`, `BadRequestError`, ...) — or `ValidationError` for a 400 field validation\n * failure — so callers can narrow with `instanceof`.\n */\nexport class ApiError extends DigisacError {\n /** The backend error name (`error` in the body), e.g. `ValidationError` or `PaymentRequired`. */\n public readonly errorClass?: ErrorClassName | undefined\n public readonly status?: number | undefined\n /** Field errors of a 400 `ValidationError`; `null` when the response carries none. */\n public readonly validationErrors: FieldError[] | null\n public readonly headers?: Headers | undefined\n /** The parsed JSON error body (`{ error, message, status, errors?, extra? }`), or the raw text. */\n public readonly body?: unknown\n\n constructor(\n message: string,\n errorClass?: ErrorClassName,\n status?: number,\n validationErrors: FieldError[] | null = null,\n options: ApiErrorOptions = {},\n ) {\n super(message)\n this.name = 'ApiError'\n this.errorClass = errorClass\n this.status = status\n this.validationErrors = validationErrors\n this.headers = options.headers\n this.body = options.body\n }\n\n /**\n * Builds the `ApiError` subclass for a response and its (already read) body: `ValidationError`\n * for a 400 `ValidationError`, else the class of the status.\n */\n static fromResponse(\n status: number,\n statusText: string,\n headers: Headers,\n body: unknown,\n ): ApiError {\n let message = `HTTP Error ${status}: ${statusText}`\n let errorClass: string | undefined\n let validationErrors: FieldError[] | null = null\n\n if (isRecord(body)) {\n const apiMessage = typeof body.message === 'string' ? body.message : undefined\n errorClass = typeof body.error === 'string' ? body.error : undefined\n message = apiMessage || errorClass || message\n validationErrors = parseFieldErrors(body.errors)\n }\n\n const ErrorClass = errorClassFor(status, errorClass)\n return new ErrorClass(message, errorClass, status, validationErrors, { headers, body })\n }\n}\n\n/** 400 (see {@link ValidationError} for field validation failures). */\nexport class BadRequestError extends ApiError {\n override name = 'BadRequestError'\n}\n\n/**\n * 400 `ValidationError` — the request failed field validation. `validationErrors` is always an\n * array here (empty when the backend sent no field details).\n */\nexport class ValidationError extends BadRequestError {\n override name = 'ValidationError'\n declare public readonly validationErrors: FieldError[]\n\n constructor(\n message: string,\n errorClass?: ErrorClassName,\n status?: number,\n validationErrors: FieldError[] | null = null,\n options: ApiErrorOptions = {},\n ) {\n super(message, errorClass, status, validationErrors ?? [], options)\n }\n}\n\n/** 401 — missing or invalid access token. */\nexport class AuthenticationError extends ApiError {\n override name = 'AuthenticationError'\n}\n\n/** 402 — the account is out of credits or over a plan limit. */\nexport class PaymentRequiredError extends ApiError {\n override name = 'PaymentRequiredError'\n}\n\n/**\n * 403 — the user lacks the permission for this action, or the action is refused (e.g. out of\n * office hours, see `OutOfOfficeHoursErrorExtra`).\n */\nexport class PermissionDeniedError extends ApiError {\n override name = 'PermissionDeniedError'\n}\n\n/** 404 */\nexport class NotFoundError extends ApiError {\n override name = 'NotFoundError'\n}\n\n/** 409 */\nexport class ConflictError extends ApiError {\n override name = 'ConflictError'\n}\n\n/** 422 */\nexport class UnprocessableEntityError extends ApiError {\n override name = 'UnprocessableEntityError'\n}\n\n/** 429 */\nexport class RateLimitError extends ApiError {\n override name = 'RateLimitError'\n\n /**\n * Seconds to wait before retrying, or `null` when the response does not say.\n *\n * Reads the standard `Retry-After` header (delay seconds or HTTP date) first, then the\n * `X-RateLimit-Reset` header the Digisac backend sends (Unix time in seconds).\n */\n get retryAfter(): number | null {\n return (\n parseRetryAfter(this.headers?.get('Retry-After')) ??\n parseRateLimitReset(this.headers?.get('X-RateLimit-Reset'))\n )\n }\n}\n\nconst secondsUntil = (epochMs: number): number =>\n Math.max(0, Math.ceil((epochMs - Date.now()) / 1000))\n\n/** `Retry-After`: delay seconds or an HTTP date. */\nfunction parseRetryAfter(value: string | null | undefined): number | null {\n if (!value?.trim()) {\n return null\n }\n const seconds = Number(value)\n if (Number.isFinite(seconds)) {\n return Math.max(0, seconds)\n }\n const date = Date.parse(value)\n return Number.isNaN(date) ? null : secondsUntil(date)\n}\n\n/**\n * `X-RateLimit-Reset`: Unix time in seconds (what the Digisac backend sends). Small values are\n * read as delay seconds instead, the other convention in use for this header.\n */\nfunction parseRateLimitReset(value: string | null | undefined): number | null {\n if (!value?.trim()) {\n return null\n }\n const reset = Number(value)\n if (!Number.isFinite(reset)) {\n return null\n }\n // Anything before 2001-09-09 (1e9 s) can't be a reset time, so it is a delay.\n return reset < 1e9 ? Math.max(0, reset) : secondsUntil(reset * 1000)\n}\n\n/** 5xx */\nexport class InternalServerError extends ApiError {\n override name = 'InternalServerError'\n}\n\nfunction errorClassFor(status: number, errorClass: string | undefined): typeof ApiError {\n if (status === 400 && errorClass === 'ValidationError') {\n return ValidationError\n }\n\n switch (status) {\n case 400:\n return BadRequestError\n case 401:\n return AuthenticationError\n case 402:\n return PaymentRequiredError\n case 403:\n return PermissionDeniedError\n case 404:\n return NotFoundError\n case 409:\n return ConflictError\n case 422:\n return UnprocessableEntityError\n case 429:\n return RateLimitError\n default:\n return status >= 500 ? InternalServerError : ApiError\n }\n}\n"],"mappings":"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"}
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -83,7 +83,10 @@ export declare class AuthenticationError extends ApiError {
|
|
|
83
83
|
export declare class PaymentRequiredError extends ApiError {
|
|
84
84
|
name: string;
|
|
85
85
|
}
|
|
86
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* 403 — the user lacks the permission for this action, or the action is refused (e.g. out of
|
|
88
|
+
* office hours, see `OutOfOfficeHoursErrorExtra`).
|
|
89
|
+
*/
|
|
87
90
|
export declare class PermissionDeniedError extends ApiError {
|
|
88
91
|
name: string;
|
|
89
92
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,wFAAwF;IACxF,QAAQ,EAAE,MAAM,CAAA;IAChB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,iBAAiB,GACjB,qBAAqB,GACrB,uBAAuB,GACvB,iBAAiB,GACjB,oBAAoB,GACpB,mBAAmB,GACnB,0BAA0B,GAC1B,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEjB,MAAM,WAAW,eAAe;IAC9B,wBAAwB;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,uEAAuE;IACvE,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAKD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,GAAG,UAAU,EAAE,GAAG,IAAI,CA6BrE;AAED,mDAAmD;AACnD,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D;AAED,8FAA8F;AAC9F,qBAAa,kBAAmB,SAAQ,YAAY;gBACtC,OAAO,SAAsB,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAIzE;AAED;;;;GAIG;AACH,qBAAa,QAAS,SAAQ,YAAY;IACxC,iGAAiG;IACjG,SAAgB,UAAU,CAAC,EAAE,cAAc,GAAG,SAAS,CAAA;IACvD,SAAgB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3C,sFAAsF;IACtF,SAAgB,gBAAgB,EAAE,UAAU,EAAE,GAAG,IAAI,CAAA;IACrD,SAAgB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7C,mGAAmG;IACnG,SAAgB,IAAI,CAAC,EAAE,OAAO,CAAA;gBAG5B,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,cAAc,EAC3B,MAAM,CAAC,EAAE,MAAM,EACf,gBAAgB,GAAE,UAAU,EAAE,GAAG,IAAW,EAC5C,OAAO,GAAE,eAAoB;IAW/B;;;OAGG;IACH,MAAM,CAAC,YAAY,CACjB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,OAAO,GACZ,QAAQ;CAeZ;AAED,uEAAuE;AACvE,qBAAa,eAAgB,SAAQ,QAAQ;IAClC,IAAI,SAAoB;CAClC;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,eAAe;IACzC,IAAI,SAAoB;IACjC,SAAwB,gBAAgB,EAAE,UAAU,EAAE,CAAA;gBAGpD,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,cAAc,EAC3B,MAAM,CAAC,EAAE,MAAM,EACf,gBAAgB,GAAE,UAAU,EAAE,GAAG,IAAW,EAC5C,OAAO,GAAE,eAAoB;CAIhC;AAED,6CAA6C;AAC7C,qBAAa,mBAAoB,SAAQ,QAAQ;IACtC,IAAI,SAAwB;CACtC;AAED,gEAAgE;AAChE,qBAAa,oBAAqB,SAAQ,QAAQ;IACvC,IAAI,SAAyB;CACvC;AAED
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/core/errors.mjs
CHANGED
|
@@ -105,7 +105,10 @@ var AuthenticationError = class extends ApiError {
|
|
|
105
105
|
var PaymentRequiredError = class extends ApiError {
|
|
106
106
|
name = "PaymentRequiredError";
|
|
107
107
|
};
|
|
108
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* 403 — the user lacks the permission for this action, or the action is refused (e.g. out of
|
|
110
|
+
* office hours, see `OutOfOfficeHoursErrorExtra`).
|
|
111
|
+
*/
|
|
109
112
|
var PermissionDeniedError = class extends ApiError {
|
|
110
113
|
name = "PermissionDeniedError";
|
|
111
114
|
};
|
package/dist/core/errors.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.mjs","names":[],"sources":["../../src/core/errors.ts"],"sourcesContent":["/**\n * A field-level error from the backend `validate` middleware — one entry per failed rule.\n */\nexport interface FieldError {\n /** Where the field was read from: `body`, `params`, `query`, `headers` or `cookies`. */\n location: string\n /** The field name, e.g. `name`. */\n field: string\n /** `<location>.<field>`, e.g. `body.name`. */\n path: string\n message: string\n /** The failed rule, e.g. `required` or `hasLengthLesserThanOrEqual`. */\n code: string\n}\n\n/**\n * The backend error name sent as `error` in the body (`ApiError.errorClass`). Known names are listed\n * for autocomplete; any other string is still accepted. `HttpError` is the generic one: most 400s\n * and every unexpected 500.\n */\nexport type ErrorClassName =\n | 'HttpError'\n | 'ValidationError'\n | 'BadRequestHttpError'\n | 'UnauthorizedHttpError'\n | 'PaymentRequired'\n | 'ForbiddenHttpError'\n | 'NotFoundHttpError'\n | 'TooManyRequestsHttpError'\n | (string & {})\n\nexport interface ApiErrorOptions {\n /** Response headers. */\n headers?: Headers\n /** The parsed JSON error body, or the raw text when it is not JSON. */\n body?: unknown\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * Flattens the backend `errors` object — `{ <location>: { <field>: { messages: string[], types: string[] } } }`\n * — into one {@link FieldError} per message. Returns `null` when there are no field errors.\n */\nexport function parseFieldErrors(errors: unknown): FieldError[] | null {\n if (!isRecord(errors)) {\n return null\n }\n\n const parsed: FieldError[] = []\n for (const [location, fields] of Object.entries(errors)) {\n if (!isRecord(fields)) {\n continue\n }\n for (const [field, detail] of Object.entries(fields)) {\n if (!isRecord(detail)) {\n continue\n }\n const messages = Array.isArray(detail.messages) ? detail.messages.map(String) : []\n const types = Array.isArray(detail.types) ? detail.types.map(String) : []\n messages.forEach((message, index) => {\n parsed.push({\n location,\n field,\n path: `${location}.${field}`,\n message,\n code: types[index] ?? types[0] ?? '',\n })\n })\n }\n }\n\n return parsed.length ? parsed : null\n}\n\n/** Base class of every error thrown by the SDK. */\nexport class DigisacError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'DigisacError'\n }\n}\n\n/** The request never got an HTTP response (network failure, DNS, connection refused, ...). */\nexport class ApiConnectionError extends DigisacError {\n constructor(message = 'Connection error.', options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'ApiConnectionError'\n }\n}\n\n/**\n * The API answered with a non-2xx status. Thrown as the subclass matching the status\n * (`NotFoundError`, `BadRequestError`, ...) — or `ValidationError` for a 400 field validation\n * failure — so callers can narrow with `instanceof`.\n */\nexport class ApiError extends DigisacError {\n /** The backend error name (`error` in the body), e.g. `ValidationError` or `PaymentRequired`. */\n public readonly errorClass?: ErrorClassName | undefined\n public readonly status?: number | undefined\n /** Field errors of a 400 `ValidationError`; `null` when the response carries none. */\n public readonly validationErrors: FieldError[] | null\n public readonly headers?: Headers | undefined\n /** The parsed JSON error body (`{ error, message, status, errors?, extra? }`), or the raw text. */\n public readonly body?: unknown\n\n constructor(\n message: string,\n errorClass?: ErrorClassName,\n status?: number,\n validationErrors: FieldError[] | null = null,\n options: ApiErrorOptions = {},\n ) {\n super(message)\n this.name = 'ApiError'\n this.errorClass = errorClass\n this.status = status\n this.validationErrors = validationErrors\n this.headers = options.headers\n this.body = options.body\n }\n\n /**\n * Builds the `ApiError` subclass for a response and its (already read) body: `ValidationError`\n * for a 400 `ValidationError`, else the class of the status.\n */\n static fromResponse(\n status: number,\n statusText: string,\n headers: Headers,\n body: unknown,\n ): ApiError {\n let message = `HTTP Error ${status}: ${statusText}`\n let errorClass: string | undefined\n let validationErrors: FieldError[] | null = null\n\n if (isRecord(body)) {\n const apiMessage = typeof body.message === 'string' ? body.message : undefined\n errorClass = typeof body.error === 'string' ? body.error : undefined\n message = apiMessage || errorClass || message\n validationErrors = parseFieldErrors(body.errors)\n }\n\n const ErrorClass = errorClassFor(status, errorClass)\n return new ErrorClass(message, errorClass, status, validationErrors, { headers, body })\n }\n}\n\n/** 400 (see {@link ValidationError} for field validation failures). */\nexport class BadRequestError extends ApiError {\n override name = 'BadRequestError'\n}\n\n/**\n * 400 `ValidationError` — the request failed field validation. `validationErrors` is always an\n * array here (empty when the backend sent no field details).\n */\nexport class ValidationError extends BadRequestError {\n override name = 'ValidationError'\n declare public readonly validationErrors: FieldError[]\n\n constructor(\n message: string,\n errorClass?: ErrorClassName,\n status?: number,\n validationErrors: FieldError[] | null = null,\n options: ApiErrorOptions = {},\n ) {\n super(message, errorClass, status, validationErrors ?? [], options)\n }\n}\n\n/** 401 — missing or invalid access token. */\nexport class AuthenticationError extends ApiError {\n override name = 'AuthenticationError'\n}\n\n/** 402 — the account is out of credits or over a plan limit. */\nexport class PaymentRequiredError extends ApiError {\n override name = 'PaymentRequiredError'\n}\n\n/** 403 — the user lacks the permission for this action. */\nexport class PermissionDeniedError extends ApiError {\n override name = 'PermissionDeniedError'\n}\n\n/** 404 */\nexport class NotFoundError extends ApiError {\n override name = 'NotFoundError'\n}\n\n/** 409 */\nexport class ConflictError extends ApiError {\n override name = 'ConflictError'\n}\n\n/** 422 */\nexport class UnprocessableEntityError extends ApiError {\n override name = 'UnprocessableEntityError'\n}\n\n/** 429 */\nexport class RateLimitError extends ApiError {\n override name = 'RateLimitError'\n\n /**\n * Seconds to wait before retrying, or `null` when the response does not say.\n *\n * Reads the standard `Retry-After` header (delay seconds or HTTP date) first, then the\n * `X-RateLimit-Reset` header the Digisac backend sends (Unix time in seconds).\n */\n get retryAfter(): number | null {\n return (\n parseRetryAfter(this.headers?.get('Retry-After')) ??\n parseRateLimitReset(this.headers?.get('X-RateLimit-Reset'))\n )\n }\n}\n\nconst secondsUntil = (epochMs: number): number =>\n Math.max(0, Math.ceil((epochMs - Date.now()) / 1000))\n\n/** `Retry-After`: delay seconds or an HTTP date. */\nfunction parseRetryAfter(value: string | null | undefined): number | null {\n if (!value?.trim()) {\n return null\n }\n const seconds = Number(value)\n if (Number.isFinite(seconds)) {\n return Math.max(0, seconds)\n }\n const date = Date.parse(value)\n return Number.isNaN(date) ? null : secondsUntil(date)\n}\n\n/**\n * `X-RateLimit-Reset`: Unix time in seconds (what the Digisac backend sends). Small values are\n * read as delay seconds instead, the other convention in use for this header.\n */\nfunction parseRateLimitReset(value: string | null | undefined): number | null {\n if (!value?.trim()) {\n return null\n }\n const reset = Number(value)\n if (!Number.isFinite(reset)) {\n return null\n }\n // Anything before 2001-09-09 (1e9 s) can't be a reset time, so it is a delay.\n return reset < 1e9 ? Math.max(0, reset) : secondsUntil(reset * 1000)\n}\n\n/** 5xx */\nexport class InternalServerError extends ApiError {\n override name = 'InternalServerError'\n}\n\nfunction errorClassFor(status: number, errorClass: string | undefined): typeof ApiError {\n if (status === 400 && errorClass === 'ValidationError') {\n return ValidationError\n }\n\n switch (status) {\n case 400:\n return BadRequestError\n case 401:\n return AuthenticationError\n case 402:\n return PaymentRequiredError\n case 403:\n return PermissionDeniedError\n case 404:\n return NotFoundError\n case 409:\n return ConflictError\n case 422:\n return UnprocessableEntityError\n case 429:\n return RateLimitError\n default:\n return status >= 500 ? InternalServerError : ApiError\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../../src/core/errors.ts"],"sourcesContent":["/**\n * A field-level error from the backend `validate` middleware — one entry per failed rule.\n */\nexport interface FieldError {\n /** Where the field was read from: `body`, `params`, `query`, `headers` or `cookies`. */\n location: string\n /** The field name, e.g. `name`. */\n field: string\n /** `<location>.<field>`, e.g. `body.name`. */\n path: string\n message: string\n /** The failed rule, e.g. `required` or `hasLengthLesserThanOrEqual`. */\n code: string\n}\n\n/**\n * The backend error name sent as `error` in the body (`ApiError.errorClass`). Known names are listed\n * for autocomplete; any other string is still accepted. `HttpError` is the generic one: most 400s\n * and every unexpected 500.\n */\nexport type ErrorClassName =\n | 'HttpError'\n | 'ValidationError'\n | 'BadRequestHttpError'\n | 'UnauthorizedHttpError'\n | 'PaymentRequired'\n | 'ForbiddenHttpError'\n | 'NotFoundHttpError'\n | 'TooManyRequestsHttpError'\n | (string & {})\n\nexport interface ApiErrorOptions {\n /** Response headers. */\n headers?: Headers\n /** The parsed JSON error body, or the raw text when it is not JSON. */\n body?: unknown\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * Flattens the backend `errors` object — `{ <location>: { <field>: { messages: string[], types: string[] } } }`\n * — into one {@link FieldError} per message. Returns `null` when there are no field errors.\n */\nexport function parseFieldErrors(errors: unknown): FieldError[] | null {\n if (!isRecord(errors)) {\n return null\n }\n\n const parsed: FieldError[] = []\n for (const [location, fields] of Object.entries(errors)) {\n if (!isRecord(fields)) {\n continue\n }\n for (const [field, detail] of Object.entries(fields)) {\n if (!isRecord(detail)) {\n continue\n }\n const messages = Array.isArray(detail.messages) ? detail.messages.map(String) : []\n const types = Array.isArray(detail.types) ? detail.types.map(String) : []\n messages.forEach((message, index) => {\n parsed.push({\n location,\n field,\n path: `${location}.${field}`,\n message,\n code: types[index] ?? types[0] ?? '',\n })\n })\n }\n }\n\n return parsed.length ? parsed : null\n}\n\n/** Base class of every error thrown by the SDK. */\nexport class DigisacError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'DigisacError'\n }\n}\n\n/** The request never got an HTTP response (network failure, DNS, connection refused, ...). */\nexport class ApiConnectionError extends DigisacError {\n constructor(message = 'Connection error.', options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'ApiConnectionError'\n }\n}\n\n/**\n * The API answered with a non-2xx status. Thrown as the subclass matching the status\n * (`NotFoundError`, `BadRequestError`, ...) — or `ValidationError` for a 400 field validation\n * failure — so callers can narrow with `instanceof`.\n */\nexport class ApiError extends DigisacError {\n /** The backend error name (`error` in the body), e.g. `ValidationError` or `PaymentRequired`. */\n public readonly errorClass?: ErrorClassName | undefined\n public readonly status?: number | undefined\n /** Field errors of a 400 `ValidationError`; `null` when the response carries none. */\n public readonly validationErrors: FieldError[] | null\n public readonly headers?: Headers | undefined\n /** The parsed JSON error body (`{ error, message, status, errors?, extra? }`), or the raw text. */\n public readonly body?: unknown\n\n constructor(\n message: string,\n errorClass?: ErrorClassName,\n status?: number,\n validationErrors: FieldError[] | null = null,\n options: ApiErrorOptions = {},\n ) {\n super(message)\n this.name = 'ApiError'\n this.errorClass = errorClass\n this.status = status\n this.validationErrors = validationErrors\n this.headers = options.headers\n this.body = options.body\n }\n\n /**\n * Builds the `ApiError` subclass for a response and its (already read) body: `ValidationError`\n * for a 400 `ValidationError`, else the class of the status.\n */\n static fromResponse(\n status: number,\n statusText: string,\n headers: Headers,\n body: unknown,\n ): ApiError {\n let message = `HTTP Error ${status}: ${statusText}`\n let errorClass: string | undefined\n let validationErrors: FieldError[] | null = null\n\n if (isRecord(body)) {\n const apiMessage = typeof body.message === 'string' ? body.message : undefined\n errorClass = typeof body.error === 'string' ? body.error : undefined\n message = apiMessage || errorClass || message\n validationErrors = parseFieldErrors(body.errors)\n }\n\n const ErrorClass = errorClassFor(status, errorClass)\n return new ErrorClass(message, errorClass, status, validationErrors, { headers, body })\n }\n}\n\n/** 400 (see {@link ValidationError} for field validation failures). */\nexport class BadRequestError extends ApiError {\n override name = 'BadRequestError'\n}\n\n/**\n * 400 `ValidationError` — the request failed field validation. `validationErrors` is always an\n * array here (empty when the backend sent no field details).\n */\nexport class ValidationError extends BadRequestError {\n override name = 'ValidationError'\n declare public readonly validationErrors: FieldError[]\n\n constructor(\n message: string,\n errorClass?: ErrorClassName,\n status?: number,\n validationErrors: FieldError[] | null = null,\n options: ApiErrorOptions = {},\n ) {\n super(message, errorClass, status, validationErrors ?? [], options)\n }\n}\n\n/** 401 — missing or invalid access token. */\nexport class AuthenticationError extends ApiError {\n override name = 'AuthenticationError'\n}\n\n/** 402 — the account is out of credits or over a plan limit. */\nexport class PaymentRequiredError extends ApiError {\n override name = 'PaymentRequiredError'\n}\n\n/**\n * 403 — the user lacks the permission for this action, or the action is refused (e.g. out of\n * office hours, see `OutOfOfficeHoursErrorExtra`).\n */\nexport class PermissionDeniedError extends ApiError {\n override name = 'PermissionDeniedError'\n}\n\n/** 404 */\nexport class NotFoundError extends ApiError {\n override name = 'NotFoundError'\n}\n\n/** 409 */\nexport class ConflictError extends ApiError {\n override name = 'ConflictError'\n}\n\n/** 422 */\nexport class UnprocessableEntityError extends ApiError {\n override name = 'UnprocessableEntityError'\n}\n\n/** 429 */\nexport class RateLimitError extends ApiError {\n override name = 'RateLimitError'\n\n /**\n * Seconds to wait before retrying, or `null` when the response does not say.\n *\n * Reads the standard `Retry-After` header (delay seconds or HTTP date) first, then the\n * `X-RateLimit-Reset` header the Digisac backend sends (Unix time in seconds).\n */\n get retryAfter(): number | null {\n return (\n parseRetryAfter(this.headers?.get('Retry-After')) ??\n parseRateLimitReset(this.headers?.get('X-RateLimit-Reset'))\n )\n }\n}\n\nconst secondsUntil = (epochMs: number): number =>\n Math.max(0, Math.ceil((epochMs - Date.now()) / 1000))\n\n/** `Retry-After`: delay seconds or an HTTP date. */\nfunction parseRetryAfter(value: string | null | undefined): number | null {\n if (!value?.trim()) {\n return null\n }\n const seconds = Number(value)\n if (Number.isFinite(seconds)) {\n return Math.max(0, seconds)\n }\n const date = Date.parse(value)\n return Number.isNaN(date) ? null : secondsUntil(date)\n}\n\n/**\n * `X-RateLimit-Reset`: Unix time in seconds (what the Digisac backend sends). Small values are\n * read as delay seconds instead, the other convention in use for this header.\n */\nfunction parseRateLimitReset(value: string | null | undefined): number | null {\n if (!value?.trim()) {\n return null\n }\n const reset = Number(value)\n if (!Number.isFinite(reset)) {\n return null\n }\n // Anything before 2001-09-09 (1e9 s) can't be a reset time, so it is a delay.\n return reset < 1e9 ? Math.max(0, reset) : secondsUntil(reset * 1000)\n}\n\n/** 5xx */\nexport class InternalServerError extends ApiError {\n override name = 'InternalServerError'\n}\n\nfunction errorClassFor(status: number, errorClass: string | undefined): typeof ApiError {\n if (status === 400 && errorClass === 'ValidationError') {\n return ValidationError\n }\n\n switch (status) {\n case 400:\n return BadRequestError\n case 401:\n return AuthenticationError\n case 402:\n return PaymentRequiredError\n case 403:\n return PermissionDeniedError\n case 404:\n return NotFoundError\n case 409:\n return ConflictError\n case 422:\n return UnprocessableEntityError\n case 429:\n return RateLimitError\n default:\n return status >= 500 ? InternalServerError : ApiError\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -23,8 +23,8 @@ export class AbsenceApi extends BaseCrudApi<Absence, CreateAbsencePayload, Updat
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* The absence report of the account (`GET /absence`), always paginated, with its own query (
|
|
27
|
-
*
|
|
26
|
+
* The absence report of the account (`GET /absence`), always paginated, with its own query (see
|
|
27
|
+
* `AbsenceReportItem`). Prefer it to `getMany`, whose `where`/`include`/`paginate` the backend
|
|
28
28
|
* ignores. Requires both permissions, always: without them it answers 403
|
|
29
29
|
* `MISSING_ABSENCE_REPORT_PERMISSION`.
|
|
30
30
|
* @permissions overview.absence.view AND myAccount.absence
|
|
@@ -56,7 +56,7 @@ export type AbsenceReportQuery = {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* A row of the report: the stored absence with its user
|
|
59
|
+
* A row of the report: the stored absence with its `user`.
|
|
60
60
|
*/
|
|
61
61
|
export type AbsenceReportItem = Omit<Absence, 'deletedAt' | 'user'> & {
|
|
62
62
|
user?: Record<string, unknown>
|
|
@@ -64,4 +64,36 @@ describe('AnswersApi', () => {
|
|
|
64
64
|
expect(await api.deleteById('a1', headers)).toBe('OK')
|
|
65
65
|
expect(client.delete).toHaveBeenCalledWith('/answers/a1', headers)
|
|
66
66
|
})
|
|
67
|
+
|
|
68
|
+
it('exportTxtRaw resolves the whole Response of requestRaw', async () => {
|
|
69
|
+
const response = new Response('Protocolo: 1\n', { headers: { 'Content-Type': 'plain/text' } })
|
|
70
|
+
const requestRaw = vi.fn().mockResolvedValue(response)
|
|
71
|
+
client.requestRaw = requestRaw
|
|
72
|
+
expect(await api.exportTxtRaw(query, headers)).toBe(response)
|
|
73
|
+
expect(requestRaw).toHaveBeenCalledWith(
|
|
74
|
+
'GET',
|
|
75
|
+
`/answers/exportation-txt${encoded}`,
|
|
76
|
+
undefined,
|
|
77
|
+
headers,
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('exportCsvRaw calls requestRaw with /answers/exportation-csv', async () => {
|
|
82
|
+
const requestRaw = vi.fn().mockResolvedValue(new Response(''))
|
|
83
|
+
client.requestRaw = requestRaw
|
|
84
|
+
await api.exportCsvRaw(query, headers)
|
|
85
|
+
expect(requestRaw).toHaveBeenCalledWith(
|
|
86
|
+
'GET',
|
|
87
|
+
`/answers/exportation-csv${encoded}`,
|
|
88
|
+
undefined,
|
|
89
|
+
headers,
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it.each(['exportCsvRaw', 'exportTxtRaw'] as const)(
|
|
94
|
+
'%s refuses a client without requestRaw',
|
|
95
|
+
async (method) => {
|
|
96
|
+
await expect(api[method](query)).rejects.toThrow(`${method} needs a client with requestRaw`)
|
|
97
|
+
},
|
|
98
|
+
)
|
|
67
99
|
})
|
|
@@ -10,8 +10,7 @@ import type {
|
|
|
10
10
|
} from './types'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Answers of the evaluation questions. They are written by the bot flow (`SEND_QUESTION`)
|
|
14
|
-
* CRUD routes exist but validate nothing.
|
|
13
|
+
* Answers of the evaluation questions. They are written by the bot flow (`SEND_QUESTION`).
|
|
15
14
|
*
|
|
16
15
|
* Permissions: `evaluation.view` to read, `evaluation.create`/`update`/`destroy` to write.
|
|
17
16
|
*/
|
|
@@ -70,4 +69,41 @@ export class AnswersApi extends BaseCrudApi<
|
|
|
70
69
|
headers,
|
|
71
70
|
)
|
|
72
71
|
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The {@link exportCsv} export as the whole `Response`, to read its headers (`Content-Type`,
|
|
75
|
+
* `Content-Disposition`) as well as the body. Needs a client with `requestRaw`. Errors are thrown
|
|
76
|
+
* as in `exportCsv`.
|
|
77
|
+
* @permissions overview.evaluation.view
|
|
78
|
+
*/
|
|
79
|
+
exportCsvRaw(query?: AnswersReportQuery, headers?: Record<string, string>): Promise<Response> {
|
|
80
|
+
return this.exportResponse('exportation-csv', 'exportCsvRaw', query, headers)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The {@link exportTxt} export as the whole `Response`, to read its headers (`Content-Type`,
|
|
85
|
+
* `Content-Disposition`) as well as the body. The Content-Type is `plain/text` (not
|
|
86
|
+
* `text/plain`). Needs a client with `requestRaw`. Errors are thrown as in `exportTxt`.
|
|
87
|
+
* @permissions overview.evaluation.view
|
|
88
|
+
*/
|
|
89
|
+
exportTxtRaw(query?: AnswersReportQuery, headers?: Record<string, string>): Promise<Response> {
|
|
90
|
+
return this.exportResponse('exportation-txt', 'exportTxtRaw', query, headers)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private async exportResponse(
|
|
94
|
+
route: 'exportation-csv' | 'exportation-txt',
|
|
95
|
+
method: 'exportCsvRaw' | 'exportTxtRaw',
|
|
96
|
+
query?: AnswersReportQuery,
|
|
97
|
+
headers?: Record<string, string>,
|
|
98
|
+
): Promise<Response> {
|
|
99
|
+
if (!this.client.requestRaw) {
|
|
100
|
+
throw new Error(`${method} needs a client with requestRaw (the answer is read as a Response)`)
|
|
101
|
+
}
|
|
102
|
+
return this.client.requestRaw(
|
|
103
|
+
'GET',
|
|
104
|
+
`${this.basePath}/${route}${this.toQueryString(query)}`,
|
|
105
|
+
undefined,
|
|
106
|
+
headers,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
73
109
|
}
|
|
@@ -26,8 +26,7 @@ export type Answer = {
|
|
|
26
26
|
export type AnswerRelationships = 'question' | 'ticket'
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* The
|
|
30
|
-
* saved even for an open ticket.
|
|
29
|
+
* The answer of a question of the account to a ticket of the account (an open ticket included).
|
|
31
30
|
*/
|
|
32
31
|
export type CreateAnswerPayload = {
|
|
33
32
|
text: string
|
|
@@ -174,4 +174,20 @@ describe('BotsApi', () => {
|
|
|
174
174
|
expect(client.post).toHaveBeenCalledWith('/bots/suggest-agent-prompt', body, undefined)
|
|
175
175
|
})
|
|
176
176
|
})
|
|
177
|
+
|
|
178
|
+
describe('exportErrors', () => {
|
|
179
|
+
it('resolves the binary xlsx of requestRaw', async () => {
|
|
180
|
+
const requestRaw = vi.fn().mockResolvedValue(new Response(new ArrayBuffer(8)))
|
|
181
|
+
client.requestRaw = requestRaw
|
|
182
|
+
const result = await api.exportErrors('b1', headers)
|
|
183
|
+
expect(result.byteLength).toBe(8)
|
|
184
|
+
expect(requestRaw).toHaveBeenCalledWith('GET', '/bots/b1/errors/export', undefined, headers)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('refuses a client without requestRaw', async () => {
|
|
188
|
+
await expect(api.exportErrors('b1')).rejects.toThrow(
|
|
189
|
+
'exportErrors needs a client with requestRaw',
|
|
190
|
+
)
|
|
191
|
+
})
|
|
192
|
+
})
|
|
177
193
|
})
|
package/src/apis/bots/BotsApi.ts
CHANGED
|
@@ -21,9 +21,6 @@ import type {
|
|
|
21
21
|
* Bots. The list route always paginates (`paginate: false` is ignored), so this class does not
|
|
22
22
|
* extend `BaseCrudApi`. The contexts travel as a list (`BotContextInput` / `BotParsedContext`),
|
|
23
23
|
* not as the stored map that `BotVersionsApi` serves.
|
|
24
|
-
*
|
|
25
|
-
* `GET /bots/:id/errors/export` is not exposed: it answers a binary xlsx, and `ApiClient` only
|
|
26
|
-
* handles JSON/text bodies.
|
|
27
24
|
*/
|
|
28
25
|
export class BotsApi
|
|
29
26
|
extends BaseApi
|
|
@@ -107,10 +104,32 @@ export class BotsApi
|
|
|
107
104
|
)
|
|
108
105
|
}
|
|
109
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Exports the failed steps of the bot runs of the last 30 days as an xlsx workbook (binary).
|
|
109
|
+
* Needs a client with `requestRaw`. A period without errors answers 400 `No errors found to
|
|
110
|
+
* export`; an unknown bot answers 404.
|
|
111
|
+
* @permissions bots.view
|
|
112
|
+
*/
|
|
113
|
+
async exportErrors(id: string, headers?: Record<string, string>): Promise<ArrayBuffer> {
|
|
114
|
+
if (!this.client.requestRaw) {
|
|
115
|
+
throw new Error('exportErrors needs a client with requestRaw (the answer is a binary xlsx)')
|
|
116
|
+
}
|
|
117
|
+
const response = await this.client.requestRaw(
|
|
118
|
+
'GET',
|
|
119
|
+
`${this.basePath}/${id}/errors/export`,
|
|
120
|
+
undefined,
|
|
121
|
+
headers,
|
|
122
|
+
)
|
|
123
|
+
return response.arrayBuffer()
|
|
124
|
+
}
|
|
125
|
+
|
|
110
126
|
/**
|
|
111
127
|
* Fires the `API_SIGNAL` trigger of the bot for the contact, with an optional `flag` (read by
|
|
112
128
|
* the `api_signal_flag` condition). Asynchronous: always resolves with no content, and the
|
|
113
|
-
* worker only runs it when the contact has an open ticket.
|
|
129
|
+
* worker only runs it when the contact has an open ticket. The worker runs the flow of `botId`
|
|
130
|
+
* on the engine of the bot of the contact's connection (see `BotSettings.botCreatedVersion`; when
|
|
131
|
+
* the connection has no bot, the engine of the `botId` bot). `triggerSignal` does not reach the
|
|
132
|
+
* bot simulator (there the `API_SIGNAL` path is picked by hand, with no `flag`).
|
|
114
133
|
* @permissions bots.view
|
|
115
134
|
*/
|
|
116
135
|
triggerSignal(
|
|
@@ -148,9 +167,8 @@ export class BotsApi
|
|
|
148
167
|
|
|
149
168
|
/**
|
|
150
169
|
* Generates a prompt for an AI agent of the bot. COSTS one AI agent credit of the account and
|
|
151
|
-
* calls OpenAI.
|
|
152
|
-
*
|
|
153
|
-
* (`Agent limit reached`), answers 500.
|
|
170
|
+
* calls OpenAI. A missing field answers 400; an option outside the lists, or the AI agent limit
|
|
171
|
+
* of the account reached (`Agent limit reached`), answers 500.
|
|
154
172
|
*/
|
|
155
173
|
suggestAgentPrompt(
|
|
156
174
|
body: SuggestAgentPromptPayload,
|
package/src/apis/bots/types.ts
CHANGED
|
@@ -14,7 +14,11 @@ export type BotLimitations = {
|
|
|
14
14
|
export type BotSettings = {
|
|
15
15
|
/** If true, only the first matching rule per trigger fires; otherwise all matching rules fire. */
|
|
16
16
|
sequentialRuleExecution?: boolean
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Version of the bot builder used to create this bot, which also picks the engine that runs it:
|
|
19
|
+
* `v3` (the visual flow builder) runs on the flow engine; `v1`, `v2` or absent, on the legacy
|
|
20
|
+
* engine. The bot simulator always runs the flow engine.
|
|
21
|
+
*/
|
|
18
22
|
botCreatedVersion?: BotCreatedVersion
|
|
19
23
|
limitations?: BotLimitations
|
|
20
24
|
}
|
|
@@ -191,6 +195,11 @@ export type BotActionDataMap = {
|
|
|
191
195
|
/** Waits `waitSeconds` to group the contact's messages before answering. */
|
|
192
196
|
messageGrouping?: { enabled?: boolean; waitSeconds?: number }
|
|
193
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Runs on the flow engine (`botCreatedVersion: 'v3'`). `endpointUrl`, `httpMethod`, `retries`,
|
|
200
|
+
* `timeout` and `bodyType` are required to run it: without one of them the step does not run.
|
|
201
|
+
* The bot simulator does not run it.
|
|
202
|
+
*/
|
|
194
203
|
HTTP_REQUEST: {
|
|
195
204
|
requestName?: string
|
|
196
205
|
endpointUrl?: string
|
|
@@ -204,8 +213,9 @@ export type BotActionDataMap = {
|
|
|
204
213
|
requestBodyFormData?: { key: string; value: string }[]
|
|
205
214
|
requestBodyJson?: string
|
|
206
215
|
requestBodyXml?: string
|
|
216
|
+
/** Number of attempts (at least 1), not of retries after the first one. */
|
|
207
217
|
retries?: number
|
|
208
|
-
/** Timeout in
|
|
218
|
+
/** Timeout of each attempt, in SECONDS. */
|
|
209
219
|
timeout?: number
|
|
210
220
|
/** Response properties to extract into bot session variables. */
|
|
211
221
|
variables?: { key: string; value: string }[]
|
|
@@ -105,6 +105,54 @@ describe('CampaignsApi', () => {
|
|
|
105
105
|
})
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
+
// ── getOne ───────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
describe('getOne', () => {
|
|
111
|
+
it('reads the first page of one campaign, leaving out paginate and limit', async () => {
|
|
112
|
+
vi.mocked(client.get).mockResolvedValue(paginated)
|
|
113
|
+
const result = await api.getOne({
|
|
114
|
+
where: { title: 'Test Campaign' },
|
|
115
|
+
status: 'ready',
|
|
116
|
+
paginate: false,
|
|
117
|
+
limit: 5,
|
|
118
|
+
page: 3,
|
|
119
|
+
})
|
|
120
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
121
|
+
expect(url.startsWith('/campaigns?query=')).toBe(true)
|
|
122
|
+
expect(JSON.parse(decodeURIComponent(url.slice('/campaigns?query='.length)))).toEqual({
|
|
123
|
+
where: { title: 'Test Campaign' },
|
|
124
|
+
status: 'ready',
|
|
125
|
+
page: 1,
|
|
126
|
+
perPage: 1,
|
|
127
|
+
})
|
|
128
|
+
expect(result).toEqual(campaign)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('resolves null when no campaign matches', async () => {
|
|
132
|
+
vi.mocked(client.get).mockResolvedValue({ ...paginated, data: [], total: 0 })
|
|
133
|
+
expect(await api.getOne({ q: 'none' })).toBeNull()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('resolves null for an empty body', async () => {
|
|
137
|
+
vi.mocked(client.get).mockResolvedValue(undefined)
|
|
138
|
+
expect(await api.getOne()).toBeNull()
|
|
139
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
140
|
+
`/campaigns?query=${encodeURIComponent(JSON.stringify({ page: 1, perPage: 1 }))}`,
|
|
141
|
+
undefined,
|
|
142
|
+
)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('forwards custom headers', async () => {
|
|
146
|
+
vi.mocked(client.get).mockResolvedValue(paginated)
|
|
147
|
+
const headers = { 'X-Custom': 'value' }
|
|
148
|
+
await api.getOne(undefined, headers)
|
|
149
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
150
|
+
`/campaigns?query=${encodeURIComponent(JSON.stringify({ page: 1, perPage: 1 }))}`,
|
|
151
|
+
headers,
|
|
152
|
+
)
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
108
156
|
// ── getById ──────────────────────────────────────────────────────
|
|
109
157
|
|
|
110
158
|
describe('getById', () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import qs from 'qs'
|
|
2
|
-
import type { ApiClient } from '../../core'
|
|
2
|
+
import type { ApiClient, Paginated } from '../../core'
|
|
3
3
|
import { BaseCrudApi } from '../../core'
|
|
4
4
|
import type {
|
|
5
5
|
AutoPauseModePayload,
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
* {@link CampaignListQuery}); `getById` of an unknown id resolves `null`. `deleteById` answers the
|
|
26
26
|
* text `'OK'`. `getMany({ paginate: false })` answers 400 `FILTER_PARAM_INVALID` with the default
|
|
27
27
|
* `'json'` query format (the route only takes `paginate` as a string): use `withQueryFormat('qs')`.
|
|
28
|
+
* `getOne` reads a page of one campaign, so it works with every query format.
|
|
28
29
|
*/
|
|
29
30
|
export class CampaignsApi extends BaseCrudApi<
|
|
30
31
|
Campaign,
|
|
@@ -37,6 +38,28 @@ export class CampaignsApi extends BaseCrudApi<
|
|
|
37
38
|
super(client, '/campaigns')
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Returns the first campaign matching the query, or `null` when none match. Reads the first page
|
|
43
|
+
* with one campaign (`page: 1`, `perPage: 1`: the `page` and `perPage` of the query are
|
|
44
|
+
* replaced, and its `paginate` and `limit` are left out), so the campaign comes with the `stats`
|
|
45
|
+
* of `getMany`.
|
|
46
|
+
* @permissions campaigns.view
|
|
47
|
+
*/
|
|
48
|
+
override async getOne(
|
|
49
|
+
query?: CampaignListQuery,
|
|
50
|
+
headers?: Record<string, string>,
|
|
51
|
+
): Promise<Campaign | null> {
|
|
52
|
+
const pageQuery: CampaignListQuery = { ...query, page: 1, perPage: 1 }
|
|
53
|
+
delete pageQuery.paginate
|
|
54
|
+
delete pageQuery.limit
|
|
55
|
+
const page = await this.client.get<Paginated<Campaign> | undefined>(
|
|
56
|
+
`${this.basePath}${this.toQueryString(pageQuery)}`,
|
|
57
|
+
headers,
|
|
58
|
+
)
|
|
59
|
+
// An empty response body resolves to `undefined`.
|
|
60
|
+
return page?.data?.[0] ?? null
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
/**
|
|
41
64
|
* Starts sending the campaign: answers it as `processing` (an `import_error` campaign goes back
|
|
42
65
|
* to `importing_contacts`). It becomes `done` once every contact is fired.
|
|
@@ -205,7 +228,13 @@ export class CampaignsApi extends BaseCrudApi<
|
|
|
205
228
|
)
|
|
206
229
|
}
|
|
207
230
|
|
|
208
|
-
/**
|
|
231
|
+
/**
|
|
232
|
+
* Returns the HSM message usage and limit of the account of the current user (send its
|
|
233
|
+
* `accountId`).
|
|
234
|
+
* @deprecated Use `WhatsappBusinessTemplatesApi.getHsmLimit` (the same route, outside
|
|
235
|
+
* `/campaigns`).
|
|
236
|
+
* @permissions hsm.view
|
|
237
|
+
*/
|
|
209
238
|
getHsmLimit(accountId: string, headers?: Record<string, string>): Promise<HsmLimit> {
|
|
210
239
|
return this.client.get<HsmLimit>(`/whatsapp-business-templates/${accountId}/hsmLimit`, headers)
|
|
211
240
|
}
|