@critical-path/client 0.20.4 → 0.20.6
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/package.json +9 -5
- package/CHANGELOG.md +0 -650
- package/dist/cli.test.d.ts +0 -2
- package/dist/cli.test.d.ts.map +0 -1
- package/dist/cli.test.js +0 -225
- package/dist/cli.test.js.map +0 -1
- package/dist/contract.test.d.ts +0 -2
- package/dist/contract.test.d.ts.map +0 -1
- package/dist/contract.test.js +0 -74
- package/dist/contract.test.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -359
- package/dist/index.test.js.map +0 -1
- package/dist/routes.test.d.ts +0 -2
- package/dist/routes.test.d.ts.map +0 -1
- package/dist/routes.test.js +0 -95
- package/dist/routes.test.js.map +0 -1
- package/dist/transport.test.d.ts +0 -2
- package/dist/transport.test.d.ts.map +0 -1
- package/dist/transport.test.js +0 -89
- package/dist/transport.test.js.map +0 -1
- package/src/bin/cli.ts +0 -480
- package/src/cli.test.ts +0 -266
- package/src/contract.test.ts +0 -88
- package/src/index.test.ts +0 -415
- package/src/index.ts +0 -888
- package/src/routes.test.ts +0 -96
- package/src/transport.test.ts +0 -111
- package/tsconfig.json +0 -8
- package/tsconfig.tsbuildinfo +0 -1
package/src/routes.test.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { ROUTES } from '@critical-path/server';
|
|
3
|
-
import { CriticalPathClient } from './index.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Every server route must be reachable through the SDK. When a route is added to the router (and
|
|
7
|
-
* its OpenAPI `ROUTES` table), add the client method that calls it here.
|
|
8
|
-
*/
|
|
9
|
-
const CLIENT_METHOD_FOR_ROUTE: Record<string, keyof CriticalPathClient> = {
|
|
10
|
-
'GET /webhooks': 'getWebhooks',
|
|
11
|
-
'POST /webhooks': 'createWebhook',
|
|
12
|
-
'GET /webhooks/{webhookId}': 'getWebhook',
|
|
13
|
-
'PATCH /webhooks/{webhookId}': 'updateWebhook',
|
|
14
|
-
'DELETE /webhooks/{webhookId}': 'deleteWebhook',
|
|
15
|
-
'GET /workflows': 'getWorkflows',
|
|
16
|
-
'POST /workflows': 'createWorkflow',
|
|
17
|
-
'GET /workflows/{workflowId}': 'getWorkflow',
|
|
18
|
-
'PATCH /workflows/{workflowId}': 'updateWorkflow',
|
|
19
|
-
'DELETE /workflows/{workflowId}': 'deleteWorkflow',
|
|
20
|
-
'GET /projects': 'getProjects',
|
|
21
|
-
'POST /projects': 'createProject',
|
|
22
|
-
'GET /projects/{projectId}': 'getProject',
|
|
23
|
-
'PATCH /projects/{projectId}': 'updateProject',
|
|
24
|
-
'DELETE /projects/{projectId}': 'deleteProject',
|
|
25
|
-
'GET /projects/{projectId}/critical-path': 'calculateCriticalPath',
|
|
26
|
-
'GET /projects/{projectId}/ladder': 'getTimelineLadder',
|
|
27
|
-
'GET /projects/{projectId}/workload': 'getWorkloadDistribution',
|
|
28
|
-
'GET /tasks': 'queryTasks',
|
|
29
|
-
'POST /tasks': 'createTask',
|
|
30
|
-
'GET /tasks/{taskId}': 'getTask',
|
|
31
|
-
'PATCH /tasks/{taskId}': 'updateTask',
|
|
32
|
-
'DELETE /tasks/{taskId}': 'deleteTask',
|
|
33
|
-
// Task-scoped aliases of the comment and attachment collections
|
|
34
|
-
'GET /tasks/{taskId}/comments': 'getComments',
|
|
35
|
-
'POST /tasks/{taskId}/comments': 'addComment',
|
|
36
|
-
'GET /tasks/{taskId}/attachments': 'getAttachments',
|
|
37
|
-
'POST /tasks/{taskId}/attachments': 'createAttachment',
|
|
38
|
-
'GET /tasks/{taskId}/dependencies': 'getTaskDependencies',
|
|
39
|
-
'POST /tasks/{taskId}/dependencies': 'addDependency',
|
|
40
|
-
'DELETE /tasks/{taskId}/dependencies/{dependencyId}': 'removeDependency',
|
|
41
|
-
'GET /tasks/{taskId}/lifecycle': 'getTaskLifecycleState',
|
|
42
|
-
'GET /tasks/{taskId}/transitions': 'getAllowedTaskTransitions',
|
|
43
|
-
'GET /tasks/{taskId}/ladder': 'getTaskLadder',
|
|
44
|
-
'GET /tasks/{taskId}/metrics': 'getTaskMetrics',
|
|
45
|
-
'GET /tasks/{taskId}/progress-history': 'getTaskProgressHistory',
|
|
46
|
-
'GET /teams': 'getTeams',
|
|
47
|
-
'POST /teams': 'createTeam',
|
|
48
|
-
'GET /teams/{teamId}': 'getTeam',
|
|
49
|
-
'PATCH /teams/{teamId}': 'updateTeam',
|
|
50
|
-
'DELETE /teams/{teamId}': 'deleteTeam',
|
|
51
|
-
'GET /containers': 'getContainers',
|
|
52
|
-
'POST /containers': 'createContainer',
|
|
53
|
-
'GET /containers/{containerId}': 'getContainer',
|
|
54
|
-
'PATCH /containers/{containerId}': 'updateContainer',
|
|
55
|
-
'DELETE /containers/{containerId}': 'deleteContainer',
|
|
56
|
-
'GET /deliverables': 'getDeliverables',
|
|
57
|
-
'POST /deliverables': 'createDeliverable',
|
|
58
|
-
'GET /deliverables/{deliverableId}': 'getDeliverable',
|
|
59
|
-
'GET /deliverables/{deliverableId}/summary': 'getDeliverableSummary',
|
|
60
|
-
'PATCH /deliverables/{deliverableId}': 'updateDeliverable',
|
|
61
|
-
'DELETE /deliverables/{deliverableId}': 'deleteDeliverable',
|
|
62
|
-
'GET /iterations': 'getIterations',
|
|
63
|
-
'POST /iterations': 'createIteration',
|
|
64
|
-
'GET /iterations/{iterationId}': 'getIteration',
|
|
65
|
-
'PATCH /iterations/{iterationId}': 'updateIteration',
|
|
66
|
-
'DELETE /iterations/{iterationId}': 'deleteIteration',
|
|
67
|
-
'GET /activities': 'queryActivities',
|
|
68
|
-
'GET /comments': 'getComments',
|
|
69
|
-
'POST /comments': 'addComment',
|
|
70
|
-
'GET /comments/{commentId}': 'getComment',
|
|
71
|
-
'PATCH /comments/{commentId}': 'updateComment',
|
|
72
|
-
'DELETE /comments/{commentId}': 'deleteComment',
|
|
73
|
-
'POST /comments/{commentId}/reactions': 'addCommentReaction',
|
|
74
|
-
'DELETE /comments/{commentId}/reactions': 'removeCommentReaction',
|
|
75
|
-
'GET /attachments': 'getAttachments',
|
|
76
|
-
'POST /attachments': 'createAttachment',
|
|
77
|
-
'POST /attachments/upload': 'uploadAttachmentFile',
|
|
78
|
-
'POST /attachments/presign': 'getPresignedAttachmentUploadUrl',
|
|
79
|
-
'GET /attachments/{attachmentId}': 'getAttachment',
|
|
80
|
-
'DELETE /attachments/{attachmentId}': 'deleteAttachment',
|
|
81
|
-
'GET /time-entries': 'getTimeEntries',
|
|
82
|
-
'POST /time-entries': 'logTime',
|
|
83
|
-
'GET /workload': 'getWorkloadDistribution',
|
|
84
|
-
'POST /status': 'updateStatus'
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
describe('client route coverage', () => {
|
|
88
|
-
it('has a client method for every server route', () => {
|
|
89
|
-
const client = new CriticalPathClient({ baseUrl: 'http://localhost' });
|
|
90
|
-
const missing = ROUTES.map((r) => `${r.method.toUpperCase()} ${r.path}`).filter((key) => {
|
|
91
|
-
const method = CLIENT_METHOD_FOR_ROUTE[key];
|
|
92
|
-
return !method || typeof client[method] !== 'function';
|
|
93
|
-
});
|
|
94
|
-
expect(missing).toEqual([]);
|
|
95
|
-
});
|
|
96
|
-
});
|
package/src/transport.test.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { CriticalPathRouter } from '@critical-path/server';
|
|
3
|
-
import { CriticalPathClient, CriticalPathError } from './index.js';
|
|
4
|
-
|
|
5
|
-
interface Call {
|
|
6
|
-
url: string;
|
|
7
|
-
method: string;
|
|
8
|
-
headers: Record<string, string>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function recordingFetch(respond: (call: Call, n: number) => Response | Promise<Response>) {
|
|
12
|
-
const calls: Call[] = [];
|
|
13
|
-
const fetchImpl = (async (url: string, init: RequestInit) => {
|
|
14
|
-
const call = { url, method: init.method ?? 'GET', headers: init.headers as Record<string, string> };
|
|
15
|
-
calls.push(call);
|
|
16
|
-
return respond(call, calls.length);
|
|
17
|
-
}) as unknown as typeof fetch;
|
|
18
|
-
return { calls, fetchImpl };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const ok = (body: unknown) => Response.json(body);
|
|
22
|
-
|
|
23
|
-
describe('client transport', () => {
|
|
24
|
-
it('throws CriticalPathError with status and server details', async () => {
|
|
25
|
-
const router = new CriticalPathRouter();
|
|
26
|
-
const client = new CriticalPathClient({
|
|
27
|
-
baseUrl: 'http://localhost/api/critical-path',
|
|
28
|
-
fetch: ((input: RequestInfo | URL, init?: RequestInit) => router.handleRequest(new Request(input, init))) as typeof fetch
|
|
29
|
-
});
|
|
30
|
-
const project = await client.createProject({ name: 'Errors' });
|
|
31
|
-
const a = await client.createTask({ projectId: project.id, title: 'A' });
|
|
32
|
-
const b = await client.createTask({ projectId: project.id, title: 'B' });
|
|
33
|
-
await client.addDependency(a.id, { dependsOnTaskId: b.id });
|
|
34
|
-
|
|
35
|
-
const cycle = await client.addDependency(b.id, { dependsOnTaskId: a.id }).catch((e) => e);
|
|
36
|
-
expect(cycle).toBeInstanceOf(CriticalPathError);
|
|
37
|
-
expect(cycle.status).toBe(409);
|
|
38
|
-
expect(cycle.body.cyclePath).toBeDefined();
|
|
39
|
-
|
|
40
|
-
const invalid = await client.createTask({ projectId: project.id, title: '' }).catch((e) => e);
|
|
41
|
-
expect(invalid.status).toBe(400);
|
|
42
|
-
expect(invalid.issues?.[0].path).toBe('title');
|
|
43
|
-
|
|
44
|
-
const missing = await client.getTask('nope').catch((e) => e);
|
|
45
|
-
expect(missing.isNotFound).toBe(true);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('resolves the headers provider for every request and sends Content-Type only with a body', async () => {
|
|
49
|
-
let token = 0;
|
|
50
|
-
const { calls, fetchImpl } = recordingFetch(() => ok({ projects: [], project: { id: 'p' } }));
|
|
51
|
-
const client = new CriticalPathClient({
|
|
52
|
-
baseUrl: 'http://api.test',
|
|
53
|
-
fetch: fetchImpl,
|
|
54
|
-
headers: async () => ({ Authorization: `Bearer t${++token}` })
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
await client.getProjects();
|
|
58
|
-
await client.createProject({ name: 'x' });
|
|
59
|
-
expect(calls.map((c) => c.headers.Authorization)).toEqual(['Bearer t1', 'Bearer t2']);
|
|
60
|
-
expect(calls[0].headers['Content-Type']).toBeUndefined();
|
|
61
|
-
expect(calls[1].headers['Content-Type']).toBe('application/json');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('encodes ids in paths', async () => {
|
|
65
|
-
const { calls, fetchImpl } = recordingFetch(() => ok({ task: {} }));
|
|
66
|
-
await new CriticalPathClient({ baseUrl: 'http://api.test', fetch: fetchImpl }).getTask('../projects/p1');
|
|
67
|
-
expect(calls[0].url).toBe('http://api.test/tasks/..%2Fprojects%2Fp1');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('times out hung requests', async () => {
|
|
71
|
-
const hanging = ((_url: string, init: RequestInit) =>
|
|
72
|
-
new Promise((_resolve, reject) => init.signal!.addEventListener('abort', () => reject(init.signal!.reason)))) as unknown as typeof fetch;
|
|
73
|
-
const client = new CriticalPathClient({ baseUrl: 'http://api.test', fetch: hanging, timeoutMs: 10 });
|
|
74
|
-
await expect(client.getProjects()).rejects.toMatchObject({ name: 'TimeoutError' });
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('scopes signals and headers with with(), leaving the original client untouched', async () => {
|
|
78
|
-
const { calls, fetchImpl } = recordingFetch(() => ok({ projects: [] }));
|
|
79
|
-
const client = new CriticalPathClient({ baseUrl: 'http://api.test', fetch: fetchImpl, headers: { 'X-App': 'web' } });
|
|
80
|
-
|
|
81
|
-
const controller = new AbortController();
|
|
82
|
-
controller.abort();
|
|
83
|
-
const aborting = ((_url: string, init: RequestInit) => Promise.reject(init.signal?.reason)) as unknown as typeof fetch;
|
|
84
|
-
const cancelled = new CriticalPathClient({ baseUrl: 'http://api.test', fetch: aborting }).with({ signal: controller.signal });
|
|
85
|
-
await expect(cancelled.getProjects()).rejects.toMatchObject({ name: 'AbortError' });
|
|
86
|
-
|
|
87
|
-
await client.with({ headers: { 'X-Trace': 'abc' } }).getProjects();
|
|
88
|
-
await client.getProjects();
|
|
89
|
-
expect(calls[0].headers).toMatchObject({ 'X-App': 'web', 'X-Trace': 'abc' });
|
|
90
|
-
expect(calls[1].headers['X-Trace']).toBeUndefined();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('retries GET requests on retryable failures but never writes', async () => {
|
|
94
|
-
const { calls, fetchImpl } = recordingFetch((_call, n) => (n < 3 ? new Response(null, { status: 503 }) : ok({ projects: [] })));
|
|
95
|
-
const client = new CriticalPathClient({ baseUrl: 'http://api.test', fetch: fetchImpl, retry: { retries: 2, baseDelayMs: 1 } });
|
|
96
|
-
await expect(client.getProjects()).resolves.toEqual([]);
|
|
97
|
-
expect(calls).toHaveLength(3);
|
|
98
|
-
|
|
99
|
-
const { calls: writes, fetchImpl: failingWrites } = recordingFetch(() => new Response(null, { status: 503 }));
|
|
100
|
-
const writer = new CriticalPathClient({ baseUrl: 'http://api.test', fetch: failingWrites, retry: { retries: 2, baseDelayMs: 1 } });
|
|
101
|
-
await expect(writer.createProject({ name: 'x' })).rejects.toBeInstanceOf(CriticalPathError);
|
|
102
|
-
expect(writes).toHaveLength(1);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('does not retry client errors', async () => {
|
|
106
|
-
const fetchSpy = vi.fn(async () => Response.json({ error: 'nope' }, { status: 400 }));
|
|
107
|
-
const client = new CriticalPathClient({ baseUrl: 'http://api.test', fetch: fetchSpy as unknown as typeof fetch, retry: { retries: 3 } });
|
|
108
|
-
await expect(client.getProjects()).rejects.toMatchObject({ status: 400 });
|
|
109
|
-
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
110
|
-
});
|
|
111
|
-
});
|
package/tsconfig.json
DELETED
package/tsconfig.tsbuildinfo
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"root":["./src/cli.test.ts","./src/contract.test.ts","./src/index.test.ts","./src/index.ts","./src/routes.test.ts","./src/transport.test.ts","./src/bin/cli.ts"],"version":"5.9.3"}
|