@work-shape/catalyst-api-client 0.8.0-rc.1 → 0.8.0-rc.10
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/dist/index.d.ts +34 -4
- package/dist/index.js +47 -6
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { ApiErrorCode, Operation, OperationPathParamName, OperationRequestHeader, apiOperations, ApiOperationName } from '@work-shape/catalyst-api-contract';
|
|
2
|
+
import { ApiErrorCode, LocaleCode, Operation, OperationPathParamName, OperationRequestHeader, apiOperations, ApiOperationName } from '@work-shape/catalyst-api-contract';
|
|
3
3
|
export * from '@work-shape/catalyst-api-contract';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -22,6 +22,8 @@ declare const authSessionSchema: z.ZodNullable<z.ZodObject<{
|
|
|
22
22
|
type AuthSession = NonNullable<z.infer<typeof authSessionSchema>>;
|
|
23
23
|
interface AuthResult {
|
|
24
24
|
ok: boolean;
|
|
25
|
+
/** The sign-in library's code for a refusal, such as `INVALID_EMAIL_OR_PASSWORD`. */
|
|
26
|
+
code?: string;
|
|
25
27
|
message?: string;
|
|
26
28
|
}
|
|
27
29
|
declare function getAuthSession(apiBaseUrl: string): Promise<AuthSession | null>;
|
|
@@ -82,6 +84,13 @@ interface ApiClientOptions {
|
|
|
82
84
|
*/
|
|
83
85
|
getToken?: () => string | undefined | Promise<string | undefined>;
|
|
84
86
|
fetchImpl?: typeof fetch;
|
|
87
|
+
/** Cancels this client's requests when its owning session ends. */
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
/**
|
|
90
|
+
* The language every request asks the server to answer in, read at each request. A browser's
|
|
91
|
+
* own Accept-Language names its settings, not the language a person chose in the interface.
|
|
92
|
+
*/
|
|
93
|
+
locale?: LocaleCode;
|
|
85
94
|
/** Only available without a token source; browser navigation cannot send a bearer token. */
|
|
86
95
|
browserManagedDownloads?: boolean;
|
|
87
96
|
/**
|
|
@@ -127,7 +136,12 @@ type ControlPart<Op extends Operation> = {
|
|
|
127
136
|
} ? {
|
|
128
137
|
/** Called when the server has nothing after the given position and sends no stream. */
|
|
129
138
|
onCaughtUp?: () => void;
|
|
130
|
-
}
|
|
139
|
+
} & (Op["response"] extends {
|
|
140
|
+
skip: infer Skip extends z.ZodType;
|
|
141
|
+
} ? {
|
|
142
|
+
/** Called for an event this client does not know, which the stream passes over. */
|
|
143
|
+
onSkipped?: (value: z.output<Skip>) => void;
|
|
144
|
+
} : unknown) : unknown);
|
|
131
145
|
/**
|
|
132
146
|
* An operation that takes an `Idempotency-Key` gets a new one with every call. A caller that
|
|
133
147
|
* sends the same call again after an answer that never arrived passes the key of the first.
|
|
@@ -139,7 +153,18 @@ type IdempotencyPart<Op> = Op extends {
|
|
|
139
153
|
} ? "Idempotency-Key" extends Header ? {
|
|
140
154
|
idempotencyKey?: string;
|
|
141
155
|
} : unknown : unknown;
|
|
142
|
-
|
|
156
|
+
/**
|
|
157
|
+
* An operation that takes `Catalyst-Unattended` is sent with it when `unattended` is set: nobody
|
|
158
|
+
* watches the caller's terminal, so a call that needs a confirmation waits in the Inbox.
|
|
159
|
+
*/
|
|
160
|
+
type UnattendedPart<Op> = Op extends {
|
|
161
|
+
headers: {
|
|
162
|
+
request: readonly (infer Header extends OperationRequestHeader)[];
|
|
163
|
+
};
|
|
164
|
+
} ? "Catalyst-Unattended" extends Header ? {
|
|
165
|
+
unattended?: boolean;
|
|
166
|
+
} : unknown : unknown;
|
|
167
|
+
type InputOf<Op extends Operation> = ParamsPart<Op["path"]> & QueryPart<Op> & BodyPart<Op> & IdempotencyPart<Op> & UnattendedPart<Op> & ControlPart<Op>;
|
|
143
168
|
/**
|
|
144
169
|
* How a call of an operation that may wait for an approval came out. A refusal and a failure
|
|
145
170
|
* are thrown as `ApiError`, as for every operation.
|
|
@@ -153,7 +178,10 @@ type OperationOutcome<Output, Run = unknown> = {
|
|
|
153
178
|
status: "done";
|
|
154
179
|
output: Output;
|
|
155
180
|
}
|
|
156
|
-
/**
|
|
181
|
+
/**
|
|
182
|
+
* Nothing ran yet: somebody decides it, an approver or, for a run `pending_confirmation`, the
|
|
183
|
+
* caller in their Inbox. `location` is where the run is read again.
|
|
184
|
+
*/
|
|
157
185
|
| {
|
|
158
186
|
status: "pending_approval";
|
|
159
187
|
run: Run;
|
|
@@ -169,6 +197,8 @@ type OutputOf<Op extends Operation> = Op["response"] extends {
|
|
|
169
197
|
kind: "sse";
|
|
170
198
|
schema: infer Schema extends z.ZodType;
|
|
171
199
|
} ? AsyncIterable<z.output<Schema>> : Op["response"] extends {
|
|
200
|
+
kind: "redirect";
|
|
201
|
+
} ? Promise<undefined> : Op["response"] extends {
|
|
172
202
|
schema: infer Schema extends z.ZodType;
|
|
173
203
|
} ? Op extends {
|
|
174
204
|
accepted: infer Accepted extends z.ZodType;
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ var authSessionSchema = z.object({
|
|
|
6
6
|
user: z.object({ id: z.string(), email: z.string(), name: z.string() })
|
|
7
7
|
}).nullable();
|
|
8
8
|
var authFailureSchema = z.object({
|
|
9
|
+
code: z.string().optional(),
|
|
9
10
|
message: z.string().optional(),
|
|
10
11
|
error: z.object({ message: z.string().optional() }).optional()
|
|
11
12
|
});
|
|
@@ -37,6 +38,7 @@ async function signInWithEmail(input) {
|
|
|
37
38
|
const failure = authFailureSchema.safeParse(await response.json().catch(() => void 0));
|
|
38
39
|
return {
|
|
39
40
|
ok: false,
|
|
41
|
+
...failure.data?.code === void 0 ? {} : { code: failure.data.code },
|
|
40
42
|
message: failure.data?.message ?? failure.data?.error?.message ?? "Sign in failed"
|
|
41
43
|
};
|
|
42
44
|
}
|
|
@@ -180,7 +182,9 @@ function readEventData(block) {
|
|
|
180
182
|
}
|
|
181
183
|
|
|
182
184
|
// src/transport.ts
|
|
183
|
-
import {
|
|
185
|
+
import {
|
|
186
|
+
isUnknownOperationResponse
|
|
187
|
+
} from "@work-shape/catalyst-api-contract";
|
|
184
188
|
function createApiTransport(options) {
|
|
185
189
|
const baseUrl = options.baseUrl.replace(/\/$/u, "");
|
|
186
190
|
const cookieMode = options.getToken === void 0;
|
|
@@ -209,10 +213,20 @@ function createApiTransport(options) {
|
|
|
209
213
|
if (operation.headers?.request?.includes("Idempotency-Key")) {
|
|
210
214
|
headers.set("idempotency-key", request.idempotencyKey ?? createIdempotencyKey());
|
|
211
215
|
}
|
|
216
|
+
if (request.unattended && operation.headers?.request?.includes("Catalyst-Unattended")) {
|
|
217
|
+
headers.set("catalyst-unattended", "1");
|
|
218
|
+
}
|
|
212
219
|
const token = await options.getToken?.();
|
|
220
|
+
const sessionSignal = options.signal;
|
|
221
|
+
const signal = sessionSignal && request.signal ? AbortSignal.any([sessionSignal, request.signal]) : sessionSignal ?? request.signal;
|
|
213
222
|
if (token) {
|
|
214
223
|
headers.set("authorization", `Bearer ${token}`);
|
|
215
224
|
}
|
|
225
|
+
const locale = options.locale;
|
|
226
|
+
if (locale !== void 0) {
|
|
227
|
+
headers.set("accept-language", locale);
|
|
228
|
+
}
|
|
229
|
+
signal?.throwIfAborted();
|
|
216
230
|
let response;
|
|
217
231
|
try {
|
|
218
232
|
response = await (options.fetchImpl ?? fetch)(url, {
|
|
@@ -220,10 +234,10 @@ function createApiTransport(options) {
|
|
|
220
234
|
headers,
|
|
221
235
|
body,
|
|
222
236
|
credentials: cookieMode ? "include" : "omit",
|
|
223
|
-
signal
|
|
237
|
+
signal
|
|
224
238
|
});
|
|
225
239
|
} catch (error) {
|
|
226
|
-
throw
|
|
240
|
+
throw signal?.aborted ? error : transportError(error);
|
|
227
241
|
}
|
|
228
242
|
if (response.ok) {
|
|
229
243
|
return response;
|
|
@@ -311,7 +325,13 @@ function hasEveryOperation(operations, tree) {
|
|
|
311
325
|
function callOperation(transport, operation, request) {
|
|
312
326
|
const { response } = operation;
|
|
313
327
|
if (response.kind === "sse") {
|
|
314
|
-
return streamEvents(transport, operation, response
|
|
328
|
+
return streamEvents(transport, operation, response, request);
|
|
329
|
+
}
|
|
330
|
+
if (response.kind === "redirect") {
|
|
331
|
+
return transport.send(operation, request).then(async (answer) => {
|
|
332
|
+
await answer.body?.cancel();
|
|
333
|
+
return void 0;
|
|
334
|
+
});
|
|
315
335
|
}
|
|
316
336
|
if (response.kind === "blob") {
|
|
317
337
|
return transport.send(operation, request).then((answer) => readBody(() => answer.blob(), request));
|
|
@@ -341,7 +361,7 @@ function callOperation(transport, operation, request) {
|
|
|
341
361
|
};
|
|
342
362
|
});
|
|
343
363
|
}
|
|
344
|
-
async function* streamEvents(transport, operation,
|
|
364
|
+
async function* streamEvents(transport, operation, response, request) {
|
|
345
365
|
const answer = await transport.send(operation, request);
|
|
346
366
|
if (answer.status === 204) {
|
|
347
367
|
request.onCaughtUp?.();
|
|
@@ -352,7 +372,12 @@ async function* streamEvents(transport, operation, schema, request) {
|
|
|
352
372
|
}
|
|
353
373
|
try {
|
|
354
374
|
for await (const data of readServerSentEvents(answer.body)) {
|
|
355
|
-
|
|
375
|
+
const event = readEvent(transport, data, response, answer.status);
|
|
376
|
+
if (event.skipped) {
|
|
377
|
+
request.onSkipped?.(event.value);
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
yield event.value;
|
|
356
381
|
}
|
|
357
382
|
} catch (error) {
|
|
358
383
|
throw asCallFailure(error, request);
|
|
@@ -368,6 +393,22 @@ async function readBody(read, request) {
|
|
|
368
393
|
function asCallFailure(error, request) {
|
|
369
394
|
return error instanceof ApiError || request.signal?.aborted ? error : transportError(error);
|
|
370
395
|
}
|
|
396
|
+
function readEvent(transport, text, response, status) {
|
|
397
|
+
const { skip } = response;
|
|
398
|
+
if (skip) {
|
|
399
|
+
let value;
|
|
400
|
+
try {
|
|
401
|
+
value = JSON.parse(text);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
throw unreadableResponseError(status, error);
|
|
404
|
+
}
|
|
405
|
+
const parsed = response.schema.safeParse(value);
|
|
406
|
+
if (parsed.success) return { skipped: false, value: parsed.data };
|
|
407
|
+
const skipped = skip.safeParse(value);
|
|
408
|
+
if (skipped.success) return { skipped: true, value: skipped.data };
|
|
409
|
+
}
|
|
410
|
+
return { skipped: false, value: parseJson(transport, text, response.schema, status) };
|
|
411
|
+
}
|
|
371
412
|
function parseJson(transport, text, schema, status) {
|
|
372
413
|
let value;
|
|
373
414
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@work-shape/catalyst-api-client",
|
|
3
|
-
"version": "0.8.0-rc.
|
|
3
|
+
"version": "0.8.0-rc.10",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"zod": "4.4.3",
|
|
32
|
-
"@work-shape/catalyst-api-contract": "0.8.0-rc.
|
|
32
|
+
"@work-shape/catalyst-api-contract": "0.8.0-rc.10"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^24.13.1",
|