@sdxc/well-known 2026.10.24 → 2026.10.25

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
@@ -120,7 +120,7 @@ The failure every reader returns, with `format` (the registered name) and `issue
120
120
  #### `WellKnownFormat<Document>`
121
121
 
122
122
  The descriptor each document subpath exports: `name`, `mediaType`, `placement`, `cors`,
123
- `stringify` and `parse`. `respond` and `serve` take one; its `parse` reads structure only, so a
123
+ optional fixed `headers`, `stringify` and `parse`. `respond` and `serve` take one; its `parse` reads structure only, so a
124
124
  client checking a fetched document's issuer or resource calls the subpath's own `parse`.
125
125
 
126
126
  ### Every document subpath
@@ -136,6 +136,11 @@ Each exports `NAME`, `MEDIA_TYPE`, `parse(text)`, `stringify(document)` and a de
136
136
  - `stringify(document, options?: StringifyOptions): string` writes unsigned text with LF endings,
137
137
  with `options.comments` at the top.
138
138
  - `isExpired(document, now?: Date): boolean` answers whether `expires` has passed.
139
+ - `cleartextSigner({ key, passphrase?, onError? })` is a `transform` for `serve`/`respond` that
140
+ wraps the file in an OpenPGP cleartext signature through
141
+ [`@sdxc/openpgp`](https://www.npmjs.com/package/@sdxc/openpgp). `key` reads an armored private
142
+ key per call; with none, or one that fails (reported to `onError`), the file is served unsigned.
143
+ Each body is signed once, so the `ETag` stays stable.
139
144
  - `securityTxt` is the descriptor.
140
145
 
141
146
  ### `@sdxc/well-known/webfinger` (RFC 7033)
@@ -153,7 +158,11 @@ Each exports `NAME`, `MEDIA_TYPE`, `parse(text)`, `stringify(document)` and a de
153
158
  specification land in `extensions` under their serialized names, validated by
154
159
  `options.extensions`, any synchronous [Standard Schema](https://standardschema.dev/).
155
160
  - `define(document)` takes `issuer` and `responseTypesSupported` and defaults lists to `[]`, URLs
156
- to `null` and flags to `false`.
161
+ to `null` and flags to `false`, except `requestUriParameterSupported`, which is `true` as the
162
+ registry defines it. The PAR (RFC 9126) and request object (RFC 9101) members are typed fields:
163
+ `pushedAuthorizationRequestEndpoint`, `requirePushedAuthorizationRequests`,
164
+ `requestParameterSupported`, `requestUriParameterSupported`,
165
+ `requestObjectSigningAlgValuesSupported` and `requireSignedRequestObject`.
157
166
  - `stringify(document): string` writes extensions beside the standard members, which win a name
158
167
  clash.
159
168
  - `authorizationServerMetadata` is the descriptor.
@@ -162,7 +171,7 @@ Each exports `NAME`, `MEDIA_TYPE`, `parse(text)`, `stringify(document)` and a de
162
171
 
163
172
  `OpenIdProviderMetadata` adds the Discovery members to the RFC 8414 document. `define` also
164
173
  requires `authorizationEndpoint`, `jwksUri`, `subjectTypesSupported` and
165
- `idTokenSigningAlgValuesSupported`, and defaults `requestUriParameterSupported` to `true`. `parse`,
174
+ `idTokenSigningAlgValuesSupported`. `parse`,
166
175
  `stringify` and `ParseOptions` work as above; `openIdConfiguration` is the descriptor, with
167
176
  `placement: "append"`.
168
177
 
@@ -184,6 +193,50 @@ requires `authorizationEndpoint`, `jwksUri`, `subjectTypesSupported` and
184
193
  - `jwks` is the descriptor. Serving through it publishes each key's public half and leaves
185
194
  symmetric keys out, so private keys can go in directly.
186
195
 
196
+ ### `@sdxc/well-known/client-id-metadata-document` (OAuth Client ID Metadata Document, draft 02)
197
+
198
+ The RFC 7591 metadata a client serves at the `https` URL it uses as `client_id`: `parse(text, { clientId })`
199
+ applies the draft's `client_id`, credential and redirect URI rules, `define` and `stringify` write
200
+ one, and `respond(clientIdMetadataDocument, document)` serves it.
201
+
202
+ ### `@sdxc/well-known/http-message-signatures-directory` (Web Bot Auth)
203
+
204
+ The key directory a signing bot publishes, served as `MEDIA_TYPE`
205
+ (`application/http-message-signatures-directory+json`) with CORS.
206
+
207
+ - `parse(text)` keeps public, asymmetric keys with a registered HTTP signature `alg` (or none) and
208
+ numeric `nbf`/`exp`, counting the rest in `skipped`.
209
+ - `build(keys)` publishes each key's public half with `use: "sig"`, failing for a symmetric key or
210
+ one without `kid`; set `kid` to the key's RFC 7638 thumbprint (`JWK.thumbprint` in `@sdxc/jwt`).
211
+ - `httpMessageSignaturesDirectory` is the descriptor. `@sdxc/http-signatures/web-bot-auth` signs
212
+ the response through `respond`'s `sign` option.
213
+
214
+ ### `@sdxc/well-known/api-catalog` (RFC 9727)
215
+
216
+ The link set listing the APIs a host publishes, served as `MEDIA_TYPE`
217
+ (`application/linkset+json` with the RFC 9727 `profile`) with CORS, and with
218
+ `Link: </.well-known/api-catalog>; rel="api-catalog"` on every answer, `HEAD` included.
219
+
220
+ - `build({ url, apis, catalogs? })` writes an `item` link from the catalog to each API, then each
221
+ API's `serviceDesc`, `serviceDoc`, `serviceMeta` and `status` targets anchored at its endpoint,
222
+ every path resolved against `url`. `catalogs` nests other catalogs.
223
+ - `parse(text)` reads any RFC 9264 JSON link set with at least one link, as `{ links }` in the
224
+ shape `@sdxc/link-header/linkset` reads.
225
+ - `apiCatalog` is the descriptor:
226
+
227
+ ```typescript
228
+ wellKnown({
229
+ "api-catalog": serve(apiCatalog, (ctx) =>
230
+ build({
231
+ url: new URL("/.well-known/api-catalog", ctx.url),
232
+ apis: [
233
+ { url: "/v1", serviceDesc: [{ href: "/v1/openapi.json" }], status: [{ href: "/health" }] },
234
+ ],
235
+ }),
236
+ ),
237
+ });
238
+ ```
239
+
187
240
  ### `@sdxc/well-known/change-password` (W3C)
188
241
 
189
242
  `redirect(target: URL | string): Response` is a 302 to the page where a signed-in person changes
@@ -213,13 +266,16 @@ object is a valid document. `passkeyEndpoints` is the descriptor.
213
266
  The document with its media type, a `Cache-Control` (`options.cache`, default public for one hour),
214
267
  an `ETag` over the body and CORS for a CORS format. With `options.request` it answers 304 when
215
268
  `If-None-Match` still matches and an empty body for `HEAD`. `options.headers` apply last.
269
+ `options.transform(body)` rewrites the serialized body before the `ETag` is computed, such as
270
+ `cleartextSigner`. `options.sign(response, request)` runs over the full 200 response first, for a
271
+ signature over the body.
216
272
 
217
273
  ### `@sdxc/well-known/middleware`
218
274
 
219
275
  #### `serve(format, produce: (ctx) => Document | null, options?): WellKnownEntry`
220
276
 
221
277
  Answers with `produce(ctx)`'s document through `respond`, built per request; `null` falls through
222
- to the next middleware. `produce` may be async, and `options` takes `cache` and `headers`.
278
+ to the next middleware. `produce` may be async, and `options` takes `cache`, `headers`, `transform` and `sign`.
223
279
 
224
280
  #### `wellKnown(entries: Record<string, WellKnownEntry>): Middleware`
225
281
 
@@ -232,7 +288,7 @@ answers `OPTIONS`; any other method gets a 405 with `Allow`. Every other path go
232
288
  `WellKnownPlacement` is `"insert" | "append"`. `WellKnownEntry` is any
233
289
  `(ctx) => Response | null`, sync or async. Each document subpath exports its document type
234
290
  (`SecurityTxt`, `Jrd`, `AuthorizationServerMetadata<Extensions>`, `OpenIdProviderMetadata<Extensions>`,
235
- `ProtectedResourceMetadata<Extensions>`, `JwkSet`, `PasskeyEndpoints`, `NodeInfo`,
291
+ `ProtectedResourceMetadata<Extensions>`, `JwkSet`, `ApiCatalog`, `PasskeyEndpoints`, `NodeInfo`,
236
292
  `NodeInfoLinks`) and the types of its members (`ParsedSecurityTxt`, `JrdLink`, `WebFingerQuery`,
237
293
  `LocalizedString`, `BearerMethod`, `Jwk`, `ParsedJwkSet`, `NodeInfoSoftware`, `NodeInfoUsage` and
238
294
  the rest).
@@ -243,7 +299,8 @@ the rest).
243
299
  import { isFailure } from "@sdxc/result";
244
300
  import { redirect } from "@sdxc/well-known/change-password";
245
301
  import { serve, wellKnown } from "@sdxc/well-known/middleware";
246
- import { isExpired, securityTxt } from "@sdxc/well-known/security-txt";
302
+ import { cleartextSigner, isExpired, securityTxt } from "@sdxc/well-known/security-txt";
303
+ import { env } from "cloudflare:workers";
247
304
  import type { SecurityTxt } from "@sdxc/well-known/security-txt";
248
305
  import { readQuery, select, webFinger } from "@sdxc/well-known/webfinger";
249
306
  import type { Jrd } from "@sdxc/well-known/webfinger";
@@ -266,7 +323,9 @@ declare function findProfile(resource: string): Promise<Jrd | null>;
266
323
  let router = createRouter({
267
324
  middleware: [
268
325
  wellKnown({
269
- "security.txt": serve(securityTxt, () => SECURITY_TXT),
326
+ "security.txt": serve(securityTxt, () => SECURITY_TXT, {
327
+ transform: cleartextSigner({ key: () => env.SECURITY_TXT_SIGNING_KEY }),
328
+ }),
270
329
  webfinger: async (ctx) => {
271
330
  let query = readQuery(ctx.url);
272
331
  if (isFailure(query)) return new Response(query.error.message, { status: 400 });
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The RFC 9727 API catalog served at `/.well-known/api-catalog`: an RFC 9264 link set
3
+ * naming every API a host publishes, each with its machine-readable description, human
4
+ * documentation, policies and health endpoint, so a client discovers them from the origin.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ import type { LinksetLink } from "@sdxc/link-header/linkset";
10
+ import type { Result } from "@sdxc/result";
11
+ import type { WellKnownFormat } from "./format.js";
12
+ import { WellKnownParseError } from "./parse-error.js";
13
+ export declare const NAME = "api-catalog";
14
+ /** The profile URI RFC 9727 §7.3 registers, marking a link set as an API catalog. */
15
+ export declare const PROFILE = "https://www.rfc-editor.org/info/rfc9727";
16
+ export declare const MEDIA_TYPE = "application/linkset+json; profile=\"https://www.rfc-editor.org/info/rfc9727\"";
17
+ /** The relation type that points at an API catalog, from a page or from another catalog. */
18
+ export declare const LINK_RELATION = "api-catalog";
19
+ /** A catalog: the links of its link set, read and written through `@sdxc/link-header/linkset`. */
20
+ export interface ApiCatalog {
21
+ links: LinksetLink[];
22
+ }
23
+ /** One resource an API's metadata link points at. */
24
+ export interface ApiCatalogTarget {
25
+ /** Resolved against the catalog's URL, so a path names a resource on the catalog's host. */
26
+ href: URL | string;
27
+ /** The media type a client gets, such as `application/json` for an OpenAPI document. */
28
+ type?: string;
29
+ title?: string;
30
+ }
31
+ /** One API, with the RFC 8631 relations RFC 9727 Appendix A.1 describes it through. */
32
+ export interface ApiDescription {
33
+ /** The API's endpoint: the catalog's `item` link points here, and its metadata hangs off it. */
34
+ url: URL | string;
35
+ /** A name for the API, carried on its `item` link. */
36
+ title?: string;
37
+ /** Machine-readable descriptions, such as an OpenAPI document (`service-desc`). */
38
+ serviceDesc?: ApiCatalogTarget[];
39
+ /** Documentation for people (`service-doc`). */
40
+ serviceDoc?: ApiCatalogTarget[];
41
+ /** Other machine-readable metadata, such as usage policies (`service-meta`). */
42
+ serviceMeta?: ApiCatalogTarget[];
43
+ /** The API's health or status endpoint (`status`). */
44
+ status?: ApiCatalogTarget[];
45
+ }
46
+ /** What a catalog lists. */
47
+ export interface ApiCatalogInput {
48
+ /** The catalog's own URL: the anchor of its `item` links and the base for every relative href. */
49
+ url: URL | string;
50
+ apis: ApiDescription[];
51
+ /** Other catalogs this one nests, for APIs grouped by domain or product (RFC 9727 §4.3). */
52
+ catalogs?: Array<URL | string>;
53
+ }
54
+ /**
55
+ * Builds a catalog combining both shapes RFC 9727 Appendix A shows: an `item` link from the
56
+ * catalog to each API, then each API's own metadata links anchored at its endpoint. Every
57
+ * reference is made absolute against `url`.
58
+ *
59
+ * @param input - The catalog's URL, its APIs and any nested catalogs.
60
+ * @returns The catalog, ready for `stringify` or `serve`.
61
+ * @example
62
+ * build({
63
+ * url: new URL("/.well-known/api-catalog", ctx.url),
64
+ * apis: [{ url: "/api/v1", serviceDesc: [{ href: "/api/v1/openapi.json", type: "application/json" }], status: [{ href: "/health" }] }],
65
+ * });
66
+ */
67
+ export declare function build(input: ApiCatalogInput): ApiCatalog;
68
+ /**
69
+ * Reads a catalog. The link set must follow RFC 9264's JSON form and hold at least one
70
+ * link, since RFC 9727 §4.1 requires a catalog to link to its APIs.
71
+ *
72
+ * @param text - The served JSON.
73
+ * @returns The catalog, or every place it breaks either RFC.
74
+ * @example let catalog = parse(await (await fetch(wellKnownUrl(origin, NAME, "append"))).text());
75
+ */
76
+ export declare function parse(text: string): Result<ApiCatalog, WellKnownParseError>;
77
+ /**
78
+ * Writes a catalog as its `application/linkset+json` document.
79
+ *
80
+ * @param document - The catalog.
81
+ */
82
+ export declare function stringify(document: ApiCatalog): string;
83
+ /**
84
+ * A catalog as a servable format: the link set media type with the RFC 9727 profile, CORS so
85
+ * a browser-based API explorer can read it, and on every answer, `HEAD` included, the
86
+ * `Link: <…>; rel="api-catalog"` that RFC 9727 §2 requires.
87
+ */
88
+ export declare const apiCatalog: WellKnownFormat<ApiCatalog>;
@@ -0,0 +1,106 @@
1
+ import { serializeLink } from "@sdxc/link-header";
2
+ import { JSON_MEDIA_TYPE, parseJson, stringifyJson } from "@sdxc/link-header/linkset";
3
+ import { failure, isFailure, success } from "@sdxc/result";
4
+ import { WellKnownParseError } from "./parse-error.js";
5
+ export const NAME = "api-catalog";
6
+ /** The profile URI RFC 9727 §7.3 registers, marking a link set as an API catalog. */
7
+ export const PROFILE = "https://www.rfc-editor.org/info/rfc9727";
8
+ export const MEDIA_TYPE = `${JSON_MEDIA_TYPE}; profile="${PROFILE}"`;
9
+ /** The relation type that points at an API catalog, from a page or from another catalog. */
10
+ export const LINK_RELATION = "api-catalog";
11
+ /** The metadata relations an {@link ApiDescription} carries, in the order the catalog lists them. */
12
+ const METADATA_RELATIONS = [
13
+ ["serviceDesc", "service-desc"],
14
+ ["serviceDoc", "service-doc"],
15
+ ["serviceMeta", "service-meta"],
16
+ ["status", "status"],
17
+ ];
18
+ /**
19
+ * A reference as an absolute URL against the catalog's, since RFC 9264 asks a link set to
20
+ * carry no relative references; one that cannot be resolved stays as written.
21
+ *
22
+ * @param reference - A URL or a path.
23
+ * @param base - The catalog's URL.
24
+ */
25
+ function absolute(reference, base) {
26
+ return URL.parse(String(reference), base)?.href ?? String(reference);
27
+ }
28
+ /**
29
+ * Builds a catalog combining both shapes RFC 9727 Appendix A shows: an `item` link from the
30
+ * catalog to each API, then each API's own metadata links anchored at its endpoint. Every
31
+ * reference is made absolute against `url`.
32
+ *
33
+ * @param input - The catalog's URL, its APIs and any nested catalogs.
34
+ * @returns The catalog, ready for `stringify` or `serve`.
35
+ * @example
36
+ * build({
37
+ * url: new URL("/.well-known/api-catalog", ctx.url),
38
+ * apis: [{ url: "/api/v1", serviceDesc: [{ href: "/api/v1/openapi.json", type: "application/json" }], status: [{ href: "/health" }] }],
39
+ * });
40
+ */
41
+ export function build(input) {
42
+ let anchor = String(input.url);
43
+ let links = [];
44
+ for (let api of input.apis) {
45
+ let link = { anchor, rel: "item", href: absolute(api.url, anchor) };
46
+ if (api.title !== undefined)
47
+ link.title = api.title;
48
+ links.push(link);
49
+ }
50
+ for (let catalog of input.catalogs ?? []) {
51
+ links.push({ anchor, rel: LINK_RELATION, href: absolute(catalog, anchor) });
52
+ }
53
+ for (let api of input.apis) {
54
+ let endpoint = absolute(api.url, anchor);
55
+ for (let [member, rel] of METADATA_RELATIONS) {
56
+ for (let target of api[member] ?? []) {
57
+ let link = { anchor: endpoint, rel, href: absolute(target.href, anchor) };
58
+ if (target.type !== undefined)
59
+ link.type = target.type;
60
+ if (target.title !== undefined)
61
+ link.title = target.title;
62
+ links.push(link);
63
+ }
64
+ }
65
+ }
66
+ return { links };
67
+ }
68
+ /**
69
+ * Reads a catalog. The link set must follow RFC 9264's JSON form and hold at least one
70
+ * link, since RFC 9727 §4.1 requires a catalog to link to its APIs.
71
+ *
72
+ * @param text - The served JSON.
73
+ * @returns The catalog, or every place it breaks either RFC.
74
+ * @example let catalog = parse(await (await fetch(wellKnownUrl(origin, NAME, "append"))).text());
75
+ */
76
+ export function parse(text) {
77
+ let links = parseJson(text);
78
+ if (isFailure(links))
79
+ return failure(new WellKnownParseError(NAME, links.error.issues));
80
+ if (links.data.length === 0) {
81
+ return failure(new WellKnownParseError(NAME, [{ at: "/linkset", message: "The catalog links to no API." }]));
82
+ }
83
+ return success({ links: links.data });
84
+ }
85
+ /**
86
+ * Writes a catalog as its `application/linkset+json` document.
87
+ *
88
+ * @param document - The catalog.
89
+ */
90
+ export function stringify(document) {
91
+ return stringifyJson(document.links);
92
+ }
93
+ /**
94
+ * A catalog as a servable format: the link set media type with the RFC 9727 profile, CORS so
95
+ * a browser-based API explorer can read it, and on every answer, `HEAD` included, the
96
+ * `Link: <…>; rel="api-catalog"` that RFC 9727 §2 requires.
97
+ */
98
+ export const apiCatalog = {
99
+ name: NAME,
100
+ mediaType: MEDIA_TYPE,
101
+ placement: "append",
102
+ cors: true,
103
+ headers: { Link: serializeLink(`/.well-known/${NAME}`, LINK_RELATION) },
104
+ stringify,
105
+ parse,
106
+ };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * OAuth Client ID Metadata Documents (draft-ietf-oauth-client-id-metadata-document-02):
3
+ * the RFC 7591 client metadata a client publishes at the `https` URL it uses as its
4
+ * `client_id`, with the reader an authorization server checks a fetched copy through.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ import type { Result } from "@sdxc/result";
10
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
11
+ import type { WellKnownFormat } from "./format.js";
12
+ import type { JwkSet } from "./jwks.js";
13
+ import type { LocalizedString } from "./oauth-protected-resource.js";
14
+ import type { WellKnownParseError } from "./parse-error.js";
15
+ export declare const NAME = "client-id-metadata-document";
16
+ export declare const MEDIA_TYPE = "application/json";
17
+ /**
18
+ * The client authentication methods a document may declare (§4.1): `none` and the
19
+ * methods proven with a key pair, since a published document can establish no shared
20
+ * secret.
21
+ */
22
+ export declare const TOKEN_ENDPOINT_AUTH_METHODS: readonly ["none", "private_key_jwt", "tls_client_auth", "self_signed_tls_client_auth"];
23
+ export type TokenEndpointAuthMethod = (typeof TOKEN_ENDPOINT_AUTH_METHODS)[number];
24
+ /**
25
+ * A client's published metadata. `clientId` and every redirect URI stay strings because
26
+ * both are matched by simple string comparison (§3, §4.2); `scope` is the space-delimited
27
+ * member read as a list.
28
+ *
29
+ * @template Extensions - The RFC 7591 members outside this list, validated by a schema on parse.
30
+ */
31
+ export interface ClientIdMetadataDocument<Extensions extends object = {}> {
32
+ /** The Client Identifier URL, exactly the URL the document is served at. */
33
+ clientId: string;
34
+ redirectUris: string[];
35
+ /** An absent member reads as `none`: with no secret to present, the client is public. */
36
+ tokenEndpointAuthMethod: TokenEndpointAuthMethod;
37
+ /** @default ["authorization_code"] */
38
+ grantTypes: string[];
39
+ /** @default ["code"] */
40
+ responseTypes: string[];
41
+ clientName: LocalizedString | null;
42
+ clientUri: URL | null;
43
+ logoUri: URL | null;
44
+ scope: string[];
45
+ contacts: string[];
46
+ tosUri: URL | null;
47
+ policyUri: URL | null;
48
+ jwksUri: URL | null;
49
+ /** Public keys only; serving the document publishes each key's public half. */
50
+ jwks: JwkSet | null;
51
+ softwareId: string | null;
52
+ softwareVersion: string | null;
53
+ /** A JWT asserting the client's provenance (§4.3), kept opaque. */
54
+ softwareStatement: string | null;
55
+ extensions: Extensions;
56
+ }
57
+ /**
58
+ * How a fetched document is read.
59
+ *
60
+ * @template Extensions - The members the schema produces.
61
+ */
62
+ export interface ParseOptions<Extensions extends object> {
63
+ /** The URL the document was fetched from; its `client_id` must equal it, byte for byte (§4). */
64
+ clientId: string;
65
+ /** Validates the members outside the typed list; without one they stay unchecked. */
66
+ extensions?: StandardSchemaV1<unknown, Extensions>;
67
+ }
68
+ /**
69
+ * Whether a string is a valid Client Identifier URL (§3): `https`, a path, and no
70
+ * userinfo, dot segments or fragment. It reads the raw string, so a dot segment the URL
71
+ * parser would resolve away still fails; a query is allowed, though discouraged.
72
+ *
73
+ * @param value - A `client_id` as presented.
74
+ * @example
75
+ * isClientIdentifierUrl("https://app.example.com/client.json"); // true
76
+ */
77
+ export declare function isClientIdentifierUrl(value: string): boolean;
78
+ /**
79
+ * Reads a fetched document. It fails on a `client_id` other than `options.clientId`, a
80
+ * `client_secret*` member, a secret-based or unknown `token_endpoint_auth_method`, a
81
+ * keyed method without exactly one key source, private key material, or a redirect URI
82
+ * the client could not register; every RFC 7591 member outside the list lands in `extensions`.
83
+ *
84
+ * @param text - The served JSON.
85
+ * @param options - The fetched URL and an extension schema.
86
+ * @template Extensions - The extension members the schema produces.
87
+ * @example
88
+ * let client = parse(await response.text(), { clientId: url });
89
+ */
90
+ export declare function parse<Extensions extends object = Record<string, unknown>>(text: string, options: ParseOptions<Extensions>): Result<ClientIdMetadataDocument<Extensions>, WellKnownParseError>;
91
+ /**
92
+ * Writes the document as its registered member names, leaving out `null` members, empty
93
+ * optional lists and default grant and response types. `token_endpoint_auth_method` is
94
+ * always written, since RFC 7591 reads an absent one as `client_secret_basic`, and
95
+ * `jwks` carries each key's public half only.
96
+ *
97
+ * @param document - The metadata to publish.
98
+ */
99
+ export declare function stringify(document: ClientIdMetadataDocument<object>): string;
100
+ /**
101
+ * Builds a document from the members a client sets: `none` authentication, the RFC 7591
102
+ * default grant and response types, empty lists, `null` single values and no extensions.
103
+ *
104
+ * @param document - The members the client publishes.
105
+ * @template Extensions - Members outside the typed list the client also publishes.
106
+ * @example
107
+ * define({ clientId: "https://app.example.com/client.json", redirectUris: ["https://app.example.com/callback"] });
108
+ */
109
+ export declare function define<Extensions extends object = {}>(document: Pick<ClientIdMetadataDocument<Extensions>, "clientId"> & Partial<ClientIdMetadataDocument<Extensions>>): ClientIdMetadataDocument<Extensions>;
110
+ /**
111
+ * The document as a servable format, for `respond` from `./response` at the route behind
112
+ * the client's own URL. Its `parse` applies every rule but the URL match, which a
113
+ * fetching authorization server checks by calling `parse` with `clientId`.
114
+ */
115
+ export declare const clientIdMetadataDocument: WellKnownFormat<ClientIdMetadataDocument<object>>;