pasika 0.10.55 → 0.10.57
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 +10 -8
- package/dist/chunk-Z3C5YVO4.js +13 -0
- package/dist/eslint/pasika/index.d.ts +1 -0
- package/dist/eslint/pasika/index.js +457 -302
- package/dist/helpers/api-contract.d.ts +20 -0
- package/dist/helpers/api-contract.js +8 -0
- package/dist/helpers/with-response.d.ts +7 -8
- package/dist/helpers/with-response.js +11 -8
- package/dist/helpers/zod-fetch.d.ts +15 -17
- package/dist/helpers/zod-fetch.js +24 -16
- package/package.json +5 -5
- package/dist/helpers/response-envelope.d.ts +0 -20
- package/dist/helpers/response-envelope.js +0 -18
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ZodType, z } from 'zod';
|
|
2
|
+
import { HttpMethod } from './http-method.js';
|
|
3
|
+
|
|
4
|
+
declare const apiErrorResponseSchema: z.ZodObject<{
|
|
5
|
+
message: z.ZodString;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
interface ApiContract<TResponseSchema extends ZodType = ZodType, TRequestSchema extends ZodType | undefined = undefined> {
|
|
8
|
+
readonly method: HttpMethod;
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly responseSchema: TResponseSchema;
|
|
11
|
+
readonly requestSchema?: TRequestSchema;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Declares one schema-validated JSON endpoint. Route, client, and mock import
|
|
15
|
+
* this same value so method, path, request validation, and response validation
|
|
16
|
+
* have one source.
|
|
17
|
+
*/
|
|
18
|
+
declare function defineApiContract<TResponseSchema extends ZodType, TRequestSchema extends ZodType | undefined = undefined>(contract: ApiContract<TResponseSchema, TRequestSchema>): Readonly<ApiContract<TResponseSchema, TRequestSchema>>;
|
|
19
|
+
|
|
20
|
+
export { type ApiContract, apiErrorResponseSchema, defineApiContract };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { ApiContract } from './api-contract.js';
|
|
4
|
+
import './http-method.js';
|
|
3
5
|
|
|
4
|
-
/** Whatever a handler's own response needs to carry; a failure never inherits it. */
|
|
5
6
|
type ResponseHeaders = Headers | Record<string, string>;
|
|
6
7
|
type HandlerResult<TData> = {
|
|
7
8
|
data: TData;
|
|
@@ -13,14 +14,12 @@ type HandlerResult<TData> = {
|
|
|
13
14
|
headers?: ResponseHeaders;
|
|
14
15
|
};
|
|
15
16
|
/**
|
|
16
|
-
* Turns a handler
|
|
17
|
-
*
|
|
18
|
-
* at its own status and is never cached
|
|
19
|
-
*
|
|
20
|
-
* A handler whose own response is not JSON calls it with the handler alone and returns
|
|
21
|
-
* `{ body, status, headers }`, so a relayed body passes through unread.
|
|
17
|
+
* Turns a handler result into the raw HTTP response described by one API contract.
|
|
18
|
+
* Successful JSON data is validated and returned as the body directly. An HttpError
|
|
19
|
+
* becomes the standard { message } error body at its own status and is never cached.
|
|
20
|
+
* Anything else remains an error.
|
|
22
21
|
*/
|
|
23
|
-
declare function withResponse<TSchema extends z.ZodType, Args extends unknown[]>(
|
|
22
|
+
declare function withResponse<TSchema extends z.ZodType, Args extends unknown[]>(contract: ApiContract<TSchema, z.ZodType | undefined>, handler?: (...args: Args) => Promise<HandlerResult<z.input<TSchema>>>): (...args: Args) => Promise<NextResponse>;
|
|
24
23
|
declare function withResponse<Args extends unknown[]>(handler: (...args: Args) => Promise<HandlerResult<ReadableStream<Uint8Array>>>): (...args: Args) => Promise<NextResponse>;
|
|
25
24
|
|
|
26
25
|
export { type HandlerResult, type ResponseHeaders, withResponse };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
apiErrorResponseSchema
|
|
3
|
+
} from "../chunk-Z3C5YVO4.js";
|
|
1
4
|
import {
|
|
2
5
|
HttpError
|
|
3
6
|
} from "../chunk-7JI2V3HF.js";
|
|
@@ -7,9 +10,9 @@ import { NextResponse } from "next/server";
|
|
|
7
10
|
import { z } from "zod";
|
|
8
11
|
var streamBody = z.custom((value) => value instanceof ReadableStream);
|
|
9
12
|
function missingHandler() {
|
|
10
|
-
throw new HttpError("withResponse requires
|
|
13
|
+
throw new HttpError("withResponse requires an API contract and a handler.", 500);
|
|
11
14
|
}
|
|
12
|
-
function withResponse(
|
|
15
|
+
function withResponse(contractOrHandler, handler) {
|
|
13
16
|
const respond = (schema, wrapped = missingHandler) => async (...requestArgs) => {
|
|
14
17
|
try {
|
|
15
18
|
const result = await wrapped(...requestArgs);
|
|
@@ -18,18 +21,18 @@ function withResponse(responseSchemaOrHandler, handler) {
|
|
|
18
21
|
return new NextResponse(streamBody.parse(body), { status: status2, headers: headers2 });
|
|
19
22
|
}
|
|
20
23
|
const { data, status, headers } = result;
|
|
21
|
-
return NextResponse.json(
|
|
24
|
+
return NextResponse.json(schema.parse(data), { status, headers });
|
|
22
25
|
} catch (error) {
|
|
23
26
|
if (error instanceof HttpError) {
|
|
24
|
-
return NextResponse.json(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
);
|
|
27
|
+
return NextResponse.json(apiErrorResponseSchema.parse({ message: error.message }), {
|
|
28
|
+
status: error.status,
|
|
29
|
+
headers: { "Cache-Control": "no-store" }
|
|
30
|
+
});
|
|
28
31
|
}
|
|
29
32
|
throw error;
|
|
30
33
|
}
|
|
31
34
|
};
|
|
32
|
-
return typeof
|
|
35
|
+
return typeof contractOrHandler === "function" ? respond(streamBody, contractOrHandler) : respond(contractOrHandler.responseSchema, handler);
|
|
33
36
|
}
|
|
34
37
|
export {
|
|
35
38
|
withResponse
|
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
import { ZodType, z } from 'zod';
|
|
2
|
+
import { ApiContract } from './api-contract.js';
|
|
3
|
+
import './http-method.js';
|
|
2
4
|
|
|
3
|
-
interface
|
|
5
|
+
interface ApiContractFetchOptions<TResponseSchema extends ZodType, TRequestSchema extends ZodType | undefined = undefined> {
|
|
6
|
+
contract: ApiContract<TResponseSchema, TRequestSchema>;
|
|
7
|
+
url?: string | URL;
|
|
8
|
+
init?: Omit<RequestInit, "method">;
|
|
9
|
+
}
|
|
10
|
+
interface ZodFetchRelayedOptions {
|
|
4
11
|
url: string | URL;
|
|
5
12
|
init?: RequestInit;
|
|
6
|
-
requestSchema?: TRequestSchema;
|
|
7
|
-
responseSchema?: TSchema;
|
|
8
13
|
}
|
|
9
|
-
/** The response itself, for a caller whose own response relays a body nothing has read. */
|
|
10
14
|
interface ZodFetchRelayedResponse {
|
|
11
15
|
body: ReadableStream<Uint8Array>;
|
|
12
16
|
status: number;
|
|
13
17
|
headers: Headers;
|
|
14
18
|
}
|
|
15
19
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* A success that carries no body is the schema's call: one that allows its data to
|
|
22
|
-
* be absent, such as `z.undefined()` for an endpoint answering `204`, accepts it, and
|
|
23
|
-
* one that does not makes it a failure of this gateway's own, a 502.
|
|
20
|
+
* Makes one schema-validated JSON request through an API contract, or relays a
|
|
21
|
+
* response body when no contract is supplied. Contract calls reuse the endpoint's
|
|
22
|
+
* method, request schema, and response schema; url only overrides the concrete
|
|
23
|
+
* address for dynamic or external endpoints.
|
|
24
24
|
*/
|
|
25
|
-
declare function zodFetch<
|
|
26
|
-
|
|
27
|
-
}): Promise<z.output<TSchema>>;
|
|
28
|
-
declare function zodFetch(options: ZodFetchOptions): Promise<ZodFetchRelayedResponse>;
|
|
25
|
+
declare function zodFetch<TResponseSchema extends ZodType, TRequestSchema extends ZodType | undefined>(options: ApiContractFetchOptions<TResponseSchema, TRequestSchema>): Promise<z.output<TResponseSchema>>;
|
|
26
|
+
declare function zodFetch(options: ZodFetchRelayedOptions): Promise<ZodFetchRelayedResponse>;
|
|
29
27
|
|
|
30
|
-
export { type
|
|
28
|
+
export { type ApiContractFetchOptions, type ZodFetchRelayedOptions, type ZodFetchRelayedResponse, zodFetch };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
apiErrorResponseSchema
|
|
3
|
+
} from "../chunk-Z3C5YVO4.js";
|
|
1
4
|
import {
|
|
2
5
|
HttpError
|
|
3
6
|
} from "../chunk-7JI2V3HF.js";
|
|
@@ -14,23 +17,28 @@ function decodeFailureBody(body) {
|
|
|
14
17
|
return body;
|
|
15
18
|
}
|
|
16
19
|
}
|
|
20
|
+
function failureMessage(status, statusText, body) {
|
|
21
|
+
const parsed = apiErrorResponseSchema.safeParse(body);
|
|
22
|
+
if (parsed.success) return parsed.data.message;
|
|
23
|
+
const suffix = statusText === "" ? "" : ` ${statusText}`;
|
|
24
|
+
return `Request failed with status ${String(status)}${suffix}`;
|
|
25
|
+
}
|
|
26
|
+
function parseRequestBody(schema, body) {
|
|
27
|
+
if (schema === void 0) return;
|
|
28
|
+
const decoded = typeof body === "string" ? JSON.parse(body) : body;
|
|
29
|
+
schema.parse(decoded);
|
|
30
|
+
}
|
|
17
31
|
async function zodFetch(options) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const response = await fetch(options.url, options.init);
|
|
32
|
+
const contract = "contract" in options ? options.contract : void 0;
|
|
33
|
+
const url = "contract" in options ? options.url ?? options.contract.path : options.url;
|
|
34
|
+
const init = contract ? { ...options.init, method: contract.method } : options.init;
|
|
35
|
+
if (contract) parseRequestBody(contract.requestSchema, init?.body);
|
|
36
|
+
const response = await fetch(url, init);
|
|
24
37
|
if (!response.ok) {
|
|
25
|
-
const body2 = await response.text();
|
|
26
|
-
|
|
27
|
-
throw new HttpError(
|
|
28
|
-
`Request failed with status ${String(response.status)}${statusText}`,
|
|
29
|
-
response.status,
|
|
30
|
-
decodeFailureBody(body2)
|
|
31
|
-
);
|
|
38
|
+
const body2 = decodeFailureBody(await response.text());
|
|
39
|
+
throw new HttpError(failureMessage(response.status, response.statusText, body2), response.status, body2);
|
|
32
40
|
}
|
|
33
|
-
if (
|
|
41
|
+
if (!contract) {
|
|
34
42
|
const streamed = streamBody.safeParse(response.body);
|
|
35
43
|
if (!streamed.success) {
|
|
36
44
|
throw new HttpError("The upstream answered without a body to relay.", BAD_GATEWAY);
|
|
@@ -39,12 +47,12 @@ async function zodFetch(options) {
|
|
|
39
47
|
}
|
|
40
48
|
const body = await response.text();
|
|
41
49
|
if (body === "") {
|
|
42
|
-
const absent =
|
|
50
|
+
const absent = contract.responseSchema.safeParse(void 0);
|
|
43
51
|
if (absent.success) return absent.data;
|
|
44
52
|
throw new HttpError("The upstream answered without a body to decode.", BAD_GATEWAY);
|
|
45
53
|
}
|
|
46
54
|
const decoded = JSON.parse(body);
|
|
47
|
-
return
|
|
55
|
+
return contract.responseSchema.parse(decoded);
|
|
48
56
|
}
|
|
49
57
|
export {
|
|
50
58
|
zodFetch
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pasika",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.57",
|
|
4
4
|
"description": "Reusable agent setup package",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"types": "./dist/eslint/pasika/index.d.ts",
|
|
14
14
|
"import": "./dist/eslint/pasika/index.js"
|
|
15
15
|
},
|
|
16
|
+
"./api-contract": {
|
|
17
|
+
"types": "./dist/helpers/api-contract.d.ts",
|
|
18
|
+
"import": "./dist/helpers/api-contract.js"
|
|
19
|
+
},
|
|
16
20
|
"./cn": {
|
|
17
21
|
"types": "./dist/helpers/cn.d.ts",
|
|
18
22
|
"import": "./dist/helpers/cn.js"
|
|
@@ -25,10 +29,6 @@
|
|
|
25
29
|
"types": "./dist/helpers/http-method.d.ts",
|
|
26
30
|
"import": "./dist/helpers/http-method.js"
|
|
27
31
|
},
|
|
28
|
-
"./response-envelope": {
|
|
29
|
-
"types": "./dist/helpers/response-envelope.d.ts",
|
|
30
|
-
"import": "./dist/helpers/response-envelope.js"
|
|
31
|
-
},
|
|
32
32
|
"./zod-fetch": {
|
|
33
33
|
"types": "./dist/helpers/zod-fetch.d.ts",
|
|
34
34
|
"import": "./dist/helpers/zod-fetch.js"
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
declare function responseEnvelope<TSchema extends z.ZodType>(dataSchema: TSchema): z.ZodType<{
|
|
4
|
-
success: true;
|
|
5
|
-
data: z.output<TSchema>;
|
|
6
|
-
} | {
|
|
7
|
-
success: false;
|
|
8
|
-
data: null;
|
|
9
|
-
message: string;
|
|
10
|
-
}, {
|
|
11
|
-
success: true;
|
|
12
|
-
data: z.input<TSchema>;
|
|
13
|
-
} | {
|
|
14
|
-
success: false;
|
|
15
|
-
data: null;
|
|
16
|
-
message: string;
|
|
17
|
-
}>;
|
|
18
|
-
declare function responseEnvelope(dataSchema: z.ZodType): z.ZodType;
|
|
19
|
-
|
|
20
|
-
export { responseEnvelope };
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// helpers/response-envelope.ts
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
function responseEnvelope(dataSchema) {
|
|
4
|
-
return z.discriminatedUnion("success", [
|
|
5
|
-
z.object({
|
|
6
|
-
success: z.literal(true),
|
|
7
|
-
data: dataSchema
|
|
8
|
-
}),
|
|
9
|
-
z.object({
|
|
10
|
-
success: z.literal(false),
|
|
11
|
-
data: z.null(),
|
|
12
|
-
message: z.string()
|
|
13
|
-
})
|
|
14
|
-
]);
|
|
15
|
-
}
|
|
16
|
-
export {
|
|
17
|
-
responseEnvelope
|
|
18
|
-
};
|