@krak-stack/registry 0.1.36 → 0.1.38
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 +48 -1
- package/dist/components/ui/data-table.js +1 -1
- package/dist/lib/docs.server.js +1 -1
- package/dist/lib/httpapi-cli.js +82 -274
- package/dist/lib/httpapi-client.d.ts +4 -0
- package/dist/lib/httpapi-client.js +10 -0
- package/dist/lib/httpapi-helpers.d.ts +11 -33
- package/dist/lib/httpapi-helpers.js +40 -196
- package/dist/lib/httpapi-mcp.d.ts +14 -9
- package/dist/lib/httpapi-mcp.js +109 -265
- package/dist/lib/httpapi-toolkit.d.ts +15 -3
- package/dist/lib/httpapi-toolkit.js +74 -224
- package/dist/lib/query.d.ts +2 -2
- package/dist/lib/query.js +18 -5
- package/dist/services/agent/index.js +1 -1
- package/dist/services/opentelemetry/api.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ import { makeAgentApiGroup } from "@krak-stack/registry/agent/schema";
|
|
|
55
55
|
import {
|
|
56
56
|
HttpApiToolkit,
|
|
57
57
|
HttpApiToolkitLayer,
|
|
58
|
-
} from "@krak-stack/registry/httpapi
|
|
58
|
+
} from "@krak-stack/registry/httpapi/toolkit";
|
|
59
59
|
import { ApiClient } from "@krak-stack/registry/httpapi/client";
|
|
60
60
|
import { HttpApiSpec } from "@krak-stack/registry/httpapi/helpers";
|
|
61
61
|
import { HttpApiOtlp } from "@krak-stack/registry/opentelemetry/api";
|
|
@@ -133,6 +133,53 @@ HTTP API client, schema, AI tool, CLI, and MCP utilities are available under
|
|
|
133
133
|
the `@krak-stack/registry/httpapi/*` subpaths. Keep application-specific API
|
|
134
134
|
layers, handlers, authentication, and client bindings in the application.
|
|
135
135
|
|
|
136
|
+
Use `@krak-stack/registry/httpapi/toolkit` for HTTP API tools. The old
|
|
137
|
+
`@krak-stack/registry/httpapi-toolkit` export has been removed in `0.1.38`.
|
|
138
|
+
|
|
139
|
+
### HTTP API tools and MCP
|
|
140
|
+
|
|
141
|
+
CLI, toolkit, and MCP adapters share `HttpApiSpec.operations`. Each operation
|
|
142
|
+
contains its tool name, typed `inputSchema`, derived `inputJsonSchema`, and method
|
|
143
|
+
annotations. Inputs use `{ params, query, headers, body }`; only the CLI turns
|
|
144
|
+
terminal arguments into that shape. The generated HTTP client handles wire
|
|
145
|
+
encoding, and payload codecs retain their transformations.
|
|
146
|
+
|
|
147
|
+
The old flat/wire-input helpers and `HttpApiSpec` schema lookup methods have been
|
|
148
|
+
removed in `0.1.38`. Use the schemas on each operation definition and
|
|
149
|
+
`executeHttpApiOperation` from `@krak-stack/registry/httpapi/client` instead.
|
|
150
|
+
|
|
151
|
+
Use `makeHttpApiToolkit(config)` to construct matching tools and handlers once:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { makeHttpApiToolkit } from "@krak-stack/registry/httpapi/toolkit";
|
|
155
|
+
|
|
156
|
+
const { toolkit, handlers } = yield * makeHttpApiToolkit({});
|
|
157
|
+
const tools = yield * toolkit.pipe(Effect.provide(handlers));
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Provide `HttpApiSpec` and `ApiClient` through the surrounding Effect layers.
|
|
161
|
+
`HttpApiToolkit` and `HttpApiToolkitLayer` remain available for integrations that
|
|
162
|
+
need to construct definitions and handlers separately.
|
|
163
|
+
|
|
164
|
+
For an HTTP MCP server, use the single-layer factory:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { Layer } from "effect";
|
|
168
|
+
import { FetchHttpClient } from "effect/http";
|
|
169
|
+
import { HttpApiMcp } from "@krak-stack/registry/httpapi/mcp";
|
|
170
|
+
import { AppApi } from "@/api";
|
|
171
|
+
|
|
172
|
+
export const mcpLayer = HttpApiMcp.layerHttp({
|
|
173
|
+
api: AppApi,
|
|
174
|
+
baseUrl: "http://localhost:3000",
|
|
175
|
+
}).pipe(Layer.provide(FetchHttpClient.layer));
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The factory defaults to `/api/mcp` and GET operations only. Set `path`, `methods`,
|
|
179
|
+
`include`, or `toolMetaKey` when needed. Provide an authenticated `HttpClient`
|
|
180
|
+
instead of `FetchHttpClient.layer` when the downstream API requires credentials.
|
|
181
|
+
Inbound MCP authentication remains application-owned.
|
|
182
|
+
|
|
136
183
|
Documentation consumers load and compile their application-owned MDX on the
|
|
137
184
|
server, then pass the validated page records to `createDocsSource` and
|
|
138
185
|
`makeDocs`:
|
package/dist/lib/docs.server.js
CHANGED
|
@@ -362,7 +362,7 @@ var compileMarkdown = (source) => {
|
|
|
362
362
|
codeBlocks.push({ code, language });
|
|
363
363
|
return `<markdown-code-block data-index="${index}"></markdown-code-block>`;
|
|
364
364
|
},
|
|
365
|
-
codespan: (children) => `<code data-inline-code>${
|
|
365
|
+
codespan: (children) => `<code data-inline-code>${children}</code>`,
|
|
366
366
|
emphasis: (children) => `<em>${children}</em>`,
|
|
367
367
|
heading: (children, { id, level }) => {
|
|
368
368
|
const headingId = escapeHtml(id ?? "");
|
package/dist/lib/httpapi-cli.js
CHANGED
|
@@ -25,6 +25,15 @@ import {
|
|
|
25
25
|
HttpApiClient as EffectHttpApiClient,
|
|
26
26
|
OpenApi
|
|
27
27
|
} from "effect/http-api";
|
|
28
|
+
var executeHttpApiOperation = Effect.fn("HttpApiClient.executeOperation")((options, client) => client.execute({
|
|
29
|
+
operation: options.operation,
|
|
30
|
+
input: {
|
|
31
|
+
body: options.input.body,
|
|
32
|
+
headers: options.input.headers ?? {},
|
|
33
|
+
params: options.input.params ?? {},
|
|
34
|
+
query: options.input.query ?? {}
|
|
35
|
+
}
|
|
36
|
+
}));
|
|
28
37
|
var UndefinedFromNull = Schema.Null.pipe(Schema.decodeTo(Schema.Undefined, SchemaTransformation.transform({
|
|
29
38
|
decode: () => {
|
|
30
39
|
return;
|
|
@@ -125,15 +134,7 @@ class ApiClient extends Context.Service()("ApiClient") {
|
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
// ../../src/lib/httpapi-helpers.tsx
|
|
128
|
-
import {
|
|
129
|
-
Context as Context2,
|
|
130
|
-
Effect as Effect2,
|
|
131
|
-
JsonSchema,
|
|
132
|
-
Layer as Layer2,
|
|
133
|
-
Option,
|
|
134
|
-
Schema as Schema2,
|
|
135
|
-
SchemaRepresentation
|
|
136
|
-
} from "effect";
|
|
137
|
+
import { Context as Context2, Effect as Effect2, Layer as Layer2, Option, Schema as Schema2 } from "effect";
|
|
137
138
|
import { HttpApi as HttpApi2, OpenApi as OpenApi2 } from "effect/http-api";
|
|
138
139
|
var JsonObjectSchema = Schema2.Record(Schema2.String, Schema2.Json).annotate({
|
|
139
140
|
identifier: "HttpJsonObject",
|
|
@@ -143,23 +144,6 @@ var JsonObjectSchema = Schema2.Record(Schema2.String, Schema2.Json).annotate({
|
|
|
143
144
|
});
|
|
144
145
|
var JsonObjectFromString = Schema2.fromJsonString(JsonObjectSchema);
|
|
145
146
|
var JsonValueFromString = Schema2.fromJsonString(Schema2.Json);
|
|
146
|
-
var JsonSchemaAnnotations = Schema2.Struct({
|
|
147
|
-
title: Schema2.optional(Schema2.String),
|
|
148
|
-
description: Schema2.optional(Schema2.String),
|
|
149
|
-
examples: Schema2.optional(Schema2.Array(Schema2.Json))
|
|
150
|
-
}).annotate({
|
|
151
|
-
identifier: "HttpJsonSchemaAnnotations",
|
|
152
|
-
title: "HTTP JSON Schema annotations",
|
|
153
|
-
description: "JSON Schema annotations surfaced on generated tool inputs."
|
|
154
|
-
});
|
|
155
|
-
var HttpApiToolInputSchema = Schema2.Struct({
|
|
156
|
-
body: Schema2.optional(Schema2.Json)
|
|
157
|
-
}).annotate({
|
|
158
|
-
identifier: "HttpApiToolInput",
|
|
159
|
-
title: "HTTP API tool input",
|
|
160
|
-
description: "Input accepted by an HTTP API-backed tool.",
|
|
161
|
-
examples: [{ body: { id: "example-id" } }]
|
|
162
|
-
});
|
|
163
147
|
var HttpApiMethods = [
|
|
164
148
|
"get",
|
|
165
149
|
"post",
|
|
@@ -183,130 +167,12 @@ var httpApiOperations = ({
|
|
|
183
167
|
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
184
168
|
for (const method of methods) {
|
|
185
169
|
const operation = decodeHttpApiOperation(pathItem[method]);
|
|
186
|
-
if (Option.isSome(operation))
|
|
170
|
+
if (Option.isSome(operation))
|
|
187
171
|
operations.push({ method, path, operation: operation.value });
|
|
188
|
-
}
|
|
189
172
|
}
|
|
190
173
|
}
|
|
191
174
|
return operations;
|
|
192
175
|
};
|
|
193
|
-
var httpApiToolEntries = Effect2.fn("Http.toolEntries")(function* (operations) {
|
|
194
|
-
return yield* Effect2.try({
|
|
195
|
-
try: () => {
|
|
196
|
-
const names = new Set;
|
|
197
|
-
return operations.map((entry) => {
|
|
198
|
-
const name = httpApiToolName(entry.method, entry.path, entry.operation);
|
|
199
|
-
if (!name || names.has(name)) {
|
|
200
|
-
throw new Error(`Duplicate or empty HTTP API tool name: ${name}`);
|
|
201
|
-
}
|
|
202
|
-
names.add(name);
|
|
203
|
-
return { ...entry, name };
|
|
204
|
-
});
|
|
205
|
-
},
|
|
206
|
-
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
var decodeAnnotations = Schema2.decodeUnknownOption(JsonSchemaAnnotations);
|
|
210
|
-
var schemaWithVisibleAnnotations = (schema) => {
|
|
211
|
-
const directAnnotations = decodeAnnotations(schema).pipe(Option.getOrElse(() => ({})));
|
|
212
|
-
const allOf = Array.isArray(schema.allOf) ? schema.allOf : [];
|
|
213
|
-
const annotations = allOf.reduce((acc, item) => ({
|
|
214
|
-
...acc,
|
|
215
|
-
...decodeAnnotations(item).pipe(Option.getOrElse(() => ({})))
|
|
216
|
-
}), directAnnotations);
|
|
217
|
-
const visible = { ...schema };
|
|
218
|
-
if ("title" in annotations && !("title" in schema)) {
|
|
219
|
-
visible.title = annotations.title;
|
|
220
|
-
}
|
|
221
|
-
if ("description" in annotations && !("description" in schema)) {
|
|
222
|
-
visible.description = annotations.description;
|
|
223
|
-
}
|
|
224
|
-
if ("examples" in annotations && !("examples" in schema)) {
|
|
225
|
-
visible.examples = annotations.examples;
|
|
226
|
-
}
|
|
227
|
-
return visible;
|
|
228
|
-
};
|
|
229
|
-
var referencedDefinitions = (schema, definitions) => {
|
|
230
|
-
const names = new Set;
|
|
231
|
-
const pending = [Schema2.decodeUnknownSync(Schema2.Json)(schema)];
|
|
232
|
-
while (pending.length > 0) {
|
|
233
|
-
const current = pending.pop();
|
|
234
|
-
if (Array.isArray(current)) {
|
|
235
|
-
pending.push(...current);
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
const record = Schema2.decodeUnknownOption(Schema2.Record(Schema2.String, Schema2.Json))(current);
|
|
239
|
-
if (Option.isNone(record))
|
|
240
|
-
continue;
|
|
241
|
-
for (const [key, value] of Object.entries(record.value)) {
|
|
242
|
-
if (key === "$ref" && Schema2.is(Schema2.String)(value)) {
|
|
243
|
-
const name = value.match(/^#\/(?:\$defs|components\/schemas)\/(.+)$/)?.[1];
|
|
244
|
-
if (name && !names.has(name) && definitions[name]) {
|
|
245
|
-
names.add(name);
|
|
246
|
-
pending.push(Schema2.decodeUnknownSync(Schema2.Json)(definitions[name]));
|
|
247
|
-
}
|
|
248
|
-
} else if (key !== "$defs") {
|
|
249
|
-
pending.push(value);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return Object.fromEntries([...names].map((name) => [name, definitions[name]]));
|
|
254
|
-
};
|
|
255
|
-
var operationParameters = (parameters, location) => parameters.filter((parameter) => parameter.in === location);
|
|
256
|
-
var httpApiOperationInputSchema = (operation) => {
|
|
257
|
-
const parameters = operation.parameters ?? [];
|
|
258
|
-
const pathParameters = operationParameters(parameters, "path");
|
|
259
|
-
const queryParameters = operationParameters(parameters, "query");
|
|
260
|
-
const headerParameters = operationParameters(parameters, "header");
|
|
261
|
-
const properties = {};
|
|
262
|
-
const required = [];
|
|
263
|
-
const body = operation.requestBody?.content?.["application/json"]?.schema;
|
|
264
|
-
for (const parameter of [
|
|
265
|
-
...pathParameters,
|
|
266
|
-
...queryParameters,
|
|
267
|
-
...headerParameters
|
|
268
|
-
]) {
|
|
269
|
-
const property = {
|
|
270
|
-
...parameter.schema ? schemaWithVisibleAnnotations(parameter.schema) : { type: "string" }
|
|
271
|
-
};
|
|
272
|
-
if (parameter.description)
|
|
273
|
-
property.description = parameter.description;
|
|
274
|
-
properties[parameter.name] = property;
|
|
275
|
-
if (parameter.required)
|
|
276
|
-
required.push(parameter.name);
|
|
277
|
-
}
|
|
278
|
-
if (body) {
|
|
279
|
-
properties.body = body;
|
|
280
|
-
if (operation.requestBody?.required)
|
|
281
|
-
required.push("body");
|
|
282
|
-
}
|
|
283
|
-
return {
|
|
284
|
-
type: "object",
|
|
285
|
-
properties,
|
|
286
|
-
required,
|
|
287
|
-
additionalProperties: false
|
|
288
|
-
};
|
|
289
|
-
};
|
|
290
|
-
var decodeJsonObject = Schema2.decodeUnknownOption(JsonObjectSchema);
|
|
291
|
-
var decodeHttpApiOperationInput = Effect2.fn("Http.decodeApiOperationInput")(function* (input, operation) {
|
|
292
|
-
const payload = yield* Schema2.decodeUnknownEffect(JsonObjectSchema)(input ?? {}).pipe(Effect2.mapError(() => new Error("Tool input must be a JSON object")));
|
|
293
|
-
const decoded = yield* Schema2.decodeUnknownEffect(HttpApiToolInputSchema)(payload).pipe(Effect2.mapError(() => new Error("Tool input must be a JSON object")));
|
|
294
|
-
const parameters = operation.parameters ?? [];
|
|
295
|
-
const pickParameters = (location) => {
|
|
296
|
-
const nestedKey = location === "path" ? "params" : location;
|
|
297
|
-
const nested = decodeJsonObject(payload[nestedKey]).pipe(Option.getOrElse(() => ({})));
|
|
298
|
-
return Object.fromEntries(operationParameters(parameters, location).map((parameter) => [
|
|
299
|
-
parameter.name,
|
|
300
|
-
nested[parameter.name] ?? payload[parameter.name]
|
|
301
|
-
]).filter(([, value]) => value !== undefined));
|
|
302
|
-
};
|
|
303
|
-
return {
|
|
304
|
-
...decoded,
|
|
305
|
-
headers: pickParameters("header"),
|
|
306
|
-
params: pickParameters("path"),
|
|
307
|
-
query: pickParameters("query")
|
|
308
|
-
};
|
|
309
|
-
});
|
|
310
176
|
var reflectedSchema2 = (schema) => Schema2.make(schema.ast);
|
|
311
177
|
var reflectedOperationInputSchemas = (api) => {
|
|
312
178
|
const schemas = new Map;
|
|
@@ -318,23 +184,18 @@ var reflectedOperationInputSchemas = (api) => {
|
|
|
318
184
|
const operationId = Context2.getOrElse(endpoint.annotations, OpenApi2.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
|
|
319
185
|
const fields = {};
|
|
320
186
|
if (endpoint.params)
|
|
321
|
-
fields.params = reflectedSchema2(endpoint.params);
|
|
322
|
-
if (endpoint.query)
|
|
323
|
-
fields.query = Schema2.optionalKey(reflectedSchema2(endpoint.query));
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
fields.body =
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
const inputSchema = Object.keys(fields).length === 0 ? Schema2.Record(Schema2.String, Schema2.Never) : Schema2.Struct(fields);
|
|
335
|
-
schemas.set(operationId, inputSchema.annotate({
|
|
336
|
-
identifier: `${sanitizeHttpName(operationId)}ToolInput`
|
|
337
|
-
}));
|
|
187
|
+
fields.params = Schema2.toType(reflectedSchema2(endpoint.params));
|
|
188
|
+
if (endpoint.query)
|
|
189
|
+
fields.query = Schema2.optionalKey(Schema2.toType(reflectedSchema2(endpoint.query)));
|
|
190
|
+
if (endpoint.headers)
|
|
191
|
+
fields.headers = Schema2.optionalKey(Schema2.toType(reflectedSchema2(endpoint.headers)));
|
|
192
|
+
const payloads = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema2));
|
|
193
|
+
if (payloads.length === 1 && payloads[0])
|
|
194
|
+
fields.body = payloads[0];
|
|
195
|
+
else if (payloads.length > 1)
|
|
196
|
+
fields.body = Schema2.Union(payloads);
|
|
197
|
+
const input = Object.keys(fields).length === 0 ? Schema2.Record(Schema2.String, Schema2.Never) : Schema2.Struct(fields);
|
|
198
|
+
schemas.set(operationId, Schema2.make(Schema2.toCodecJson(input).ast).annotate({ identifier: `${sanitizeHttpName(operationId)}ToolInput` }));
|
|
338
199
|
}
|
|
339
200
|
});
|
|
340
201
|
return schemas;
|
|
@@ -354,42 +215,37 @@ class HttpApiSpec extends Context2.Service()("HttpApiSpec", {
|
|
|
354
215
|
make: (config) => Effect2.try({
|
|
355
216
|
try: () => {
|
|
356
217
|
const api = config.api;
|
|
357
|
-
if (!HttpApi2.isHttpApi(api))
|
|
218
|
+
if (!HttpApi2.isHttpApi(api))
|
|
358
219
|
throw new Error("HttpApiSpec requires a valid HttpApi");
|
|
359
|
-
}
|
|
360
220
|
const spec = OpenApi2.fromApi(api);
|
|
361
|
-
const
|
|
221
|
+
const schemas = reflectedOperationInputSchemas(api);
|
|
222
|
+
const names = new Set;
|
|
362
223
|
const operations = httpApiOperations({
|
|
363
224
|
spec,
|
|
364
225
|
methods: config.methods
|
|
365
|
-
}).filter((
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
return reflected;
|
|
389
|
-
const document = JsonSchema.fromSchemaOpenApi3_1(operationJsonSchema(operation));
|
|
390
|
-
return reflectedSchema2(SchemaRepresentation.fromJsonSchemaDocument(document));
|
|
391
|
-
}
|
|
392
|
-
};
|
|
226
|
+
}).filter((entry) => config.include?.(entry) ?? true).map((entry) => {
|
|
227
|
+
const name = httpApiToolName(entry.method, entry.path, entry.operation);
|
|
228
|
+
if (!name || names.has(name))
|
|
229
|
+
throw new Error(`Duplicate or empty HTTP API tool name: ${name}`);
|
|
230
|
+
names.add(name);
|
|
231
|
+
const inputSchema = entry.operation.operationId ? schemas.get(entry.operation.operationId) : undefined;
|
|
232
|
+
if (!inputSchema)
|
|
233
|
+
throw new Error(`Missing endpoint schema for ${entry.operation.operationId}`);
|
|
234
|
+
const document = Schema2.toJsonSchemaDocument(inputSchema.annotate({ identifier: undefined }));
|
|
235
|
+
return {
|
|
236
|
+
...entry,
|
|
237
|
+
name,
|
|
238
|
+
inputSchema,
|
|
239
|
+
inputJsonSchema: {
|
|
240
|
+
...document.schema,
|
|
241
|
+
$defs: document.definitions
|
|
242
|
+
},
|
|
243
|
+
readOnly: entry.method === "get",
|
|
244
|
+
destructive: entry.method === "delete",
|
|
245
|
+
idempotent: entry.method === "get" || entry.method === "put" || entry.method === "delete"
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
return { info: spec.info, operations };
|
|
393
249
|
},
|
|
394
250
|
catch: (error) => toHttpError("Failed to build HTTP API spec", error)
|
|
395
251
|
})
|
|
@@ -418,12 +274,6 @@ var CliParsedOption = Schema3.declare((value) => Option2.isOption(value) && Opti
|
|
|
418
274
|
onSome: Schema3.is(Schema3.Json)
|
|
419
275
|
})).annotate({ identifier: "HttpApiCliParsedOption" });
|
|
420
276
|
var CliParsedConfig = Schema3.Record(Schema3.String, Schema3.Union([Schema3.Json, CliParsedOption])).annotate({ identifier: "HttpApiCliParsedConfig" });
|
|
421
|
-
var DecodedOperationInput = Schema3.Struct({
|
|
422
|
-
body: Schema3.optional(Schema3.Unknown),
|
|
423
|
-
headers: Schema3.optional(Schema3.Record(Schema3.String, Schema3.Unknown)),
|
|
424
|
-
params: Schema3.optional(Schema3.Record(Schema3.String, Schema3.Unknown)),
|
|
425
|
-
query: Schema3.optional(Schema3.Record(Schema3.String, Schema3.Unknown))
|
|
426
|
-
}).annotate({ identifier: "HttpApiCliDecodedOperationInput" });
|
|
427
277
|
|
|
428
278
|
class HttpApiCli extends Context3.Service()("HttpApiCli", {
|
|
429
279
|
make: () => Effect3.gen(function* () {
|
|
@@ -443,22 +293,12 @@ var operationIdParts = (operation) => operation.operationId?.split(".").filter(B
|
|
|
443
293
|
var operationGroupName = (operation) => fallbackName(operationIdParts(operation)[0] ?? operation.tags?.[0] ?? "default", "default");
|
|
444
294
|
var operationGroupTitle = (operation) => operation.tags?.[0] ?? operationGroupName(operation);
|
|
445
295
|
var operationName = (method, path, operation) => fallbackName(operationIdParts(operation).at(-1) ?? `${method}_${path.replace(/^\/api\//, "").replace(/[/:{}]/g, "_")}`, `${method}_operation`);
|
|
446
|
-
var toCliOperation = ({
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
operation,
|
|
451
|
-
|
|
452
|
-
}) => ({
|
|
453
|
-
groupName: operationGroupName(operation),
|
|
454
|
-
groupTitle: operationGroupTitle(operation),
|
|
455
|
-
name: operationName(method, path, operation),
|
|
456
|
-
method,
|
|
457
|
-
path,
|
|
458
|
-
summary: operation.summary ?? operation.description ?? "",
|
|
459
|
-
operation,
|
|
460
|
-
inputSchema,
|
|
461
|
-
inputJsonSchema
|
|
296
|
+
var toCliOperation = (entry) => ({
|
|
297
|
+
...entry,
|
|
298
|
+
groupName: operationGroupName(entry.operation),
|
|
299
|
+
groupTitle: operationGroupTitle(entry.operation),
|
|
300
|
+
name: operationName(entry.method, entry.path, entry.operation),
|
|
301
|
+
summary: entry.operation.summary ?? entry.operation.description ?? ""
|
|
462
302
|
});
|
|
463
303
|
var httpApiCliOperationGroups = (operations) => {
|
|
464
304
|
const groups = new Map;
|
|
@@ -504,28 +344,18 @@ var schemaType = (schema) => {
|
|
|
504
344
|
return schemaType(effectiveSchema(schema));
|
|
505
345
|
};
|
|
506
346
|
var schemaEnum = (schema) => Schema3.decodeUnknownOption(Schema3.Array(Schema3.String))(effectiveSchema(schema).enum).pipe(Option2.getOrElse(() => []));
|
|
507
|
-
var hasNumericPattern = (schema) => {
|
|
508
|
-
const effective = effectiveSchema(schema);
|
|
509
|
-
if (effective.pattern?.includes("\\d"))
|
|
510
|
-
return true;
|
|
511
|
-
return (effective.allOf ?? []).some((part) => hasNumericPattern(cliJsonSchema(part)));
|
|
512
|
-
};
|
|
513
|
-
var isBooleanSchema = (schema) => {
|
|
514
|
-
const values = schemaEnum(schema);
|
|
515
|
-
return schemaType(schema) === "boolean" || values.includes("true") && values.includes("false");
|
|
516
|
-
};
|
|
517
347
|
var jsonFlag = (name) => Flag.String(name).pipe(Flag.mapTryCatch((value) => Schema3.decodeUnknownSync(JsonFromString)(value), () => `--${name} must be valid JSON`));
|
|
518
348
|
var primitiveFlag = (name, schema, required) => {
|
|
519
349
|
const type = schemaType(schema);
|
|
520
350
|
const choices = schemaEnum(schema);
|
|
521
351
|
let flag;
|
|
522
|
-
if (
|
|
352
|
+
if (type === "boolean") {
|
|
523
353
|
flag = required ? Flag.Literals(name, ["true", "false"]).pipe(Flag.map((value) => value === "true")) : Flag.Boolean(name);
|
|
524
354
|
} else if (choices.length > 0) {
|
|
525
355
|
flag = Flag.Literals(name, choices);
|
|
526
356
|
} else if (type === "integer") {
|
|
527
357
|
flag = Flag.Int(name);
|
|
528
|
-
} else if (type === "number"
|
|
358
|
+
} else if (type === "number") {
|
|
529
359
|
flag = Flag.Finite(name);
|
|
530
360
|
} else if (type === "object" || type === "array") {
|
|
531
361
|
flag = jsonFlag(name);
|
|
@@ -544,7 +374,7 @@ var primitiveArgument = (name, schema) => {
|
|
|
544
374
|
argument = Argument.Literals(name, choices);
|
|
545
375
|
} else if (type === "integer") {
|
|
546
376
|
argument = Argument.Int(name);
|
|
547
|
-
} else if (type === "number"
|
|
377
|
+
} else if (type === "number") {
|
|
548
378
|
argument = Argument.Finite(name);
|
|
549
379
|
} else {
|
|
550
380
|
argument = Argument.String(name);
|
|
@@ -556,26 +386,26 @@ var operationInputDrafts = (operation) => {
|
|
|
556
386
|
const document = cliJsonSchema(operation.inputJsonSchema);
|
|
557
387
|
const definitions = document.$defs ?? {};
|
|
558
388
|
const resolveSchema = (value) => {
|
|
559
|
-
const schema = cliJsonSchema(value);
|
|
389
|
+
const schema = effectiveSchema(cliJsonSchema(value));
|
|
560
390
|
const name = schema.$ref?.match(/^#\/(?:\$defs|components\/schemas)\/(.+)$/)?.[1];
|
|
561
391
|
return name && definitions[name] ? effectiveSchema(cliJsonSchema(definitions[name])) : effectiveSchema(schema);
|
|
562
392
|
};
|
|
563
393
|
const properties = document.properties ?? {};
|
|
564
|
-
const drafts =
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
{
|
|
394
|
+
const drafts = [];
|
|
395
|
+
for (const location of ["params", "headers", "query"]) {
|
|
396
|
+
const group = resolveSchema(properties[location]);
|
|
397
|
+
const required = new Set(group.required ?? []);
|
|
398
|
+
for (const [name, schema] of Object.entries(group.properties ?? {})) {
|
|
399
|
+
drafts.push({
|
|
570
400
|
location,
|
|
571
|
-
name
|
|
572
|
-
preferredName: fallbackName(
|
|
573
|
-
required:
|
|
574
|
-
schema: resolveSchema(
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
const body = resolveSchema(properties.body
|
|
401
|
+
name,
|
|
402
|
+
preferredName: fallbackName(name, location),
|
|
403
|
+
required: required.has(name),
|
|
404
|
+
schema: resolveSchema(schema)
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const body = resolveSchema(properties.body);
|
|
579
409
|
if (schemaType(body) === "object" && body.properties) {
|
|
580
410
|
const required = new Set(body.required ?? []);
|
|
581
411
|
for (const [name, schema] of Object.entries(body.properties)) {
|
|
@@ -587,12 +417,12 @@ var operationInputDrafts = (operation) => {
|
|
|
587
417
|
schema: resolveSchema(schema)
|
|
588
418
|
});
|
|
589
419
|
}
|
|
590
|
-
} else if (
|
|
420
|
+
} else if (properties.body) {
|
|
591
421
|
drafts.push({
|
|
592
422
|
location: "body",
|
|
593
423
|
name: "body",
|
|
594
424
|
preferredName: "body",
|
|
595
|
-
required:
|
|
425
|
+
required: document.required?.includes("body") ?? false,
|
|
596
426
|
schema: body
|
|
597
427
|
});
|
|
598
428
|
}
|
|
@@ -618,7 +448,6 @@ var operationInputs = (operation) => {
|
|
|
618
448
|
location: draft.location,
|
|
619
449
|
name: draft.name,
|
|
620
450
|
optionName,
|
|
621
|
-
wireString: draft.location !== "body" && schemaType(draft.schema) === "string",
|
|
622
451
|
param: draft.location === "params" ? primitiveArgument(optionName, draft.schema) : primitiveFlag(optionName, draft.schema, draft.required)
|
|
623
452
|
};
|
|
624
453
|
});
|
|
@@ -632,7 +461,7 @@ var printHttpApiCliResult = Effect3.fn("HttpApiCli.printResult")(function* (resp
|
|
|
632
461
|
const encoded = yield* client.encodeResult(response, operation);
|
|
633
462
|
return yield* print(JSON.stringify(encoded, null, 2) ?? "null");
|
|
634
463
|
});
|
|
635
|
-
var callOperation = (operation, inputs, callConfig, client)
|
|
464
|
+
var callOperation = Effect3.fn("HttpApiCli.callOperation")(function* (operation, inputs, callConfig, client) {
|
|
636
465
|
const emptyValues = () => ({});
|
|
637
466
|
const assembled = {
|
|
638
467
|
body: emptyValues(),
|
|
@@ -644,10 +473,10 @@ var callOperation = (operation, inputs, callConfig, client) => Effect3.gen(funct
|
|
|
644
473
|
const parsed = callConfig[input2.configKey];
|
|
645
474
|
const value = Option2.isOption(parsed) ? Option2.getOrUndefined(parsed) : parsed;
|
|
646
475
|
if (value !== undefined) {
|
|
647
|
-
assembled[input2.location][input2.name] =
|
|
476
|
+
assembled[input2.location][input2.name] = value;
|
|
648
477
|
}
|
|
649
478
|
}
|
|
650
|
-
const bodyInput = inputs.some((input2) => input2.location === "body" && input2.name === "body") ? assembled.body.body : Object.keys(assembled.body).length > 0 || operation.
|
|
479
|
+
const bodyInput = inputs.some((input2) => input2.location === "body" && input2.name === "body") ? assembled.body.body : Object.keys(assembled.body).length > 0 || cliJsonSchema(operation.inputJsonSchema).required?.includes("body") ? assembled.body : undefined;
|
|
651
480
|
const rawInput = {};
|
|
652
481
|
if (inputs.some((input2) => input2.location === "params")) {
|
|
653
482
|
rawInput.params = assembled.params;
|
|
@@ -660,26 +489,9 @@ var callOperation = (operation, inputs, callConfig, client) => Effect3.gen(funct
|
|
|
660
489
|
}
|
|
661
490
|
if (bodyInput !== undefined)
|
|
662
491
|
rawInput.body = bodyInput;
|
|
663
|
-
const
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
operation: {
|
|
667
|
-
method: operation.method,
|
|
668
|
-
path: operation.path,
|
|
669
|
-
operation: operation.operation
|
|
670
|
-
},
|
|
671
|
-
input: {
|
|
672
|
-
body: input.body,
|
|
673
|
-
headers: input.headers ?? {},
|
|
674
|
-
params: input.params ?? {},
|
|
675
|
-
query: input.query ?? {}
|
|
676
|
-
}
|
|
677
|
-
});
|
|
678
|
-
return yield* printHttpApiCliResult(response, {
|
|
679
|
-
method: operation.method,
|
|
680
|
-
path: operation.path,
|
|
681
|
-
operation: operation.operation
|
|
682
|
-
}, client);
|
|
492
|
+
const input = yield* Schema3.decodeUnknownEffect(operation.inputSchema)(rawInput).pipe(Effect3.mapError((error) => new Error(`Invalid input for ${operation.groupName} ${operation.name}: ${error.message}`)));
|
|
493
|
+
const response = yield* executeHttpApiOperation({ operation, input }, client);
|
|
494
|
+
return yield* printHttpApiCliResult(response, operation, client);
|
|
683
495
|
});
|
|
684
496
|
var listCommand = (groups) => Command.make("list", {}, () => print([
|
|
685
497
|
"GROUP\tTITLE\tCOMMANDS",
|
|
@@ -706,11 +518,7 @@ var makeHttpApiCliCommand = Effect3.fn("HttpApiCli.makeCommand")(function* () {
|
|
|
706
518
|
const client = yield* ApiClient;
|
|
707
519
|
const name = fallbackName(spec.info.title, "api");
|
|
708
520
|
const groups = yield* Effect3.try({
|
|
709
|
-
try: () => httpApiCliOperationGroups(spec.operations.map(
|
|
710
|
-
...operation,
|
|
711
|
-
inputSchema: spec.operationSchema(operation.operation),
|
|
712
|
-
inputJsonSchema: spec.operationJsonSchema(operation.operation)
|
|
713
|
-
}))),
|
|
521
|
+
try: () => httpApiCliOperationGroups(spec.operations.map(toCliOperation)),
|
|
714
522
|
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
715
523
|
});
|
|
716
524
|
const groupCommands = yield* Effect3.all(groups.map((group) => groupCommand(group, client)));
|
|
@@ -12,6 +12,10 @@ export type ApiClientExecuteOptions = {
|
|
|
12
12
|
readonly operation: HttpApiOperationEntry;
|
|
13
13
|
readonly input: HttpApiOperationInput;
|
|
14
14
|
};
|
|
15
|
+
export declare const executeHttpApiOperation: (options: {
|
|
16
|
+
readonly operation: HttpApiOperationEntry;
|
|
17
|
+
readonly input: Partial<HttpApiOperationInput>;
|
|
18
|
+
}, client: ApiClientService) => Effect.Effect<unknown, Error, never>;
|
|
15
19
|
export type HttpApiOperationResultValue = null | undefined | string | number | boolean | Date | Uint8Array | ReadonlyArray<HttpApiOperationResultValue> | {
|
|
16
20
|
readonly [key: string]: HttpApiOperationResultValue;
|
|
17
21
|
};
|
|
@@ -6,6 +6,15 @@ import {
|
|
|
6
6
|
HttpApiClient as EffectHttpApiClient,
|
|
7
7
|
OpenApi
|
|
8
8
|
} from "effect/http-api";
|
|
9
|
+
var executeHttpApiOperation = Effect.fn("HttpApiClient.executeOperation")((options, client) => client.execute({
|
|
10
|
+
operation: options.operation,
|
|
11
|
+
input: {
|
|
12
|
+
body: options.input.body,
|
|
13
|
+
headers: options.input.headers ?? {},
|
|
14
|
+
params: options.input.params ?? {},
|
|
15
|
+
query: options.input.query ?? {}
|
|
16
|
+
}
|
|
17
|
+
}));
|
|
9
18
|
var UndefinedFromNull = Schema.Null.pipe(Schema.decodeTo(Schema.Undefined, SchemaTransformation.transform({
|
|
10
19
|
decode: () => {
|
|
11
20
|
return;
|
|
@@ -108,5 +117,6 @@ export {
|
|
|
108
117
|
ApiClient,
|
|
109
118
|
HttpApiOperationResult,
|
|
110
119
|
encodeHttpApiOperationResult,
|
|
120
|
+
executeHttpApiOperation,
|
|
111
121
|
makeHttpApiOperationResultEncoder
|
|
112
122
|
};
|