@x12i/api-hub-core 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/capability.d.ts +49 -0
- package/dist/capability.js +40 -0
- package/dist/capability.js.map +1 -0
- package/dist/connections.d.ts +42 -0
- package/dist/connections.js +2 -0
- package/dist/connections.js.map +1 -0
- package/dist/context.d.ts +216 -0
- package/dist/context.js +81 -0
- package/dist/context.js.map +1 -0
- package/dist/dns-types.d.ts +64 -0
- package/dist/dns-types.js +2 -0
- package/dist/dns-types.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.js +108 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/invoke.d.ts +17 -0
- package/dist/invoke.js +81 -0
- package/dist/invoke.js.map +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +38 -0
- package/dist/logger.js.map +1 -0
- package/dist/openapi.d.ts +8 -0
- package/dist/openapi.js +208 -0
- package/dist/openapi.js.map +1 -0
- package/dist/passthrough.d.ts +53 -0
- package/dist/passthrough.js +158 -0
- package/dist/passthrough.js.map +1 -0
- package/dist/registry-types.d.ts +82 -0
- package/dist/registry-types.js +19 -0
- package/dist/registry-types.js.map +1 -0
- package/dist/registry.d.ts +41 -0
- package/dist/registry.js +145 -0
- package/dist/registry.js.map +1 -0
- package/dist/schema.d.ts +4 -0
- package/dist/schema.js +8 -0
- package/dist/schema.js.map +1 -0
- package/dist/services.d.ts +109 -0
- package/dist/services.js +116 -0
- package/dist/services.js.map +1 -0
- package/dist/upstream-types.d.ts +42 -0
- package/dist/upstream-types.js +2 -0
- package/dist/upstream-types.js.map +1 -0
- package/package.json +34 -4
package/README.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @x12i/api-hub-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Capability contract, error model, and catalog types for the x12i API hub
|
|
4
|
+
|
|
5
|
+
ESM package for Node.js 24 or newer. Import the public name:
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import * as core from '@x12i/api-hub-core';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The whole set is also available from [`@x12i/api-hub`](https://www.npmjs.com/package/@x12i/api-hub).
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Requirement } from './connections.js';
|
|
3
|
+
import type { Ctx } from './context.js';
|
|
4
|
+
import type { ProxySpec } from './upstream-types.js';
|
|
5
|
+
import type { Sourcing } from './registry-types.js';
|
|
6
|
+
export type CapabilityMode = 'sync' | 'job';
|
|
7
|
+
export interface CapabilityLimits {
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
maxInputBytes: number;
|
|
10
|
+
}
|
|
11
|
+
/** A hub that can serve this capability. `failover` is true when the runtime may call it with the same input. */
|
|
12
|
+
export interface CatalogUpstream {
|
|
13
|
+
hub: string;
|
|
14
|
+
api: string | null;
|
|
15
|
+
priceUsd: number | null;
|
|
16
|
+
failover: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface CapabilityDefinition<I extends z.ZodType = z.ZodType, O extends z.ZodType = z.ZodType> {
|
|
19
|
+
/** Exactly the workbook's Capability value. */
|
|
20
|
+
slug: string;
|
|
21
|
+
/** One sentence, in our own words. */
|
|
22
|
+
summary: string;
|
|
23
|
+
input: I;
|
|
24
|
+
output: O;
|
|
25
|
+
sourcing: Sourcing;
|
|
26
|
+
mode: CapabilityMode;
|
|
27
|
+
limits: CapabilityLimits;
|
|
28
|
+
/** x12i packages actually used, after verification. */
|
|
29
|
+
reuse: string[];
|
|
30
|
+
/** Engineer-days actually spent, for the next sizing round. */
|
|
31
|
+
actualDays?: number;
|
|
32
|
+
/** Operator-configured services the handler needs; drives `connected` and `missing` in the catalog. */
|
|
33
|
+
requires?: Requirement[];
|
|
34
|
+
/** Example input used by docs and the contract check. */
|
|
35
|
+
examples?: z.input<I>[];
|
|
36
|
+
/** Hubs that can serve this capability, including ones the runtime does not fail over to. */
|
|
37
|
+
upstreams?: CatalogUpstream[];
|
|
38
|
+
/** Upstream routes (from `proxy.ts`) when the capability is served by proxying to hubs. */
|
|
39
|
+
proxy?: ProxySpec<z.output<I>, z.input<O>>;
|
|
40
|
+
handler: (input: z.output<I>, ctx: Ctx) => Promise<z.input<O>>;
|
|
41
|
+
}
|
|
42
|
+
export type AnyCapability = CapabilityDefinition<z.ZodType, z.ZodType>;
|
|
43
|
+
/** Our own `handler`, or a `proxy` spec whose routes `ctx.upstream` runs. A handler wins when both are given. */
|
|
44
|
+
export type CapabilitySpec<I extends z.ZodType, O extends z.ZodType> = Omit<CapabilityDefinition<I, O>, 'handler'> & {
|
|
45
|
+
handler?: CapabilityDefinition<I, O>['handler'];
|
|
46
|
+
};
|
|
47
|
+
export declare function defineCapability<I extends z.ZodType, O extends z.ZodType>(spec: CapabilitySpec<I, O>): CapabilityDefinition<I, O>;
|
|
48
|
+
/** Hubs a served capability can call. Typed proxies fail over across every route; pass-through sets `upstreams` itself. */
|
|
49
|
+
export declare function upstreamsOf(impl: AnyCapability | undefined): CatalogUpstream[];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3
|
+
export function defineCapability(spec) {
|
|
4
|
+
const { proxy, handler } = spec;
|
|
5
|
+
if (!handler && !proxy)
|
|
6
|
+
throw new Error(`Capability ${spec.slug} needs a handler or a proxy`);
|
|
7
|
+
if (proxy && proxy.routes.length === 0)
|
|
8
|
+
throw new Error(`Capability ${spec.slug} has a proxy with no routes`);
|
|
9
|
+
const definition = {
|
|
10
|
+
...spec,
|
|
11
|
+
proxy: handler ? undefined : proxy,
|
|
12
|
+
handler: handler ?? ((input, ctx) => ctx.upstream.run(spec.slug, proxy, input, { signal: ctx.signal, callerId: ctx.auth.callerId })),
|
|
13
|
+
};
|
|
14
|
+
if (!SLUG.test(definition.slug)) {
|
|
15
|
+
throw new Error(`Invalid capability slug: ${definition.slug}`);
|
|
16
|
+
}
|
|
17
|
+
if (definition.limits.timeoutMs <= 0 || definition.limits.maxInputBytes <= 0) {
|
|
18
|
+
throw new Error(`Capability ${definition.slug} must set positive limits`);
|
|
19
|
+
}
|
|
20
|
+
if (!(definition.input instanceof z.ZodObject)) {
|
|
21
|
+
throw new Error(`Capability ${definition.slug} input must be a z.object so it maps to an MCP tool`);
|
|
22
|
+
}
|
|
23
|
+
return Object.freeze(definition);
|
|
24
|
+
}
|
|
25
|
+
/** Hubs a served capability can call. Typed proxies fail over across every route; pass-through sets `upstreams` itself. */
|
|
26
|
+
export function upstreamsOf(impl) {
|
|
27
|
+
if (!impl)
|
|
28
|
+
return [];
|
|
29
|
+
if (impl.upstreams)
|
|
30
|
+
return impl.upstreams;
|
|
31
|
+
const routes = impl.proxy?.routes ?? [];
|
|
32
|
+
const failover = routes.length > 1;
|
|
33
|
+
return routes.map((route) => ({
|
|
34
|
+
hub: route.hub,
|
|
35
|
+
api: route.api ?? null,
|
|
36
|
+
priceUsd: route.priceUsd ?? null,
|
|
37
|
+
failover,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=capability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../../../../../packages/core/src/capability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAqDxB,MAAM,IAAI,GAAG,4BAA4B,CAAC;AAE1C,MAAM,UAAU,gBAAgB,CAA2C,IAA0B;IACnG,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChC,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,IAAI,6BAA6B,CAAC,CAAC;IAC9F,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,IAAI,6BAA6B,CAAC,CAAC;IAC9G,MAAM,UAAU,GAA+B;QAC7C,GAAG,IAAI;QACP,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK;QAClC,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;KACtI,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,4BAA4B,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,aAAa,IAAI,CAAC,EAAE,CAAC;QAC7E,MAAM,IAAI,KAAK,CAAC,cAAc,UAAU,CAAC,IAAI,2BAA2B,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,YAAY,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,cAAc,UAAU,CAAC,IAAI,qDAAqD,CAAC,CAAC;IACtG,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;AACnC,CAAC;AAED,2HAA2H;AAC3H,MAAM,UAAU,WAAW,CAAC,IAA+B;IACzD,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,IAAI,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC;IACxC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,IAAI;QACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI;QAChC,QAAQ;KACT,CAAC,CAAC,CAAC;AACN,CAAC","sourcesContent":["import { z } from 'zod';\nimport type { Requirement } from './connections.ts';\nimport type { Ctx } from './context.ts';\nimport type { ProxySpec } from './upstream-types.ts';\nimport type { Sourcing } from './registry-types.ts';\n\nexport type CapabilityMode = 'sync' | 'job';\n\nexport interface CapabilityLimits {\n timeoutMs: number;\n maxInputBytes: number;\n}\n\n/** A hub that can serve this capability. `failover` is true when the runtime may call it with the same input. */\nexport interface CatalogUpstream {\n hub: string;\n api: string | null;\n priceUsd: number | null;\n failover: boolean;\n}\n\nexport interface CapabilityDefinition<I extends z.ZodType = z.ZodType, O extends z.ZodType = z.ZodType> {\n /** Exactly the workbook's Capability value. */\n slug: string;\n /** One sentence, in our own words. */\n summary: string;\n input: I;\n output: O;\n sourcing: Sourcing;\n mode: CapabilityMode;\n limits: CapabilityLimits;\n /** x12i packages actually used, after verification. */\n reuse: string[];\n /** Engineer-days actually spent, for the next sizing round. */\n actualDays?: number;\n /** Operator-configured services the handler needs; drives `connected` and `missing` in the catalog. */\n requires?: Requirement[];\n /** Example input used by docs and the contract check. */\n examples?: z.input<I>[];\n /** Hubs that can serve this capability, including ones the runtime does not fail over to. */\n upstreams?: CatalogUpstream[];\n /** Upstream routes (from `proxy.ts`) when the capability is served by proxying to hubs. */\n proxy?: ProxySpec<z.output<I>, z.input<O>>;\n handler: (input: z.output<I>, ctx: Ctx) => Promise<z.input<O>>;\n}\n\nexport type AnyCapability = CapabilityDefinition<z.ZodType, z.ZodType>;\n\n/** Our own `handler`, or a `proxy` spec whose routes `ctx.upstream` runs. A handler wins when both are given. */\nexport type CapabilitySpec<I extends z.ZodType, O extends z.ZodType> = Omit<CapabilityDefinition<I, O>, 'handler'> & {\n handler?: CapabilityDefinition<I, O>['handler'];\n};\n\nconst SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport function defineCapability<I extends z.ZodType, O extends z.ZodType>(spec: CapabilitySpec<I, O>): CapabilityDefinition<I, O> {\n const { proxy, handler } = spec;\n if (!handler && !proxy) throw new Error(`Capability ${spec.slug} needs a handler or a proxy`);\n if (proxy && proxy.routes.length === 0) throw new Error(`Capability ${spec.slug} has a proxy with no routes`);\n const definition: CapabilityDefinition<I, O> = {\n ...spec,\n proxy: handler ? undefined : proxy,\n handler: handler ?? ((input, ctx) => ctx.upstream.run(spec.slug, proxy!, input, { signal: ctx.signal, callerId: ctx.auth.callerId })),\n };\n if (!SLUG.test(definition.slug)) {\n throw new Error(`Invalid capability slug: ${definition.slug}`);\n }\n if (definition.limits.timeoutMs <= 0 || definition.limits.maxInputBytes <= 0) {\n throw new Error(`Capability ${definition.slug} must set positive limits`);\n }\n if (!(definition.input instanceof z.ZodObject)) {\n throw new Error(`Capability ${definition.slug} input must be a z.object so it maps to an MCP tool`);\n }\n return Object.freeze(definition);\n}\n\n/** Hubs a served capability can call. Typed proxies fail over across every route; pass-through sets `upstreams` itself. */\nexport function upstreamsOf(impl: AnyCapability | undefined): CatalogUpstream[] {\n if (!impl) return [];\n if (impl.upstreams) return impl.upstreams;\n const routes = impl.proxy?.routes ?? [];\n const failover = routes.length > 1;\n return routes.map((route) => ({\n hub: route.hub,\n api: route.api ?? null,\n priceUsd: route.priceUsd ?? null,\n failover,\n }));\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ServiceId } from './services.js';
|
|
2
|
+
/** Something a served capability needs before it can answer. Values are never exposed, only variable names. */
|
|
3
|
+
export type MissingItem = {
|
|
4
|
+
kind: 'service';
|
|
5
|
+
service: string;
|
|
6
|
+
env: string[];
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'hub';
|
|
9
|
+
hub: string;
|
|
10
|
+
env: string;
|
|
11
|
+
missing: 'key' | 'consent' | 'wallet';
|
|
12
|
+
};
|
|
13
|
+
export interface ConnectionInfo {
|
|
14
|
+
/** `null` when the capability is not served at all (planned, connector, blocked-terms and so on). */
|
|
15
|
+
connected: boolean | null;
|
|
16
|
+
missing: MissingItem[];
|
|
17
|
+
}
|
|
18
|
+
export interface Requirement {
|
|
19
|
+
service: ServiceId;
|
|
20
|
+
}
|
|
21
|
+
/** One row of GET /v1/connections. */
|
|
22
|
+
export interface ConnectionEntry {
|
|
23
|
+
kind: 'service' | 'hub';
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
connected: boolean;
|
|
27
|
+
/** Variable names that connect it (alternatives joined with " or "). */
|
|
28
|
+
env: string[];
|
|
29
|
+
missingEnv: string[];
|
|
30
|
+
/** Capabilities that become callable once this is connected. */
|
|
31
|
+
unlocks: number;
|
|
32
|
+
docs: string;
|
|
33
|
+
note?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Supplies connection state for capabilities served through something other than our own
|
|
37
|
+
* handlers, such as the upstream proxy layer.
|
|
38
|
+
*/
|
|
39
|
+
export interface ConnectionProvider {
|
|
40
|
+
capability(slug: string): ConnectionInfo | undefined;
|
|
41
|
+
entries(): ConnectionEntry[];
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connections.js","sourceRoot":"","sources":["../../../../../packages/core/src/connections.ts"],"names":[],"mappings":"","sourcesContent":["import type { ServiceId } from './services.ts';\n\n/** Something a served capability needs before it can answer. Values are never exposed, only variable names. */\nexport type MissingItem =\n | { kind: 'service'; service: string; env: string[] }\n | { kind: 'hub'; hub: string; env: string; missing: 'key' | 'consent' | 'wallet' };\n\nexport interface ConnectionInfo {\n /** `null` when the capability is not served at all (planned, connector, blocked-terms and so on). */\n connected: boolean | null;\n missing: MissingItem[];\n}\n\nexport interface Requirement {\n service: ServiceId;\n}\n\n/** One row of GET /v1/connections. */\nexport interface ConnectionEntry {\n kind: 'service' | 'hub';\n id: string;\n title: string;\n connected: boolean;\n /** Variable names that connect it (alternatives joined with \" or \"). */\n env: string[];\n missingEnv: string[];\n /** Capabilities that become callable once this is connected. */\n unlocks: number;\n docs: string;\n note?: string;\n}\n\n/**\n * Supplies connection state for capabilities served through something other than our own\n * handlers, such as the upstream proxy layer.\n */\nexport interface ConnectionProvider {\n capability(slug: string): ConnectionInfo | undefined;\n entries(): ConnectionEntry[];\n}\n"]}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { Dns } from './dns-types.js';
|
|
3
|
+
import { type Logger } from './logger.js';
|
|
4
|
+
import { type ServiceId } from './services.js';
|
|
5
|
+
import type { UpstreamClient } from './upstream-types.js';
|
|
6
|
+
/** Opaque caller identity, provided by the auth layer outside this repo. Never inspect tokens. */
|
|
7
|
+
export interface AuthContext {
|
|
8
|
+
readonly callerId: string;
|
|
9
|
+
readonly opaque?: unknown;
|
|
10
|
+
}
|
|
11
|
+
export type Meter = (units: number) => void;
|
|
12
|
+
export interface SafeFetchOptions {
|
|
13
|
+
method?: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
body?: string | Uint8Array;
|
|
16
|
+
maxBytes?: number;
|
|
17
|
+
/** Return the first `maxBytes` (with `truncated: true`) instead of failing with `limit_exceeded`. */
|
|
18
|
+
truncate?: boolean;
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
maxRedirects?: number;
|
|
21
|
+
/** Check robots.txt before fetching. Defaults to false; crawling capabilities must set it. */
|
|
22
|
+
respectRobots?: boolean;
|
|
23
|
+
userAgent?: string;
|
|
24
|
+
signal?: AbortSignal;
|
|
25
|
+
}
|
|
26
|
+
export interface SafeResponse {
|
|
27
|
+
requestedUrl: string;
|
|
28
|
+
url: string;
|
|
29
|
+
status: number;
|
|
30
|
+
/** Lower-case names. Repeated headers are joined with ", ", except Set-Cookie, which is joined with "\n". */
|
|
31
|
+
headers: Record<string, string>;
|
|
32
|
+
redirects: string[];
|
|
33
|
+
remoteAddress: string;
|
|
34
|
+
body: Uint8Array;
|
|
35
|
+
truncated: boolean;
|
|
36
|
+
text(): string;
|
|
37
|
+
json<T = unknown>(): T;
|
|
38
|
+
}
|
|
39
|
+
export type SafeFetch = (url: string, options?: SafeFetchOptions) => Promise<SafeResponse>;
|
|
40
|
+
export interface PromptSource {
|
|
41
|
+
/** Stable id, e.g. `sentiment-analyze/classify@v1`. */
|
|
42
|
+
id: string;
|
|
43
|
+
/** Template text with `{{name}}` placeholders. */
|
|
44
|
+
template: string;
|
|
45
|
+
}
|
|
46
|
+
export interface LlmJsonRequest<S extends z.ZodType> {
|
|
47
|
+
prompt: PromptSource;
|
|
48
|
+
variables?: Record<string, string>;
|
|
49
|
+
schema: S;
|
|
50
|
+
temperature?: number;
|
|
51
|
+
maxOutputTokens?: number;
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
}
|
|
54
|
+
export interface LlmTextRequest {
|
|
55
|
+
prompt: PromptSource;
|
|
56
|
+
variables?: Record<string, string>;
|
|
57
|
+
temperature?: number;
|
|
58
|
+
maxOutputTokens?: number;
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
}
|
|
61
|
+
export interface LlmChatMessage {
|
|
62
|
+
role: 'system' | 'user' | 'assistant';
|
|
63
|
+
content: string;
|
|
64
|
+
}
|
|
65
|
+
/** A caller-built conversation sent as is, for chat-completion capabilities. No prompt template is involved. */
|
|
66
|
+
export interface LlmChatRequest {
|
|
67
|
+
messages: LlmChatMessage[];
|
|
68
|
+
/** Stable id for logs and recorded fixtures, e.g. `llm-chat-completion/chat`. */
|
|
69
|
+
id?: string;
|
|
70
|
+
temperature?: number;
|
|
71
|
+
maxOutputTokens?: number;
|
|
72
|
+
signal?: AbortSignal;
|
|
73
|
+
}
|
|
74
|
+
export interface LlmChatResult {
|
|
75
|
+
text: string;
|
|
76
|
+
model?: string;
|
|
77
|
+
inputTokens?: number;
|
|
78
|
+
outputTokens?: number;
|
|
79
|
+
}
|
|
80
|
+
export interface LlmClient {
|
|
81
|
+
generateJson<S extends z.ZodType>(request: LlmJsonRequest<S>): Promise<z.infer<S>>;
|
|
82
|
+
generateText(request: LlmTextRequest): Promise<string>;
|
|
83
|
+
chat(request: LlmChatRequest): Promise<LlmChatResult>;
|
|
84
|
+
}
|
|
85
|
+
export interface ModelRunRequest {
|
|
86
|
+
model: string;
|
|
87
|
+
input: Record<string, unknown>;
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
timeoutMs?: number;
|
|
90
|
+
}
|
|
91
|
+
export interface ModelOutput {
|
|
92
|
+
model: string;
|
|
93
|
+
/** Normalised outputs: text, URLs of produced files, or structured data. */
|
|
94
|
+
text?: string;
|
|
95
|
+
files?: {
|
|
96
|
+
url: string;
|
|
97
|
+
contentType?: string;
|
|
98
|
+
}[];
|
|
99
|
+
data?: unknown;
|
|
100
|
+
}
|
|
101
|
+
export interface ModelsClient {
|
|
102
|
+
run(request: ModelRunRequest): Promise<ModelOutput>;
|
|
103
|
+
}
|
|
104
|
+
export type SearchKind = 'web' | 'news';
|
|
105
|
+
export type SearchTimeRange = 'day' | 'week' | 'month' | 'year';
|
|
106
|
+
export interface WebSearchRequest {
|
|
107
|
+
query: string;
|
|
108
|
+
kind?: SearchKind;
|
|
109
|
+
limit?: number;
|
|
110
|
+
/** ISO 3166-1 alpha-2, lower case. */
|
|
111
|
+
country?: string;
|
|
112
|
+
/** ISO 639-1. */
|
|
113
|
+
language?: string;
|
|
114
|
+
timeRange?: SearchTimeRange;
|
|
115
|
+
signal?: AbortSignal;
|
|
116
|
+
}
|
|
117
|
+
export interface WebSearchResult {
|
|
118
|
+
position: number;
|
|
119
|
+
title: string;
|
|
120
|
+
url: string;
|
|
121
|
+
domain: string;
|
|
122
|
+
snippet?: string;
|
|
123
|
+
/** As the provider shows it; not always an ISO date. */
|
|
124
|
+
date?: string;
|
|
125
|
+
}
|
|
126
|
+
export interface WebSearchResponse {
|
|
127
|
+
provider: string;
|
|
128
|
+
query: string;
|
|
129
|
+
results: WebSearchResult[];
|
|
130
|
+
/** Short answer when the provider returns one. Not verified. */
|
|
131
|
+
answer?: string;
|
|
132
|
+
}
|
|
133
|
+
/** Operator-configured web search (`@x12i/api-hub-search`). */
|
|
134
|
+
export interface WebSearch {
|
|
135
|
+
search(request: WebSearchRequest): Promise<WebSearchResponse>;
|
|
136
|
+
}
|
|
137
|
+
export type CacheTtl = '6h' | '24h' | number;
|
|
138
|
+
export interface Cache {
|
|
139
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
140
|
+
set<T>(key: string, value: T, ttl: CacheTtl): Promise<void>;
|
|
141
|
+
wrap<T>(key: string, ttl: CacheTtl, fn: () => Promise<T>): Promise<T>;
|
|
142
|
+
}
|
|
143
|
+
export interface StoredFile {
|
|
144
|
+
key: string;
|
|
145
|
+
url: string;
|
|
146
|
+
contentType: string;
|
|
147
|
+
bytes: number;
|
|
148
|
+
expiresAt: string;
|
|
149
|
+
}
|
|
150
|
+
export interface Files {
|
|
151
|
+
decodeBase64(data: string, options?: {
|
|
152
|
+
maxBytes?: number;
|
|
153
|
+
}): Uint8Array;
|
|
154
|
+
put(bytes: Uint8Array, options: {
|
|
155
|
+
contentType: string;
|
|
156
|
+
ttlSeconds?: number;
|
|
157
|
+
}): Promise<StoredFile>;
|
|
158
|
+
get(key: string): Promise<{
|
|
159
|
+
bytes: Uint8Array;
|
|
160
|
+
contentType: string;
|
|
161
|
+
} | undefined>;
|
|
162
|
+
}
|
|
163
|
+
export type JobStatus = 'queued' | 'running' | 'succeeded' | 'failed';
|
|
164
|
+
export interface JobRecord {
|
|
165
|
+
id: string;
|
|
166
|
+
capability: string;
|
|
167
|
+
status: JobStatus;
|
|
168
|
+
createdAt: string;
|
|
169
|
+
updatedAt: string;
|
|
170
|
+
expiresAt: string;
|
|
171
|
+
result?: unknown;
|
|
172
|
+
error?: {
|
|
173
|
+
code: string;
|
|
174
|
+
message: string;
|
|
175
|
+
details?: unknown;
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/** Async job runner (F1), implemented by `@x12i/api-hub-jobs`. */
|
|
179
|
+
export interface JobRunner {
|
|
180
|
+
submit(capability: import('./capability.js').AnyCapability, input: unknown, auth: AuthContext, makeCtx: (signal: AbortSignal) => Ctx): Promise<JobRecord>;
|
|
181
|
+
get(id: string, auth: AuthContext): Promise<JobRecord | undefined>;
|
|
182
|
+
}
|
|
183
|
+
export interface Ctx {
|
|
184
|
+
auth: AuthContext;
|
|
185
|
+
meter: Meter;
|
|
186
|
+
log: Logger;
|
|
187
|
+
fetch: SafeFetch;
|
|
188
|
+
dns: Dns;
|
|
189
|
+
llm: LlmClient;
|
|
190
|
+
models: ModelsClient;
|
|
191
|
+
search: WebSearch;
|
|
192
|
+
cache: Cache;
|
|
193
|
+
files: Files;
|
|
194
|
+
/** Proxy runner for capabilities served by upstream hubs (`@x12i/api-hub-upstream`). */
|
|
195
|
+
upstream: UpstreamClient;
|
|
196
|
+
/**
|
|
197
|
+
* Operator-configured variables of a service the capability lists in `requires`, keyed by variable name.
|
|
198
|
+
* Throws `service_not_connected` when they are not set. Never log or return the values.
|
|
199
|
+
*/
|
|
200
|
+
credentials: (service: ServiceId) => Record<string, string>;
|
|
201
|
+
signal: AbortSignal;
|
|
202
|
+
}
|
|
203
|
+
export declare const stubFetch: SafeFetch;
|
|
204
|
+
export declare const stubDns: Dns;
|
|
205
|
+
export declare const stubLlm: LlmClient;
|
|
206
|
+
export declare const stubModels: ModelsClient;
|
|
207
|
+
export declare const stubSearch: WebSearch;
|
|
208
|
+
export declare function createNoopCache(): Cache;
|
|
209
|
+
export declare const stubFiles: Files;
|
|
210
|
+
/** No upstream layer wired: every proxied call reports that none of its hubs is connected. */
|
|
211
|
+
export declare const stubUpstream: UpstreamClient;
|
|
212
|
+
export declare const stubCredentials: (service: ServiceId) => never;
|
|
213
|
+
export declare const anonymousAuth: AuthContext;
|
|
214
|
+
/** Billing is solved elsewhere; this stub only exists so capabilities call it at the right moment. */
|
|
215
|
+
export declare const noopMeter: Meter;
|
|
216
|
+
export declare function createStubContext(overrides?: Partial<Ctx>): Ctx;
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { HubError } from "./errors.js";
|
|
2
|
+
import { createLogger } from "./logger.js";
|
|
3
|
+
import { serviceNotConnected } from "./services.js";
|
|
4
|
+
function notConfigured(what) {
|
|
5
|
+
throw new HubError('unsupported', `${what} is not configured on this server`);
|
|
6
|
+
}
|
|
7
|
+
export const stubFetch = async () => notConfigured('Outbound fetching');
|
|
8
|
+
export const stubDns = {
|
|
9
|
+
resolve: async () => notConfigured('DNS resolution'),
|
|
10
|
+
mailRecords: async () => notConfigured('DNS resolution'),
|
|
11
|
+
reverse: async () => notConfigured('DNS resolution'),
|
|
12
|
+
tlsCertificate: async () => notConfigured('TLS inspection'),
|
|
13
|
+
};
|
|
14
|
+
export const stubLlm = {
|
|
15
|
+
generateJson: async () => {
|
|
16
|
+
throw serviceNotConnected('llm');
|
|
17
|
+
},
|
|
18
|
+
generateText: async () => {
|
|
19
|
+
throw serviceNotConnected('llm');
|
|
20
|
+
},
|
|
21
|
+
chat: async () => {
|
|
22
|
+
throw serviceNotConnected('llm');
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export const stubModels = {
|
|
26
|
+
run: async () => {
|
|
27
|
+
throw serviceNotConnected('models');
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
export const stubSearch = {
|
|
31
|
+
search: async () => {
|
|
32
|
+
throw serviceNotConnected('search');
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export function createNoopCache() {
|
|
36
|
+
return {
|
|
37
|
+
get: async () => undefined,
|
|
38
|
+
set: async () => undefined,
|
|
39
|
+
wrap: async (_key, _ttl, fn) => fn(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export const stubFiles = {
|
|
43
|
+
decodeBase64: () => notConfigured('File handling'),
|
|
44
|
+
put: async () => notConfigured('File storage'),
|
|
45
|
+
get: async () => notConfigured('File storage'),
|
|
46
|
+
};
|
|
47
|
+
/** No upstream layer wired: every proxied call reports that none of its hubs is connected. */
|
|
48
|
+
export const stubUpstream = {
|
|
49
|
+
run: async (slug, spec) => {
|
|
50
|
+
throw new HubError('hub_not_connected', `No hub that sells ${slug} is connected on this server`, {
|
|
51
|
+
reason: spec.routes.length ? 'missing_key' : 'no_hub_available',
|
|
52
|
+
hubs: spec.routes.map((r) => ({ hub: r.hub, missing: 'key' })),
|
|
53
|
+
docs: 'docs/ENV.md#hubs',
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
export const stubCredentials = (service) => {
|
|
58
|
+
throw serviceNotConnected(service);
|
|
59
|
+
};
|
|
60
|
+
export const anonymousAuth = { callerId: 'anonymous' };
|
|
61
|
+
/** Billing is solved elsewhere; this stub only exists so capabilities call it at the right moment. */
|
|
62
|
+
export const noopMeter = () => undefined;
|
|
63
|
+
export function createStubContext(overrides = {}) {
|
|
64
|
+
return {
|
|
65
|
+
auth: anonymousAuth,
|
|
66
|
+
meter: noopMeter,
|
|
67
|
+
log: createLogger({ level: 'silent' }),
|
|
68
|
+
fetch: stubFetch,
|
|
69
|
+
dns: stubDns,
|
|
70
|
+
llm: stubLlm,
|
|
71
|
+
models: stubModels,
|
|
72
|
+
search: stubSearch,
|
|
73
|
+
cache: createNoopCache(),
|
|
74
|
+
files: stubFiles,
|
|
75
|
+
upstream: stubUpstream,
|
|
76
|
+
credentials: stubCredentials,
|
|
77
|
+
signal: new AbortController().signal,
|
|
78
|
+
...overrides,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../../../packages/core/src/context.ts"],"names":[],"mappings":"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","sourcesContent":["import type { z } from 'zod';\nimport type { Dns } from './dns-types.ts';\nimport { HubError } from './errors.ts';\nimport { createLogger, type Logger } from './logger.ts';\nimport { serviceNotConnected, type ServiceId } from './services.ts';\nimport type { UpstreamClient } from './upstream-types.ts';\n\n/** Opaque caller identity, provided by the auth layer outside this repo. Never inspect tokens. */\nexport interface AuthContext {\n readonly callerId: string;\n readonly opaque?: unknown;\n}\n\nexport type Meter = (units: number) => void;\n\nexport interface SafeFetchOptions {\n method?: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n headers?: Record<string, string>;\n body?: string | Uint8Array;\n maxBytes?: number;\n /** Return the first `maxBytes` (with `truncated: true`) instead of failing with `limit_exceeded`. */\n truncate?: boolean;\n timeoutMs?: number;\n maxRedirects?: number;\n /** Check robots.txt before fetching. Defaults to false; crawling capabilities must set it. */\n respectRobots?: boolean;\n userAgent?: string;\n signal?: AbortSignal;\n}\n\nexport interface SafeResponse {\n requestedUrl: string;\n url: string;\n status: number;\n /** Lower-case names. Repeated headers are joined with \", \", except Set-Cookie, which is joined with \"\\n\". */\n headers: Record<string, string>;\n redirects: string[];\n remoteAddress: string;\n body: Uint8Array;\n truncated: boolean;\n text(): string;\n json<T = unknown>(): T;\n}\n\nexport type SafeFetch = (url: string, options?: SafeFetchOptions) => Promise<SafeResponse>;\n\nexport interface PromptSource {\n /** Stable id, e.g. `sentiment-analyze/classify@v1`. */\n id: string;\n /** Template text with `{{name}}` placeholders. */\n template: string;\n}\n\nexport interface LlmJsonRequest<S extends z.ZodType> {\n prompt: PromptSource;\n variables?: Record<string, string>;\n schema: S;\n temperature?: number;\n maxOutputTokens?: number;\n signal?: AbortSignal;\n}\n\nexport interface LlmTextRequest {\n prompt: PromptSource;\n variables?: Record<string, string>;\n temperature?: number;\n maxOutputTokens?: number;\n signal?: AbortSignal;\n}\n\nexport interface LlmChatMessage {\n role: 'system' | 'user' | 'assistant';\n content: string;\n}\n\n/** A caller-built conversation sent as is, for chat-completion capabilities. No prompt template is involved. */\nexport interface LlmChatRequest {\n messages: LlmChatMessage[];\n /** Stable id for logs and recorded fixtures, e.g. `llm-chat-completion/chat`. */\n id?: string;\n temperature?: number;\n maxOutputTokens?: number;\n signal?: AbortSignal;\n}\n\nexport interface LlmChatResult {\n text: string;\n model?: string;\n inputTokens?: number;\n outputTokens?: number;\n}\n\nexport interface LlmClient {\n generateJson<S extends z.ZodType>(request: LlmJsonRequest<S>): Promise<z.infer<S>>;\n generateText(request: LlmTextRequest): Promise<string>;\n chat(request: LlmChatRequest): Promise<LlmChatResult>;\n}\n\nexport interface ModelRunRequest {\n model: string;\n input: Record<string, unknown>;\n signal?: AbortSignal;\n timeoutMs?: number;\n}\n\nexport interface ModelOutput {\n model: string;\n /** Normalised outputs: text, URLs of produced files, or structured data. */\n text?: string;\n files?: { url: string; contentType?: string }[];\n data?: unknown;\n}\n\nexport interface ModelsClient {\n run(request: ModelRunRequest): Promise<ModelOutput>;\n}\n\nexport type SearchKind = 'web' | 'news';\nexport type SearchTimeRange = 'day' | 'week' | 'month' | 'year';\n\nexport interface WebSearchRequest {\n query: string;\n kind?: SearchKind;\n limit?: number;\n /** ISO 3166-1 alpha-2, lower case. */\n country?: string;\n /** ISO 639-1. */\n language?: string;\n timeRange?: SearchTimeRange;\n signal?: AbortSignal;\n}\n\nexport interface WebSearchResult {\n position: number;\n title: string;\n url: string;\n domain: string;\n snippet?: string;\n /** As the provider shows it; not always an ISO date. */\n date?: string;\n}\n\nexport interface WebSearchResponse {\n provider: string;\n query: string;\n results: WebSearchResult[];\n /** Short answer when the provider returns one. Not verified. */\n answer?: string;\n}\n\n/** Operator-configured web search (`@x12i/api-hub-search`). */\nexport interface WebSearch {\n search(request: WebSearchRequest): Promise<WebSearchResponse>;\n}\n\nexport type CacheTtl = '6h' | '24h' | number;\n\nexport interface Cache {\n get<T>(key: string): Promise<T | undefined>;\n set<T>(key: string, value: T, ttl: CacheTtl): Promise<void>;\n wrap<T>(key: string, ttl: CacheTtl, fn: () => Promise<T>): Promise<T>;\n}\n\nexport interface StoredFile {\n key: string;\n url: string;\n contentType: string;\n bytes: number;\n expiresAt: string;\n}\n\nexport interface Files {\n decodeBase64(data: string, options?: { maxBytes?: number }): Uint8Array;\n put(bytes: Uint8Array, options: { contentType: string; ttlSeconds?: number }): Promise<StoredFile>;\n get(key: string): Promise<{ bytes: Uint8Array; contentType: string } | undefined>;\n}\n\nexport type JobStatus = 'queued' | 'running' | 'succeeded' | 'failed';\n\nexport interface JobRecord {\n id: string;\n capability: string;\n status: JobStatus;\n createdAt: string;\n updatedAt: string;\n expiresAt: string;\n result?: unknown;\n error?: { code: string; message: string; details?: unknown };\n}\n\n/** Async job runner (F1), implemented by `@x12i/api-hub-jobs`. */\nexport interface JobRunner {\n submit(\n capability: import('./capability.ts').AnyCapability,\n input: unknown,\n auth: AuthContext,\n makeCtx: (signal: AbortSignal) => Ctx,\n ): Promise<JobRecord>;\n get(id: string, auth: AuthContext): Promise<JobRecord | undefined>;\n}\n\nexport interface Ctx {\n auth: AuthContext;\n meter: Meter;\n log: Logger;\n fetch: SafeFetch;\n dns: Dns;\n llm: LlmClient;\n models: ModelsClient;\n search: WebSearch;\n cache: Cache;\n files: Files;\n /** Proxy runner for capabilities served by upstream hubs (`@x12i/api-hub-upstream`). */\n upstream: UpstreamClient;\n /**\n * Operator-configured variables of a service the capability lists in `requires`, keyed by variable name.\n * Throws `service_not_connected` when they are not set. Never log or return the values.\n */\n credentials: (service: ServiceId) => Record<string, string>;\n signal: AbortSignal;\n}\n\nfunction notConfigured(what: string): never {\n throw new HubError('unsupported', `${what} is not configured on this server`);\n}\n\nexport const stubFetch: SafeFetch = async () => notConfigured('Outbound fetching');\n\nexport const stubDns: Dns = {\n resolve: async () => notConfigured('DNS resolution'),\n mailRecords: async () => notConfigured('DNS resolution'),\n reverse: async () => notConfigured('DNS resolution'),\n tlsCertificate: async () => notConfigured('TLS inspection'),\n};\n\nexport const stubLlm: LlmClient = {\n generateJson: async () => {\n throw serviceNotConnected('llm');\n },\n generateText: async () => {\n throw serviceNotConnected('llm');\n },\n chat: async () => {\n throw serviceNotConnected('llm');\n },\n};\n\nexport const stubModels: ModelsClient = {\n run: async () => {\n throw serviceNotConnected('models');\n },\n};\n\nexport const stubSearch: WebSearch = {\n search: async () => {\n throw serviceNotConnected('search');\n },\n};\n\nexport function createNoopCache(): Cache {\n return {\n get: async () => undefined,\n set: async () => undefined,\n wrap: async (_key, _ttl, fn) => fn(),\n };\n}\n\nexport const stubFiles: Files = {\n decodeBase64: () => notConfigured('File handling'),\n put: async () => notConfigured('File storage'),\n get: async () => notConfigured('File storage'),\n};\n\n/** No upstream layer wired: every proxied call reports that none of its hubs is connected. */\nexport const stubUpstream: UpstreamClient = {\n run: async (slug, spec) => {\n throw new HubError('hub_not_connected', `No hub that sells ${slug} is connected on this server`, {\n reason: spec.routes.length ? 'missing_key' : 'no_hub_available',\n hubs: spec.routes.map((r) => ({ hub: r.hub, missing: 'key' })),\n docs: 'docs/ENV.md#hubs',\n });\n },\n};\n\nexport const stubCredentials = (service: ServiceId): never => {\n throw serviceNotConnected(service);\n};\n\nexport const anonymousAuth: AuthContext = { callerId: 'anonymous' };\n\n/** Billing is solved elsewhere; this stub only exists so capabilities call it at the right moment. */\nexport const noopMeter: Meter = () => undefined;\n\nexport function createStubContext(overrides: Partial<Ctx> = {}): Ctx {\n return {\n auth: anonymousAuth,\n meter: noopMeter,\n log: createLogger({ level: 'silent' }),\n fetch: stubFetch,\n dns: stubDns,\n llm: stubLlm,\n models: stubModels,\n search: stubSearch,\n cache: createNoopCache(),\n files: stubFiles,\n upstream: stubUpstream,\n credentials: stubCredentials,\n signal: new AbortController().signal,\n ...overrides,\n };\n}\n"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export declare const DNS_RECORD_TYPES: readonly ["A", "AAAA", "CNAME", "MX", "NS", "TXT", "SOA", "CAA", "SRV"];
|
|
2
|
+
export type DnsRecordType = (typeof DNS_RECORD_TYPES)[number];
|
|
3
|
+
export interface DnsRecord {
|
|
4
|
+
type: DnsRecordType;
|
|
5
|
+
value: string;
|
|
6
|
+
priority?: number;
|
|
7
|
+
ttl?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SpfRecord {
|
|
10
|
+
raw: string;
|
|
11
|
+
mechanisms: {
|
|
12
|
+
qualifier: '+' | '-' | '~' | '?';
|
|
13
|
+
type: string;
|
|
14
|
+
value?: string;
|
|
15
|
+
}[];
|
|
16
|
+
all: '+' | '-' | '~' | '?' | null;
|
|
17
|
+
includes: string[];
|
|
18
|
+
lookupCount: number;
|
|
19
|
+
}
|
|
20
|
+
export interface DmarcRecord {
|
|
21
|
+
raw: string;
|
|
22
|
+
policy: 'none' | 'quarantine' | 'reject' | null;
|
|
23
|
+
subdomainPolicy: 'none' | 'quarantine' | 'reject' | null;
|
|
24
|
+
pct: number;
|
|
25
|
+
rua: string[];
|
|
26
|
+
ruf: string[];
|
|
27
|
+
adkim: 'r' | 's';
|
|
28
|
+
aspf: 'r' | 's';
|
|
29
|
+
}
|
|
30
|
+
export interface MailRecords {
|
|
31
|
+
domain: string;
|
|
32
|
+
mx: {
|
|
33
|
+
exchange: string;
|
|
34
|
+
priority: number;
|
|
35
|
+
}[];
|
|
36
|
+
nullMx: boolean;
|
|
37
|
+
spf: SpfRecord | null;
|
|
38
|
+
spfRecords: number;
|
|
39
|
+
dmarc: DmarcRecord | null;
|
|
40
|
+
}
|
|
41
|
+
export interface TlsCertificateInfo {
|
|
42
|
+
host: string;
|
|
43
|
+
port: number;
|
|
44
|
+
remoteAddress: string;
|
|
45
|
+
authorized: boolean;
|
|
46
|
+
authorizationError: string | null;
|
|
47
|
+
protocol: string | null;
|
|
48
|
+
subject: Record<string, string>;
|
|
49
|
+
issuer: Record<string, string>;
|
|
50
|
+
subjectAltNames: string[];
|
|
51
|
+
validFrom: string;
|
|
52
|
+
validTo: string;
|
|
53
|
+
daysRemaining: number;
|
|
54
|
+
serialNumber: string;
|
|
55
|
+
fingerprint256: string;
|
|
56
|
+
}
|
|
57
|
+
/** DNS and mail helper (F7), implemented by `@x12i/api-hub-dns`. Names are validated and IDN-normalised by the implementation. */
|
|
58
|
+
export interface Dns {
|
|
59
|
+
resolve(name: string, type: DnsRecordType): Promise<DnsRecord[]>;
|
|
60
|
+
mailRecords(domain: string): Promise<MailRecords>;
|
|
61
|
+
/** PTR names of a public IPv4 or IPv6 address; empty when none are published. */
|
|
62
|
+
reverse(ip: string): Promise<string[]>;
|
|
63
|
+
tlsCertificate(host: string, port?: number, timeoutMs?: number): Promise<TlsCertificateInfo>;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dns-types.js","sourceRoot":"","sources":["../../../../../packages/core/src/dns-types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC","sourcesContent":["export const DNS_RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SOA', 'CAA', 'SRV'] as const;\nexport type DnsRecordType = (typeof DNS_RECORD_TYPES)[number];\n\nexport interface DnsRecord {\n type: DnsRecordType;\n value: string;\n priority?: number;\n ttl?: number;\n}\n\nexport interface SpfRecord {\n raw: string;\n mechanisms: { qualifier: '+' | '-' | '~' | '?'; type: string; value?: string }[];\n all: '+' | '-' | '~' | '?' | null;\n includes: string[];\n lookupCount: number;\n}\n\nexport interface DmarcRecord {\n raw: string;\n policy: 'none' | 'quarantine' | 'reject' | null;\n subdomainPolicy: 'none' | 'quarantine' | 'reject' | null;\n pct: number;\n rua: string[];\n ruf: string[];\n adkim: 'r' | 's';\n aspf: 'r' | 's';\n}\n\nexport interface MailRecords {\n domain: string;\n mx: { exchange: string; priority: number }[];\n nullMx: boolean;\n spf: SpfRecord | null;\n spfRecords: number;\n dmarc: DmarcRecord | null;\n}\n\nexport interface TlsCertificateInfo {\n host: string;\n port: number;\n remoteAddress: string;\n authorized: boolean;\n authorizationError: string | null;\n protocol: string | null;\n subject: Record<string, string>;\n issuer: Record<string, string>;\n subjectAltNames: string[];\n validFrom: string;\n validTo: string;\n daysRemaining: number;\n serialNumber: string;\n fingerprint256: string;\n}\n\n/** DNS and mail helper (F7), implemented by `@x12i/api-hub-dns`. Names are validated and IDN-normalised by the implementation. */\nexport interface Dns {\n resolve(name: string, type: DnsRecordType): Promise<DnsRecord[]>;\n mailRecords(domain: string): Promise<MailRecords>;\n /** PTR names of a public IPv4 or IPv6 address; empty when none are published. */\n reverse(ip: string): Promise<string[]>;\n tlsCertificate(host: string, port?: number, timeoutMs?: number): Promise<TlsCertificateInfo>;\n}\n"]}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const ERROR_CODES: readonly ["invalid_input", "limit_exceeded", "timeout", "unsupported", "upstream_unavailable", "not_permitted", "internal", "service_not_connected", "hub_not_connected", "spend_limit_reached"];
|
|
2
|
+
export type ErrorCode = (typeof ERROR_CODES)[number];
|
|
3
|
+
export interface ErrorBody {
|
|
4
|
+
error: {
|
|
5
|
+
code: ErrorCode;
|
|
6
|
+
message: string;
|
|
7
|
+
details?: unknown;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare const HTTP_STATUS: Record<ErrorCode, number>;
|
|
11
|
+
/** One line per code, rendered into docs/ERRORS.md. */
|
|
12
|
+
export declare const ERROR_DESCRIPTIONS: Record<ErrorCode, {
|
|
13
|
+
when: string;
|
|
14
|
+
details: string;
|
|
15
|
+
callerAction: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare class HubError extends Error {
|
|
18
|
+
readonly code: ErrorCode;
|
|
19
|
+
readonly details: unknown;
|
|
20
|
+
constructor(code: ErrorCode, message: string, details?: unknown);
|
|
21
|
+
get httpStatus(): number;
|
|
22
|
+
toBody(): ErrorBody;
|
|
23
|
+
}
|
|
24
|
+
export declare function isHubError(err: unknown): err is HubError;
|
|
25
|
+
export declare function toHubError(err: unknown): HubError;
|