@sdxc/well-known 2026.10.6 → 2026.10.9

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
@@ -1,6 +1,6 @@
1
1
  # @sdxc/well-known
2
2
 
3
- Typed documents for well-known URIs: security.txt, WebFinger, OAuth and OIDC metadata, JWKS and more.
3
+ Typed documents for well-known URIs: security.txt, WebFinger, NodeInfo, OAuth and OIDC metadata, JWKS and more.
4
4
 
5
5
  ## Installation
6
6
 
@@ -204,6 +204,25 @@ discovery document calls the subpath's `parse` with the identifier it expected.
204
204
  `PasskeyEndpoints` has `enroll`, `manage` and `prfUsageDetails`, each `URL | null`; an empty
205
205
  object is a valid document. `passkeyEndpoints` is the descriptor.
206
206
 
207
+ ### `@sdxc/well-known/nodeinfo` (NodeInfo 2.0 and 2.1)
208
+
209
+ - `NodeInfoLinks` is the `/.well-known/nodeinfo` document, `{ links }`, each `NodeInfoLink`
210
+ a `rel` (the schema URI, `NODEINFO_2_0` or `NODEINFO_2_1`) and an absolute `href`.
211
+ `parseLinks(text)` and `stringifyLinks(document)` read and write it; `nodeInfoLinks` is its
212
+ descriptor, served as `application/json` with `cors: true`.
213
+ - `NodeInfo` has `version`, `software` (`name`, `version`, and 2.1's `repository` and
214
+ `homepage`, `null` when absent), `protocols`, `services` (`inbound`, `outbound`),
215
+ `openRegistrations`, `usage` (`users` with `total`, `activeMonth`, `activeHalfyear`, plus
216
+ `localPosts` and `localComments`, each `number | null`) and `metadata`.
217
+ - `parse(text)` reads 2.0 and 2.1 and checks the schema: `software.name` uses lowercase
218
+ letters, digits and hyphens, at least one protocol, every protocol and service a non-empty
219
+ string, and non-negative integer counts. Names outside the schema's registry, such as
220
+ `atproto`, are kept; the types autocomplete the registered ones. A 2.0 document reads with `repository` and `homepage`
221
+ `null`.
222
+ - `stringify(document)` writes 2.1 whatever `version` says, leaving out `null` members.
223
+ - `nodeInfo` is the descriptor for the document itself, served at the path the links
224
+ document names with `DOCUMENT_MEDIA_TYPE`, the 2.1 profile.
225
+
207
226
  ### `@sdxc/well-known/response`
208
227
 
209
228
  #### `respond(format, document, options?)`
@@ -250,6 +269,41 @@ let middleware = wellKnown({
250
269
  let challenge = `Bearer resource_metadata="${metadataUrl(API_METADATA.resource)}"`;
251
270
  ```
252
271
 
272
+ ## Pattern: Advertising A Server Through NodeInfo
273
+
274
+ ```typescript
275
+ import { serve, wellKnown } from "@sdxc/well-known/middleware";
276
+ import { NODEINFO_2_1, nodeInfo, nodeInfoLinks } from "@sdxc/well-known/nodeinfo";
277
+ import { respond } from "@sdxc/well-known/response";
278
+ import { createAction } from "remix/router";
279
+
280
+ let middleware = wellKnown({
281
+ nodeinfo: serve(nodeInfoLinks, () => ({
282
+ links: [{ rel: NODEINFO_2_1, href: "https://example.com/nodeinfo/2.1" }],
283
+ })),
284
+ });
285
+
286
+ export default createAction(routes.nodeInfo, (ctx) =>
287
+ respond(
288
+ nodeInfo,
289
+ {
290
+ version: "2.1",
291
+ software: { name: "example", version: "1.0.0", repository: null, homepage: null },
292
+ protocols: ["activitypub"],
293
+ services: { inbound: [], outbound: ["rss2.0"] },
294
+ openRegistrations: false,
295
+ usage: {
296
+ users: { total: 1, activeMonth: 1, activeHalfyear: 1 },
297
+ localPosts: 42,
298
+ localComments: null,
299
+ },
300
+ metadata: {},
301
+ },
302
+ { request: ctx.request },
303
+ ),
304
+ );
305
+ ```
306
+
253
307
  ## Pattern: A Change-Password Redirect Beside Documents
254
308
 
255
309
  ```typescript
@@ -0,0 +1,123 @@
1
+ /**
2
+ * NodeInfo: the `/.well-known/nodeinfo` links document and the NodeInfo 2.0/2.1 document
3
+ * it points at, so a server tells the fediverse which software it runs, which protocols
4
+ * it speaks and how many people use it. Reads 2.0 and 2.1, writes 2.1.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ import type { Result } from "@sdxc/result";
10
+ import type { WellKnownFormat } from "./format.js";
11
+ import type { JsonObject } from "./lib/json.js";
12
+ import { WellKnownParseError } from "./parse-error.js";
13
+ export declare const NAME = "nodeinfo";
14
+ export declare const MEDIA_TYPE = "application/json";
15
+ /** The schema URI that names NodeInfo 2.0, as a link's `rel`. */
16
+ export declare const NODEINFO_2_0 = "http://nodeinfo.diaspora.software/ns/schema/2.0";
17
+ /** The schema URI that names NodeInfo 2.1, as a link's `rel`. */
18
+ export declare const NODEINFO_2_1 = "http://nodeinfo.diaspora.software/ns/schema/2.1";
19
+ /** The `Content-Type` of a NodeInfo 2.1 document, whose profile names the schema it follows. */
20
+ export declare const DOCUMENT_MEDIA_TYPE = "application/json; profile=\"http://nodeinfo.diaspora.software/ns/schema/2.1#\"";
21
+ /**
22
+ * A protocol the server speaks: one the NodeInfo schema registers, or any other name a
23
+ * server publishes, such as `atproto`.
24
+ */
25
+ export type NodeInfoProtocol = "activitypub" | "buddycloud" | "dfrn" | "diaspora" | "libertree" | "ostatus" | "pumpio" | "tent" | "xmpp" | "zot" | (string & {});
26
+ /** A site the server can retrieve messages from, registered or not. */
27
+ export type InboundService = "atom1.0" | "gnusocial" | "imap" | "pnut" | "pop3" | "pumpio" | "rss2.0" | "twitter" | (string & {});
28
+ /** A site the server can publish messages to, registered or not. */
29
+ export type OutboundService = "atom1.0" | "blogger" | "buddycloud" | "diaspora" | "dreamwidth" | "drupal" | "facebook" | "friendica" | "gnusocial" | "google" | "insanejournal" | "libertree" | "linkedin" | "livejournal" | "mediagoblin" | "myspace" | "pinterest" | "pnut" | "posterous" | "pumpio" | "redmatrix" | "rss2.0" | "smtp" | "tent" | "tumblr" | "twitter" | "wordpress" | "xmpp" | (string & {});
30
+ /** The schema versions this module reads; every write is 2.1. */
31
+ export type NodeInfoVersion = "2.0" | "2.1";
32
+ /** The `/.well-known/nodeinfo` document: where each NodeInfo version a server offers lives. */
33
+ export interface NodeInfoLinks {
34
+ links: NodeInfoLink[];
35
+ }
36
+ /** One NodeInfo version a server serves. */
37
+ export interface NodeInfoLink {
38
+ /** The schema URI, such as `NODEINFO_2_1`. */
39
+ rel: string;
40
+ /** The absolute URL of the document. */
41
+ href: string;
42
+ }
43
+ /** The NodeInfo document, in the 2.1 shape; a 2.0 document reads with 2.1's additions `null`. */
44
+ export interface NodeInfo {
45
+ /** The schema version the document was read as; `stringify` always writes `"2.1"`. */
46
+ version: NodeInfoVersion;
47
+ software: NodeInfoSoftware;
48
+ /** At least one protocol, as the schema requires. */
49
+ protocols: NodeInfoProtocol[];
50
+ services: NodeInfoServices;
51
+ openRegistrations: boolean;
52
+ usage: NodeInfoUsage;
53
+ /** Free-form, server-specific data. */
54
+ metadata: JsonObject;
55
+ }
56
+ /** The server software. */
57
+ export interface NodeInfoSoftware {
58
+ /** Lowercase letters, digits and hyphens only, as the schema's pattern requires. */
59
+ name: string;
60
+ version: string;
61
+ /** NodeInfo 2.1; `null` when absent and for every 2.0 document. */
62
+ repository: string | null;
63
+ /** NodeInfo 2.1; `null` when absent and for every 2.0 document. */
64
+ homepage: string | null;
65
+ }
66
+ /** The third-party sites the server bridges. */
67
+ export interface NodeInfoServices {
68
+ inbound: InboundService[];
69
+ outbound: OutboundService[];
70
+ }
71
+ /** Usage statistics; a count the server withholds is `null`. */
72
+ export interface NodeInfoUsage {
73
+ users: NodeInfoUsers;
74
+ localPosts: number | null;
75
+ localComments: number | null;
76
+ }
77
+ /** User counts; a count the server withholds is `null`. */
78
+ export interface NodeInfoUsers {
79
+ total: number | null;
80
+ activeMonth: number | null;
81
+ activeHalfyear: number | null;
82
+ }
83
+ /**
84
+ * Reads the links document. Every link needs a string `rel` and an absolute `href`;
85
+ * any `rel` is kept, so a caller picks the version it supports.
86
+ *
87
+ * @param text - The served JSON.
88
+ * @example
89
+ * let links = parseLinks(body);
90
+ * let href = links.data.links.find((link) => link.rel === NODEINFO_2_1)?.href;
91
+ */
92
+ export declare function parseLinks(text: string): Result<NodeInfoLinks, WellKnownParseError>;
93
+ /**
94
+ * Writes the links document.
95
+ *
96
+ * @param document - The versions to advertise.
97
+ */
98
+ export declare function stringifyLinks(document: NodeInfoLinks): string;
99
+ /**
100
+ * Reads a NodeInfo 2.0 or 2.1 document, checking it against its schema: every required
101
+ * member present, `software.name` matching the pattern, at least one protocol, every
102
+ * protocol and service a non-empty string, and non-negative integer counts. Every issue is reported.
103
+ *
104
+ * @param text - The served JSON.
105
+ */
106
+ export declare function parse(text: string): Result<NodeInfo, WellKnownParseError>;
107
+ /**
108
+ * Writes the document as NodeInfo 2.1 whatever version it was read as, which upgrades a
109
+ * 2.0 document since 2.1 only adds members. `null` members are left out.
110
+ *
111
+ * @param document - The document to publish.
112
+ */
113
+ export declare function stringify(document: NodeInfo): string;
114
+ /**
115
+ * The `/.well-known/nodeinfo` links document as a servable format; every answer carries
116
+ * CORS `*` so a browser-based client reads it from any origin.
117
+ */
118
+ export declare const nodeInfoLinks: WellKnownFormat<NodeInfoLinks>;
119
+ /**
120
+ * The NodeInfo document as a servable format, answered at the path the links document
121
+ * names (such as `/nodeinfo/2.1`) with the 2.1 profile in its media type.
122
+ */
123
+ export declare const nodeInfo: WellKnownFormat<NodeInfo>;
@@ -0,0 +1,303 @@
1
+ import { failure, success } from "@sdxc/result";
2
+ import { isObject, pointer, readJsonObject } from "./lib/json.js";
3
+ import { WellKnownParseError } from "./parse-error.js";
4
+ export const NAME = "nodeinfo";
5
+ export const MEDIA_TYPE = "application/json";
6
+ /** The schema URI that names NodeInfo 2.0, as a link's `rel`. */
7
+ export const NODEINFO_2_0 = "http://nodeinfo.diaspora.software/ns/schema/2.0";
8
+ /** The schema URI that names NodeInfo 2.1, as a link's `rel`. */
9
+ export const NODEINFO_2_1 = "http://nodeinfo.diaspora.software/ns/schema/2.1";
10
+ /** The `Content-Type` of a NodeInfo 2.1 document, whose profile names the schema it follows. */
11
+ export const DOCUMENT_MEDIA_TYPE = `application/json; profile="${NODEINFO_2_1}#"`;
12
+ /** The schema's pattern for `software.name`: lowercase letters, digits and hyphens. */
13
+ const SOFTWARE_NAME = /^[a-z0-9-]+$/;
14
+ /**
15
+ * Reads the links document. Every link needs a string `rel` and an absolute `href`;
16
+ * any `rel` is kept, so a caller picks the version it supports.
17
+ *
18
+ * @param text - The served JSON.
19
+ * @example
20
+ * let links = parseLinks(body);
21
+ * let href = links.data.links.find((link) => link.rel === NODEINFO_2_1)?.href;
22
+ */
23
+ export function parseLinks(text) {
24
+ let decoded = readJsonObject(text, NAME);
25
+ if (decoded.status === "failure")
26
+ return decoded;
27
+ let body = decoded.data;
28
+ let issues = [];
29
+ let links = [];
30
+ if (!Array.isArray(body.links)) {
31
+ issues.push({ at: "/links", message: '"links" is not an array.' });
32
+ }
33
+ else {
34
+ for (let [index, entry] of body.links.entries()) {
35
+ let at = pointer("links", index);
36
+ if (!isObject(entry)) {
37
+ issues.push({ at, message: "A link is not an object." });
38
+ continue;
39
+ }
40
+ let valid = true;
41
+ if (typeof entry.rel !== "string") {
42
+ issues.push({ at: `${at}/rel`, message: 'A link has no string "rel".' });
43
+ valid = false;
44
+ }
45
+ if (typeof entry.href !== "string" || !URL.canParse(entry.href)) {
46
+ issues.push({ at: `${at}/href`, message: 'A link has no absolute URL "href".' });
47
+ valid = false;
48
+ }
49
+ if (valid)
50
+ links.push({ rel: entry.rel, href: entry.href });
51
+ }
52
+ }
53
+ if (issues.length > 0)
54
+ return failure(new WellKnownParseError(NAME, issues));
55
+ return success({ links });
56
+ }
57
+ /**
58
+ * Writes the links document.
59
+ *
60
+ * @param document - The versions to advertise.
61
+ */
62
+ export function stringifyLinks(document) {
63
+ return JSON.stringify({
64
+ links: document.links.map((link) => ({ rel: link.rel, href: link.href })),
65
+ });
66
+ }
67
+ /**
68
+ * Reads a string member, pushing an issue when it is required and missing or of the
69
+ * wrong type.
70
+ *
71
+ * @param body - The object holding it.
72
+ * @param member - The member name.
73
+ * @param at - The object's pointer.
74
+ * @param required - Whether absence is an issue.
75
+ * @param issues - Where problems are collected.
76
+ */
77
+ function readString(body, member, at, required, issues) {
78
+ let value = body[member];
79
+ if (typeof value === "string")
80
+ return value;
81
+ if (value === undefined || value === null) {
82
+ if (required)
83
+ issues.push({ at: `${at}${pointer(member)}`, message: `"${member}" is missing.` });
84
+ return null;
85
+ }
86
+ issues.push({ at: `${at}${pointer(member)}`, message: `"${member}" is not a string.` });
87
+ return null;
88
+ }
89
+ /**
90
+ * Reads an optional count, which the schema requires to be a non-negative integer.
91
+ *
92
+ * @param body - The object holding it.
93
+ * @param member - The member name.
94
+ * @param at - The object's pointer.
95
+ * @param issues - Where problems are collected.
96
+ */
97
+ function readCount(body, member, at, issues) {
98
+ let value = body[member];
99
+ if (value === undefined || value === null)
100
+ return null;
101
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0)
102
+ return value;
103
+ issues.push({
104
+ at: `${at}${pointer(member)}`,
105
+ message: `"${member}" is not a non-negative integer.`,
106
+ });
107
+ return null;
108
+ }
109
+ /**
110
+ * Reads a list of names, each a non-empty string; names outside the schema's registry
111
+ * are kept, so a crawler reads servers that speak newer protocols.
112
+ *
113
+ * @param value - The decoded member.
114
+ * @param at - The member's pointer.
115
+ * @param issues - Where problems are collected.
116
+ */
117
+ function readNames(value, at, issues) {
118
+ if (!Array.isArray(value)) {
119
+ issues.push({ at, message: "The member is not an array." });
120
+ return [];
121
+ }
122
+ let result = [];
123
+ for (let [index, item] of value.entries()) {
124
+ if (typeof item === "string" && item !== "")
125
+ result.push(item);
126
+ else
127
+ issues.push({ at: `${at}/${index}`, message: "A name is not a non-empty string." });
128
+ }
129
+ return result;
130
+ }
131
+ /**
132
+ * Reads `software`, whose 2.1-only `repository` and `homepage` stay `null` for a 2.0
133
+ * document whatever it carries.
134
+ *
135
+ * @param value - The decoded member.
136
+ * @param version - The document's schema version.
137
+ * @param issues - Where problems are collected.
138
+ */
139
+ function readSoftware(value, version, issues) {
140
+ let software = { name: "", version: "", repository: null, homepage: null };
141
+ if (!isObject(value)) {
142
+ issues.push({ at: "/software", message: '"software" is not an object.' });
143
+ return software;
144
+ }
145
+ let name = readString(value, "name", "/software", true, issues);
146
+ if (name !== null && !SOFTWARE_NAME.test(name)) {
147
+ issues.push({
148
+ at: "/software/name",
149
+ message: '"name" may hold only lowercase letters, digits and hyphens.',
150
+ });
151
+ }
152
+ software.name = name ?? "";
153
+ software.version = readString(value, "version", "/software", true, issues) ?? "";
154
+ if (version === "2.1") {
155
+ software.repository = readString(value, "repository", "/software", false, issues);
156
+ software.homepage = readString(value, "homepage", "/software", false, issues);
157
+ }
158
+ return software;
159
+ }
160
+ /**
161
+ * Reads `usage`, which must hold a `users` object; every count is optional.
162
+ *
163
+ * @param value - The decoded member.
164
+ * @param issues - Where problems are collected.
165
+ */
166
+ function readUsage(value, issues) {
167
+ let users = { total: null, activeMonth: null, activeHalfyear: null };
168
+ if (!isObject(value)) {
169
+ issues.push({ at: "/usage", message: '"usage" is not an object.' });
170
+ return { users, localPosts: null, localComments: null };
171
+ }
172
+ if (isObject(value.users)) {
173
+ users = {
174
+ total: readCount(value.users, "total", "/usage/users", issues),
175
+ activeMonth: readCount(value.users, "activeMonth", "/usage/users", issues),
176
+ activeHalfyear: readCount(value.users, "activeHalfyear", "/usage/users", issues),
177
+ };
178
+ }
179
+ else
180
+ issues.push({ at: "/usage/users", message: '"users" is not an object.' });
181
+ return {
182
+ users,
183
+ localPosts: readCount(value, "localPosts", "/usage", issues),
184
+ localComments: readCount(value, "localComments", "/usage", issues),
185
+ };
186
+ }
187
+ /**
188
+ * Reads a NodeInfo 2.0 or 2.1 document, checking it against its schema: every required
189
+ * member present, `software.name` matching the pattern, at least one protocol, every
190
+ * protocol and service a non-empty string, and non-negative integer counts. Every issue is reported.
191
+ *
192
+ * @param text - The served JSON.
193
+ */
194
+ export function parse(text) {
195
+ let decoded = readJsonObject(text, NAME);
196
+ if (decoded.status === "failure")
197
+ return decoded;
198
+ let body = decoded.data;
199
+ let issues = [];
200
+ let version = "2.1";
201
+ if (body.version === "2.0" || body.version === "2.1")
202
+ version = body.version;
203
+ else
204
+ issues.push({ at: "/version", message: '"version" is not "2.0" or "2.1".' });
205
+ let software = readSoftware(body.software, version, issues);
206
+ let protocols = readNames(body.protocols, "/protocols", issues);
207
+ if (Array.isArray(body.protocols) && body.protocols.length === 0) {
208
+ issues.push({ at: "/protocols", message: '"protocols" lists no protocol.' });
209
+ }
210
+ let services = { inbound: [], outbound: [] };
211
+ if (isObject(body.services)) {
212
+ services = {
213
+ inbound: readNames(body.services.inbound, "/services/inbound", issues),
214
+ outbound: readNames(body.services.outbound, "/services/outbound", issues),
215
+ };
216
+ }
217
+ else
218
+ issues.push({ at: "/services", message: '"services" is not an object.' });
219
+ let openRegistrations = false;
220
+ if (typeof body.openRegistrations === "boolean")
221
+ openRegistrations = body.openRegistrations;
222
+ else
223
+ issues.push({ at: "/openRegistrations", message: '"openRegistrations" is not a boolean.' });
224
+ let usage = readUsage(body.usage, issues);
225
+ let metadata = {};
226
+ if (isObject(body.metadata))
227
+ metadata = body.metadata;
228
+ else
229
+ issues.push({ at: "/metadata", message: '"metadata" is not an object.' });
230
+ if (issues.length > 0)
231
+ return failure(new WellKnownParseError(NAME, issues));
232
+ return success({
233
+ version,
234
+ software,
235
+ protocols,
236
+ services,
237
+ openRegistrations,
238
+ usage,
239
+ metadata,
240
+ });
241
+ }
242
+ /**
243
+ * Writes the document as NodeInfo 2.1 whatever version it was read as, which upgrades a
244
+ * 2.0 document since 2.1 only adds members. `null` members are left out.
245
+ *
246
+ * @param document - The document to publish.
247
+ */
248
+ export function stringify(document) {
249
+ let software = {
250
+ name: document.software.name,
251
+ version: document.software.version,
252
+ };
253
+ if (document.software.repository !== null)
254
+ software.repository = document.software.repository;
255
+ if (document.software.homepage !== null)
256
+ software.homepage = document.software.homepage;
257
+ let users = {};
258
+ let { total, activeMonth, activeHalfyear } = document.usage.users;
259
+ if (total !== null)
260
+ users.total = total;
261
+ if (activeHalfyear !== null)
262
+ users.activeHalfyear = activeHalfyear;
263
+ if (activeMonth !== null)
264
+ users.activeMonth = activeMonth;
265
+ let usage = { users };
266
+ if (document.usage.localPosts !== null)
267
+ usage.localPosts = document.usage.localPosts;
268
+ if (document.usage.localComments !== null)
269
+ usage.localComments = document.usage.localComments;
270
+ return JSON.stringify({
271
+ version: "2.1",
272
+ software,
273
+ protocols: document.protocols,
274
+ services: { inbound: document.services.inbound, outbound: document.services.outbound },
275
+ openRegistrations: document.openRegistrations,
276
+ usage,
277
+ metadata: document.metadata,
278
+ });
279
+ }
280
+ /**
281
+ * The `/.well-known/nodeinfo` links document as a servable format; every answer carries
282
+ * CORS `*` so a browser-based client reads it from any origin.
283
+ */
284
+ export const nodeInfoLinks = {
285
+ name: NAME,
286
+ mediaType: MEDIA_TYPE,
287
+ placement: "insert",
288
+ cors: true,
289
+ stringify: stringifyLinks,
290
+ parse: parseLinks,
291
+ };
292
+ /**
293
+ * The NodeInfo document as a servable format, answered at the path the links document
294
+ * names (such as `/nodeinfo/2.1`) with the 2.1 profile in its media type.
295
+ */
296
+ export const nodeInfo = {
297
+ name: NAME,
298
+ mediaType: DOCUMENT_MEDIA_TYPE,
299
+ placement: "insert",
300
+ cors: true,
301
+ stringify,
302
+ parse,
303
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sdxc/well-known",
3
- "version": "2026.10.6",
4
- "description": "Typed well-known URI documents: security.txt, WebFinger, OIDC metadata, JWKS",
3
+ "version": "2026.10.9",
4
+ "description": "Typed documents for well-known URIs: security.txt, WebFinger, NodeInfo, OAuth and OIDC metadata, JWKS and more",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "exports": {
@@ -14,6 +14,7 @@
14
14
  "./jwks": "./dist/jwks.js",
15
15
  "./change-password": "./dist/change-password.js",
16
16
  "./passkey-endpoints": "./dist/passkey-endpoints.js",
17
+ "./nodeinfo": "./dist/nodeinfo.js",
17
18
  "./response": "./dist/response.js",
18
19
  "./middleware": "./dist/middleware.js"
19
20
  },
@@ -23,7 +24,7 @@
23
24
  "@standard-schema/spec": "^1.1.0"
24
25
  },
25
26
  "peerDependencies": {
26
- "@sdxc/http": "2026.10.6",
27
+ "@sdxc/http": "2026.10.9",
27
28
  "remix": "3.0.0"
28
29
  },
29
30
  "peerDependenciesMeta": {
@@ -34,7 +35,7 @@
34
35
  "optional": true
35
36
  }
36
37
  },
37
- "gitHead": "b1067eb20fc47ec51ff8c4de47d07dc3f9c00466",
38
+ "gitHead": "64fa732ed0a8ca8693a35aeef6345ae5be2534ef",
38
39
  "publishConfig": {
39
40
  "access": "public"
40
41
  },