@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 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-toolkit";
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`:
@@ -1056,7 +1056,7 @@ var DEFAULT_WIDTH = 208;
1056
1056
  var DEFAULT_MAX_WIDTH = 640;
1057
1057
  var DEFAULT_QUERY = {
1058
1058
  page: 0,
1059
- pageSize: 10
1059
+ pageSize: 20
1060
1060
  };
1061
1061
  var DEFAULT_UI_STATE = {
1062
1062
  columnVisibility: {},
@@ -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>${escapeHtml(children)}</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 ?? "");
@@ -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
- if (endpoint.headers) {
326
- fields.headers = Schema2.optionalKey(reflectedSchema2(endpoint.headers));
327
- }
328
- const payloadSchemas = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema2));
329
- if (payloadSchemas.length === 1 && payloadSchemas[0]) {
330
- fields.body = payloadSchemas[0];
331
- } else if (payloadSchemas.length > 1) {
332
- fields.body = Schema2.Union(payloadSchemas);
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 reflectedSchemas = reflectedOperationInputSchemas(api);
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((operation) => config.include?.(operation) ?? true);
366
- const operationJsonSchema = (operation) => {
367
- const schema = httpApiOperationInputSchema(operation);
368
- const document = JsonSchema.fromSchemaOpenApi3_1({
369
- ...schema,
370
- $defs: spec.components?.schemas
371
- });
372
- const definitions = document.definitions ?? {};
373
- const usedDefinitions = referencedDefinitions(document.schema, definitions);
374
- const result = { ...document.schema };
375
- if (Object.keys(usedDefinitions).length > 0) {
376
- result.$defs = usedDefinitions;
377
- }
378
- return result;
379
- };
380
- return {
381
- info: spec.info,
382
- operations,
383
- decodeOperationInput: decodeHttpApiOperationInput,
384
- operationJsonSchema,
385
- operationSchema: (operation) => {
386
- const reflected = operation.operationId ? reflectedSchemas.get(operation.operationId) : undefined;
387
- if (reflected)
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
- inputJsonSchema,
448
- inputSchema,
449
- method,
450
- operation,
451
- path
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 (isBooleanSchema(schema)) {
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" || hasNumericPattern(schema)) {
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" || hasNumericPattern(schema)) {
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 = (operation.operation.parameters ?? []).flatMap((parameter) => {
565
- if (parameter.in === "cookie")
566
- return [];
567
- const location = parameter.in === "path" ? "params" : parameter.in === "header" ? "headers" : "query";
568
- return [
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: parameter.name,
572
- preferredName: fallbackName(parameter.name, location),
573
- required: parameter.required ?? false,
574
- schema: resolveSchema(properties[parameter.name] ?? parameter.schema)
575
- }
576
- ];
577
- });
578
- const body = resolveSchema(properties.body ?? operation.operation.requestBody?.content?.["application/json"]?.schema);
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 (operation.operation.requestBody) {
420
+ } else if (properties.body) {
591
421
  drafts.push({
592
422
  location: "body",
593
423
  name: "body",
594
424
  preferredName: "body",
595
- required: operation.operation.requestBody.required ?? false,
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) => Effect3.gen(function* () {
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] = input2.wireString ? String(value) : value;
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.operation.requestBody?.required ? assembled.body : undefined;
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 decoded = yield* Schema3.decodeUnknownEffect(operation.inputSchema)(rawInput).pipe(Effect3.mapError((error) => new Error(`Invalid input for ${operation.groupName} ${operation.name}: ${error.message}`)));
664
- const input = yield* Schema3.decodeUnknownEffect(DecodedOperationInput)(decoded);
665
- const response = yield* client.execute({
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((operation) => toCliOperation({
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
  };