zinkee 0.1.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/AGENTS.md +46 -0
- package/README.md +395 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +7142 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -0
- package/src/api/automations.ts +403 -0
- package/src/api/comments.ts +51 -0
- package/src/api/displays.ts +276 -0
- package/src/api/files.ts +50 -0
- package/src/api/navigation.ts +71 -0
- package/src/api/records.ts +167 -0
- package/src/api/schemas.ts +130 -0
- package/src/api/teamspace.ts +110 -0
- package/src/cli-examples.ts +175 -0
- package/src/cli-runner.ts +73 -0
- package/src/client.test.ts +128 -0
- package/src/client.ts +243 -0
- package/src/command-registry.ts +749 -0
- package/src/commands/automations.test.ts +728 -0
- package/src/commands/automations.ts +2013 -0
- package/src/commands/comments.test.ts +214 -0
- package/src/commands/comments.ts +303 -0
- package/src/commands/config.test.ts +81 -0
- package/src/commands/config.ts +150 -0
- package/src/commands/displays.test.ts +266 -0
- package/src/commands/displays.ts +755 -0
- package/src/commands/files.test.ts +284 -0
- package/src/commands/files.ts +280 -0
- package/src/commands/navigation.test.ts +214 -0
- package/src/commands/navigation.ts +348 -0
- package/src/commands/profiles.test.ts +82 -0
- package/src/commands/profiles.ts +281 -0
- package/src/commands/records.test.ts +367 -0
- package/src/commands/records.ts +726 -0
- package/src/commands/schemas.test.ts +748 -0
- package/src/commands/schemas.ts +746 -0
- package/src/commands/teamspace.test.ts +229 -0
- package/src/commands/teamspace.ts +540 -0
- package/src/config.test.ts +165 -0
- package/src/config.ts +248 -0
- package/src/index.test.ts +140 -0
- package/src/index.ts +8 -0
- package/src/parsers/expressions.test.ts +50 -0
- package/src/parsers/expressions.ts +111 -0
- package/src/parsers/kv.test.ts +35 -0
- package/src/parsers/kv.ts +49 -0
- package/src/parsers/selectors.test.ts +23 -0
- package/src/parsers/selectors.ts +29 -0
- package/src/program.ts +56 -0
- package/src/runtime-context.ts +18 -0
- package/src/types.ts +71 -0
- package/src/utils/errors.test.ts +48 -0
- package/src/utils/errors.ts +183 -0
- package/src/utils/examples.test.ts +67 -0
- package/src/utils/examples.ts +855 -0
- package/src/utils/output.test.ts +39 -0
- package/src/utils/output.ts +124 -0
- package/src/utils/schema-fields.ts +529 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +13 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ApiClient } from "../client.js";
|
|
2
|
+
import type { NormalizedUuidOrSlug } from "../parsers/selectors.js";
|
|
3
|
+
import type { JsonObject } from "../types.js";
|
|
4
|
+
|
|
5
|
+
export interface ApiTeamspaceResource {
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
id?: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ApiTeamspaceFolder {
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
id?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
order?: number;
|
|
16
|
+
additionalProperties?: JsonObject;
|
|
17
|
+
resources?: ApiTeamspaceResource[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CreateTeamspaceFolderInput {
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
name: string;
|
|
23
|
+
order?: number;
|
|
24
|
+
additionalProperties?: JsonObject;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface UpdateTeamspaceFolderInput {
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
name?: string;
|
|
30
|
+
order?: number;
|
|
31
|
+
additionalProperties?: JsonObject;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PublishTeamspaceResourceInput {
|
|
35
|
+
resourceId: string;
|
|
36
|
+
targetFolderId: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function jsonOptions(body: unknown, method: string): RequestInit {
|
|
40
|
+
return {
|
|
41
|
+
method,
|
|
42
|
+
headers: {
|
|
43
|
+
"content-type": "application/json",
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify(body),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function selectorValue(selector: NormalizedUuidOrSlug): string {
|
|
50
|
+
return selector.value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class TeamspaceApi {
|
|
54
|
+
constructor(private readonly client: ApiClient) {}
|
|
55
|
+
|
|
56
|
+
listFolders(includeResources: boolean = false): Promise<ApiTeamspaceFolder[]> {
|
|
57
|
+
const params = new URLSearchParams();
|
|
58
|
+
|
|
59
|
+
if (includeResources) {
|
|
60
|
+
params.set("include", "resources");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const path = "/api/v2/teamspaces/folders";
|
|
64
|
+
const queryString = params.toString();
|
|
65
|
+
|
|
66
|
+
return this.client.getJson<ApiTeamspaceFolder[]>(
|
|
67
|
+
queryString ? `${path}?${queryString}` : path,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
createFolder(input: CreateTeamspaceFolderInput): Promise<ApiTeamspaceFolder> {
|
|
72
|
+
return this.client.requestJson<ApiTeamspaceFolder>(
|
|
73
|
+
"/api/v2/teamspaces/folders",
|
|
74
|
+
jsonOptions(input, "POST"),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
updateFolder(
|
|
79
|
+
folder: NormalizedUuidOrSlug,
|
|
80
|
+
input: UpdateTeamspaceFolderInput,
|
|
81
|
+
): Promise<ApiTeamspaceFolder> {
|
|
82
|
+
return this.client.requestJson<ApiTeamspaceFolder>(
|
|
83
|
+
`/api/v2/teamspaces/folders/${selectorValue(folder)}`,
|
|
84
|
+
jsonOptions(input, "PATCH"),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
deleteFolder(folder: NormalizedUuidOrSlug): Promise<Record<string, unknown>> {
|
|
89
|
+
return this.client.requestJson<Record<string, unknown>>(
|
|
90
|
+
`/api/v2/teamspaces/folders/${selectorValue(folder)}`,
|
|
91
|
+
{ method: "DELETE" },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
publishResource(
|
|
96
|
+
input: PublishTeamspaceResourceInput,
|
|
97
|
+
): Promise<Record<string, unknown>> {
|
|
98
|
+
return this.client.requestJson<Record<string, unknown>>(
|
|
99
|
+
"/api/v2/teamspaces/resources:publish",
|
|
100
|
+
jsonOptions(input, "POST"),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
unpublishResource(resourceId: string): Promise<Record<string, unknown>> {
|
|
105
|
+
return this.client.requestJson<Record<string, unknown>>(
|
|
106
|
+
"/api/v2/teamspaces/resources:unpublish",
|
|
107
|
+
jsonOptions({ resourceId }, "POST"),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { commandRegistry } from "./command-registry.js";
|
|
2
|
+
import { CliError } from "./utils/errors.js";
|
|
3
|
+
import { getResolvedCommandExamples } from "./utils/examples.js";
|
|
4
|
+
import { buildJsonSuccess } from "./utils/output.js";
|
|
5
|
+
|
|
6
|
+
interface CommandIo {
|
|
7
|
+
write(chunk: string): unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ExampleRequest {
|
|
11
|
+
commandName: string;
|
|
12
|
+
filters: {
|
|
13
|
+
type?: string;
|
|
14
|
+
};
|
|
15
|
+
overrides: {
|
|
16
|
+
profile?: string;
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
};
|
|
20
|
+
json: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getStdout(stdout?: CommandIo): CommandIo {
|
|
24
|
+
return stdout ?? process.stdout;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveExampleCommand(tokens: readonly string[]): {
|
|
28
|
+
commandName: string;
|
|
29
|
+
remainingTokens: readonly string[];
|
|
30
|
+
} {
|
|
31
|
+
const commands = Object.keys(commandRegistry).sort(
|
|
32
|
+
(left, right) => right.split(" ").length - left.split(" ").length,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
for (const commandName of commands) {
|
|
36
|
+
const commandTokens = commandName.split(" ");
|
|
37
|
+
const matches = commandTokens.every((token, index) => tokens[index] === token);
|
|
38
|
+
|
|
39
|
+
if (matches) {
|
|
40
|
+
return {
|
|
41
|
+
commandName,
|
|
42
|
+
remainingTokens: tokens.slice(commandTokens.length),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const commandTokens = tokens.filter((token) => !token.startsWith("-"));
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
commandName: commandTokens.join(" "),
|
|
51
|
+
remainingTokens: [],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function extractExampleFilters(tokens: readonly string[]): { type?: string } {
|
|
56
|
+
const filters: { type?: string } = {};
|
|
57
|
+
|
|
58
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
59
|
+
const token = tokens[index];
|
|
60
|
+
|
|
61
|
+
if (token === "--type") {
|
|
62
|
+
const value = tokens[index + 1];
|
|
63
|
+
if (value && !value.startsWith("-")) {
|
|
64
|
+
filters.type = value;
|
|
65
|
+
index += 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return filters;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function extractExampleOverrides(tokens: readonly string[]): {
|
|
74
|
+
profile?: string;
|
|
75
|
+
baseUrl?: string;
|
|
76
|
+
apiKey?: string;
|
|
77
|
+
} {
|
|
78
|
+
const overrides: {
|
|
79
|
+
profile?: string;
|
|
80
|
+
baseUrl?: string;
|
|
81
|
+
apiKey?: string;
|
|
82
|
+
} = {};
|
|
83
|
+
|
|
84
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
85
|
+
const token = tokens[index];
|
|
86
|
+
const value = tokens[index + 1];
|
|
87
|
+
|
|
88
|
+
if (!value || value.startsWith("-")) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (token === "--profile") {
|
|
93
|
+
overrides.profile = value;
|
|
94
|
+
index += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (token === "--base-url") {
|
|
99
|
+
overrides.baseUrl = value;
|
|
100
|
+
index += 1;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (token === "--api-key") {
|
|
105
|
+
overrides.apiKey = value;
|
|
106
|
+
index += 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return overrides;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function extractExampleRequest(argv: readonly string[]): ExampleRequest | null {
|
|
114
|
+
const exampleIndex = argv.indexOf("--example");
|
|
115
|
+
|
|
116
|
+
if (exampleIndex === -1) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const requestTokens = argv.slice(exampleIndex + 1);
|
|
121
|
+
const { commandName, remainingTokens } = resolveExampleCommand(requestTokens);
|
|
122
|
+
|
|
123
|
+
if (commandName.length === 0) {
|
|
124
|
+
throw new CliError("invalid_cli_usage", 'Provide a command after "--example".');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
commandName,
|
|
129
|
+
filters: extractExampleFilters(remainingTokens),
|
|
130
|
+
overrides: extractExampleOverrides(argv),
|
|
131
|
+
json: argv.includes("--json"),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function showExamples(
|
|
136
|
+
commandName: string,
|
|
137
|
+
json: boolean,
|
|
138
|
+
stdout?: CommandIo,
|
|
139
|
+
filters: { type?: string } = {},
|
|
140
|
+
options: {
|
|
141
|
+
enabled?: boolean;
|
|
142
|
+
configPath?: string;
|
|
143
|
+
overrides?: {
|
|
144
|
+
profile?: string;
|
|
145
|
+
baseUrl?: string;
|
|
146
|
+
apiKey?: string;
|
|
147
|
+
};
|
|
148
|
+
} = {},
|
|
149
|
+
): Promise<void> {
|
|
150
|
+
const examples = await getResolvedCommandExamples(commandName, filters, options);
|
|
151
|
+
|
|
152
|
+
if (examples.length === 0) {
|
|
153
|
+
throw new CliError("invalid_cli_usage", `No examples registered for "${commandName}".`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const writer = getStdout(stdout);
|
|
157
|
+
|
|
158
|
+
if (json) {
|
|
159
|
+
writer.write(`${JSON.stringify(buildJsonSuccess({ examples }, { command: commandName }))}\n`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
writer.write("Examples:\n");
|
|
164
|
+
for (const [index, example] of examples.entries()) {
|
|
165
|
+
writer.write(`\n${index + 1}. ${example.description}\n`);
|
|
166
|
+
writer.write(` ${example.command}\n`);
|
|
167
|
+
|
|
168
|
+
if (example.raw) {
|
|
169
|
+
writer.write(" Equivalent JSON payload:\n");
|
|
170
|
+
for (const line of example.raw.split("\n")) {
|
|
171
|
+
writer.write(` ${line}\n`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
import { extractExampleRequest, showExamples } from "./cli-examples.js";
|
|
7
|
+
import { buildProgram } from "./program.js";
|
|
8
|
+
import {
|
|
9
|
+
formatCliErrorJson,
|
|
10
|
+
formatCliErrorText,
|
|
11
|
+
getExitCode,
|
|
12
|
+
} from "./utils/errors.js";
|
|
13
|
+
|
|
14
|
+
class ExampleDisplayed extends Error {
|
|
15
|
+
override name = "ExampleDisplayed";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function hasJsonOutput(argv: readonly string[]): boolean {
|
|
19
|
+
return argv.includes("--json");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveExecutablePath(candidatePath: string): string {
|
|
23
|
+
try {
|
|
24
|
+
return realpathSync(candidatePath);
|
|
25
|
+
} catch {
|
|
26
|
+
return candidatePath;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isExecutedAsEntry(argvEntry: string | undefined, moduleUrl: string): boolean {
|
|
31
|
+
if (!argvEntry) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return resolveExecutablePath(argvEntry) === resolveExecutablePath(fileURLToPath(moduleUrl));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function runCli(argv: string[]): Promise<void> {
|
|
39
|
+
try {
|
|
40
|
+
const exampleRequest = extractExampleRequest(argv);
|
|
41
|
+
|
|
42
|
+
if (exampleRequest) {
|
|
43
|
+
await showExamples(
|
|
44
|
+
exampleRequest.commandName,
|
|
45
|
+
exampleRequest.json,
|
|
46
|
+
undefined,
|
|
47
|
+
exampleRequest.filters,
|
|
48
|
+
{
|
|
49
|
+
enabled: true,
|
|
50
|
+
overrides: exampleRequest.overrides,
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
throw new ExampleDisplayed();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await buildProgram().parseAsync(argv);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error instanceof ExampleDisplayed) {
|
|
59
|
+
process.exitCode = 0;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const exitCode = getExitCode(error);
|
|
64
|
+
|
|
65
|
+
if (hasJsonOutput(argv)) {
|
|
66
|
+
process.stderr.write(`${formatCliErrorJson(error, { exitCode })}\n`);
|
|
67
|
+
} else {
|
|
68
|
+
process.stderr.write(`${chalk.red("Error:")} ${formatCliErrorText(error)}\n`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
process.exitCode = exitCode;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { ApiClient, ApiClientError } from "./client.js";
|
|
4
|
+
|
|
5
|
+
type FetchInput = Parameters<typeof fetch>[0];
|
|
6
|
+
|
|
7
|
+
function jsonResponse(body: unknown, init: ResponseInit = {}): Response {
|
|
8
|
+
return new Response(JSON.stringify(body), {
|
|
9
|
+
...init,
|
|
10
|
+
headers: {
|
|
11
|
+
"content-type": "application/json",
|
|
12
|
+
...(init.headers ?? {}),
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function binaryResponse(bytes: number[], init: ResponseInit = {}): Response {
|
|
18
|
+
return new Response(new Uint8Array(bytes), {
|
|
19
|
+
...init,
|
|
20
|
+
headers: {
|
|
21
|
+
"content-type": "application/octet-stream",
|
|
22
|
+
...(init.headers ?? {}),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("ApiClient", () => {
|
|
28
|
+
it("adds auth headers and parses JSON responses", async () => {
|
|
29
|
+
const fetchMock = vi.fn(async (_input: FetchInput, init?: RequestInit) => {
|
|
30
|
+
const headers = new Headers(init?.headers);
|
|
31
|
+
|
|
32
|
+
expect(headers.get("authorization")).toBe("Bearer secret");
|
|
33
|
+
expect(headers.get("accept")).toBe("application/json");
|
|
34
|
+
|
|
35
|
+
return jsonResponse({ data: { id: "1" } });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const client = new ApiClient({
|
|
39
|
+
baseUrl: "https://api.example.com",
|
|
40
|
+
apiKey: "secret",
|
|
41
|
+
fetch: fetchMock,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await expect(client.getJson<{ data: { id: string } }>("/api/v2/schemas")).resolves.toEqual({
|
|
45
|
+
data: { id: "1" },
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("preserves JSON error payloads from failed responses", async () => {
|
|
50
|
+
const fetchMock = vi.fn(async () =>
|
|
51
|
+
jsonResponse(
|
|
52
|
+
{
|
|
53
|
+
error: { code: "bad_request", message: "Bad request" },
|
|
54
|
+
meta: { requestId: "req_1" },
|
|
55
|
+
},
|
|
56
|
+
{ status: 400, statusText: "Bad Request" },
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const client = new ApiClient({
|
|
61
|
+
baseUrl: "https://api.example.com",
|
|
62
|
+
fetch: fetchMock,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await expect(client.getJson("/api/v2/schemas")).rejects.toMatchObject({
|
|
66
|
+
name: "ApiClientError",
|
|
67
|
+
code: "http_error",
|
|
68
|
+
status: 400,
|
|
69
|
+
responseBody: {
|
|
70
|
+
error: { code: "bad_request", message: "Bad request" },
|
|
71
|
+
meta: { requestId: "req_1" },
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("times out slow requests", async () => {
|
|
77
|
+
vi.useFakeTimers();
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const fetchMock = vi.fn(
|
|
81
|
+
(_input: FetchInput, init?: RequestInit) =>
|
|
82
|
+
new Promise<Response>((_resolve, reject) => {
|
|
83
|
+
const signal = init?.signal;
|
|
84
|
+
|
|
85
|
+
signal?.addEventListener(
|
|
86
|
+
"abort",
|
|
87
|
+
() => {
|
|
88
|
+
reject(new Error("aborted"));
|
|
89
|
+
},
|
|
90
|
+
{ once: true },
|
|
91
|
+
);
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const client = new ApiClient({
|
|
96
|
+
baseUrl: "https://api.example.com",
|
|
97
|
+
timeoutMs: 10,
|
|
98
|
+
fetch: fetchMock,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const promise = client.getJson("/api/v2/schemas");
|
|
102
|
+
const assertion = expect(promise).rejects.toMatchObject({
|
|
103
|
+
name: "ApiClientError",
|
|
104
|
+
code: "timeout",
|
|
105
|
+
message: "Request timed out after 10ms.",
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
109
|
+
|
|
110
|
+
await assertion;
|
|
111
|
+
} finally {
|
|
112
|
+
vi.useRealTimers();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("returns binary responses as ArrayBuffers", async () => {
|
|
117
|
+
const fetchMock = vi.fn(async () => binaryResponse([1, 2, 3]));
|
|
118
|
+
|
|
119
|
+
const client = new ApiClient({
|
|
120
|
+
baseUrl: "https://api.example.com",
|
|
121
|
+
fetch: fetchMock,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const result = await client.requestBinary("/api/v2/files/abc");
|
|
125
|
+
|
|
126
|
+
expect(Array.from(new Uint8Array(result))).toEqual([1, 2, 3]);
|
|
127
|
+
});
|
|
128
|
+
});
|