matrix-nodejs-homeserver 0.3.3 → 0.3.5
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 +89 -44
- package/dist/api/account.js +109 -64
- package/dist/api/admin.js +9 -0
- package/dist/api/federation.js +1 -1
- package/dist/api/index.js +2 -0
- package/dist/api/longpoll.js +61 -0
- package/dist/api/misc.js +3 -3
- package/dist/api/oauthserver.js +620 -0
- package/dist/api/slidingsync.js +28 -30
- package/dist/api/sync.js +181 -120
- package/dist/api/threepid.js +1 -1
- package/dist/api/uia.js +20 -8
- package/dist/api/user.js +6 -3
- package/dist/core/admin.js +1 -0
- package/dist/core/auth.d.ts +33 -2
- package/dist/core/auth.js +84 -21
- package/dist/core/changes.d.ts +79 -0
- package/dist/core/changes.js +332 -0
- package/dist/core/cluster.d.ts +19 -3
- package/dist/core/cluster.js +44 -4
- package/dist/core/keys.js +4 -2
- package/dist/core/notifier.d.ts +49 -3
- package/dist/core/notifier.js +123 -26
- package/dist/core/oauth.js +2 -1
- package/dist/core/oauthserver.d.ts +141 -0
- package/dist/core/oauthserver.js +603 -0
- package/dist/core/push.d.ts +12 -11
- package/dist/core/push.js +6 -4
- package/dist/core/ratelimit.d.ts +1 -1
- package/dist/core/ratelimit.js +4 -0
- package/dist/core/rooms.js +7 -1
- package/dist/core/semaphore.js +1 -1
- package/dist/core/sso.d.ts +51 -15
- package/dist/core/sso.js +86 -26
- package/dist/homeserver.d.ts +19 -2
- package/dist/homeserver.js +33 -9
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/storage/schema.js +20 -2
- package/dist/storage/sql.js +2 -2
- package/dist/vendor/zod/v4/classic/errors.js +1 -1
- package/dist/vendor/zod/v4/classic/parse.js +1 -1
- package/dist/vendor/zod/v4/classic/schemas.js +1 -1
- package/dist/vendor/zod/v4/core/api.js +1 -1
- package/dist/vendor/zod/v4/core/checks.js +1 -1
- package/dist/vendor/zod/v4/core/core.js +1 -1
- package/dist/vendor/zod/v4/core/doc.js +1 -1
- package/dist/vendor/zod/v4/core/errors.js +1 -1
- package/dist/vendor/zod/v4/core/json-schema-processors.js +1 -1
- package/dist/vendor/zod/v4/core/memoizer.js +1 -1
- package/dist/vendor/zod/v4/core/parse.js +1 -1
- package/dist/vendor/zod/v4/core/regexes.js +1 -1
- package/dist/vendor/zod/v4/core/registries.js +1 -1
- package/dist/vendor/zod/v4/core/schemas.js +1 -1
- package/dist/vendor/zod/v4/core/to-json-schema.js +1 -1
- package/dist/vendor/zod/v4/core/util.js +1 -1
- package/dist/vendor/zod/v4/core/versions.js +1 -1
- package/dist/vendor/zod/v4/locales/en.js +1 -1
- package/package.json +12 -15
package/dist/core/ratelimit.js
CHANGED
package/dist/core/rooms.js
CHANGED
|
@@ -731,6 +731,12 @@ var RoomService = class {
|
|
|
731
731
|
events: touched,
|
|
732
732
|
sharing: true
|
|
733
733
|
});
|
|
734
|
+
const changedMembers = /* @__PURE__ */ new Set();
|
|
735
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(oldIds), ...Object.keys(newIds)])) {
|
|
736
|
+
const [type, userId] = splitStateKey(k);
|
|
737
|
+
if (type === M.ROOM.MEMBER && oldIds[k] !== newIds[k] && this.hs.isLocalUser(userId)) changedMembers.add(userId);
|
|
738
|
+
}
|
|
739
|
+
if (changedMembers.size) this.hs.notifier.notify([...changedMembers, ...await this.interestedUsers(roomId)]);
|
|
734
740
|
for (const k of Object.keys(oldIds)) {
|
|
735
741
|
const [type, userId] = splitStateKey(k);
|
|
736
742
|
if (type !== M.ROOM.MEMBER || this.hs.isLocalUser(userId) || oldIds[k] === newIds[k]) continue;
|
|
@@ -995,7 +1001,7 @@ var RoomService = class {
|
|
|
995
1001
|
if (this.hs.appservices.registrations.length) await this.hs.appservices.onEvent(view, newState).catch((err) => this.hs.logger.warn("Appservice push failed", err));
|
|
996
1002
|
if (isState) await this.hs.delayedEvents.onStateEvent(roomId, pdu.type, pdu.state_key, sender).catch(() => {});
|
|
997
1003
|
wake = async () => {
|
|
998
|
-
this.hs.notifier.notify(await this.interestedUsers(roomId));
|
|
1004
|
+
this.hs.notifier.notify(await this.interestedUsers(roomId), backfill ? void 0 : stream);
|
|
999
1005
|
this.hs.emit("event", toClientEvent(view));
|
|
1000
1006
|
if (this.hs.adminRoom) this.hs.adminRoom.onEvent(view).catch((err) => this.hs.logger.error("Admin command failed", err));
|
|
1001
1007
|
};
|
package/dist/core/semaphore.js
CHANGED
package/dist/core/sso.d.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import { Homeserver } from "../homeserver.js";
|
|
2
2
|
//#region src/core/sso.d.ts
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* An upstream identity provider: OpenID Connect (found through `issuer`), or plain OAuth 2.0 with
|
|
5
|
+
* a user info endpoint (`endpoints`, e.g. GitHub; see `githubProvider`).
|
|
6
|
+
*/
|
|
4
7
|
export interface SsoProvider {
|
|
5
8
|
/** Stable identifier, used in URLs and stored with linked accounts. */
|
|
6
9
|
id: string;
|
|
7
10
|
name: string;
|
|
8
|
-
issuer
|
|
11
|
+
/** OpenID Connect issuer; its discovery document gives the endpoints. Required without `endpoints`. */
|
|
12
|
+
issuer?: string;
|
|
13
|
+
/** Endpoints of a provider without OpenID Connect discovery. */
|
|
14
|
+
endpoints?: {
|
|
15
|
+
authorization: string;
|
|
16
|
+
token: string;
|
|
17
|
+
userinfo?: string;
|
|
18
|
+
};
|
|
9
19
|
clientId: string;
|
|
10
20
|
clientSecret?: string;
|
|
11
|
-
/** Default: ["openid", "profile", "email"]
|
|
21
|
+
/** Default: ["openid", "profile", "email"] with `issuer`, none with `endpoints`. */
|
|
12
22
|
scopes?: string[];
|
|
23
|
+
/** Claim that identifies the user at the provider. Default: "sub". */
|
|
24
|
+
subjectClaim?: string;
|
|
13
25
|
icon?: string;
|
|
14
26
|
brand?: string;
|
|
15
27
|
/**
|
|
@@ -35,26 +47,50 @@ export interface SsoOptions {
|
|
|
35
47
|
*/
|
|
36
48
|
clientAllowlist?: string[];
|
|
37
49
|
}
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
/**
|
|
51
|
+
* What a login through a provider is for: a client of the legacy API (`m.login.sso`, which gets a
|
|
52
|
+
* login token at `redirectUrl`), or the server's own web pages (OAuth 2.0 API), which continue
|
|
53
|
+
* at `next` in the browser session `sid`.
|
|
54
|
+
*/
|
|
55
|
+
export type SsoTarget = {
|
|
56
|
+
kind: "client";
|
|
57
|
+
redirectUrl: string;
|
|
58
|
+
} | {
|
|
59
|
+
kind: "web";
|
|
60
|
+
next: string;
|
|
61
|
+
sid: string;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* GitHub as an identity provider (an OAuth app or GitHub app; its callback URL is
|
|
65
|
+
* `{publicBaseUrl}/_matrix/client/v3/login/sso/callback/github`). Users are matched by their
|
|
66
|
+
* numeric GitHub ID, so renaming the GitHub account keeps the Matrix account; new accounts take
|
|
67
|
+
* the GitHub login as their localpart.
|
|
68
|
+
*/
|
|
69
|
+
export declare function githubProvider(opts: {
|
|
70
|
+
clientId: string;
|
|
71
|
+
clientSecret: string;
|
|
72
|
+
id?: string;
|
|
73
|
+
name?: string;
|
|
74
|
+
allowRegistration?: boolean;
|
|
75
|
+
mapUser?: SsoProvider["mapUser"];
|
|
76
|
+
}): SsoProvider;
|
|
77
|
+
/** Upstream login (authorization code + PKCE) for the legacy `m.login.sso` and the OAuth 2.0 API's web pages. */
|
|
46
78
|
export declare class SsoService {
|
|
47
79
|
private hs;
|
|
48
80
|
readonly providers: SsoProvider[];
|
|
49
|
-
|
|
81
|
+
readonly clientAllowlist: string[];
|
|
50
82
|
private discovery;
|
|
51
83
|
constructor(hs: Homeserver, options: SsoOptions);
|
|
52
84
|
private callbackUrl;
|
|
53
85
|
private discover;
|
|
86
|
+
private scopes;
|
|
54
87
|
/** URL to send the user's browser to. */
|
|
55
|
-
startLogin(providerId: string | null,
|
|
56
|
-
/** Handle the provider's redirect
|
|
57
|
-
finishLogin(providerId: string, params: URLSearchParams): Promise<
|
|
88
|
+
startLogin(providerId: string | null, target: SsoTarget): Promise<string>;
|
|
89
|
+
/** Handle the provider's redirect: the user who logged in, and what the login was for. */
|
|
90
|
+
finishLogin(providerId: string, params: URLSearchParams): Promise<{
|
|
91
|
+
userId: string;
|
|
92
|
+
target: SsoTarget;
|
|
93
|
+
}>;
|
|
58
94
|
private resolveUser;
|
|
59
95
|
}
|
|
60
96
|
//#endregion
|
package/dist/core/sso.js
CHANGED
|
@@ -39,7 +39,35 @@ function decodeJwtPayload(jwt) {
|
|
|
39
39
|
if (!part) throw new Error("Malformed ID token");
|
|
40
40
|
return JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
|
|
41
41
|
}
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* GitHub as an identity provider (an OAuth app or GitHub app; its callback URL is
|
|
44
|
+
* `{publicBaseUrl}/_matrix/client/v3/login/sso/callback/github`). Users are matched by their
|
|
45
|
+
* numeric GitHub ID, so renaming the GitHub account keeps the Matrix account; new accounts take
|
|
46
|
+
* the GitHub login as their localpart.
|
|
47
|
+
*/
|
|
48
|
+
function githubProvider(opts) {
|
|
49
|
+
return {
|
|
50
|
+
id: opts.id ?? "github",
|
|
51
|
+
name: opts.name ?? "GitHub",
|
|
52
|
+
brand: "github",
|
|
53
|
+
clientId: opts.clientId,
|
|
54
|
+
clientSecret: opts.clientSecret,
|
|
55
|
+
endpoints: {
|
|
56
|
+
authorization: "https://github.com/login/oauth/authorize",
|
|
57
|
+
token: "https://github.com/login/oauth/access_token",
|
|
58
|
+
userinfo: "https://api.github.com/user"
|
|
59
|
+
},
|
|
60
|
+
scopes: ["read:user"],
|
|
61
|
+
subjectClaim: "id",
|
|
62
|
+
mapUser: opts.mapUser ?? ((c) => ({
|
|
63
|
+
localpart: sanitizeLocalpart(String(c.login ?? c.id)),
|
|
64
|
+
displayname: c.name ?? c.login
|
|
65
|
+
})),
|
|
66
|
+
...opts.allowRegistration !== void 0 ? { allowRegistration: opts.allowRegistration } : {}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const USER_AGENT = "matrix-nodejs-homeserver";
|
|
70
|
+
/** Upstream login (authorization code + PKCE) for the legacy `m.login.sso` and the OAuth 2.0 API's web pages. */
|
|
43
71
|
var SsoService = class {
|
|
44
72
|
hs;
|
|
45
73
|
providers;
|
|
@@ -56,8 +84,18 @@ var SsoService = class {
|
|
|
56
84
|
return `${base.replace(/\/$/, "")}/_matrix/client/v3/login/sso/callback/${encodeURIComponent(provider.id)}`;
|
|
57
85
|
}
|
|
58
86
|
async discover(provider) {
|
|
87
|
+
if (provider.endpoints) {
|
|
88
|
+
const e = provider.endpoints;
|
|
89
|
+
return {
|
|
90
|
+
issuer: provider.issuer,
|
|
91
|
+
authorization_endpoint: e.authorization,
|
|
92
|
+
token_endpoint: e.token,
|
|
93
|
+
userinfo_endpoint: e.userinfo
|
|
94
|
+
};
|
|
95
|
+
}
|
|
59
96
|
const cached = this.discovery.get(provider.id);
|
|
60
97
|
if (cached) return cached;
|
|
98
|
+
if (!provider.issuer) throw new Error(`Identity provider ${provider.id} needs an issuer or endpoints`);
|
|
61
99
|
const res = await this.hs.fetch(`${provider.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`, { signal: AbortSignal.timeout(1e4) });
|
|
62
100
|
if (!res.ok) throw new Error(`OIDC discovery for ${provider.id} failed with ${res.status}`);
|
|
63
101
|
const meta = await res.json();
|
|
@@ -65,8 +103,15 @@ var SsoService = class {
|
|
|
65
103
|
this.discovery.set(provider.id, meta);
|
|
66
104
|
return meta;
|
|
67
105
|
}
|
|
106
|
+
scopes(provider) {
|
|
107
|
+
return provider.scopes ?? (provider.endpoints ? [] : [
|
|
108
|
+
"openid",
|
|
109
|
+
"profile",
|
|
110
|
+
"email"
|
|
111
|
+
]);
|
|
112
|
+
}
|
|
68
113
|
/** URL to send the user's browser to. */
|
|
69
|
-
async startLogin(providerId,
|
|
114
|
+
async startLogin(providerId, target) {
|
|
70
115
|
const provider = providerId ? this.providers.find((p) => p.id === providerId) : this.providers[0];
|
|
71
116
|
if (!provider) throw errors.notFound("Unknown identity provider");
|
|
72
117
|
const meta = await this.discover(provider);
|
|
@@ -75,7 +120,7 @@ var SsoService = class {
|
|
|
75
120
|
const nonce = randomToken(16);
|
|
76
121
|
await this.hs.cluster.set(`sso:${state}`, {
|
|
77
122
|
providerId: provider.id,
|
|
78
|
-
|
|
123
|
+
target,
|
|
79
124
|
verifier,
|
|
80
125
|
nonce,
|
|
81
126
|
expires: Date.now() + 6e5
|
|
@@ -84,18 +129,15 @@ var SsoService = class {
|
|
|
84
129
|
url.searchParams.set("response_type", "code");
|
|
85
130
|
url.searchParams.set("client_id", provider.clientId);
|
|
86
131
|
url.searchParams.set("redirect_uri", this.callbackUrl(provider));
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"profile",
|
|
90
|
-
"email"
|
|
91
|
-
]).join(" "));
|
|
132
|
+
const scopes = this.scopes(provider);
|
|
133
|
+
if (scopes.length) url.searchParams.set("scope", scopes.join(" "));
|
|
92
134
|
url.searchParams.set("state", state);
|
|
93
135
|
url.searchParams.set("nonce", nonce);
|
|
94
136
|
url.searchParams.set("code_challenge", urlSafeBase64(createHash("sha256").update(verifier).digest()));
|
|
95
137
|
url.searchParams.set("code_challenge_method", "S256");
|
|
96
138
|
return url.toString();
|
|
97
139
|
}
|
|
98
|
-
/** Handle the provider's redirect
|
|
140
|
+
/** Handle the provider's redirect: the user who logged in, and what the login was for. */
|
|
99
141
|
async finishLogin(providerId, params) {
|
|
100
142
|
const state = params.get("state") ?? "";
|
|
101
143
|
const pending = await this.hs.cluster.get(`sso:${state}`);
|
|
@@ -115,7 +157,8 @@ var SsoService = class {
|
|
|
115
157
|
});
|
|
116
158
|
const headers = {
|
|
117
159
|
"content-type": "application/x-www-form-urlencoded",
|
|
118
|
-
accept: "application/json"
|
|
160
|
+
accept: "application/json",
|
|
161
|
+
"user-agent": USER_AGENT
|
|
119
162
|
};
|
|
120
163
|
if (provider.clientSecret) headers.authorization = `Basic ${Buffer.from(`${encodeURIComponent(provider.clientId)}:${encodeURIComponent(provider.clientSecret)}`).toString("base64")}`;
|
|
121
164
|
const res = await this.hs.fetch(meta.token_endpoint, {
|
|
@@ -126,8 +169,9 @@ var SsoService = class {
|
|
|
126
169
|
});
|
|
127
170
|
if (!res.ok) throw errors.forbidden("Code exchange failed");
|
|
128
171
|
const tokens = await res.json();
|
|
172
|
+
if (typeof tokens.error === "string") throw errors.forbidden("Code exchange failed");
|
|
129
173
|
let claims = {};
|
|
130
|
-
if ((provider
|
|
174
|
+
if (this.scopes(provider).includes("openid") && typeof tokens.id_token !== "string") throw errors.forbidden("The provider returned no ID token");
|
|
131
175
|
if (typeof tokens.id_token === "string") {
|
|
132
176
|
claims = decodeJwtPayload(tokens.id_token);
|
|
133
177
|
const aud = Array.isArray(claims.aud) ? claims.aud : [claims.aud];
|
|
@@ -137,7 +181,11 @@ var SsoService = class {
|
|
|
137
181
|
}
|
|
138
182
|
if (meta.userinfo_endpoint && typeof tokens.access_token === "string") {
|
|
139
183
|
const info = await this.hs.fetch(meta.userinfo_endpoint, {
|
|
140
|
-
headers: {
|
|
184
|
+
headers: {
|
|
185
|
+
authorization: `Bearer ${tokens.access_token}`,
|
|
186
|
+
accept: "application/json",
|
|
187
|
+
"user-agent": USER_AGENT
|
|
188
|
+
},
|
|
141
189
|
signal: AbortSignal.timeout(1e4)
|
|
142
190
|
});
|
|
143
191
|
if (info.ok) {
|
|
@@ -149,37 +197,35 @@ var SsoService = class {
|
|
|
149
197
|
};
|
|
150
198
|
}
|
|
151
199
|
}
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
200
|
+
const rawSubject = claims[provider.subjectClaim ?? "sub"];
|
|
201
|
+
const subject = typeof rawSubject === "number" ? String(rawSubject) : rawSubject;
|
|
202
|
+
if (typeof subject !== "string" || !subject) throw errors.forbidden("Provider did not return a subject");
|
|
203
|
+
const userId = await this.resolveUser(provider, subject, claims);
|
|
155
204
|
await this.hs.audit.record({
|
|
156
205
|
action: "sso_login",
|
|
157
206
|
actor: userId,
|
|
158
207
|
details: {
|
|
159
208
|
idp: provider.id,
|
|
160
|
-
subject
|
|
209
|
+
subject
|
|
161
210
|
}
|
|
162
211
|
});
|
|
163
|
-
const redirect = new URL(pending.redirectUrl);
|
|
164
|
-
redirect.searchParams.set("loginToken", loginToken);
|
|
165
212
|
return {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
client: redirect.host || redirect.protocol.replace(/:$/, "")
|
|
213
|
+
userId,
|
|
214
|
+
target: pending.target
|
|
169
215
|
};
|
|
170
216
|
}
|
|
171
|
-
async resolveUser(provider, claims) {
|
|
217
|
+
async resolveUser(provider, subject, claims) {
|
|
172
218
|
const link = await this.hs.db.findOne("sso_identities", [{
|
|
173
219
|
field: "idp_id",
|
|
174
220
|
value: provider.id
|
|
175
221
|
}, {
|
|
176
222
|
field: "subject",
|
|
177
|
-
value:
|
|
223
|
+
value: subject
|
|
178
224
|
}]);
|
|
179
225
|
if (link) return link.user_id;
|
|
180
226
|
if (provider.allowRegistration === false) throw errors.forbidden("No account is linked to this identity");
|
|
181
227
|
const mapped = provider.mapUser ? await provider.mapUser(claims) : {
|
|
182
|
-
localpart: sanitizeLocalpart(claims.preferred_username ??
|
|
228
|
+
localpart: sanitizeLocalpart(claims.preferred_username ?? subject),
|
|
183
229
|
displayname: claims.name
|
|
184
230
|
};
|
|
185
231
|
if (!mapped || !LOCALPART_RE.test(mapped.localpart)) throw errors.forbidden("Could not map identity to a Matrix user");
|
|
@@ -191,11 +237,25 @@ var SsoService = class {
|
|
|
191
237
|
const userId = await this.hs.admin.createUser(localpart, { displayname: mapped.displayname });
|
|
192
238
|
await this.hs.db.create("sso_identities", {
|
|
193
239
|
idp_id: provider.id,
|
|
194
|
-
subject
|
|
240
|
+
subject,
|
|
195
241
|
user_id: userId
|
|
196
242
|
});
|
|
197
243
|
return userId;
|
|
198
244
|
}
|
|
199
245
|
};
|
|
246
|
+
/**
|
|
247
|
+
* The legacy API's last step: the client URL carrying a login token for `userId`, and whether
|
|
248
|
+
* the user confirms the client first (clients not on `sso.clientAllowlist`).
|
|
249
|
+
*/
|
|
250
|
+
async function legacyClientRedirect(hs, userId, redirectUrl) {
|
|
251
|
+
const loginToken = await hs.auth.createLoginToken(userId);
|
|
252
|
+
const redirect = new URL(redirectUrl);
|
|
253
|
+
redirect.searchParams.set("loginToken", loginToken);
|
|
254
|
+
return {
|
|
255
|
+
url: redirect.toString(),
|
|
256
|
+
confirm: !(hs.sso?.clientAllowlist ?? []).some((entry) => clientMatches(redirectUrl, entry)),
|
|
257
|
+
client: redirect.host || redirect.protocol.replace(/:$/, "")
|
|
258
|
+
};
|
|
259
|
+
}
|
|
200
260
|
//#endregion
|
|
201
|
-
export { SsoService, checkRedirectUrl };
|
|
261
|
+
export { SsoService, checkRedirectUrl, githubProvider, legacyClientRedirect, sanitizeLocalpart };
|
package/dist/homeserver.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { TypingService } from "./core/typing.js";
|
|
|
15
15
|
import { PushService } from "./core/push.js";
|
|
16
16
|
import { AdminService, ServerNoticesOptions } from "./core/admin.js";
|
|
17
17
|
import { OAuthDelegation, OAuthDelegationOptions } from "./core/oauth.js";
|
|
18
|
+
import { OAuthServer, OAuthServerOptions } from "./core/oauthserver.js";
|
|
18
19
|
import { SsoOptions, SsoService } from "./core/sso.js";
|
|
19
20
|
import { FederationOptions, FederationService } from "./federation/index.js";
|
|
20
21
|
import { DelayedEventService, DelayedEventsOptions } from "./core/delayed.js";
|
|
@@ -26,6 +27,7 @@ import { SettingsService } from "./core/settings.js";
|
|
|
26
27
|
import { AdminRoomOptions, AdminRoomService } from "./core/adminroom.js";
|
|
27
28
|
import { ResolvedUserAuth, UserAuthAdapter } from "./adapters/auth.js";
|
|
28
29
|
import { MediaAdapter, Thumbnailer } from "./adapters/media.js";
|
|
30
|
+
import { ChangeTracker } from "./core/changes.js";
|
|
29
31
|
import { AddressPolicy } from "./core/outbound.js";
|
|
30
32
|
import { EventEmitter } from "node:events";
|
|
31
33
|
//#region src/homeserver.d.ts
|
|
@@ -84,7 +86,7 @@ export interface HomeserverOptions {
|
|
|
84
86
|
/** Maximum `/sync` long-poll duration in ms. Default: 30000. */
|
|
85
87
|
maxSyncTimeoutMs?: number;
|
|
86
88
|
/**
|
|
87
|
-
* How many `/sync` responses one process builds at once (default
|
|
89
|
+
* How many `/sync` responses one process builds at once (default 16). Requests woken together
|
|
88
90
|
* wait in the process instead of queueing their database reads ahead of everything else.
|
|
89
91
|
*/
|
|
90
92
|
maxConcurrentSyncs?: number;
|
|
@@ -111,7 +113,16 @@ export interface HomeserverOptions {
|
|
|
111
113
|
*/
|
|
112
114
|
/** Delegate authentication to an external OAuth 2.0 / OIDC provider (disables password login and registration). */
|
|
113
115
|
oauth?: OAuthDelegationOptions;
|
|
114
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* Serve the spec's OAuth 2.0 API: users log in on the server's own web pages, with a password
|
|
118
|
+
* and/or the upstream providers of `sso` (e.g. `githubProvider`). Requires `publicBaseUrl`.
|
|
119
|
+
* Cannot be combined with `oauth`.
|
|
120
|
+
*/
|
|
121
|
+
oauthServer?: boolean | OAuthServerOptions;
|
|
122
|
+
/**
|
|
123
|
+
* Upstream identity providers (OpenID Connect, or OAuth 2.0 such as GitHub) for `m.login.sso`
|
|
124
|
+
* and the `oauthServer` login page. Requires `publicBaseUrl`.
|
|
125
|
+
*/
|
|
115
126
|
sso?: SsoOptions;
|
|
116
127
|
/**
|
|
117
128
|
* Coordination between instances. The default (`localCluster()`) supports one process;
|
|
@@ -176,6 +187,11 @@ export declare class Homeserver extends EventEmitter {
|
|
|
176
187
|
readonly serverName: string;
|
|
177
188
|
readonly config: ResolvedConfig;
|
|
178
189
|
readonly db: DatabaseAdapter;
|
|
190
|
+
/**
|
|
191
|
+
* Where keys last changed, when this is the only instance on its database (no cluster
|
|
192
|
+
* adapter): /sync skips the reads that would find nothing new (#51). Null in a cluster.
|
|
193
|
+
*/
|
|
194
|
+
readonly changes: ChangeTracker | null;
|
|
179
195
|
readonly media: MediaAdapter;
|
|
180
196
|
readonly userAuth: ResolvedUserAuth;
|
|
181
197
|
readonly logger: Logger;
|
|
@@ -192,6 +208,7 @@ export declare class Homeserver extends EventEmitter {
|
|
|
192
208
|
readonly rateLimits: RateLimits;
|
|
193
209
|
readonly admin: AdminService;
|
|
194
210
|
readonly oauth: OAuthDelegation | null;
|
|
211
|
+
readonly oauthServer: OAuthServer | null;
|
|
195
212
|
readonly sso: SsoService | null;
|
|
196
213
|
readonly federation: FederationService;
|
|
197
214
|
readonly delayedEvents: DelayedEventService;
|
package/dist/homeserver.js
CHANGED
|
@@ -14,7 +14,9 @@ import { Notifier } from "./core/notifier.js";
|
|
|
14
14
|
import { StreamManager, localCluster, newInstanceId } from "./core/cluster.js";
|
|
15
15
|
import { TypingService } from "./core/typing.js";
|
|
16
16
|
import { coalesceReads } from "./storage/batch.js";
|
|
17
|
+
import { ChangeTracker } from "./core/changes.js";
|
|
17
18
|
import { PushService } from "./core/push.js";
|
|
19
|
+
import { OAuthServer } from "./core/oauthserver.js";
|
|
18
20
|
import { SsoService } from "./core/sso.js";
|
|
19
21
|
import { AdminService } from "./core/admin.js";
|
|
20
22
|
import { OAuthDelegation } from "./core/oauth.js";
|
|
@@ -53,6 +55,11 @@ var Homeserver = class extends EventEmitter {
|
|
|
53
55
|
serverName;
|
|
54
56
|
config;
|
|
55
57
|
db;
|
|
58
|
+
/**
|
|
59
|
+
* Where keys last changed, when this is the only instance on its database (no cluster
|
|
60
|
+
* adapter): /sync skips the reads that would find nothing new (#51). Null in a cluster.
|
|
61
|
+
*/
|
|
62
|
+
changes;
|
|
56
63
|
media;
|
|
57
64
|
userAuth;
|
|
58
65
|
logger;
|
|
@@ -69,6 +76,7 @@ var Homeserver = class extends EventEmitter {
|
|
|
69
76
|
rateLimits;
|
|
70
77
|
admin;
|
|
71
78
|
oauth;
|
|
79
|
+
oauthServer;
|
|
72
80
|
sso;
|
|
73
81
|
federation;
|
|
74
82
|
delayedEvents;
|
|
@@ -115,7 +123,7 @@ var Homeserver = class extends EventEmitter {
|
|
|
115
123
|
defaultRoomVersion,
|
|
116
124
|
accessTokenLifetimeMs: options.accessTokenLifetimeMs ?? null,
|
|
117
125
|
maxSyncTimeoutMs: options.maxSyncTimeoutMs ?? 3e4,
|
|
118
|
-
maxConcurrentSyncs: Math.max(1, options.maxConcurrentSyncs ??
|
|
126
|
+
maxConcurrentSyncs: Math.max(1, options.maxConcurrentSyncs ?? 16),
|
|
119
127
|
urlPreviews: options.urlPreviews ? { allowPrivateAddresses: typeof options.urlPreviews === "object" && !!options.urlPreviews.allowPrivateAddresses } : null,
|
|
120
128
|
thumbnailer: options.thumbnailer ?? null,
|
|
121
129
|
rtc: options.rtc ?? {},
|
|
@@ -125,19 +133,21 @@ var Homeserver = class extends EventEmitter {
|
|
|
125
133
|
roomName: options.serverNotices?.roomName ?? "Server Notices"
|
|
126
134
|
}
|
|
127
135
|
};
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
this.changes = options.cluster ? null : new ChangeTracker();
|
|
137
|
+
let db = options.databaseAdapter;
|
|
138
|
+
if (!db || typeof db.findOne !== "function") {
|
|
131
139
|
this.logger.warn("No database adapter given; using in-memory storage (data is lost on restart)");
|
|
132
|
-
|
|
140
|
+
db = memoryAdapter();
|
|
133
141
|
}
|
|
142
|
+
this.db = coalesceReads(this.changes ? this.changes.wrap(db) : db);
|
|
134
143
|
const media = options.mediaAdapter;
|
|
135
144
|
this.media = media && typeof media.put === "function" ? media : memoryMedia();
|
|
136
145
|
this.userAuth = resolveUserAuth(options.userAuthAdapter);
|
|
137
146
|
this.cluster = options.cluster ?? localCluster();
|
|
138
147
|
this.instanceId = newInstanceId();
|
|
139
|
-
this.streams = new StreamManager(this.cluster, this.instanceId);
|
|
140
|
-
this.notifier = new Notifier(this.cluster, this.instanceId);
|
|
148
|
+
this.streams = new StreamManager(this.cluster, this.instanceId, (...args) => this.logger.warn(...args));
|
|
149
|
+
this.notifier = new Notifier(this.cluster, this.instanceId, () => this.streams.lastAllocated, (fresh) => this.streams.position(fresh));
|
|
150
|
+
this.streams.onAdvance = () => this.notifier.advanced();
|
|
141
151
|
this.auth = new AuthService(this);
|
|
142
152
|
this.rooms = new RoomService(this, !options.cluster);
|
|
143
153
|
this.keys = new KeyService(this);
|
|
@@ -145,6 +155,9 @@ var Homeserver = class extends EventEmitter {
|
|
|
145
155
|
this.push = new PushService(this);
|
|
146
156
|
this.admin = new AdminService(this);
|
|
147
157
|
this.oauth = options.oauth ? new OAuthDelegation(this, options.oauth) : null;
|
|
158
|
+
if (options.oauthServer && options.oauth) throw new Error("`oauthServer` and `oauth` (delegated OAuth) cannot be used together");
|
|
159
|
+
if (options.oauthServer && !options.publicBaseUrl) throw new Error("`publicBaseUrl` is required for `oauthServer`");
|
|
160
|
+
this.oauthServer = options.oauthServer ? new OAuthServer(this, options.oauthServer === true ? {} : options.oauthServer, options.publicBaseUrl) : null;
|
|
148
161
|
this.fetch = options.fetch ?? globalThis.fetch;
|
|
149
162
|
this.addressPolicy = addressPolicy(options.allowPrivateAddresses);
|
|
150
163
|
this.publicFetch = policyFetch(this.addressPolicy, options.fetch);
|
|
@@ -159,7 +172,7 @@ var Homeserver = class extends EventEmitter {
|
|
|
159
172
|
this.identityServers = new IdentityServers(this);
|
|
160
173
|
const adminRoom = options.adminRoom === true ? {} : options.adminRoom || null;
|
|
161
174
|
this.adminRoom = adminRoom && adminRoom.enabled !== false ? new AdminRoomService(this, adminRoom) : null;
|
|
162
|
-
this.sso = options.sso
|
|
175
|
+
this.sso = options.sso && (options.sso.providers.length || options.oauthServer) ? new SsoService(this, options.sso) : null;
|
|
163
176
|
if (this.sso && !options.publicBaseUrl) throw new Error("`publicBaseUrl` is required when SSO is configured");
|
|
164
177
|
this.rateLimits = options.rateLimits === false ? new RateLimits(null) : new RateLimits(options.rateLimits?.limiter ?? memoryRateLimiter(), options.rateLimits?.rules);
|
|
165
178
|
const cleanupEvery = options.cleanupIntervalMs ?? 6e5;
|
|
@@ -197,6 +210,7 @@ var Homeserver = class extends EventEmitter {
|
|
|
197
210
|
}
|
|
198
211
|
const snap = await this.db.max("state_snapshots", "id");
|
|
199
212
|
if (snap !== null) await this.streams.advanceTo(snap);
|
|
213
|
+
this.changes?.start(await this.streams.position(true));
|
|
200
214
|
const oldest = await this.db.findMany("events", {
|
|
201
215
|
sortBy: {
|
|
202
216
|
field: "stream",
|
|
@@ -213,7 +227,13 @@ var Homeserver = class extends EventEmitter {
|
|
|
213
227
|
limit: 1
|
|
214
228
|
});
|
|
215
229
|
if (oldestSnap[0] && oldestSnap[0].id < 0) await this.streams.lowerBackfillTo(oldestSnap[0].id);
|
|
216
|
-
|
|
230
|
+
for (const model of [
|
|
231
|
+
"federation_outbox",
|
|
232
|
+
"audit_log",
|
|
233
|
+
"event_reports",
|
|
234
|
+
"user_reports",
|
|
235
|
+
"appservice_outbox"
|
|
236
|
+
]) await this.cluster.advance(model, await this.db.max(model, "id") ?? 0);
|
|
217
237
|
if (this.federation.enabled) this.federation.sender.start();
|
|
218
238
|
this.delayedEvents.start();
|
|
219
239
|
this.appservices.start();
|
|
@@ -226,7 +246,11 @@ var Homeserver = class extends EventEmitter {
|
|
|
226
246
|
field: "expires_ts",
|
|
227
247
|
operator: "lt",
|
|
228
248
|
value: now - 6048e5
|
|
249
|
+
}, {
|
|
250
|
+
field: "refresh_used",
|
|
251
|
+
value: true
|
|
229
252
|
}]);
|
|
253
|
+
await this.oauthServer?.prune();
|
|
230
254
|
await this.db.delete("login_tokens", [{
|
|
231
255
|
field: "expires_ts",
|
|
232
256
|
operator: "lt",
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { DatabaseAdapter, FieldDef, FindManyOptions, ModelDef, Operator, Row, Sc
|
|
|
5
5
|
import { ClusterAdapter, localCluster } from "./core/cluster.js";
|
|
6
6
|
import { AdminService, ServerNoticesOptions } from "./core/admin.js";
|
|
7
7
|
import { OAuthDelegationOptions } from "./core/oauth.js";
|
|
8
|
-
import {
|
|
8
|
+
import { OAuthServerOptions } from "./core/oauthserver.js";
|
|
9
|
+
import { SsoOptions, SsoProvider, githubProvider } from "./core/sso.js";
|
|
9
10
|
import { MatrixError } from "./core/errors.js";
|
|
10
11
|
import { FederationOptions } from "./federation/index.js";
|
|
11
12
|
import { DelayedEventsOptions } from "./core/delayed.js";
|
|
@@ -23,4 +24,4 @@ import { MysqlPoolLike, PgPoolLike, SqlAdapterOptions, SqlDialect, SqlExecutor,
|
|
|
23
24
|
import { schema } from "./storage/schema.js";
|
|
24
25
|
import { S3MediaOptions, s3Media } from "./adapters/s3.js";
|
|
25
26
|
import { PgClusterPoolLike, postgresCluster } from "./adapters/cluster.js";
|
|
26
|
-
export { type AdminRoomOptions, type AdminService, type AppServiceRegistration, type AuditAction, type AuditEntry, type AuditOptions, type ClusterAdapter, type DatabaseAdapter, type DelayedEventsOptions, type FederationOptions, type FieldDef, type FindManyOptions, Homeserver, type HomeserverOptions, type Logger, M, MatrixError, type MediaAdapter, type MediaMetadata, type ModelDef, type MysqlPoolLike, type NodeHandler, type OAuthDelegationOptions, type Operator, type PasswordCheckContext, type PgClusterPoolLike, type PgPoolLike, type RateLimitAction, type RateLimitRule, type RateLimiter, type Row, type RtcOptions, type S3MediaOptions, type Schema, type ServerNoticesOptions, type SqlAdapterOptions, type SqlDialect, type SqlExecutor, type SqliteDatabaseLike, type SsoOptions, type SsoProvider, type ThreepidEmail, type ThreepidOptions, type ThreepidPurpose, type ThreepidSms, type ThumbnailRequest, type Thumbnailer, type TurnOptions, UNSTABLE, type UserAuthAdapter, type WhereClause, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
|
|
27
|
+
export { type AdminRoomOptions, type AdminService, type AppServiceRegistration, type AuditAction, type AuditEntry, type AuditOptions, type ClusterAdapter, type DatabaseAdapter, type DelayedEventsOptions, type FederationOptions, type FieldDef, type FindManyOptions, Homeserver, type HomeserverOptions, type Logger, M, MatrixError, type MediaAdapter, type MediaMetadata, type ModelDef, type MysqlPoolLike, type NodeHandler, type OAuthDelegationOptions, type OAuthServerOptions, type Operator, type PasswordCheckContext, type PgClusterPoolLike, type PgPoolLike, type RateLimitAction, type RateLimitRule, type RateLimiter, type Row, type RtcOptions, type S3MediaOptions, type Schema, type ServerNoticesOptions, type SqlAdapterOptions, type SqlDialect, type SqlExecutor, type SqliteDatabaseLike, type SsoOptions, type SsoProvider, type ThreepidEmail, type ThreepidOptions, type ThreepidPurpose, type ThreepidSms, type ThumbnailRequest, type Thumbnailer, type TurnOptions, UNSTABLE, type UserAuthAdapter, type WhereClause, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, githubProvider, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { M, UNSTABLE } from "./core/m.js";
|
|
2
2
|
import { MatrixError } from "./core/errors.js";
|
|
3
3
|
import { localCluster } from "./core/cluster.js";
|
|
4
|
+
import { githubProvider } from "./core/sso.js";
|
|
4
5
|
import { loadAppServiceRegistrations, parseAppServiceRegistration } from "./core/appservices.js";
|
|
5
6
|
import { memoryRateLimiter } from "./core/ratelimit.js";
|
|
6
7
|
import { filesystemMedia, memoryMedia, sharpThumbnailer } from "./adapters/media.js";
|
|
@@ -12,4 +13,4 @@ import { getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, g
|
|
|
12
13
|
import { mysqlAdapter, postgresAdapter, sqlAdapter, sqliteAdapter } from "./storage/sql.js";
|
|
13
14
|
import { s3Media } from "./adapters/s3.js";
|
|
14
15
|
import { postgresCluster } from "./adapters/cluster.js";
|
|
15
|
-
export { Homeserver, M, MatrixError, UNSTABLE, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
|
|
16
|
+
export { Homeserver, M, MatrixError, UNSTABLE, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, githubProvider, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
|
package/dist/storage/schema.js
CHANGED
|
@@ -48,10 +48,28 @@ const schema = {
|
|
|
48
48
|
user_id: s,
|
|
49
49
|
device_id: s,
|
|
50
50
|
refresh_token: sn,
|
|
51
|
-
expires_ts: nn
|
|
51
|
+
expires_ts: nn,
|
|
52
|
+
client_id: sn,
|
|
53
|
+
scope: sn,
|
|
54
|
+
refresh_parent: sn,
|
|
55
|
+
refresh_used: bn
|
|
52
56
|
},
|
|
53
57
|
primaryKey: ["token"],
|
|
54
|
-
indexes: [
|
|
58
|
+
indexes: [
|
|
59
|
+
["user_id"],
|
|
60
|
+
["refresh_token"],
|
|
61
|
+
["client_id"],
|
|
62
|
+
["refresh_parent"]
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
oauth_clients: {
|
|
66
|
+
fields: {
|
|
67
|
+
client_id: s,
|
|
68
|
+
metadata: j,
|
|
69
|
+
created_ts: n
|
|
70
|
+
},
|
|
71
|
+
primaryKey: ["client_id"],
|
|
72
|
+
indexes: [["created_ts"]]
|
|
55
73
|
},
|
|
56
74
|
rooms: {
|
|
57
75
|
fields: {
|
package/dist/storage/sql.js
CHANGED
|
@@ -207,8 +207,8 @@ function sqlAdapter({ dialect, executor: rootExecutor, tablePrefix = "", transac
|
|
|
207
207
|
};
|
|
208
208
|
return build(rootExecutor, false);
|
|
209
209
|
}
|
|
210
|
-
/** How long SQLite statements may run back to back before yielding to the event loop (#47). */
|
|
211
|
-
const YIELD_AFTER_MS =
|
|
210
|
+
/** How long SQLite statements may run back to back before yielding to the event loop (#47, #52). */
|
|
211
|
+
const YIELD_AFTER_MS = 50;
|
|
212
212
|
/** Prepared statements an SQLite adapter keeps (#40). */
|
|
213
213
|
const STATEMENT_CACHE = 500;
|
|
214
214
|
/** SQLite adapter for `node:sqlite` (`new DatabaseSync(path)`) or `better-sqlite3`. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsonStringifyReplacer } from "../core/util.js";
|
|
2
2
|
import { $constructor } from "../core/core.js";
|
|
3
3
|
import { $ZodError, flattenError, formatError } from "../core/errors.js";
|
|
4
|
-
//#region node_modules/zod/v4/classic/errors.js
|
|
4
|
+
//#region ../../node_modules/zod/v4/classic/errors.js
|
|
5
5
|
const _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
|
|
6
6
|
function _lazyMethod(proto, key, make) {
|
|
7
7
|
Object.defineProperty(proto, key, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _decode, _decodeAsync, _encode, _encodeAsync, _parse, _parseAsync, _safeDecode, _safeDecodeAsync, _safeEncode, _safeEncodeAsync, _safeParse, _safeParseAsync } from "../core/parse.js";
|
|
2
2
|
import { ZodRealError } from "./errors.js";
|
|
3
|
-
//#region node_modules/zod/v4/classic/parse.js
|
|
3
|
+
//#region ../../node_modules/zod/v4/classic/parse.js
|
|
4
4
|
const parse = /* @__PURE__ */ _parse(ZodRealError);
|
|
5
5
|
const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
6
6
|
const safeParse = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
@@ -9,7 +9,7 @@ import { _array, _base64, _base64url, _boolean, _cidrv4, _cidrv6, _cuid, _cuid2,
|
|
|
9
9
|
import { createStandardJSONSchemaMethod, createToJSONSchemaMethod } from "../core/to-json-schema.js";
|
|
10
10
|
import { aggregateChecks, arrayProcessor, booleanProcessor, catchProcessor, customProcessor, defaultProcessor, enumProcessor, intersectionProcessor, neverProcessor, nonoptionalProcessor, nullableProcessor, numberProcessor, objectProcessor, optionalProcessor, pipeProcessor, prefaultProcessor, readonlyProcessor, recordProcessor, stringProcessor, transformProcessor, unionProcessor, unknownProcessor } from "../core/json-schema-processors.js";
|
|
11
11
|
import { decode, decodeAsync, encode, encodeAsync, parse, parseAsync, safeDecode, safeDecodeAsync, safeEncode, safeEncodeAsync, safeParse, safeParseAsync } from "./parse.js";
|
|
12
|
-
//#region node_modules/zod/v4/classic/schemas.js
|
|
12
|
+
//#region ../../node_modules/zod/v4/classic/schemas.js
|
|
13
13
|
function _ensureDefaultLocale() {
|
|
14
14
|
if (!globalConfig.localeError) config(en_default());
|
|
15
15
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { issue, normalizeParams, slugify } from "./util.js";
|
|
2
2
|
import { $ZodCheck, $ZodCheckEndsWith, $ZodCheckGreaterThan, $ZodCheckIncludes, $ZodCheckLengthEquals, $ZodCheckLessThan, $ZodCheckLowerCase, $ZodCheckMaxLength, $ZodCheckMinLength, $ZodCheckMultipleOf, $ZodCheckOverwrite, $ZodCheckRegex, $ZodCheckStartsWith, $ZodCheckUpperCase } from "./checks.js";
|
|
3
|
-
//#region node_modules/zod/v4/core/api.js
|
|
3
|
+
//#region ../../node_modules/zod/v4/core/api.js
|
|
4
4
|
function snapshotChecks(def) {
|
|
5
5
|
if (def.checks) def.checks = [...def.checks];
|
|
6
6
|
return def;
|