@krak-stack/registry 0.1.38 → 0.1.39
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 +106 -29
- package/dist/components/ui/app-brand.js +17 -17
- package/dist/components/ui/code-block.js +4 -4
- package/dist/components/ui/copy-button.js +2 -2
- package/dist/components/ui/data-table.js +55 -55
- package/dist/components/ui/editing-locale-switcher.js +8 -8
- package/dist/components/ui/effect-form.js +12 -12
- package/dist/components/ui/error-component.js +6 -6
- package/dist/components/ui/file-picker.js +2 -2
- package/dist/components/ui/form.js +24 -24
- package/dist/components/ui/icon-input.js +5 -5
- package/dist/components/ui/locale-switcher.js +2 -2
- package/dist/components/ui/search-menu.js +2 -2
- package/dist/components/ui/sidebar-layout.js +1 -1
- package/dist/components/ui/theme-switcher.js +3 -3
- package/dist/lib/docs.js +52 -111
- package/dist/lib/docs.server.js +0 -28
- package/dist/lib/documentation-toolkit.js +0 -28
- package/dist/lib/httpapi-adapter.d.ts +73 -0
- package/dist/lib/httpapi-adapter.js +189 -0
- package/dist/lib/httpapi-cli.d.ts +4 -22
- package/dist/lib/httpapi-cli.js +199 -313
- package/dist/lib/httpapi-toolkit.d.ts +7 -16
- package/dist/lib/httpapi-toolkit.js +158 -214
- package/dist/lib/query.d.ts +2 -2
- package/dist/lib/query.js +2 -2
- package/dist/lib/webfetch-toolkit.d.ts +1 -1
- package/dist/lib/webfetch-toolkit.js +48 -50
- package/dist/oxlint/anti-slop/index.js +2 -2
- package/dist/services/agent/client/index.js +6 -6
- package/dist/services/agent/index.js +2 -2
- package/package.json +4 -12
- package/dist/lib/httpapi-client.d.ts +0 -34
- package/dist/lib/httpapi-client.js +0 -122
- package/dist/lib/httpapi-helpers.d.ts +0 -79
- package/dist/lib/httpapi-helpers.js +0 -130
- package/dist/lib/httpapi-mcp.d.ts +0 -25
- package/dist/lib/httpapi-mcp.js +0 -321
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Context, Effect, JsonSchema, Layer, Schema } from "effect";
|
|
2
|
+
import type { Json } from "effect/Schema";
|
|
3
|
+
import { HttpClient } from "effect/http";
|
|
4
|
+
import { HttpApi, HttpApiGroup, OpenApi } from "effect/http-api";
|
|
5
|
+
export type HttpApiMethod = "get" | "post" | "put" | "patch" | "delete";
|
|
6
|
+
declare const HttpApiOperation: Schema.Struct<{
|
|
7
|
+
readonly operationId: Schema.optional<Schema.String>;
|
|
8
|
+
readonly summary: Schema.optional<Schema.String>;
|
|
9
|
+
readonly description: Schema.optional<Schema.String>;
|
|
10
|
+
readonly tags: Schema.optional<Schema.$Array<Schema.String>>;
|
|
11
|
+
readonly parameters: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
12
|
+
readonly name: Schema.String;
|
|
13
|
+
readonly in: Schema.Literals<readonly ["path", "query", "header", "cookie"]>;
|
|
14
|
+
readonly required: Schema.optional<Schema.Boolean>;
|
|
15
|
+
readonly description: Schema.optional<Schema.String>;
|
|
16
|
+
readonly schema: Schema.optional<Schema.Codec<Json, Json, never, never>>;
|
|
17
|
+
}>>>;
|
|
18
|
+
readonly requestBody: Schema.optional<Schema.Struct<{
|
|
19
|
+
readonly required: Schema.optional<Schema.Boolean>;
|
|
20
|
+
readonly content: Schema.optional<Schema.$Record<Schema.String, Schema.Struct<{
|
|
21
|
+
readonly schema: Schema.optional<Schema.Codec<Json, Json, never, never>>;
|
|
22
|
+
}>>>;
|
|
23
|
+
}>>;
|
|
24
|
+
}>;
|
|
25
|
+
export type HttpApiOperation = typeof HttpApiOperation.Type;
|
|
26
|
+
export type HttpApiOperationEntry = {
|
|
27
|
+
readonly method: HttpApiMethod;
|
|
28
|
+
readonly path: string;
|
|
29
|
+
readonly operation: HttpApiOperation;
|
|
30
|
+
};
|
|
31
|
+
export type HttpApiOperationInputValues = Readonly<Record<string, unknown>>;
|
|
32
|
+
export type HttpApiOperationInput = {
|
|
33
|
+
readonly body?: unknown;
|
|
34
|
+
readonly headers: HttpApiOperationInputValues;
|
|
35
|
+
readonly params: HttpApiOperationInputValues;
|
|
36
|
+
readonly query: HttpApiOperationInputValues;
|
|
37
|
+
};
|
|
38
|
+
export type HttpApiOperationDefinition = HttpApiOperationEntry & {
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly groupIdentifier: string;
|
|
41
|
+
readonly endpointIdentifier: string;
|
|
42
|
+
readonly topLevel: boolean;
|
|
43
|
+
readonly inputSchema: Schema.Codec<Partial<HttpApiOperationInput>, Json>;
|
|
44
|
+
readonly inputJsonSchema: JsonSchema.JsonSchema;
|
|
45
|
+
readonly successSchema?: Schema.Codec<unknown, unknown>;
|
|
46
|
+
readonly readOnly: boolean;
|
|
47
|
+
readonly destructive: boolean;
|
|
48
|
+
readonly idempotent: boolean;
|
|
49
|
+
};
|
|
50
|
+
export type HttpApiAdapterConfig<Id extends string, Groups extends HttpApiGroup.Constraint> = {
|
|
51
|
+
readonly api: HttpApi.HttpApi<Id, Groups>;
|
|
52
|
+
readonly baseUrl: string;
|
|
53
|
+
readonly methods?: ReadonlyArray<HttpApiMethod>;
|
|
54
|
+
readonly include?: (operation: HttpApiOperationEntry) => boolean;
|
|
55
|
+
readonly encodeResult?: (result: unknown, operation: HttpApiOperationDefinition) => Effect.Effect<Json, Error>;
|
|
56
|
+
};
|
|
57
|
+
export type HttpApiAdapterExecuteOptions = {
|
|
58
|
+
readonly operation: HttpApiOperationDefinition;
|
|
59
|
+
readonly input: Partial<HttpApiOperationInput>;
|
|
60
|
+
};
|
|
61
|
+
export type HttpApiAdapterService = {
|
|
62
|
+
readonly info: ReturnType<typeof OpenApi.fromApi>["info"];
|
|
63
|
+
readonly operations: ReadonlyArray<HttpApiOperationDefinition>;
|
|
64
|
+
readonly execute: (options: HttpApiAdapterExecuteOptions) => Effect.Effect<unknown, Error>;
|
|
65
|
+
readonly encodeResult: (result: unknown, operation: HttpApiOperationDefinition) => Effect.Effect<Json, Error>;
|
|
66
|
+
};
|
|
67
|
+
export declare const encodeHttpApiOperationResult: (result: unknown) => Effect.Effect<string | number | boolean | Schema.JsonArray | Schema.JsonObject | null, Error, never>;
|
|
68
|
+
declare const HttpApiAdapter_base: Context.ServiceClass<HttpApiAdapter, "@krak-stack/registry/HttpApiAdapter", HttpApiAdapterService>;
|
|
69
|
+
export declare class HttpApiAdapter extends HttpApiAdapter_base {
|
|
70
|
+
static readonly make: <Id extends string, Groups extends HttpApiGroup.Constraint>(config: HttpApiAdapterConfig<Id, Groups>) => Effect.Effect<HttpApiAdapterService, Error, HttpClient.HttpClient | import("effect/http-api/HttpApiMiddleware").MiddlewareClient<import("effect/http-api/HttpApiEndpoint").Middleware<HttpApiGroup.Endpoints<Groups>>>>;
|
|
71
|
+
static readonly layer: <Id extends string, Groups extends HttpApiGroup.Constraint>(config: HttpApiAdapterConfig<Id, Groups>) => Layer.Layer<HttpApiAdapter, Error, HttpClient.HttpClient | Exclude<import("effect/http-api/HttpApiMiddleware").MiddlewareClient<import("effect/http-api/HttpApiEndpoint").Middleware<HttpApiGroup.Endpoints<Groups>>>, import("effect/Scope").Scope>>;
|
|
72
|
+
}
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// ../../src/lib/httpapi-adapter.ts
|
|
2
|
+
import {
|
|
3
|
+
Context,
|
|
4
|
+
Effect,
|
|
5
|
+
Layer,
|
|
6
|
+
Option,
|
|
7
|
+
Schema,
|
|
8
|
+
SchemaTransformation
|
|
9
|
+
} from "effect";
|
|
10
|
+
import { HttpClient } from "effect/http";
|
|
11
|
+
import { HttpApi, HttpApiClient, OpenApi } from "effect/http-api";
|
|
12
|
+
var HttpApiMethods = [
|
|
13
|
+
"get",
|
|
14
|
+
"post",
|
|
15
|
+
"put",
|
|
16
|
+
"patch",
|
|
17
|
+
"delete"
|
|
18
|
+
];
|
|
19
|
+
var HttpApiOperation = Schema.Struct({
|
|
20
|
+
operationId: Schema.optional(Schema.String),
|
|
21
|
+
summary: Schema.optional(Schema.String),
|
|
22
|
+
description: Schema.optional(Schema.String),
|
|
23
|
+
tags: Schema.optional(Schema.Array(Schema.String)),
|
|
24
|
+
parameters: Schema.optional(Schema.Array(Schema.Struct({
|
|
25
|
+
name: Schema.String,
|
|
26
|
+
in: Schema.Literals(["path", "query", "header", "cookie"]),
|
|
27
|
+
required: Schema.optional(Schema.Boolean),
|
|
28
|
+
description: Schema.optional(Schema.String),
|
|
29
|
+
schema: Schema.optional(Schema.Json)
|
|
30
|
+
}))),
|
|
31
|
+
requestBody: Schema.optional(Schema.Struct({
|
|
32
|
+
required: Schema.optional(Schema.Boolean),
|
|
33
|
+
content: Schema.optional(Schema.Record(Schema.String, Schema.Struct({
|
|
34
|
+
schema: Schema.optional(Schema.Json)
|
|
35
|
+
})))
|
|
36
|
+
}))
|
|
37
|
+
}).annotate({ identifier: "HttpApiOperation" });
|
|
38
|
+
var UndefinedFromNull = Schema.Null.pipe(Schema.decodeTo(Schema.Undefined, SchemaTransformation.transform({
|
|
39
|
+
decode: () => {
|
|
40
|
+
return;
|
|
41
|
+
},
|
|
42
|
+
encode: () => null
|
|
43
|
+
})));
|
|
44
|
+
var HttpApiOperationResultValue = Schema.suspend(() => Schema.Union([
|
|
45
|
+
Schema.Null,
|
|
46
|
+
UndefinedFromNull,
|
|
47
|
+
Schema.String,
|
|
48
|
+
Schema.Number,
|
|
49
|
+
Schema.Boolean,
|
|
50
|
+
Schema.DateFromString,
|
|
51
|
+
Schema.Uint8ArrayFromBase64,
|
|
52
|
+
Schema.Array(HttpApiOperationResultValue),
|
|
53
|
+
Schema.Record(Schema.String, HttpApiOperationResultValue)
|
|
54
|
+
]));
|
|
55
|
+
var HttpApiOperationResult = Schema.Union([
|
|
56
|
+
HttpApiOperationResultValue,
|
|
57
|
+
Schema.Undefined
|
|
58
|
+
]).annotate({ identifier: "HttpApiOperationResult" });
|
|
59
|
+
var encodeHttpApiOperationResult = Effect.fn("HttpApiAdapter.encodeOperationResult")((result) => Schema.encodeUnknownEffect(HttpApiOperationResult)(result).pipe(Effect.map((encoded) => encoded ?? null), Effect.mapError((cause) => new Error("HTTP API result is not serializable", { cause }))));
|
|
60
|
+
var encodeOperationResult = Effect.fn("HttpApiAdapter.encodeResult")((result, operation) => operation.successSchema ? Schema.encodeUnknownEffect(operation.successSchema)(result).pipe(Effect.flatMap(encodeHttpApiOperationResult), Effect.mapError((cause) => new Error("HTTP API result does not match its success schema", {
|
|
61
|
+
cause
|
|
62
|
+
}))) : encodeHttpApiOperationResult(result));
|
|
63
|
+
var reflectedSchema = (schema) => Schema.make(schema.ast);
|
|
64
|
+
var sanitizeHttpName = (name) => name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
65
|
+
var httpApiToolName = (entry) => (entry.operation.operationId || `${entry.method}_${entry.path.replace(/^\/api\//, "").replace(/[/:{}]/g, "_")}`).replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").slice(0, 64);
|
|
66
|
+
var makeCatalogue = (api, methods, include) => Effect.try({
|
|
67
|
+
try: () => {
|
|
68
|
+
if (!HttpApi.isHttpApi(api))
|
|
69
|
+
throw new Error("HttpApiAdapter requires a valid HttpApi");
|
|
70
|
+
const spec = OpenApi.fromApi(api);
|
|
71
|
+
const reflected = new Map;
|
|
72
|
+
HttpApi.reflect(api, {
|
|
73
|
+
onGroup: () => {
|
|
74
|
+
return;
|
|
75
|
+
},
|
|
76
|
+
onEndpoint: ({ endpoint, group, successes }) => {
|
|
77
|
+
const operationId = Context.getOrElse(endpoint.annotations, OpenApi.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
|
|
78
|
+
if (reflected.has(operationId))
|
|
79
|
+
throw new Error(`Duplicate HTTP API operation ID: ${operationId}`);
|
|
80
|
+
const fields = {};
|
|
81
|
+
if (endpoint.params)
|
|
82
|
+
fields.params = Schema.toType(reflectedSchema(endpoint.params));
|
|
83
|
+
if (endpoint.query)
|
|
84
|
+
fields.query = Schema.optionalKey(Schema.toType(reflectedSchema(endpoint.query)));
|
|
85
|
+
if (endpoint.headers)
|
|
86
|
+
fields.headers = Schema.optionalKey(Schema.toType(reflectedSchema(endpoint.headers)));
|
|
87
|
+
const payloads = Array.from(endpoint.payload.values()).flatMap(({ schemas }) => schemas.map(reflectedSchema));
|
|
88
|
+
if (payloads.length === 1 && payloads[0])
|
|
89
|
+
fields.body = payloads[0];
|
|
90
|
+
else if (payloads.length > 1)
|
|
91
|
+
fields.body = Schema.Union(payloads);
|
|
92
|
+
const input = Object.keys(fields).length === 0 ? Schema.Record(Schema.String, Schema.Never) : Schema.Struct(fields);
|
|
93
|
+
const inputSchema = Schema.make(Schema.toCodecJson(input).ast).annotate({
|
|
94
|
+
identifier: `${sanitizeHttpName(operationId)}ToolInput`
|
|
95
|
+
});
|
|
96
|
+
const successSchemas = Array.from(successes.values()).flat().map(reflectedSchema);
|
|
97
|
+
reflected.set(operationId, {
|
|
98
|
+
groupIdentifier: group.identifier,
|
|
99
|
+
endpointIdentifier: endpoint.identifier,
|
|
100
|
+
topLevel: group.topLevel,
|
|
101
|
+
inputSchema,
|
|
102
|
+
successSchema: successSchemas.length === 1 ? successSchemas[0] : successSchemas.length > 1 ? Schema.Union(successSchemas) : undefined
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
const names = new Set;
|
|
107
|
+
const operations = [];
|
|
108
|
+
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
109
|
+
for (const method of methods) {
|
|
110
|
+
const decoded = Schema.decodeUnknownOption(HttpApiOperation)(pathItem[method]);
|
|
111
|
+
if (Option.isNone(decoded))
|
|
112
|
+
continue;
|
|
113
|
+
const entry = { method, path, operation: decoded.value };
|
|
114
|
+
if (include && !include(entry))
|
|
115
|
+
continue;
|
|
116
|
+
const metadata = entry.operation.operationId ? reflected.get(entry.operation.operationId) : undefined;
|
|
117
|
+
if (!metadata)
|
|
118
|
+
throw new Error(`Missing endpoint schema for ${entry.operation.operationId}`);
|
|
119
|
+
const name = httpApiToolName(entry);
|
|
120
|
+
if (!name || names.has(name))
|
|
121
|
+
throw new Error(`Duplicate or empty HTTP API tool name: ${name}`);
|
|
122
|
+
names.add(name);
|
|
123
|
+
const document = Schema.toJsonSchemaDocument(metadata.inputSchema.annotate({ identifier: undefined }));
|
|
124
|
+
operations.push({
|
|
125
|
+
...entry,
|
|
126
|
+
...metadata,
|
|
127
|
+
name,
|
|
128
|
+
inputJsonSchema: {
|
|
129
|
+
...document.schema,
|
|
130
|
+
$defs: document.definitions
|
|
131
|
+
},
|
|
132
|
+
readOnly: method === "get",
|
|
133
|
+
destructive: method === "delete",
|
|
134
|
+
idempotent: method === "get" || method === "put" || method === "delete"
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { info: spec.info, operations };
|
|
139
|
+
},
|
|
140
|
+
catch: (cause) => new Error("Failed to build HTTP API adapter catalogue", { cause })
|
|
141
|
+
});
|
|
142
|
+
var GeneratedOperation = Schema.declare((value) => value instanceof Function).annotate({ identifier: "GeneratedOperation" });
|
|
143
|
+
var GeneratedOperationEffect = Schema.declare((value) => Effect.isEffect(value)).annotate({ identifier: "GeneratedOperationEffect" });
|
|
144
|
+
var executeGeneratedOperation = Effect.fn("HttpApiAdapter.executeGeneratedOperation")(function* (client, { operation, input }) {
|
|
145
|
+
const group = operation.topLevel ? client : Object.entries(Object(client)).find(([name]) => name === operation.groupIdentifier)?.[1];
|
|
146
|
+
const candidate = Object.entries(Object(group)).find(([name]) => name === operation.endpointIdentifier)?.[1];
|
|
147
|
+
const endpoint = Schema.decodeUnknownOption(GeneratedOperation)(candidate);
|
|
148
|
+
if (Option.isNone(endpoint))
|
|
149
|
+
return yield* Effect.fail(new Error(`No generated API client operation for ${operation.operation.operationId}`));
|
|
150
|
+
const result = Schema.decodeUnknownOption(GeneratedOperationEffect)(endpoint.value({
|
|
151
|
+
headers: input.headers ?? {},
|
|
152
|
+
params: input.params ?? {},
|
|
153
|
+
query: input.query ?? {},
|
|
154
|
+
payload: input.body
|
|
155
|
+
}));
|
|
156
|
+
if (Option.isNone(result))
|
|
157
|
+
return yield* Effect.fail(new Error(`Generated API client operation ${operation.operation.operationId} is invalid`));
|
|
158
|
+
return yield* result.value;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
class HttpApiAdapter extends Context.Service()("@krak-stack/registry/HttpApiAdapter") {
|
|
162
|
+
static make = Effect.fn("HttpApiAdapter.make")(function* (config) {
|
|
163
|
+
const catalogue = yield* makeCatalogue(config.api, config.methods ?? HttpApiMethods, config.include);
|
|
164
|
+
const httpClient = yield* HttpClient.HttpClient;
|
|
165
|
+
const client = yield* HttpApiClient.makeWith(config.api, {
|
|
166
|
+
baseUrl: config.baseUrl,
|
|
167
|
+
httpClient
|
|
168
|
+
});
|
|
169
|
+
return HttpApiAdapter.of({
|
|
170
|
+
...catalogue,
|
|
171
|
+
encodeResult: config.encodeResult ?? encodeOperationResult,
|
|
172
|
+
execute: Effect.fn("HttpApiAdapter.execute")(function* (options) {
|
|
173
|
+
const operation = catalogue.operations.find((entry) => entry.method === options.operation.method && entry.path === options.operation.path && entry.operation.operationId === options.operation.operation.operationId);
|
|
174
|
+
if (!operation) {
|
|
175
|
+
return yield* Effect.fail(new Error("Operation is not in this HTTP API adapter catalogue"));
|
|
176
|
+
}
|
|
177
|
+
return yield* executeGeneratedOperation(client, {
|
|
178
|
+
operation,
|
|
179
|
+
input: options.input
|
|
180
|
+
});
|
|
181
|
+
})
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
static layer = (config) => Layer.effect(this, this.make(config));
|
|
185
|
+
}
|
|
186
|
+
export {
|
|
187
|
+
HttpApiAdapter,
|
|
188
|
+
encodeHttpApiOperationResult
|
|
189
|
+
};
|
|
@@ -1,25 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Effect } from "effect";
|
|
2
2
|
import { Command } from "effect/cli";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import { HttpApiSpec, type HttpApiOperationEntry } from "./httpapi-helpers.js";
|
|
6
|
-
declare const HttpApiCli_base: Context.ServiceClass<HttpApiCli, "HttpApiCli", {
|
|
3
|
+
import { HttpApiAdapter } from "./httpapi-adapter.js";
|
|
4
|
+
export declare const makeHttpApiCli: () => Effect.Effect<{
|
|
7
5
|
command: Command.Command<string, {}, {}, unknown, never>;
|
|
8
6
|
run: (args: ReadonlyArray<string>) => Effect.Effect<void, unknown, Command.Environment>;
|
|
9
|
-
}
|
|
10
|
-
readonly make: () => Effect.Effect<{
|
|
11
|
-
command: Command.Command<string, {}, {}, unknown, never>;
|
|
12
|
-
run: (args: ReadonlyArray<string>) => Effect.Effect<void, unknown, Command.Environment>;
|
|
13
|
-
}, Error, ApiClient | HttpApiSpec>;
|
|
14
|
-
};
|
|
15
|
-
export declare class HttpApiCli extends HttpApiCli_base {
|
|
16
|
-
static readonly layer: Layer.Layer<HttpApiCli, Error, ApiClient | HttpApiSpec>;
|
|
17
|
-
}
|
|
18
|
-
export declare const toHttpApiCliName: (value: string) => string;
|
|
19
|
-
export declare const printHttpApiCliResult: (response: unknown, operation: HttpApiOperationEntry, client: ApiClientService) => Effect.Effect<void, unknown, never>;
|
|
20
|
-
export declare const makeHttpApiCliCommand: () => Effect.Effect<Command.Command<string, {}, {}, unknown, never>, Error, ApiClient | HttpApiSpec>;
|
|
21
|
-
export declare const httpApiCliEnvironmentLayer: (args: ReadonlyArray<string>) => Layer.Layer<ChildProcessSpawner | FileSystem.FileSystem | Path.Path | Stdio.Stdio | Terminal.Terminal, never, never>;
|
|
22
|
-
export declare const httpApiCli: (args?: string[]) => Effect.Effect<void, unknown, HttpApiCli | Command.Environment>;
|
|
23
|
-
export declare const formatHttpApiCliCause: (cause: Cause.Cause<unknown>) => string;
|
|
24
|
-
export declare const runHttpApiCli: <E>(layer: Layer.Layer<HttpApiCli, E>, args?: string[]) => void;
|
|
25
|
-
export {};
|
|
7
|
+
}, Error, HttpApiAdapter>;
|