@catandbox/schrodinger-contracts 0.1.28 → 0.1.32
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 +117 -0
- package/dist/src/client.d.ts +118 -0
- package/dist/src/client.js +334 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @catandbox/schrodinger-contracts
|
|
2
|
+
|
|
3
|
+
TypeScript contracts and API client for [Schrodinger](https://github.com/yetone/schrodinger) — an AI-powered headless helpdesk built on Cloudflare Workers.
|
|
4
|
+
|
|
5
|
+
This package contains:
|
|
6
|
+
- **Generated TypeScript types** from the OpenAPI spec
|
|
7
|
+
- **`SchrodingerApiClient`** — low-level generated HTTP client
|
|
8
|
+
- **`SupportApiClient`** — high-level fetch-based client for the customer-facing portal API
|
|
9
|
+
- All shared interfaces used by the web adapter and any custom integrations
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @catandbox/schrodinger-contracts
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
### SupportApiClient
|
|
20
|
+
|
|
21
|
+
The primary client for interacting with the Schrodinger support portal API from a browser or edge runtime. No framework dependencies — works anywhere `fetch` is available.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { SupportApiClient } from "@catandbox/schrodinger-contracts";
|
|
25
|
+
|
|
26
|
+
const client = new SupportApiClient({
|
|
27
|
+
basePath: "/support/api", // path prefix where the proxy is mounted
|
|
28
|
+
// baseUrl: "https://...", // explicit base URL (optional)
|
|
29
|
+
// headers: { "X-My-Header": "value" }, // static headers
|
|
30
|
+
// getHeaders: async () => ({ ... }), // dynamic headers (e.g. auth tokens)
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// List tickets
|
|
34
|
+
const { items } = await client.listTickets({ status: "Active" });
|
|
35
|
+
|
|
36
|
+
// Get a single ticket with its messages
|
|
37
|
+
const { ticket, messages, events } = await client.getTicket(ticketId);
|
|
38
|
+
|
|
39
|
+
// Create a new ticket
|
|
40
|
+
const ticket = await client.createTicket({
|
|
41
|
+
title: "Something isn't working",
|
|
42
|
+
body: "Here are the details...",
|
|
43
|
+
categoryId: "cat_123", // optional
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Reply to a ticket
|
|
47
|
+
const message = await client.createReply(ticketId, { body: "Thank you!" });
|
|
48
|
+
|
|
49
|
+
// Close / reopen
|
|
50
|
+
await client.closeTicket(ticketId);
|
|
51
|
+
await client.reopenTicket(ticketId);
|
|
52
|
+
|
|
53
|
+
// Rate a ticket or a message
|
|
54
|
+
await client.rateTicket(ticketId, { stars: 5, comment: "Great support!" });
|
|
55
|
+
await client.rateMessage(messageId, { stars: 4 });
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### File uploads
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// 1. Initialise uploads
|
|
62
|
+
const { uploads } = await client.initUploads({
|
|
63
|
+
ticketId,
|
|
64
|
+
files: [{ filename: "screenshot.png", mime: "image/png", sizeBytes: 12345, sha256: "..." }],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 2. PUT each file to its pre-signed URL
|
|
68
|
+
await client.putUpload(uploads[0].putUrl, file, (loaded, total) => {
|
|
69
|
+
console.log(`${Math.round((loaded / total) * 100)}%`);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// 3. Confirm completion
|
|
73
|
+
await client.completeUploads({
|
|
74
|
+
ticketId,
|
|
75
|
+
uploads: [{ uploadId: uploads[0].uploadId, sizeBytes: file.size, sha256: "..." }],
|
|
76
|
+
messageId, // optional — attach to an existing message
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### SupportApiError
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { SupportApiClient, SupportApiError } from "@catandbox/schrodinger-contracts";
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await client.createTicket({ title: "...", body: "..." });
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err instanceof SupportApiError) {
|
|
89
|
+
console.error(err.status, err.code, err.message, err.requestId);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Types
|
|
95
|
+
|
|
96
|
+
All types are exported from the package root:
|
|
97
|
+
|
|
98
|
+
| Type | Description |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `SupportClientOptions` | Constructor options for `SupportApiClient` |
|
|
101
|
+
| `ClientHeadersProvider` | Async function returning dynamic request headers |
|
|
102
|
+
| `ListTicketsParams` | Parameters for `listTickets()` |
|
|
103
|
+
| `SupportCategory` | A support category |
|
|
104
|
+
| `TicketDetailData` | Return type of `getTicket()` — ticket + messages + events |
|
|
105
|
+
| `UploadInputFile` | File descriptor for `initUploads()` |
|
|
106
|
+
| `UploadInitResult` | Return type of `initUploads()` |
|
|
107
|
+
| `UploadCompleteInput` | Input for `completeUploads()` |
|
|
108
|
+
| `Ticket`, `Message`, `Alias` | Core domain types (generated from OpenAPI) |
|
|
109
|
+
| `TicketStatus` | `"Active" \| "InProgress" \| "AwaitingResponse" \| "Closed" \| "Archived"` |
|
|
110
|
+
|
|
111
|
+
## OpenAPI spec
|
|
112
|
+
|
|
113
|
+
The raw OpenAPI specification is included in the package at `openapi/schrodinger.openapi.yaml` and can be used to generate clients in other languages.
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Alias, Message, RatingRequest, Ticket, TicketStatus } from "../generated/types";
|
|
2
|
+
export interface ClientHeadersProvider {
|
|
3
|
+
(): Record<string, string> | Promise<Record<string, string>>;
|
|
4
|
+
}
|
|
5
|
+
export interface SupportClientOptions {
|
|
6
|
+
basePath?: string;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
fetchImpl?: typeof fetch;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
getHeaders?: ClientHeadersProvider;
|
|
11
|
+
}
|
|
12
|
+
export interface ListTicketsParams {
|
|
13
|
+
status?: TicketStatus;
|
|
14
|
+
}
|
|
15
|
+
export interface SupportCategory {
|
|
16
|
+
id: string;
|
|
17
|
+
integrationId?: string;
|
|
18
|
+
name: string;
|
|
19
|
+
isDeleted?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface TicketDetailData {
|
|
22
|
+
ticket: Ticket;
|
|
23
|
+
messages: Message[];
|
|
24
|
+
events: Array<{
|
|
25
|
+
id: string;
|
|
26
|
+
eventType: string;
|
|
27
|
+
createdAt: number;
|
|
28
|
+
payloadJson?: string;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
export interface UploadInputFile {
|
|
32
|
+
filename: string;
|
|
33
|
+
mime: string;
|
|
34
|
+
sizeBytes: number;
|
|
35
|
+
sha256: string;
|
|
36
|
+
}
|
|
37
|
+
export interface UploadInitResult {
|
|
38
|
+
uploads: Array<{
|
|
39
|
+
uploadId: string;
|
|
40
|
+
attachmentId: string;
|
|
41
|
+
filename: string;
|
|
42
|
+
putUrl: string;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
export interface UploadCompleteInput {
|
|
46
|
+
ticketId: string;
|
|
47
|
+
uploads: Array<{
|
|
48
|
+
uploadId: string;
|
|
49
|
+
sizeBytes: number;
|
|
50
|
+
sha256: string;
|
|
51
|
+
}>;
|
|
52
|
+
messageId?: string | null;
|
|
53
|
+
}
|
|
54
|
+
export declare class SupportApiError extends Error {
|
|
55
|
+
readonly code: string;
|
|
56
|
+
readonly requestId: string | null;
|
|
57
|
+
readonly status: number;
|
|
58
|
+
constructor(input: {
|
|
59
|
+
message: string;
|
|
60
|
+
status: number;
|
|
61
|
+
code?: string;
|
|
62
|
+
requestId?: string | null;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export declare class SupportApiClient {
|
|
66
|
+
private readonly basePath;
|
|
67
|
+
private readonly baseUrl;
|
|
68
|
+
private readonly fetchImpl;
|
|
69
|
+
private readonly staticHeaders;
|
|
70
|
+
private readonly getHeaders;
|
|
71
|
+
private readonly contractsClient;
|
|
72
|
+
constructor(options?: SupportClientOptions);
|
|
73
|
+
getCategories(): Promise<{
|
|
74
|
+
items: SupportCategory[];
|
|
75
|
+
}>;
|
|
76
|
+
getPortalConfig(): Promise<{
|
|
77
|
+
categories: Array<{
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
}>;
|
|
81
|
+
aliases: Alias[];
|
|
82
|
+
}>;
|
|
83
|
+
listTickets(params?: ListTicketsParams): Promise<{
|
|
84
|
+
items: Ticket[];
|
|
85
|
+
}>;
|
|
86
|
+
getTicket(ticketId: string): Promise<TicketDetailData>;
|
|
87
|
+
createTicket(input: {
|
|
88
|
+
categoryId?: string | null;
|
|
89
|
+
title: string;
|
|
90
|
+
body: string;
|
|
91
|
+
clientMessageId?: string | null;
|
|
92
|
+
}): Promise<Ticket>;
|
|
93
|
+
createReply(ticketId: string, input: {
|
|
94
|
+
body: string;
|
|
95
|
+
clientMessageId?: string | null;
|
|
96
|
+
}): Promise<Message>;
|
|
97
|
+
closeTicket(ticketId: string): Promise<Ticket>;
|
|
98
|
+
archiveTicket(ticketId: string): Promise<Ticket>;
|
|
99
|
+
reopenTicket(ticketId: string): Promise<Ticket>;
|
|
100
|
+
rateTicket(ticketId: string, input: Omit<RatingRequest, "tenantExternalId" | "principalExternalId">): Promise<{
|
|
101
|
+
ok: true;
|
|
102
|
+
}>;
|
|
103
|
+
rateMessage(messageId: string, input: Omit<RatingRequest, "tenantExternalId" | "principalExternalId">): Promise<{
|
|
104
|
+
ok: true;
|
|
105
|
+
aliasRated: boolean;
|
|
106
|
+
}>;
|
|
107
|
+
initUploads(input: {
|
|
108
|
+
ticketId: string;
|
|
109
|
+
files: UploadInputFile[];
|
|
110
|
+
}): Promise<UploadInitResult>;
|
|
111
|
+
completeUploads(input: UploadCompleteInput): Promise<{
|
|
112
|
+
completed: number;
|
|
113
|
+
scanStatus: "pending";
|
|
114
|
+
}>;
|
|
115
|
+
putUpload(putUrl: string, file: File, onProgress?: (loaded: number, total: number) => void): Promise<void>;
|
|
116
|
+
private requestJson;
|
|
117
|
+
private resolveHeaders;
|
|
118
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { SchrodingerApiClient } from "../generated/client";
|
|
2
|
+
export class SupportApiError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
requestId;
|
|
5
|
+
status;
|
|
6
|
+
constructor(input) {
|
|
7
|
+
super(input.message);
|
|
8
|
+
this.name = "SupportApiError";
|
|
9
|
+
this.status = input.status;
|
|
10
|
+
this.code = input.code ?? "SUPPORT_API_ERROR";
|
|
11
|
+
this.requestId = input.requestId ?? null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class SupportApiClient {
|
|
15
|
+
basePath;
|
|
16
|
+
baseUrl;
|
|
17
|
+
fetchImpl;
|
|
18
|
+
staticHeaders;
|
|
19
|
+
getHeaders;
|
|
20
|
+
contractsClient;
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.basePath = normalizeBasePath(options.basePath ?? "/support/api");
|
|
23
|
+
this.baseUrl = resolveBaseUrl(options.baseUrl, this.basePath);
|
|
24
|
+
this.fetchImpl = (options.fetchImpl ?? fetch).bind(globalThis);
|
|
25
|
+
this.staticHeaders = options.headers ?? {};
|
|
26
|
+
this.getHeaders = options.getHeaders ?? null;
|
|
27
|
+
this.contractsClient = new SchrodingerApiClient({
|
|
28
|
+
baseUrl: this.baseUrl,
|
|
29
|
+
fetchImpl: (input, init) => {
|
|
30
|
+
const requestUrl = new URL(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url, this.baseUrl);
|
|
31
|
+
requestUrl.pathname = requestUrl.pathname.replace(new RegExp(`^${escapeRegExp(this.basePath)}/v1(?=/|$)`), // nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp
|
|
32
|
+
this.basePath);
|
|
33
|
+
return this.fetchImpl(requestUrl.toString(), init);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async getCategories() {
|
|
38
|
+
return await this.requestJson("GET", "/categories");
|
|
39
|
+
}
|
|
40
|
+
async getPortalConfig() {
|
|
41
|
+
return await this.contractsClient.request("GET /v1/portal-config", undefined, {
|
|
42
|
+
headers: await this.resolveHeaders()
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async listTickets(params = {}) {
|
|
46
|
+
return await this.contractsClient.request("GET /v1/tickets", undefined, {
|
|
47
|
+
headers: await this.resolveHeaders(),
|
|
48
|
+
query: {
|
|
49
|
+
status: params.status
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async getTicket(ticketId) {
|
|
54
|
+
const [payload, historyPayload] = await Promise.all([
|
|
55
|
+
this.requestJson("GET", `/tickets/${encodeURIComponent(ticketId)}`),
|
|
56
|
+
this.requestJson("GET", `/tickets/${encodeURIComponent(ticketId)}/history`).catch(() => null)
|
|
57
|
+
]);
|
|
58
|
+
const detail = normalizeTicketDetailPayload(payload);
|
|
59
|
+
if (historyPayload !== null) {
|
|
60
|
+
const historyEvents = asEventArrayFromHistory(historyPayload);
|
|
61
|
+
if (historyEvents.length > 0) {
|
|
62
|
+
detail.events = historyEvents;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return detail;
|
|
66
|
+
}
|
|
67
|
+
async createTicket(input) {
|
|
68
|
+
return await this.contractsClient.request("POST /v1/tickets", input, {
|
|
69
|
+
headers: await this.resolveHeaders()
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async createReply(ticketId, input) {
|
|
73
|
+
return await this.contractsClient.request("POST /v1/tickets/{ticketId}/messages", input, {
|
|
74
|
+
headers: await this.resolveHeaders(),
|
|
75
|
+
pathParams: {
|
|
76
|
+
ticketId
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async closeTicket(ticketId) {
|
|
81
|
+
return await this.requestJson("POST", `/tickets/${encodeURIComponent(ticketId)}/close`, {});
|
|
82
|
+
}
|
|
83
|
+
async archiveTicket(ticketId) {
|
|
84
|
+
return await this.requestJson("POST", `/tickets/${encodeURIComponent(ticketId)}/archive`, {});
|
|
85
|
+
}
|
|
86
|
+
async reopenTicket(ticketId) {
|
|
87
|
+
return await this.requestJson("POST", `/tickets/${encodeURIComponent(ticketId)}/reopen`, {});
|
|
88
|
+
}
|
|
89
|
+
async rateTicket(ticketId, input) {
|
|
90
|
+
return await this.contractsClient.request("POST /v1/tickets/{ticketId}/ratings", input, {
|
|
91
|
+
headers: await this.resolveHeaders(),
|
|
92
|
+
pathParams: {
|
|
93
|
+
ticketId
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async rateMessage(messageId, input) {
|
|
98
|
+
return await this.contractsClient.request("POST /v1/messages/{messageId}/ratings", input, {
|
|
99
|
+
headers: await this.resolveHeaders(),
|
|
100
|
+
pathParams: {
|
|
101
|
+
messageId
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async initUploads(input) {
|
|
106
|
+
return await this.contractsClient.request("POST /v1/uploads/init", input, {
|
|
107
|
+
headers: await this.resolveHeaders()
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async completeUploads(input) {
|
|
111
|
+
return await this.contractsClient.request("POST /v1/uploads/complete", input, {
|
|
112
|
+
headers: await this.resolveHeaders()
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async putUpload(putUrl, file, onProgress) {
|
|
116
|
+
if (onProgress) {
|
|
117
|
+
await uploadViaXmlHttpRequest({
|
|
118
|
+
url: putUrl,
|
|
119
|
+
file,
|
|
120
|
+
onProgress
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
await uploadViaXmlHttpRequest({
|
|
125
|
+
url: putUrl,
|
|
126
|
+
file
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async requestJson(method, path, body) {
|
|
130
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
131
|
+
const headers = await this.resolveHeaders();
|
|
132
|
+
const response = await this.fetchImpl(url.toString(), {
|
|
133
|
+
method,
|
|
134
|
+
headers: {
|
|
135
|
+
...headers,
|
|
136
|
+
...(body === undefined ? {} : { "content-type": "application/json" })
|
|
137
|
+
},
|
|
138
|
+
body: body === undefined ? null : JSON.stringify(body)
|
|
139
|
+
});
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
throw await toSupportApiError(response);
|
|
142
|
+
}
|
|
143
|
+
return (await response.json());
|
|
144
|
+
}
|
|
145
|
+
async resolveHeaders() {
|
|
146
|
+
const dynamic = this.getHeaders ? await this.getHeaders() : {};
|
|
147
|
+
return {
|
|
148
|
+
...this.staticHeaders,
|
|
149
|
+
...dynamic
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function uploadViaXmlHttpRequest(input) {
|
|
154
|
+
await new Promise((resolve, reject) => {
|
|
155
|
+
const xhr = new XMLHttpRequest();
|
|
156
|
+
xhr.open("PUT", input.url);
|
|
157
|
+
xhr.setRequestHeader("content-type", input.file.type || "application/octet-stream");
|
|
158
|
+
xhr.upload.onprogress = (event) => {
|
|
159
|
+
if (event.lengthComputable) {
|
|
160
|
+
input.onProgress?.(event.loaded, event.total);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
xhr.onerror = () => reject(new Error("Upload failed"));
|
|
164
|
+
xhr.onload = () => {
|
|
165
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
166
|
+
resolve();
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
reject(new Error(`Upload failed with status ${xhr.status}`));
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
xhr.send(input.file);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async function toSupportApiError(response) {
|
|
176
|
+
try {
|
|
177
|
+
const payload = (await response.json());
|
|
178
|
+
return new SupportApiError(payload.error
|
|
179
|
+
? {
|
|
180
|
+
status: response.status,
|
|
181
|
+
message: payload.message ?? `Request failed with status ${response.status}`,
|
|
182
|
+
code: payload.error,
|
|
183
|
+
requestId: payload.requestId ?? response.headers.get("X-Request-Id")
|
|
184
|
+
}
|
|
185
|
+
: {
|
|
186
|
+
status: response.status,
|
|
187
|
+
message: payload.message ?? `Request failed with status ${response.status}`,
|
|
188
|
+
requestId: payload.requestId ?? response.headers.get("X-Request-Id")
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return new SupportApiError({
|
|
193
|
+
status: response.status,
|
|
194
|
+
message: `Request failed with status ${response.status}`,
|
|
195
|
+
requestId: response.headers.get("X-Request-Id")
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function resolveBaseUrl(baseUrl, basePath) {
|
|
200
|
+
if (baseUrl) {
|
|
201
|
+
return trimTrailingSlash(baseUrl);
|
|
202
|
+
}
|
|
203
|
+
if (typeof window !== "undefined" && window.location?.origin) {
|
|
204
|
+
return `${window.location.origin}${basePath}`;
|
|
205
|
+
}
|
|
206
|
+
return `http://localhost${basePath}`;
|
|
207
|
+
}
|
|
208
|
+
function normalizeBasePath(path) {
|
|
209
|
+
const withSlash = path.startsWith("/") ? path : `/${path}`;
|
|
210
|
+
return withSlash.replace(/\/+$/, "");
|
|
211
|
+
}
|
|
212
|
+
function trimTrailingSlash(value) {
|
|
213
|
+
return value.replace(/\/+$/, "");
|
|
214
|
+
}
|
|
215
|
+
function escapeRegExp(value) {
|
|
216
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
217
|
+
}
|
|
218
|
+
function normalizeTicketDetailPayload(payload) {
|
|
219
|
+
if (isTicket(payload)) {
|
|
220
|
+
return {
|
|
221
|
+
ticket: payload,
|
|
222
|
+
messages: [],
|
|
223
|
+
events: []
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (isRecord(payload) && isTicket(payload.ticket)) {
|
|
227
|
+
const messages = asMessageArray(payload.messages);
|
|
228
|
+
const events = asEventArray(payload.events);
|
|
229
|
+
return {
|
|
230
|
+
ticket: payload.ticket,
|
|
231
|
+
messages,
|
|
232
|
+
events
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
throw new Error("Invalid ticket detail payload");
|
|
236
|
+
}
|
|
237
|
+
function asMessageArray(value) {
|
|
238
|
+
if (!Array.isArray(value)) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
return value.filter(isMessage).sort((left, right) => left.createdAt - right.createdAt);
|
|
242
|
+
}
|
|
243
|
+
function asEventArray(value) {
|
|
244
|
+
if (!Array.isArray(value)) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
const events = [];
|
|
248
|
+
for (const entry of value) {
|
|
249
|
+
if (!isRecord(entry)) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (typeof entry.id !== "string" ||
|
|
253
|
+
typeof entry.eventType !== "string" ||
|
|
254
|
+
typeof entry.createdAt !== "number") {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if ("payloadJson" in entry &&
|
|
258
|
+
entry.payloadJson !== undefined &&
|
|
259
|
+
typeof entry.payloadJson !== "string") {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (typeof entry.payloadJson === "string") {
|
|
263
|
+
events.push({
|
|
264
|
+
id: entry.id,
|
|
265
|
+
eventType: entry.eventType,
|
|
266
|
+
createdAt: entry.createdAt,
|
|
267
|
+
payloadJson: entry.payloadJson
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
events.push({
|
|
272
|
+
id: entry.id,
|
|
273
|
+
eventType: entry.eventType,
|
|
274
|
+
createdAt: entry.createdAt
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return events.sort((left, right) => left.createdAt - right.createdAt);
|
|
279
|
+
}
|
|
280
|
+
function isTicket(value) {
|
|
281
|
+
if (!isRecord(value)) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
return (typeof value.id === "string" &&
|
|
285
|
+
typeof value.tenantId === "string" &&
|
|
286
|
+
typeof value.principalId === "string" &&
|
|
287
|
+
(typeof value.categoryId === "string" || value.categoryId === null) &&
|
|
288
|
+
typeof value.status === "string" &&
|
|
289
|
+
typeof value.title === "string" &&
|
|
290
|
+
(typeof value.assignedAliasId === "string" || value.assignedAliasId === null) &&
|
|
291
|
+
typeof value.createdAt === "number" &&
|
|
292
|
+
typeof value.updatedAt === "number");
|
|
293
|
+
}
|
|
294
|
+
function isMessage(value) {
|
|
295
|
+
if (!isRecord(value)) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
return (typeof value.id === "string" &&
|
|
299
|
+
typeof value.ticketId === "string" &&
|
|
300
|
+
typeof value.authorType === "string" &&
|
|
301
|
+
typeof value.isPublic === "boolean" &&
|
|
302
|
+
typeof value.bodyPlain === "string" &&
|
|
303
|
+
(typeof value.authorAliasId === "string" || value.authorAliasId === null) &&
|
|
304
|
+
typeof value.createdAt === "number");
|
|
305
|
+
}
|
|
306
|
+
function isRecord(value) {
|
|
307
|
+
return typeof value === "object" && value !== null;
|
|
308
|
+
}
|
|
309
|
+
function asEventArrayFromHistory(payload) {
|
|
310
|
+
const items = isRecord(payload) && Array.isArray(payload.items) ? payload.items : [];
|
|
311
|
+
const events = [];
|
|
312
|
+
for (const entry of items) {
|
|
313
|
+
if (!isRecord(entry)) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (typeof entry.id !== "string" ||
|
|
317
|
+
typeof entry.eventType !== "string" ||
|
|
318
|
+
typeof entry.createdAt !== "number") {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const payloadJson = typeof entry.payloadJson === "string"
|
|
322
|
+
? entry.payloadJson
|
|
323
|
+
: isRecord(entry.payload)
|
|
324
|
+
? JSON.stringify(entry.payload)
|
|
325
|
+
: undefined;
|
|
326
|
+
events.push({
|
|
327
|
+
id: entry.id,
|
|
328
|
+
eventType: entry.eventType,
|
|
329
|
+
createdAt: entry.createdAt,
|
|
330
|
+
...(payloadJson !== undefined ? { payloadJson } : {})
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return events.sort((left, right) => left.createdAt - right.createdAt);
|
|
334
|
+
}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED