@krak-stack/registry 0.1.17 → 0.1.19

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
@@ -33,6 +33,11 @@ import { Query } from "@krak-stack/registry/query";
33
33
  import { createSeo } from "@krak-stack/registry/seo";
34
34
  import { FileExtractionService } from "@krak-stack/registry/service-file-extraction";
35
35
  import { FileExtractedTextSchema } from "@krak-stack/registry/service-file-extraction/schema";
36
+ import {
37
+ HealthApiGroup,
38
+ healthHandler,
39
+ HealthService,
40
+ } from "@krak-stack/registry/service-health";
36
41
  import { NotificationService } from "@krak-stack/registry/service-notification";
37
42
  ```
38
43
 
@@ -1,4 +1,4 @@
1
- import { Context, Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect";
1
+ import { Cause, Context, Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect";
2
2
  import { Command } from "effect/unstable/cli";
3
3
  import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
4
4
  import { ApiClient, type ApiClientService } from "./httpapi-client.js";
@@ -19,5 +19,6 @@ export declare const printHttpApiCliResult: (response: unknown, operation: HttpA
19
19
  export declare const makeHttpApiCliCommand: () => Effect.Effect<Command.Command<string, {}, {}, unknown, never>, never, ApiClient | HttpApiSpec>;
20
20
  export declare const httpApiCliEnvironmentLayer: (args: ReadonlyArray<string>) => Layer.Layer<ChildProcessSpawner | FileSystem.FileSystem | Path.Path | Stdio.Stdio | Terminal.Terminal, never, never>;
21
21
  export declare const httpApiCli: (args?: string[]) => Effect.Effect<void, unknown, HttpApiCli | Command.Environment>;
22
+ export declare const formatHttpApiCliCause: <E>(cause: Cause.Cause<E>) => string;
22
23
  export declare const runHttpApiCli: <E>(layer: Layer.Layer<HttpApiCli, E>, args?: string[]) => void;
23
24
  export {};
@@ -1,8 +1,10 @@
1
1
  // ../../src/lib/httpapi-cli.ts
2
2
  import {
3
+ Cause,
3
4
  Console,
4
5
  Context as Context3,
5
6
  Effect as Effect3,
7
+ Exit,
6
8
  FileSystem,
7
9
  Layer as Layer3,
8
10
  Path,
@@ -15,13 +17,22 @@ import { Command, Flag } from "effect/unstable/cli";
15
17
  import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
16
18
 
17
19
  // ../../src/lib/httpapi-client.ts
18
- import { Context, Effect, Layer, Schema } from "effect";
20
+ import { Context, Effect, Layer, Schema, SchemaTransformation } from "effect";
19
21
  import { HttpClient } from "effect/unstable/http";
20
22
  import {
21
- HttpApiClient as EffectHttpApiClient
23
+ HttpApi,
24
+ HttpApiClient as EffectHttpApiClient,
25
+ OpenApi
22
26
  } from "effect/unstable/httpapi";
27
+ var UndefinedFromNull = Schema.Null.pipe(Schema.decodeTo(Schema.Undefined, SchemaTransformation.transform({
28
+ decode: () => {
29
+ return;
30
+ },
31
+ encode: () => null
32
+ })));
23
33
  var HttpApiOperationResultValue = Schema.suspend(() => Schema.Union([
24
34
  Schema.Null,
35
+ UndefinedFromNull,
25
36
  Schema.String,
26
37
  Schema.Number,
27
38
  Schema.Boolean,
@@ -35,6 +46,37 @@ var HttpApiOperationResult = Schema.Union([
35
46
  Schema.Undefined
36
47
  ]).annotate({ identifier: "HttpApiOperationResult" });
37
48
  var encodeHttpApiOperationResult = Effect.fn("HttpApiClient.encodeOperationResult")((result) => Schema.encodeUnknownEffect(HttpApiOperationResult)(result).pipe(Effect.map((encoded) => encoded ?? null), Effect.mapError((cause) => new Error("HTTP API result is not serializable", { cause }))));
49
+ var reflectedSchema = (schema) => Schema.make(schema.ast);
50
+ var operationResultSchemas = (api) => {
51
+ const schemas = new Map;
52
+ HttpApi.reflect(api, {
53
+ onGroup: () => {
54
+ return;
55
+ },
56
+ onEndpoint: ({ endpoint, group, successes }) => {
57
+ const operationId = Context.getOrElse(endpoint.annotations, OpenApi.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
58
+ const operationSchemas = Array.from(successes.values()).flat().map(reflectedSchema);
59
+ if (operationSchemas.length === 1 && operationSchemas[0]) {
60
+ schemas.set(operationId, operationSchemas[0]);
61
+ } else if (operationSchemas.length > 1) {
62
+ schemas.set(operationId, Schema.Union(operationSchemas));
63
+ }
64
+ }
65
+ });
66
+ return schemas;
67
+ };
68
+ var makeHttpApiOperationResultEncoder = (api) => {
69
+ const schemas = operationResultSchemas(api);
70
+ return Effect.fn("HttpApiClient.encodeOperationResultWithSchema")((result, operation) => {
71
+ const operationId = operation.operation.operationId;
72
+ const schema = operationId ? schemas.get(operationId) : undefined;
73
+ if (!schema)
74
+ return encodeHttpApiOperationResult(result);
75
+ return Schema.encodeUnknownEffect(schema)(result).pipe(Effect.flatMap(encodeHttpApiOperationResult), Effect.mapError((cause) => new Error("HTTP API result does not match its success schema", {
76
+ cause
77
+ })));
78
+ });
79
+ };
38
80
  var GeneratedOperation = Schema.declare((value) => value instanceof Function).annotate({ identifier: "GeneratedOperation" });
39
81
  var GeneratedOperationEffect = Schema.declare((value) => Effect.isEffect(value)).annotate({ identifier: "GeneratedOperationEffect" });
40
82
  var makeGeneratedClient = (api, http, baseUrl) => EffectHttpApiClient.makeWith(api, {
@@ -70,9 +112,10 @@ var executeGeneratedOperation = Effect.fn("HttpApiClient.executeGeneratedOperati
70
112
  class ApiClient extends Context.Service()("ApiClient") {
71
113
  static layer = (config) => Layer.effect(this, Effect.gen(function* () {
72
114
  const http = yield* HttpClient.HttpClient;
115
+ const encodeResult = makeHttpApiOperationResultEncoder(config.api);
73
116
  const client = yield* makeGeneratedClient(config.api, http, config.baseUrl);
74
117
  return {
75
- encodeResult: config.encodeResult ?? ((result) => encodeHttpApiOperationResult(result)),
118
+ encodeResult: config.encodeResult ?? ((result, operation) => encodeResult(result, operation)),
76
119
  execute: Effect.fn("ApiClient.execute")(function* (options) {
77
120
  return yield* executeGeneratedOperation(client, options);
78
121
  })
@@ -90,7 +133,7 @@ import {
90
133
  Schema as Schema2,
91
134
  SchemaRepresentation
92
135
  } from "effect";
93
- import { HttpApi as HttpApi2, OpenApi } from "effect/unstable/httpapi";
136
+ import { HttpApi as HttpApi2, OpenApi as OpenApi2 } from "effect/unstable/httpapi";
94
137
  var JsonObjectSchema = Schema2.Record(Schema2.String, Schema2.Json).annotate({
95
138
  identifier: "HttpJsonObject",
96
139
  title: "HTTP JSON object",
@@ -263,7 +306,7 @@ var decodeHttpApiOperationInput = Effect2.fn("Http.decodeApiOperationInput")(fun
263
306
  query: pickParameters("query")
264
307
  };
265
308
  });
266
- var reflectedSchema = (schema) => Schema2.make(schema.ast);
309
+ var reflectedSchema2 = (schema) => Schema2.make(schema.ast);
267
310
  var reflectedOperationInputSchemas = (api) => {
268
311
  const schemas = new Map;
269
312
  HttpApi2.reflect(api, {
@@ -271,17 +314,17 @@ var reflectedOperationInputSchemas = (api) => {
271
314
  return;
272
315
  },
273
316
  onEndpoint: ({ endpoint, group }) => {
274
- const operationId = Context2.getOrElse(endpoint.annotations, OpenApi.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
317
+ const operationId = Context2.getOrElse(endpoint.annotations, OpenApi2.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
275
318
  const fields = {};
276
319
  if (endpoint.params)
277
- fields.params = reflectedSchema(endpoint.params);
320
+ fields.params = reflectedSchema2(endpoint.params);
278
321
  if (endpoint.query) {
279
- fields.query = Schema2.optionalKey(reflectedSchema(endpoint.query));
322
+ fields.query = Schema2.optionalKey(reflectedSchema2(endpoint.query));
280
323
  }
281
324
  if (endpoint.headers) {
282
- fields.headers = Schema2.optionalKey(reflectedSchema(endpoint.headers));
325
+ fields.headers = Schema2.optionalKey(reflectedSchema2(endpoint.headers));
283
326
  }
284
- const payloadSchemas = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema));
327
+ const payloadSchemas = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema2));
285
328
  if (payloadSchemas.length === 1 && payloadSchemas[0]) {
286
329
  fields.body = payloadSchemas[0];
287
330
  } else if (payloadSchemas.length > 1) {
@@ -313,7 +356,7 @@ class HttpApiSpec extends Context2.Service()("HttpApiSpec", {
313
356
  if (!HttpApi2.isHttpApi(api)) {
314
357
  throw new Error("HttpApiSpec requires a valid HttpApi");
315
358
  }
316
- const spec = OpenApi.fromApi(api);
359
+ const spec = OpenApi2.fromApi(api);
317
360
  const reflectedSchemas = reflectedOperationInputSchemas(api);
318
361
  const operations = httpApiOperations({
319
362
  spec,
@@ -477,11 +520,17 @@ var httpApiCli = (args = process.argv.slice(2)) => Effect3.gen(function* () {
477
520
  const cli = yield* HttpApiCli;
478
521
  return yield* cli.run(args);
479
522
  });
523
+ var formatHttpApiCliCause = Cause.pretty;
480
524
  var runHttpApiCli = (layer, args = process.argv.slice(2)) => {
481
- Effect3.runPromise(httpApiCli(args).pipe(Effect3.provide(layer), Effect3.provide(httpApiCliEnvironmentLayer(args)))).catch((error) => {
482
- console.error(error instanceof Error ? error.message : error);
483
- process.exit(1);
484
- });
525
+ Effect3.runPromiseExit(httpApiCli(args).pipe(Effect3.provide(layer), Effect3.provide(httpApiCliEnvironmentLayer(args)))).then(Exit.match({
526
+ onSuccess: () => {
527
+ return;
528
+ },
529
+ onFailure: (cause) => {
530
+ console.error(formatHttpApiCliCause(cause));
531
+ process.exitCode = 1;
532
+ }
533
+ }));
485
534
  };
486
535
  export {
487
536
  runHttpApiCli,
@@ -489,5 +538,6 @@ export {
489
538
  makeHttpApiCliCommand,
490
539
  httpApiCliEnvironmentLayer,
491
540
  httpApiCli,
541
+ formatHttpApiCliCause,
492
542
  HttpApiCli
493
543
  };
@@ -12,12 +12,13 @@ export type ApiClientExecuteOptions = {
12
12
  readonly operation: HttpApiOperationEntry;
13
13
  readonly input: HttpApiOperationInput;
14
14
  };
15
- export type HttpApiOperationResultValue = null | string | number | boolean | Date | Uint8Array | ReadonlyArray<HttpApiOperationResultValue> | {
15
+ export type HttpApiOperationResultValue = null | undefined | string | number | boolean | Date | Uint8Array | ReadonlyArray<HttpApiOperationResultValue> | {
16
16
  readonly [key: string]: HttpApiOperationResultValue;
17
17
  };
18
18
  export declare const HttpApiOperationResult: Schema.Union<readonly [Schema.Codec<HttpApiOperationResultValue, Json, never, never>, Schema.Undefined]>;
19
19
  export type HttpApiOperationResult = typeof HttpApiOperationResult.Type;
20
20
  export declare const encodeHttpApiOperationResult: (result: unknown) => Effect.Effect<string | number | boolean | Schema.JsonArray | Schema.JsonObject | null, Error, never>;
21
+ export declare const makeHttpApiOperationResultEncoder: <Id extends string, Groups extends HttpApiGroup.Constraint>(api: HttpApi.HttpApi<Id, Groups>) => (result: unknown, operation: HttpApiOperationEntry) => Effect.Effect<string | number | boolean | Schema.JsonArray | Schema.JsonObject | null, Error, never>;
21
22
  export type ApiClientService = {
22
23
  readonly encodeResult: (result: ErrorOptions["cause"], operation: HttpApiOperationEntry) => Effect.Effect<Json, Error>;
23
24
  readonly execute: (options: ApiClientExecuteOptions) => Effect.Effect<ErrorOptions["cause"], Error>;
@@ -1,11 +1,20 @@
1
1
  // ../../src/lib/httpapi-client.ts
2
- import { Context, Effect, Layer, Schema } from "effect";
2
+ import { Context, Effect, Layer, Schema, SchemaTransformation } from "effect";
3
3
  import { HttpClient } from "effect/unstable/http";
4
4
  import {
5
- HttpApiClient as EffectHttpApiClient
5
+ HttpApi,
6
+ HttpApiClient as EffectHttpApiClient,
7
+ OpenApi
6
8
  } from "effect/unstable/httpapi";
9
+ var UndefinedFromNull = Schema.Null.pipe(Schema.decodeTo(Schema.Undefined, SchemaTransformation.transform({
10
+ decode: () => {
11
+ return;
12
+ },
13
+ encode: () => null
14
+ })));
7
15
  var HttpApiOperationResultValue = Schema.suspend(() => Schema.Union([
8
16
  Schema.Null,
17
+ UndefinedFromNull,
9
18
  Schema.String,
10
19
  Schema.Number,
11
20
  Schema.Boolean,
@@ -19,6 +28,37 @@ var HttpApiOperationResult = Schema.Union([
19
28
  Schema.Undefined
20
29
  ]).annotate({ identifier: "HttpApiOperationResult" });
21
30
  var encodeHttpApiOperationResult = Effect.fn("HttpApiClient.encodeOperationResult")((result) => Schema.encodeUnknownEffect(HttpApiOperationResult)(result).pipe(Effect.map((encoded) => encoded ?? null), Effect.mapError((cause) => new Error("HTTP API result is not serializable", { cause }))));
31
+ var reflectedSchema = (schema) => Schema.make(schema.ast);
32
+ var operationResultSchemas = (api) => {
33
+ const schemas = new Map;
34
+ HttpApi.reflect(api, {
35
+ onGroup: () => {
36
+ return;
37
+ },
38
+ onEndpoint: ({ endpoint, group, successes }) => {
39
+ const operationId = Context.getOrElse(endpoint.annotations, OpenApi.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
40
+ const operationSchemas = Array.from(successes.values()).flat().map(reflectedSchema);
41
+ if (operationSchemas.length === 1 && operationSchemas[0]) {
42
+ schemas.set(operationId, operationSchemas[0]);
43
+ } else if (operationSchemas.length > 1) {
44
+ schemas.set(operationId, Schema.Union(operationSchemas));
45
+ }
46
+ }
47
+ });
48
+ return schemas;
49
+ };
50
+ var makeHttpApiOperationResultEncoder = (api) => {
51
+ const schemas = operationResultSchemas(api);
52
+ return Effect.fn("HttpApiClient.encodeOperationResultWithSchema")((result, operation) => {
53
+ const operationId = operation.operation.operationId;
54
+ const schema = operationId ? schemas.get(operationId) : undefined;
55
+ if (!schema)
56
+ return encodeHttpApiOperationResult(result);
57
+ return Schema.encodeUnknownEffect(schema)(result).pipe(Effect.flatMap(encodeHttpApiOperationResult), Effect.mapError((cause) => new Error("HTTP API result does not match its success schema", {
58
+ cause
59
+ })));
60
+ });
61
+ };
22
62
  var GeneratedOperation = Schema.declare((value) => value instanceof Function).annotate({ identifier: "GeneratedOperation" });
23
63
  var GeneratedOperationEffect = Schema.declare((value) => Effect.isEffect(value)).annotate({ identifier: "GeneratedOperationEffect" });
24
64
  var makeGeneratedClient = (api, http, baseUrl) => EffectHttpApiClient.makeWith(api, {
@@ -54,9 +94,10 @@ var executeGeneratedOperation = Effect.fn("HttpApiClient.executeGeneratedOperati
54
94
  class ApiClient extends Context.Service()("ApiClient") {
55
95
  static layer = (config) => Layer.effect(this, Effect.gen(function* () {
56
96
  const http = yield* HttpClient.HttpClient;
97
+ const encodeResult = makeHttpApiOperationResultEncoder(config.api);
57
98
  const client = yield* makeGeneratedClient(config.api, http, config.baseUrl);
58
99
  return {
59
- encodeResult: config.encodeResult ?? ((result) => encodeHttpApiOperationResult(result)),
100
+ encodeResult: config.encodeResult ?? ((result, operation) => encodeResult(result, operation)),
60
101
  execute: Effect.fn("ApiClient.execute")(function* (options) {
61
102
  return yield* executeGeneratedOperation(client, options);
62
103
  })
@@ -64,6 +105,7 @@ class ApiClient extends Context.Service()("ApiClient") {
64
105
  }));
65
106
  }
66
107
  export {
108
+ makeHttpApiOperationResultEncoder,
67
109
  encodeHttpApiOperationResult,
68
110
  HttpApiOperationResult,
69
111
  ApiClient
@@ -3,13 +3,22 @@ import { Context as Context3, Effect as Effect3, Layer as Layer3, Option as Opti
3
3
  import { McpSchema, McpServer } from "effect/unstable/ai";
4
4
 
5
5
  // ../../src/lib/httpapi-client.ts
6
- import { Context, Effect, Layer, Schema } from "effect";
6
+ import { Context, Effect, Layer, Schema, SchemaTransformation } from "effect";
7
7
  import { HttpClient } from "effect/unstable/http";
8
8
  import {
9
- HttpApiClient as EffectHttpApiClient
9
+ HttpApi,
10
+ HttpApiClient as EffectHttpApiClient,
11
+ OpenApi
10
12
  } from "effect/unstable/httpapi";
13
+ var UndefinedFromNull = Schema.Null.pipe(Schema.decodeTo(Schema.Undefined, SchemaTransformation.transform({
14
+ decode: () => {
15
+ return;
16
+ },
17
+ encode: () => null
18
+ })));
11
19
  var HttpApiOperationResultValue = Schema.suspend(() => Schema.Union([
12
20
  Schema.Null,
21
+ UndefinedFromNull,
13
22
  Schema.String,
14
23
  Schema.Number,
15
24
  Schema.Boolean,
@@ -23,6 +32,37 @@ var HttpApiOperationResult = Schema.Union([
23
32
  Schema.Undefined
24
33
  ]).annotate({ identifier: "HttpApiOperationResult" });
25
34
  var encodeHttpApiOperationResult = Effect.fn("HttpApiClient.encodeOperationResult")((result) => Schema.encodeUnknownEffect(HttpApiOperationResult)(result).pipe(Effect.map((encoded) => encoded ?? null), Effect.mapError((cause) => new Error("HTTP API result is not serializable", { cause }))));
35
+ var reflectedSchema = (schema) => Schema.make(schema.ast);
36
+ var operationResultSchemas = (api) => {
37
+ const schemas = new Map;
38
+ HttpApi.reflect(api, {
39
+ onGroup: () => {
40
+ return;
41
+ },
42
+ onEndpoint: ({ endpoint, group, successes }) => {
43
+ const operationId = Context.getOrElse(endpoint.annotations, OpenApi.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
44
+ const operationSchemas = Array.from(successes.values()).flat().map(reflectedSchema);
45
+ if (operationSchemas.length === 1 && operationSchemas[0]) {
46
+ schemas.set(operationId, operationSchemas[0]);
47
+ } else if (operationSchemas.length > 1) {
48
+ schemas.set(operationId, Schema.Union(operationSchemas));
49
+ }
50
+ }
51
+ });
52
+ return schemas;
53
+ };
54
+ var makeHttpApiOperationResultEncoder = (api) => {
55
+ const schemas = operationResultSchemas(api);
56
+ return Effect.fn("HttpApiClient.encodeOperationResultWithSchema")((result, operation) => {
57
+ const operationId = operation.operation.operationId;
58
+ const schema = operationId ? schemas.get(operationId) : undefined;
59
+ if (!schema)
60
+ return encodeHttpApiOperationResult(result);
61
+ return Schema.encodeUnknownEffect(schema)(result).pipe(Effect.flatMap(encodeHttpApiOperationResult), Effect.mapError((cause) => new Error("HTTP API result does not match its success schema", {
62
+ cause
63
+ })));
64
+ });
65
+ };
26
66
  var GeneratedOperation = Schema.declare((value) => value instanceof Function).annotate({ identifier: "GeneratedOperation" });
27
67
  var GeneratedOperationEffect = Schema.declare((value) => Effect.isEffect(value)).annotate({ identifier: "GeneratedOperationEffect" });
28
68
  var makeGeneratedClient = (api, http, baseUrl) => EffectHttpApiClient.makeWith(api, {
@@ -58,9 +98,10 @@ var executeGeneratedOperation = Effect.fn("HttpApiClient.executeGeneratedOperati
58
98
  class ApiClient extends Context.Service()("ApiClient") {
59
99
  static layer = (config) => Layer.effect(this, Effect.gen(function* () {
60
100
  const http = yield* HttpClient.HttpClient;
101
+ const encodeResult = makeHttpApiOperationResultEncoder(config.api);
61
102
  const client = yield* makeGeneratedClient(config.api, http, config.baseUrl);
62
103
  return {
63
- encodeResult: config.encodeResult ?? ((result) => encodeHttpApiOperationResult(result)),
104
+ encodeResult: config.encodeResult ?? ((result, operation) => encodeResult(result, operation)),
64
105
  execute: Effect.fn("ApiClient.execute")(function* (options) {
65
106
  return yield* executeGeneratedOperation(client, options);
66
107
  })
@@ -78,7 +119,7 @@ import {
78
119
  Schema as Schema2,
79
120
  SchemaRepresentation
80
121
  } from "effect";
81
- import { HttpApi as HttpApi2, OpenApi } from "effect/unstable/httpapi";
122
+ import { HttpApi as HttpApi2, OpenApi as OpenApi2 } from "effect/unstable/httpapi";
82
123
  var JsonObjectSchema = Schema2.Record(Schema2.String, Schema2.Json).annotate({
83
124
  identifier: "HttpJsonObject",
84
125
  title: "HTTP JSON object",
@@ -251,7 +292,7 @@ var decodeHttpApiOperationInput = Effect2.fn("Http.decodeApiOperationInput")(fun
251
292
  query: pickParameters("query")
252
293
  };
253
294
  });
254
- var reflectedSchema = (schema) => Schema2.make(schema.ast);
295
+ var reflectedSchema2 = (schema) => Schema2.make(schema.ast);
255
296
  var reflectedOperationInputSchemas = (api) => {
256
297
  const schemas = new Map;
257
298
  HttpApi2.reflect(api, {
@@ -259,17 +300,17 @@ var reflectedOperationInputSchemas = (api) => {
259
300
  return;
260
301
  },
261
302
  onEndpoint: ({ endpoint, group }) => {
262
- const operationId = Context2.getOrElse(endpoint.annotations, OpenApi.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
303
+ const operationId = Context2.getOrElse(endpoint.annotations, OpenApi2.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
263
304
  const fields = {};
264
305
  if (endpoint.params)
265
- fields.params = reflectedSchema(endpoint.params);
306
+ fields.params = reflectedSchema2(endpoint.params);
266
307
  if (endpoint.query) {
267
- fields.query = Schema2.optionalKey(reflectedSchema(endpoint.query));
308
+ fields.query = Schema2.optionalKey(reflectedSchema2(endpoint.query));
268
309
  }
269
310
  if (endpoint.headers) {
270
- fields.headers = Schema2.optionalKey(reflectedSchema(endpoint.headers));
311
+ fields.headers = Schema2.optionalKey(reflectedSchema2(endpoint.headers));
271
312
  }
272
- const payloadSchemas = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema));
313
+ const payloadSchemas = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema2));
273
314
  if (payloadSchemas.length === 1 && payloadSchemas[0]) {
274
315
  fields.body = payloadSchemas[0];
275
316
  } else if (payloadSchemas.length > 1) {
@@ -301,7 +342,7 @@ class HttpApiSpec extends Context2.Service()("HttpApiSpec", {
301
342
  if (!HttpApi2.isHttpApi(api)) {
302
343
  throw new Error("HttpApiSpec requires a valid HttpApi");
303
344
  }
304
- const spec = OpenApi.fromApi(api);
345
+ const spec = OpenApi2.fromApi(api);
305
346
  const reflectedSchemas = reflectedOperationInputSchemas(api);
306
347
  const operations = httpApiOperations({
307
348
  spec,
@@ -3,13 +3,22 @@ import { Effect as Effect3, Layer as Layer3, Schema as Schema3 } from "effect";
3
3
  import { Tool, Toolkit } from "effect/unstable/ai";
4
4
 
5
5
  // ../../src/lib/httpapi-client.ts
6
- import { Context, Effect, Layer, Schema } from "effect";
6
+ import { Context, Effect, Layer, Schema, SchemaTransformation } from "effect";
7
7
  import { HttpClient } from "effect/unstable/http";
8
8
  import {
9
- HttpApiClient as EffectHttpApiClient
9
+ HttpApi,
10
+ HttpApiClient as EffectHttpApiClient,
11
+ OpenApi
10
12
  } from "effect/unstable/httpapi";
13
+ var UndefinedFromNull = Schema.Null.pipe(Schema.decodeTo(Schema.Undefined, SchemaTransformation.transform({
14
+ decode: () => {
15
+ return;
16
+ },
17
+ encode: () => null
18
+ })));
11
19
  var HttpApiOperationResultValue = Schema.suspend(() => Schema.Union([
12
20
  Schema.Null,
21
+ UndefinedFromNull,
13
22
  Schema.String,
14
23
  Schema.Number,
15
24
  Schema.Boolean,
@@ -23,6 +32,37 @@ var HttpApiOperationResult = Schema.Union([
23
32
  Schema.Undefined
24
33
  ]).annotate({ identifier: "HttpApiOperationResult" });
25
34
  var encodeHttpApiOperationResult = Effect.fn("HttpApiClient.encodeOperationResult")((result) => Schema.encodeUnknownEffect(HttpApiOperationResult)(result).pipe(Effect.map((encoded) => encoded ?? null), Effect.mapError((cause) => new Error("HTTP API result is not serializable", { cause }))));
35
+ var reflectedSchema = (schema) => Schema.make(schema.ast);
36
+ var operationResultSchemas = (api) => {
37
+ const schemas = new Map;
38
+ HttpApi.reflect(api, {
39
+ onGroup: () => {
40
+ return;
41
+ },
42
+ onEndpoint: ({ endpoint, group, successes }) => {
43
+ const operationId = Context.getOrElse(endpoint.annotations, OpenApi.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
44
+ const operationSchemas = Array.from(successes.values()).flat().map(reflectedSchema);
45
+ if (operationSchemas.length === 1 && operationSchemas[0]) {
46
+ schemas.set(operationId, operationSchemas[0]);
47
+ } else if (operationSchemas.length > 1) {
48
+ schemas.set(operationId, Schema.Union(operationSchemas));
49
+ }
50
+ }
51
+ });
52
+ return schemas;
53
+ };
54
+ var makeHttpApiOperationResultEncoder = (api) => {
55
+ const schemas = operationResultSchemas(api);
56
+ return Effect.fn("HttpApiClient.encodeOperationResultWithSchema")((result, operation) => {
57
+ const operationId = operation.operation.operationId;
58
+ const schema = operationId ? schemas.get(operationId) : undefined;
59
+ if (!schema)
60
+ return encodeHttpApiOperationResult(result);
61
+ return Schema.encodeUnknownEffect(schema)(result).pipe(Effect.flatMap(encodeHttpApiOperationResult), Effect.mapError((cause) => new Error("HTTP API result does not match its success schema", {
62
+ cause
63
+ })));
64
+ });
65
+ };
26
66
  var GeneratedOperation = Schema.declare((value) => value instanceof Function).annotate({ identifier: "GeneratedOperation" });
27
67
  var GeneratedOperationEffect = Schema.declare((value) => Effect.isEffect(value)).annotate({ identifier: "GeneratedOperationEffect" });
28
68
  var makeGeneratedClient = (api, http, baseUrl) => EffectHttpApiClient.makeWith(api, {
@@ -58,9 +98,10 @@ var executeGeneratedOperation = Effect.fn("HttpApiClient.executeGeneratedOperati
58
98
  class ApiClient extends Context.Service()("ApiClient") {
59
99
  static layer = (config) => Layer.effect(this, Effect.gen(function* () {
60
100
  const http = yield* HttpClient.HttpClient;
101
+ const encodeResult = makeHttpApiOperationResultEncoder(config.api);
61
102
  const client = yield* makeGeneratedClient(config.api, http, config.baseUrl);
62
103
  return {
63
- encodeResult: config.encodeResult ?? ((result) => encodeHttpApiOperationResult(result)),
104
+ encodeResult: config.encodeResult ?? ((result, operation) => encodeResult(result, operation)),
64
105
  execute: Effect.fn("ApiClient.execute")(function* (options) {
65
106
  return yield* executeGeneratedOperation(client, options);
66
107
  })
@@ -78,7 +119,7 @@ import {
78
119
  Schema as Schema2,
79
120
  SchemaRepresentation
80
121
  } from "effect";
81
- import { HttpApi as HttpApi2, OpenApi } from "effect/unstable/httpapi";
122
+ import { HttpApi as HttpApi2, OpenApi as OpenApi2 } from "effect/unstable/httpapi";
82
123
  var JsonObjectSchema = Schema2.Record(Schema2.String, Schema2.Json).annotate({
83
124
  identifier: "HttpJsonObject",
84
125
  title: "HTTP JSON object",
@@ -251,7 +292,7 @@ var decodeHttpApiOperationInput = Effect2.fn("Http.decodeApiOperationInput")(fun
251
292
  query: pickParameters("query")
252
293
  };
253
294
  });
254
- var reflectedSchema = (schema) => Schema2.make(schema.ast);
295
+ var reflectedSchema2 = (schema) => Schema2.make(schema.ast);
255
296
  var reflectedOperationInputSchemas = (api) => {
256
297
  const schemas = new Map;
257
298
  HttpApi2.reflect(api, {
@@ -259,17 +300,17 @@ var reflectedOperationInputSchemas = (api) => {
259
300
  return;
260
301
  },
261
302
  onEndpoint: ({ endpoint, group }) => {
262
- const operationId = Context2.getOrElse(endpoint.annotations, OpenApi.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
303
+ const operationId = Context2.getOrElse(endpoint.annotations, OpenApi2.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
263
304
  const fields = {};
264
305
  if (endpoint.params)
265
- fields.params = reflectedSchema(endpoint.params);
306
+ fields.params = reflectedSchema2(endpoint.params);
266
307
  if (endpoint.query) {
267
- fields.query = Schema2.optionalKey(reflectedSchema(endpoint.query));
308
+ fields.query = Schema2.optionalKey(reflectedSchema2(endpoint.query));
268
309
  }
269
310
  if (endpoint.headers) {
270
- fields.headers = Schema2.optionalKey(reflectedSchema(endpoint.headers));
311
+ fields.headers = Schema2.optionalKey(reflectedSchema2(endpoint.headers));
271
312
  }
272
- const payloadSchemas = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema));
313
+ const payloadSchemas = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema2));
273
314
  if (payloadSchemas.length === 1 && payloadSchemas[0]) {
274
315
  fields.body = payloadSchemas[0];
275
316
  } else if (payloadSchemas.length > 1) {
@@ -301,7 +342,7 @@ class HttpApiSpec extends Context2.Service()("HttpApiSpec", {
301
342
  if (!HttpApi2.isHttpApi(api)) {
302
343
  throw new Error("HttpApiSpec requires a valid HttpApi");
303
344
  }
304
- const spec = OpenApi.fromApi(api);
345
+ const spec = OpenApi2.fromApi(api);
305
346
  const reflectedSchemas = reflectedOperationInputSchemas(api);
306
347
  const operations = httpApiOperations({
307
348
  spec,
@@ -0,0 +1,66 @@
1
+ import { HttpRouter } from "effect/unstable/http";
2
+ import { HttpApiBuilder, HttpApiGroup } from "effect/unstable/httpapi";
3
+ import { HealthApiGroup } from "./api.group.js";
4
+ import { HealthService } from "./index.js";
5
+ export declare const healthHandler: <const Prefix extends HttpRouter.PathInput>(handlers: HttpApiBuilder.Handlers.FromGroup<HttpApiGroup.AddPrefix<typeof HealthApiGroup, Prefix>>) => HttpApiBuilder.Handlers<HttpRouter.Request<"Requires", HealthService>, {
6
+ readonly getHealth: import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"getHealth", "GET", `${Prefix}/health`, never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
7
+ readonly status: import("effect/Schema").Literal<"UP">;
8
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
9
+ readonly name: import("effect/Schema").NonEmptyString;
10
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
11
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
12
+ }>>;
13
+ }>>, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
14
+ readonly status: import("effect/Schema").Literal<"DOWN">;
15
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
16
+ readonly name: import("effect/Schema").NonEmptyString;
17
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
18
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
19
+ }>>;
20
+ }> | typeof import("effect/unstable/httpapi/HttpApiError").InternalServerError>, never, never>;
21
+ readonly getLiveness: import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"getLiveness", "GET", `${Prefix}/health/live`, never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
22
+ readonly status: import("effect/Schema").Literal<"UP">;
23
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
24
+ readonly name: import("effect/Schema").NonEmptyString;
25
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
26
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
27
+ }>>;
28
+ }>>, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
29
+ readonly status: import("effect/Schema").Literal<"DOWN">;
30
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
31
+ readonly name: import("effect/Schema").NonEmptyString;
32
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
33
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
34
+ }>>;
35
+ }> | typeof import("effect/unstable/httpapi/HttpApiError").InternalServerError>, never, never>;
36
+ readonly getReadiness: import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"getReadiness", "GET", `${Prefix}/health/ready`, never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
37
+ readonly status: import("effect/Schema").Literal<"UP">;
38
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
39
+ readonly name: import("effect/Schema").NonEmptyString;
40
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
41
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
42
+ }>>;
43
+ }>>, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
44
+ readonly status: import("effect/Schema").Literal<"DOWN">;
45
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
46
+ readonly name: import("effect/Schema").NonEmptyString;
47
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
48
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
49
+ }>>;
50
+ }> | typeof import("effect/unstable/httpapi/HttpApiError").InternalServerError>, never, never>;
51
+ readonly getStartup: import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"getStartup", "GET", `${Prefix}/health/started`, never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
52
+ readonly status: import("effect/Schema").Literal<"UP">;
53
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
54
+ readonly name: import("effect/Schema").NonEmptyString;
55
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
56
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
57
+ }>>;
58
+ }>>, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
59
+ readonly status: import("effect/Schema").Literal<"DOWN">;
60
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
61
+ readonly name: import("effect/Schema").NonEmptyString;
62
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
63
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
64
+ }>>;
65
+ }> | typeof import("effect/unstable/httpapi/HttpApiError").InternalServerError>, never, never>;
66
+ }, "getHealth" | "getLiveness" | "getReadiness" | "getStartup">;
@@ -0,0 +1,58 @@
1
+ import { HttpApiEndpoint, HttpApiError, HttpApiGroup } from "effect/unstable/httpapi";
2
+ export declare const HealthApiGroup: HttpApiGroup.HttpApiGroup<"health", HttpApiEndpoint.HttpApiEndpoint<"getHealth", "GET", "/health", never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
3
+ readonly status: import("effect/Schema").Literal<"UP">;
4
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
5
+ readonly name: import("effect/Schema").NonEmptyString;
6
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
7
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
8
+ }>>;
9
+ }>>, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
10
+ readonly status: import("effect/Schema").Literal<"DOWN">;
11
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
12
+ readonly name: import("effect/Schema").NonEmptyString;
13
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
14
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
15
+ }>>;
16
+ }> | typeof HttpApiError.InternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"getLiveness", "GET", "/health/live", never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
17
+ readonly status: import("effect/Schema").Literal<"UP">;
18
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
19
+ readonly name: import("effect/Schema").NonEmptyString;
20
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
21
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
22
+ }>>;
23
+ }>>, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
24
+ readonly status: import("effect/Schema").Literal<"DOWN">;
25
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
26
+ readonly name: import("effect/Schema").NonEmptyString;
27
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
28
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
29
+ }>>;
30
+ }> | typeof HttpApiError.InternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"getReadiness", "GET", "/health/ready", never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
31
+ readonly status: import("effect/Schema").Literal<"UP">;
32
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
33
+ readonly name: import("effect/Schema").NonEmptyString;
34
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
35
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
36
+ }>>;
37
+ }>>, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
38
+ readonly status: import("effect/Schema").Literal<"DOWN">;
39
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
40
+ readonly name: import("effect/Schema").NonEmptyString;
41
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
42
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
43
+ }>>;
44
+ }> | typeof HttpApiError.InternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"getStartup", "GET", "/health/started", never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
45
+ readonly status: import("effect/Schema").Literal<"UP">;
46
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
47
+ readonly name: import("effect/Schema").NonEmptyString;
48
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
49
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
50
+ }>>;
51
+ }>>, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
52
+ readonly status: import("effect/Schema").Literal<"DOWN">;
53
+ readonly checks: import("effect/Schema").$Array<import("effect/Schema").Struct<{
54
+ readonly name: import("effect/Schema").NonEmptyString;
55
+ readonly status: import("effect/Schema").Literals<readonly ["UP", "DOWN"]>;
56
+ readonly data: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Union<readonly [import("effect/Schema").String, import("effect/Schema").Boolean, import("effect/Schema").Number]>>>;
57
+ }>>;
58
+ }> | typeof HttpApiError.InternalServerError>, never, never>, false>;
@@ -0,0 +1,253 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import type { HealthCheckData, HealthCheckOutcome } from "./schema.js";
3
+ export type HealthCheck<Requirements = never> = {
4
+ readonly name: string;
5
+ readonly check: Effect.Effect<HealthCheckOutcome, unknown, Requirements>;
6
+ };
7
+ export type HealthServiceChecks<Requirements = never> = {
8
+ readonly live?: ReadonlyArray<HealthCheck<Requirements>>;
9
+ readonly ready?: ReadonlyArray<HealthCheck<Requirements>>;
10
+ readonly started?: ReadonlyArray<HealthCheck<Requirements>>;
11
+ };
12
+ export type HealthServiceOptions<Requirements = never> = {
13
+ readonly checks?: HealthServiceChecks<Requirements>;
14
+ };
15
+ type RegisteredHealthChecks = {
16
+ readonly live: ReadonlyArray<HealthCheck>;
17
+ readonly ready: ReadonlyArray<HealthCheck>;
18
+ readonly started: ReadonlyArray<HealthCheck>;
19
+ };
20
+ declare const HealthServiceConfig_base: Context.ServiceClass<HealthServiceConfig, "@krak-stack/registry/HealthServiceConfig", RegisteredHealthChecks>;
21
+ declare class HealthServiceConfig extends HealthServiceConfig_base {
22
+ static readonly layerWith: <Requirements = never>({ checks, }?: HealthServiceOptions<Requirements>) => Layer.Layer<HealthServiceConfig, never, Exclude<Requirements, import("effect/Scope").Scope>>;
23
+ }
24
+ declare const HealthService_base: Context.ServiceClass<HealthService, "@krak-stack/registry/HealthService", {
25
+ aggregate: () => Effect.Effect<{
26
+ status: "UP";
27
+ checks: ({
28
+ name: string;
29
+ status: "DOWN" | "UP";
30
+ data?: undefined;
31
+ } | {
32
+ name: string;
33
+ status: "DOWN" | "UP";
34
+ data: {
35
+ readonly [x: string]: string | number | boolean;
36
+ };
37
+ })[];
38
+ } | {
39
+ status: "DOWN";
40
+ checks: ({
41
+ name: string;
42
+ status: "DOWN" | "UP";
43
+ data?: undefined;
44
+ } | {
45
+ name: string;
46
+ status: "DOWN" | "UP";
47
+ data: {
48
+ readonly [x: string]: string | number | boolean;
49
+ };
50
+ })[];
51
+ }, never, never>;
52
+ live: () => Effect.Effect<{
53
+ status: "UP";
54
+ checks: ({
55
+ name: string;
56
+ status: "DOWN" | "UP";
57
+ data?: undefined;
58
+ } | {
59
+ name: string;
60
+ status: "DOWN" | "UP";
61
+ data: {
62
+ readonly [x: string]: string | number | boolean;
63
+ };
64
+ })[];
65
+ } | {
66
+ status: "DOWN";
67
+ checks: ({
68
+ name: string;
69
+ status: "DOWN" | "UP";
70
+ data?: undefined;
71
+ } | {
72
+ name: string;
73
+ status: "DOWN" | "UP";
74
+ data: {
75
+ readonly [x: string]: string | number | boolean;
76
+ };
77
+ })[];
78
+ }, never, never>;
79
+ ready: () => Effect.Effect<{
80
+ status: "UP";
81
+ checks: ({
82
+ name: string;
83
+ status: "DOWN" | "UP";
84
+ data?: undefined;
85
+ } | {
86
+ name: string;
87
+ status: "DOWN" | "UP";
88
+ data: {
89
+ readonly [x: string]: string | number | boolean;
90
+ };
91
+ })[];
92
+ } | {
93
+ status: "DOWN";
94
+ checks: ({
95
+ name: string;
96
+ status: "DOWN" | "UP";
97
+ data?: undefined;
98
+ } | {
99
+ name: string;
100
+ status: "DOWN" | "UP";
101
+ data: {
102
+ readonly [x: string]: string | number | boolean;
103
+ };
104
+ })[];
105
+ }, never, never>;
106
+ started: () => Effect.Effect<{
107
+ status: "UP";
108
+ checks: ({
109
+ name: string;
110
+ status: "DOWN" | "UP";
111
+ data?: undefined;
112
+ } | {
113
+ name: string;
114
+ status: "DOWN" | "UP";
115
+ data: {
116
+ readonly [x: string]: string | number | boolean;
117
+ };
118
+ })[];
119
+ } | {
120
+ status: "DOWN";
121
+ checks: ({
122
+ name: string;
123
+ status: "DOWN" | "UP";
124
+ data?: undefined;
125
+ } | {
126
+ name: string;
127
+ status: "DOWN" | "UP";
128
+ data: {
129
+ readonly [x: string]: string | number | boolean;
130
+ };
131
+ })[];
132
+ }, never, never>;
133
+ }> & {
134
+ readonly make: Effect.Effect<{
135
+ aggregate: () => Effect.Effect<{
136
+ status: "UP";
137
+ checks: ({
138
+ name: string;
139
+ status: "DOWN" | "UP";
140
+ data?: undefined;
141
+ } | {
142
+ name: string;
143
+ status: "DOWN" | "UP";
144
+ data: {
145
+ readonly [x: string]: string | number | boolean;
146
+ };
147
+ })[];
148
+ } | {
149
+ status: "DOWN";
150
+ checks: ({
151
+ name: string;
152
+ status: "DOWN" | "UP";
153
+ data?: undefined;
154
+ } | {
155
+ name: string;
156
+ status: "DOWN" | "UP";
157
+ data: {
158
+ readonly [x: string]: string | number | boolean;
159
+ };
160
+ })[];
161
+ }, never, never>;
162
+ live: () => Effect.Effect<{
163
+ status: "UP";
164
+ checks: ({
165
+ name: string;
166
+ status: "DOWN" | "UP";
167
+ data?: undefined;
168
+ } | {
169
+ name: string;
170
+ status: "DOWN" | "UP";
171
+ data: {
172
+ readonly [x: string]: string | number | boolean;
173
+ };
174
+ })[];
175
+ } | {
176
+ status: "DOWN";
177
+ checks: ({
178
+ name: string;
179
+ status: "DOWN" | "UP";
180
+ data?: undefined;
181
+ } | {
182
+ name: string;
183
+ status: "DOWN" | "UP";
184
+ data: {
185
+ readonly [x: string]: string | number | boolean;
186
+ };
187
+ })[];
188
+ }, never, never>;
189
+ ready: () => Effect.Effect<{
190
+ status: "UP";
191
+ checks: ({
192
+ name: string;
193
+ status: "DOWN" | "UP";
194
+ data?: undefined;
195
+ } | {
196
+ name: string;
197
+ status: "DOWN" | "UP";
198
+ data: {
199
+ readonly [x: string]: string | number | boolean;
200
+ };
201
+ })[];
202
+ } | {
203
+ status: "DOWN";
204
+ checks: ({
205
+ name: string;
206
+ status: "DOWN" | "UP";
207
+ data?: undefined;
208
+ } | {
209
+ name: string;
210
+ status: "DOWN" | "UP";
211
+ data: {
212
+ readonly [x: string]: string | number | boolean;
213
+ };
214
+ })[];
215
+ }, never, never>;
216
+ started: () => Effect.Effect<{
217
+ status: "UP";
218
+ checks: ({
219
+ name: string;
220
+ status: "DOWN" | "UP";
221
+ data?: undefined;
222
+ } | {
223
+ name: string;
224
+ status: "DOWN" | "UP";
225
+ data: {
226
+ readonly [x: string]: string | number | boolean;
227
+ };
228
+ })[];
229
+ } | {
230
+ status: "DOWN";
231
+ checks: ({
232
+ name: string;
233
+ status: "DOWN" | "UP";
234
+ data?: undefined;
235
+ } | {
236
+ name: string;
237
+ status: "DOWN" | "UP";
238
+ data: {
239
+ readonly [x: string]: string | number | boolean;
240
+ };
241
+ })[];
242
+ }, never, never>;
243
+ }, never, HealthServiceConfig>;
244
+ };
245
+ export declare class HealthService extends HealthService_base {
246
+ static readonly up: (data?: HealthCheckData) => HealthCheckOutcome;
247
+ static readonly down: (data?: HealthCheckData) => HealthCheckOutcome;
248
+ static readonly layer: Layer.Layer<HealthService, never, never>;
249
+ static readonly layerWith: <Requirements = never>(options: HealthServiceOptions<Requirements>) => Layer.Layer<HealthService, never, Exclude<Requirements, import("effect/Scope").Scope>>;
250
+ }
251
+ export * from "./api.builder.js";
252
+ export * from "./api.group.js";
253
+ export * from "./schema.js";
@@ -0,0 +1,186 @@
1
+ // ../../src/services/health/index.ts
2
+ import { Context, Effect as Effect2, Exit, Layer } from "effect";
3
+
4
+ // ../../src/services/health/api.builder.ts
5
+ import { Effect } from "effect";
6
+ var respond = (check) => check.pipe(Effect.flatMap((response) => response.status === "UP" ? Effect.succeed(response) : Effect.fail(response)));
7
+ var healthHandler = (handlers) => handlers.handle("getHealth", () => Effect.flatMap(HealthService, ({ aggregate }) => respond(aggregate()))).handle("getLiveness", () => Effect.flatMap(HealthService, ({ live }) => respond(live()))).handle("getReadiness", () => Effect.flatMap(HealthService, ({ ready }) => respond(ready()))).handle("getStartup", () => Effect.flatMap(HealthService, ({ started }) => respond(started())));
8
+ // ../../src/services/health/api.group.ts
9
+ import {
10
+ HttpApiEndpoint,
11
+ HttpApiError,
12
+ HttpApiGroup,
13
+ OpenApi
14
+ } from "effect/unstable/httpapi";
15
+
16
+ // ../../src/services/health/schema.ts
17
+ import { Schema } from "effect";
18
+ import { HttpApiSchema } from "effect/unstable/httpapi";
19
+ var HealthStatus = Schema.Literals(["UP", "DOWN"]).annotate({
20
+ identifier: "HealthStatus",
21
+ title: "Health Status",
22
+ description: "Whether a health check is up or down"
23
+ });
24
+ var HealthCheckData = Schema.Record(Schema.String, Schema.Union([Schema.String, Schema.Boolean, Schema.Number])).annotate({
25
+ identifier: "HealthCheckData",
26
+ title: "Health Check Data",
27
+ description: "Non-sensitive diagnostic values for a health check"
28
+ });
29
+ var HealthCheckOutcome = Schema.Struct({
30
+ status: HealthStatus,
31
+ data: Schema.optionalKey(HealthCheckData)
32
+ }).annotate({
33
+ identifier: "HealthCheckOutcome",
34
+ title: "Health Check Outcome",
35
+ description: "The status and optional diagnostic data returned by a check"
36
+ });
37
+ var HealthCheckResult = Schema.Struct({
38
+ name: Schema.NonEmptyString,
39
+ status: HealthStatus,
40
+ data: Schema.optionalKey(HealthCheckData)
41
+ }).annotate({
42
+ identifier: "HealthCheckResult",
43
+ title: "Health Check Result",
44
+ description: "The outcome of a named health check"
45
+ });
46
+ var HealthUpResponse = Schema.Struct({
47
+ status: Schema.Literal("UP"),
48
+ checks: Schema.Array(HealthCheckResult)
49
+ }).annotate({
50
+ identifier: "HealthUpResponse",
51
+ title: "Healthy Response",
52
+ description: "A response where all health checks are up",
53
+ examples: [{ status: "UP", checks: [] }]
54
+ });
55
+ var HealthDownResponse = Schema.Struct({
56
+ status: Schema.Literal("DOWN"),
57
+ checks: Schema.Array(HealthCheckResult)
58
+ }).pipe(HttpApiSchema.status(503)).annotate({
59
+ identifier: "HealthDownResponse",
60
+ title: "Unhealthy Response",
61
+ description: "A response where one or more health checks are down",
62
+ examples: [
63
+ {
64
+ status: "DOWN",
65
+ checks: [{ name: "database", status: "DOWN" }]
66
+ }
67
+ ]
68
+ });
69
+ var HealthResponse = Schema.Union([
70
+ HealthUpResponse,
71
+ HealthDownResponse
72
+ ]).annotate({
73
+ identifier: "HealthResponse",
74
+ title: "Health Response",
75
+ description: "An aggregate health response"
76
+ });
77
+
78
+ // ../../src/services/health/api.group.ts
79
+ var errors = [HealthDownResponse, HttpApiError.InternalServerError];
80
+ var HealthApiGroup = HttpApiGroup.make("health").annotateMerge(OpenApi.annotations({
81
+ title: "Health",
82
+ description: "Application health checks"
83
+ })).add(HttpApiEndpoint.get("getHealth", "/health", {
84
+ success: HealthUpResponse,
85
+ error: errors
86
+ }).annotateMerge(OpenApi.annotations({
87
+ summary: "Get aggregate health",
88
+ description: "Runs all registered liveness, readiness, and startup checks."
89
+ }))).add(HttpApiEndpoint.get("getLiveness", "/health/live", {
90
+ success: HealthUpResponse,
91
+ error: errors
92
+ }).annotateMerge(OpenApi.annotations({
93
+ summary: "Get application liveness",
94
+ description: "Returns whether the application process is responsive."
95
+ }))).add(HttpApiEndpoint.get("getReadiness", "/health/ready", {
96
+ success: HealthUpResponse,
97
+ error: errors
98
+ }).annotateMerge(OpenApi.annotations({
99
+ summary: "Get application readiness",
100
+ description: "Returns whether the application is ready to serve traffic."
101
+ }))).add(HttpApiEndpoint.get("getStartup", "/health/started", {
102
+ success: HealthUpResponse,
103
+ error: errors
104
+ }).annotateMerge(OpenApi.annotations({
105
+ summary: "Get application startup status",
106
+ description: "Returns whether the application has completed startup."
107
+ })));
108
+
109
+ // ../../src/services/health/index.ts
110
+ class HealthServiceConfig extends Context.Service()("@krak-stack/registry/HealthServiceConfig") {
111
+ static layerWith = ({
112
+ checks = {}
113
+ } = {}) => Layer.effect(this, Effect2.gen(function* () {
114
+ const services = yield* Effect2.context();
115
+ const resolve = (registered) => (registered ?? []).map(({ name, check }) => ({
116
+ name,
117
+ check: Effect2.provide(check, services)
118
+ }));
119
+ return {
120
+ live: resolve(checks.live),
121
+ ready: resolve(checks.ready),
122
+ started: resolve(checks.started)
123
+ };
124
+ }));
125
+ }
126
+
127
+ class HealthService extends Context.Service()("@krak-stack/registry/HealthService", {
128
+ make: Effect2.gen(function* () {
129
+ const checks = yield* HealthServiceConfig;
130
+ const allChecks = Array.from(new Set([...checks.live, ...checks.ready, ...checks.started]));
131
+ const execute = Effect2.fn("HealthService.execute")(function* (healthCheck) {
132
+ const exit = yield* Effect2.exit(healthCheck.check);
133
+ if (!Exit.isSuccess(exit)) {
134
+ return {
135
+ name: healthCheck.name,
136
+ status: "DOWN"
137
+ };
138
+ }
139
+ return exit.value.data === undefined ? {
140
+ name: healthCheck.name,
141
+ status: exit.value.status
142
+ } : {
143
+ name: healthCheck.name,
144
+ status: exit.value.status,
145
+ data: exit.value.data
146
+ };
147
+ });
148
+ const run = Effect2.fn("HealthService.run")(function* (registered) {
149
+ const results = yield* Effect2.forEach(registered, execute, {
150
+ concurrency: "unbounded"
151
+ });
152
+ if (results.every(({ status }) => status === "UP")) {
153
+ return {
154
+ status: "UP",
155
+ checks: results
156
+ };
157
+ }
158
+ return {
159
+ status: "DOWN",
160
+ checks: results
161
+ };
162
+ });
163
+ const aggregate = Effect2.fn("HealthService.aggregate")(() => run(allChecks));
164
+ const live = Effect2.fn("HealthService.live")(() => run(checks.live));
165
+ const ready = Effect2.fn("HealthService.ready")(() => run(checks.ready));
166
+ const started = Effect2.fn("HealthService.started")(() => run(checks.started));
167
+ return { aggregate, live, ready, started };
168
+ })
169
+ }) {
170
+ static up = (data) => data === undefined ? { status: "UP" } : { status: "UP", data };
171
+ static down = (data) => data === undefined ? { status: "DOWN" } : { status: "DOWN", data };
172
+ static layer = Layer.effect(this, this.make).pipe(Layer.provide(HealthServiceConfig.layerWith()));
173
+ static layerWith = (options) => Layer.effect(this, this.make).pipe(Layer.provide(HealthServiceConfig.layerWith(options)));
174
+ }
175
+ export {
176
+ healthHandler,
177
+ HealthUpResponse,
178
+ HealthStatus,
179
+ HealthService,
180
+ HealthResponse,
181
+ HealthDownResponse,
182
+ HealthCheckResult,
183
+ HealthCheckOutcome,
184
+ HealthCheckData,
185
+ HealthApiGroup
186
+ };
@@ -0,0 +1,50 @@
1
+ import { Schema } from "effect";
2
+ export declare const HealthStatus: Schema.Literals<readonly ["UP", "DOWN"]>;
3
+ export type HealthStatus = typeof HealthStatus.Type;
4
+ export declare const HealthCheckData: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.Number]>>;
5
+ export type HealthCheckData = typeof HealthCheckData.Type;
6
+ export declare const HealthCheckOutcome: Schema.Struct<{
7
+ readonly status: Schema.Literals<readonly ["UP", "DOWN"]>;
8
+ readonly data: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.Number]>>>;
9
+ }>;
10
+ export type HealthCheckOutcome = typeof HealthCheckOutcome.Type;
11
+ export declare const HealthCheckResult: Schema.Struct<{
12
+ readonly name: Schema.NonEmptyString;
13
+ readonly status: Schema.Literals<readonly ["UP", "DOWN"]>;
14
+ readonly data: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.Number]>>>;
15
+ }>;
16
+ export type HealthCheckResult = typeof HealthCheckResult.Type;
17
+ export declare const HealthUpResponse: Schema.Struct<{
18
+ readonly status: Schema.Literal<"UP">;
19
+ readonly checks: Schema.$Array<Schema.Struct<{
20
+ readonly name: Schema.NonEmptyString;
21
+ readonly status: Schema.Literals<readonly ["UP", "DOWN"]>;
22
+ readonly data: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.Number]>>>;
23
+ }>>;
24
+ }>;
25
+ export type HealthUpResponse = typeof HealthUpResponse.Type;
26
+ export declare const HealthDownResponse: Schema.Struct<{
27
+ readonly status: Schema.Literal<"DOWN">;
28
+ readonly checks: Schema.$Array<Schema.Struct<{
29
+ readonly name: Schema.NonEmptyString;
30
+ readonly status: Schema.Literals<readonly ["UP", "DOWN"]>;
31
+ readonly data: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.Number]>>>;
32
+ }>>;
33
+ }>;
34
+ export type HealthDownResponse = typeof HealthDownResponse.Type;
35
+ export declare const HealthResponse: Schema.Union<readonly [Schema.Struct<{
36
+ readonly status: Schema.Literal<"UP">;
37
+ readonly checks: Schema.$Array<Schema.Struct<{
38
+ readonly name: Schema.NonEmptyString;
39
+ readonly status: Schema.Literals<readonly ["UP", "DOWN"]>;
40
+ readonly data: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.Number]>>>;
41
+ }>>;
42
+ }>, Schema.Struct<{
43
+ readonly status: Schema.Literal<"DOWN">;
44
+ readonly checks: Schema.$Array<Schema.Struct<{
45
+ readonly name: Schema.NonEmptyString;
46
+ readonly status: Schema.Literals<readonly ["UP", "DOWN"]>;
47
+ readonly data: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Boolean, Schema.Number]>>>;
48
+ }>>;
49
+ }>]>;
50
+ export type HealthResponse = typeof HealthResponse.Type;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krak-stack/registry",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Tree-shakable KrakStack components and Effect services.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -199,6 +199,10 @@
199
199
  "types": "./dist/services/file-extraction/schema.d.ts",
200
200
  "import": "./dist/services/file-extraction/schema.js"
201
201
  },
202
+ "./service-health": {
203
+ "types": "./dist/services/health/index.d.ts",
204
+ "import": "./dist/services/health/index.js"
205
+ },
202
206
  "./tailwind.css": "./tailwind.css",
203
207
  "./package.json": "./package.json"
204
208
  },