matrix-nodejs-homeserver 0.2.0 → 0.3.2
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 -11
- package/dist/adapters/cluster.d.ts +1 -26
- package/dist/adapters/cluster.js +50 -103
- package/dist/api/account.js +407 -354
- package/dist/api/admin.js +82 -61
- package/dist/api/appservice.js +37 -25
- package/dist/api/backup.js +73 -62
- package/dist/api/federation.js +172 -138
- package/dist/api/filters.js +4 -3
- package/dist/api/legacy.js +91 -78
- package/dist/api/media.js +71 -55
- package/dist/api/misc.js +46 -37
- package/dist/api/preview.js +37 -73
- package/dist/api/profile.js +108 -97
- package/dist/api/push.js +114 -99
- package/dist/api/relationships.js +64 -58
- package/dist/api/rooms.js +703 -580
- package/dist/api/rtc.d.ts +0 -1
- package/dist/api/rtc.js +48 -34
- package/dist/api/search.js +133 -127
- package/dist/api/slidingsync.js +41 -31
- package/dist/api/spaces.js +82 -77
- package/dist/api/sync.js +325 -135
- package/dist/api/threadsubs.js +72 -61
- package/dist/api/threepid.js +99 -66
- package/dist/api/uia.js +17 -16
- package/dist/api/user.js +221 -178
- package/dist/core/admin.d.ts +0 -1
- package/dist/core/admin.js +6 -5
- package/dist/core/adminroom.js +9 -8
- package/dist/core/appservices.d.ts +2 -1
- package/dist/core/appservices.js +5 -4
- package/dist/core/audit.d.ts +0 -1
- package/dist/core/authrules.js +26 -25
- package/dist/core/cluster.d.ts +12 -5
- package/dist/core/cluster.js +46 -29
- package/dist/core/delayed.js +2 -1
- package/dist/core/events.js +11 -10
- package/dist/core/html.js +13 -0
- package/dist/core/identity.js +1 -1
- package/dist/core/invitefilter.js +2 -1
- package/dist/core/json.js +10 -1
- package/dist/core/keys.d.ts +10 -1
- package/dist/core/keys.js +62 -42
- package/dist/core/m.d.ts +209 -0
- package/dist/core/m.js +182 -0
- package/dist/core/outbound.d.ts +16 -0
- package/dist/core/outbound.js +172 -0
- package/dist/core/partialstate.js +2 -1
- package/dist/core/policy.js +4 -3
- package/dist/core/push.d.ts +0 -1
- package/dist/core/push.js +41 -28
- package/dist/core/pushrules.js +36 -35
- package/dist/core/ratelimit.d.ts +1 -1
- package/dist/core/rooms.d.ts +10 -1
- package/dist/core/rooms.js +101 -74
- package/dist/core/semaphore.js +23 -0
- package/dist/core/sso.d.ts +15 -1
- package/dist/core/sso.js +38 -2
- package/dist/core/stateres.js +7 -6
- package/dist/core/threepid.js +1 -9
- package/dist/federation/client.js +9 -1
- package/dist/federation/index.d.ts +2 -2
- package/dist/federation/index.js +43 -35
- package/dist/homeserver.d.ts +23 -4
- package/dist/homeserver.js +12 -3
- package/dist/http/handler.js +2 -23
- package/dist/http/input.js +2 -2
- package/dist/http/procedure.js +25 -0
- package/dist/http/router.d.ts +9 -26
- package/dist/http/router.js +10 -30
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/storage/batch.js +11 -0
- package/dist/storage/memory.js +1 -0
- package/dist/storage/schema.js +1 -1
- package/dist/storage/sql.js +20 -1
- package/dist/storage/types.d.ts +16 -0
- package/dist/vendor/zod/LICENSE +21 -0
- package/dist/vendor/zod/v4/classic/errors.js +54 -0
- package/dist/vendor/zod/v4/classic/parse.js +17 -0
- package/dist/vendor/zod/v4/classic/schemas.js +865 -0
- package/dist/vendor/zod/v4/core/api.js +512 -0
- package/dist/vendor/zod/v4/core/checks.js +300 -0
- package/dist/vendor/zod/v4/core/core.js +109 -0
- package/dist/vendor/zod/v4/core/doc.js +35 -0
- package/dist/vendor/zod/v4/core/errors.js +118 -0
- package/dist/vendor/zod/v4/core/json-schema-processors.js +428 -0
- package/dist/vendor/zod/v4/core/memoizer.js +255 -0
- package/dist/vendor/zod/v4/core/parse.js +206 -0
- package/dist/vendor/zod/v4/core/regexes.js +73 -0
- package/dist/vendor/zod/v4/core/registries.js +48 -0
- package/dist/vendor/zod/v4/core/schemas.js +1378 -0
- package/dist/vendor/zod/v4/core/to-json-schema.js +448 -0
- package/dist/vendor/zod/v4/core/util.js +537 -0
- package/dist/vendor/zod/v4/core/versions.js +8 -0
- package/dist/vendor/zod/v4/locales/en.js +110 -0
- package/package.json +8 -6
- package/dist/core/pushrules.d.ts +0 -1
package/dist/homeserver.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { loadSigningKey, randomString } from "./core/crypto.js";
|
|
2
|
+
import "./core/m.js";
|
|
2
3
|
import { SUPPORTED_ROOM_VERSIONS } from "./core/events.js";
|
|
3
4
|
import { PartialStateService } from "./core/partialstate.js";
|
|
4
5
|
import { RoomService } from "./core/rooms.js";
|
|
6
|
+
import { addressPolicy, policyFetch } from "./core/outbound.js";
|
|
5
7
|
import { FederationService } from "./federation/index.js";
|
|
6
8
|
import { PolicyServers } from "./core/policy.js";
|
|
7
9
|
import { ThreepidService } from "./core/threepid.js";
|
|
@@ -12,9 +14,9 @@ import { Notifier } from "./core/notifier.js";
|
|
|
12
14
|
import { StreamManager, localCluster, newInstanceId } from "./core/cluster.js";
|
|
13
15
|
import { TypingService } from "./core/typing.js";
|
|
14
16
|
import { PushService } from "./core/push.js";
|
|
17
|
+
import { SsoService } from "./core/sso.js";
|
|
15
18
|
import { AdminService } from "./core/admin.js";
|
|
16
19
|
import { OAuthDelegation } from "./core/oauth.js";
|
|
17
|
-
import { SsoService } from "./core/sso.js";
|
|
18
20
|
import { DelayedEventService } from "./core/delayed.js";
|
|
19
21
|
import { AuditService } from "./core/audit.js";
|
|
20
22
|
import { SettingsService } from "./core/settings.js";
|
|
@@ -77,8 +79,12 @@ var Homeserver = class extends EventEmitter {
|
|
|
77
79
|
policyServers;
|
|
78
80
|
threepids;
|
|
79
81
|
identityServers;
|
|
80
|
-
/** `fetch`
|
|
82
|
+
/** `fetch` for requests to addresses the operator configured (OAuth, SSO, application services). */
|
|
81
83
|
fetch;
|
|
84
|
+
/** Which addresses requests to user- or remote-chosen destinations may reach (see `allowPrivateAddresses`). */
|
|
85
|
+
addressPolicy;
|
|
86
|
+
/** `fetch` for user- or remote-chosen URLs: refuses private addresses unless allowed, never follows redirects. */
|
|
87
|
+
publicFetch;
|
|
82
88
|
/** Resolves once storage is migrated and the server is ready to serve requests. */
|
|
83
89
|
ready;
|
|
84
90
|
_signingKey = null;
|
|
@@ -108,6 +114,7 @@ var Homeserver = class extends EventEmitter {
|
|
|
108
114
|
defaultRoomVersion,
|
|
109
115
|
accessTokenLifetimeMs: options.accessTokenLifetimeMs ?? null,
|
|
110
116
|
maxSyncTimeoutMs: options.maxSyncTimeoutMs ?? 3e4,
|
|
117
|
+
maxConcurrentSyncs: Math.max(1, options.maxConcurrentSyncs ?? 4),
|
|
111
118
|
urlPreviews: options.urlPreviews ? { allowPrivateAddresses: typeof options.urlPreviews === "object" && !!options.urlPreviews.allowPrivateAddresses } : null,
|
|
112
119
|
thumbnailer: options.thumbnailer ?? null,
|
|
113
120
|
rtc: options.rtc ?? {},
|
|
@@ -137,6 +144,9 @@ var Homeserver = class extends EventEmitter {
|
|
|
137
144
|
this.push = new PushService(this);
|
|
138
145
|
this.admin = new AdminService(this);
|
|
139
146
|
this.oauth = options.oauth ? new OAuthDelegation(this, options.oauth) : null;
|
|
147
|
+
this.fetch = options.fetch ?? globalThis.fetch;
|
|
148
|
+
this.addressPolicy = addressPolicy(options.allowPrivateAddresses);
|
|
149
|
+
this.publicFetch = policyFetch(this.addressPolicy, options.fetch);
|
|
140
150
|
this.federation = new FederationService(this, options.federation);
|
|
141
151
|
this.delayedEvents = new DelayedEventService(this, options.delayedEvents);
|
|
142
152
|
this.audit = new AuditService(this, options.audit);
|
|
@@ -156,7 +166,6 @@ var Homeserver = class extends EventEmitter {
|
|
|
156
166
|
this.cleanupTimer = setInterval(() => void this.cleanup().catch((err) => this.logger.warn("Cleanup failed", err)), cleanupEvery);
|
|
157
167
|
this.cleanupTimer.unref?.();
|
|
158
168
|
}
|
|
159
|
-
this.fetch = options.fetch ?? globalThis.fetch;
|
|
160
169
|
registerRoutes(this.router);
|
|
161
170
|
this.ready = this.init(options);
|
|
162
171
|
this.ready.catch((err) => this.logger.error("Homeserver failed to start", err));
|
package/dist/http/handler.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canonicalJson } from "../core/json.js";
|
|
2
|
+
import { UNSTABLE } from "../core/m.js";
|
|
2
3
|
import { MatrixError, errors } from "../core/errors.js";
|
|
3
|
-
import { inputError } from "./input.js";
|
|
4
4
|
//#region src/http/handler.ts
|
|
5
5
|
const CORS_HEADERS = {
|
|
6
6
|
"access-control-allow-origin": "*",
|
|
@@ -95,7 +95,7 @@ async function dispatch(hs, request, url, options) {
|
|
|
95
95
|
const reg = hs.appservices.byToken(t);
|
|
96
96
|
if (reg) {
|
|
97
97
|
const userId = await hs.appservices.masquerade(reg, url.searchParams.get("user_id"));
|
|
98
|
-
const deviceId = url.searchParams.get("device_id") ?? url.searchParams.get(
|
|
98
|
+
const deviceId = url.searchParams.get("device_id") ?? url.searchParams.get(UNSTABLE.ORG.MATRIX.MSC3202.DEVICE_ID);
|
|
99
99
|
if (deviceId && !await hs.db.findOne("devices", [{
|
|
100
100
|
field: "user_id",
|
|
101
101
|
value: userId
|
|
@@ -124,33 +124,12 @@ async function dispatch(hs, request, url, options) {
|
|
|
124
124
|
async optionalAuth() {
|
|
125
125
|
return token() ? ctx.auth() : null;
|
|
126
126
|
},
|
|
127
|
-
input: void 0,
|
|
128
127
|
async rateLimit(action) {
|
|
129
128
|
const s = token() ? await ctx.auth().catch(() => null) : null;
|
|
130
129
|
if (s?.isAdmin || s?.appservice && s.appservice.rate_limited === false) return;
|
|
131
130
|
await hs.rateLimits.check(action, s?.userId ?? ctx.ip ?? "unknown");
|
|
132
131
|
}
|
|
133
132
|
};
|
|
134
|
-
if (matched.input) {
|
|
135
|
-
const schema = matched.input;
|
|
136
|
-
let raw;
|
|
137
|
-
let readError;
|
|
138
|
-
try {
|
|
139
|
-
raw = await ctx.json();
|
|
140
|
-
} catch (err) {
|
|
141
|
-
readError = err;
|
|
142
|
-
}
|
|
143
|
-
let parsed = null;
|
|
144
|
-
Object.defineProperty(ctx, "input", { get() {
|
|
145
|
-
if (readError) throw readError;
|
|
146
|
-
if (!parsed) {
|
|
147
|
-
const result = schema.safeParse(raw);
|
|
148
|
-
if (!result.success) throw inputError(result.error);
|
|
149
|
-
parsed = { data: result.data };
|
|
150
|
-
}
|
|
151
|
-
return parsed.data;
|
|
152
|
-
} });
|
|
153
|
-
}
|
|
154
133
|
const result = await matched.handler(ctx);
|
|
155
134
|
if (result instanceof Response) return withCors(result);
|
|
156
135
|
return jsonResponse(result ?? {});
|
package/dist/http/input.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MatrixError } from "../core/errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { looseObject } from "../vendor/zod/v4/classic/schemas.js";
|
|
3
3
|
//#region src/http/input.ts
|
|
4
4
|
/** A request body: a JSON object whose unknown keys are kept. */
|
|
5
|
-
const body = (shape) =>
|
|
5
|
+
const body = (shape) => looseObject(shape);
|
|
6
6
|
/**
|
|
7
7
|
* Errors carry their Matrix errcode in the message, as `M_CODE: text`: `fail` builds the zod
|
|
8
8
|
* error option for a field. Without one, a missing field is M_MISSING_PARAM and any other
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { inputError } from "./input.js";
|
|
2
|
+
//#region src/http/procedure.ts
|
|
3
|
+
function procedure(def) {
|
|
4
|
+
return async (ctx) => {
|
|
5
|
+
const session = def.auth === "user" ? await ctx.auth() : def.auth === "optional" ? await ctx.optionalAuth() : void 0;
|
|
6
|
+
const extra = def.guard ? await def.guard(ctx) : {};
|
|
7
|
+
if (def.rateLimit) await ctx.rateLimit(def.rateLimit);
|
|
8
|
+
let input;
|
|
9
|
+
if (def.input) {
|
|
10
|
+
const parsed = def.input.safeParse(await ctx.json());
|
|
11
|
+
if (!parsed.success) throw inputError(parsed.error);
|
|
12
|
+
input = parsed.data;
|
|
13
|
+
}
|
|
14
|
+
const result = await def.handler(Object.assign(ctx, extra, {
|
|
15
|
+
session,
|
|
16
|
+
input
|
|
17
|
+
}));
|
|
18
|
+
if (!def.output || result instanceof Response) return result;
|
|
19
|
+
const checked = def.output.safeParse(result);
|
|
20
|
+
if (!checked.success) throw new Error(`Response does not match the output schema: ${checked.error.message}`);
|
|
21
|
+
return checked.data;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { procedure };
|
package/dist/http/router.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Session } from "../core/auth.js";
|
|
2
2
|
import { RateLimitAction } from "../core/ratelimit.js";
|
|
3
3
|
import { Homeserver } from "../homeserver.js";
|
|
4
|
-
import { z } from "zod";
|
|
5
4
|
//#region src/http/router.d.ts
|
|
6
5
|
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
|
|
7
|
-
export interface RequestContext
|
|
6
|
+
export interface RequestContext {
|
|
8
7
|
hs: Homeserver;
|
|
9
8
|
request: Request;
|
|
10
9
|
url: URL;
|
|
@@ -23,38 +22,22 @@ export interface RequestContext<I = unknown> {
|
|
|
23
22
|
accessToken(): string | null;
|
|
24
23
|
/** Apply the rate limit for `action` to the requesting user (or IP when unauthenticated). */
|
|
25
24
|
rateLimit(action: RateLimitAction): Promise<void>;
|
|
26
|
-
/**
|
|
27
|
-
* The request body parsed with the route's `input` schema (undefined for routes without one).
|
|
28
|
-
* Validated on first access, which throws the Matrix error for an invalid body: read it where
|
|
29
|
-
* the handler needs the body, after authenticating.
|
|
30
|
-
*/
|
|
31
|
-
input: I;
|
|
32
25
|
}
|
|
33
26
|
export type HandlerResult = Response | object | void;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
export interface RouteOptions<S extends z.ZodType = z.ZodType> {
|
|
37
|
-
input?: S;
|
|
38
|
-
}
|
|
39
|
-
/** A route's handler, with the body type its `input` schema produces. */
|
|
40
|
-
type Routed<S> = S extends z.ZodType ? Handler<z.output<S>> : Handler;
|
|
27
|
+
/** What the router calls; endpoints build one with `procedure()` (see http/procedure.ts). */
|
|
28
|
+
export type Handler = (ctx: RequestContext) => Promise<HandlerResult> | HandlerResult;
|
|
41
29
|
export declare class Router {
|
|
42
30
|
private routes;
|
|
43
|
-
add
|
|
44
|
-
get(path: string, h: Handler)
|
|
45
|
-
post
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
put(path: string, h: Handler): void;
|
|
49
|
-
delete(path: string, h: Handler): void;
|
|
31
|
+
add(method: HttpMethod, path: string, handler: Handler): void;
|
|
32
|
+
get: (path: string, h: Handler) => void;
|
|
33
|
+
post: (path: string, h: Handler) => void;
|
|
34
|
+
put: (path: string, h: Handler) => void;
|
|
35
|
+
delete: (path: string, h: Handler) => void;
|
|
50
36
|
/** Register a client-server API route under both `r0` and `v3` (or the given versions). */
|
|
51
|
-
client<S extends z.ZodType>(method: HttpMethod, path: string, options: RouteOptions<S>, handler: Routed<S>, versions?: string[]): void;
|
|
52
37
|
client(method: HttpMethod, path: string, handler: Handler, versions?: string[]): void;
|
|
53
38
|
match(method: string, pathname: string): {
|
|
54
39
|
handler: Handler;
|
|
55
40
|
params: Record<string, string>;
|
|
56
|
-
input?: z.ZodType;
|
|
57
41
|
} | "method" | null;
|
|
58
42
|
}
|
|
59
|
-
//#endregion
|
|
60
|
-
export {};
|
|
43
|
+
//#endregion
|
package/dist/http/router.js
CHANGED
|
@@ -1,39 +1,20 @@
|
|
|
1
1
|
//#region src/http/router.ts
|
|
2
2
|
var Router = class {
|
|
3
3
|
routes = [];
|
|
4
|
-
add(method, path, handler
|
|
4
|
+
add(method, path, handler) {
|
|
5
5
|
this.routes.push({
|
|
6
6
|
method,
|
|
7
7
|
segments: path.split("/").filter(Boolean),
|
|
8
|
-
handler
|
|
9
|
-
input: options.input
|
|
8
|
+
handler
|
|
10
9
|
});
|
|
11
10
|
}
|
|
12
|
-
get(path, h)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
put(path, a, b) {
|
|
20
|
-
if (typeof a === "function") this.add("PUT", path, a);
|
|
21
|
-
else this.add("PUT", path, b, a);
|
|
22
|
-
}
|
|
23
|
-
delete(path, h) {
|
|
24
|
-
this.add("DELETE", path, h);
|
|
25
|
-
}
|
|
26
|
-
client(method, path, a, b, c) {
|
|
27
|
-
const [options, handler, versions] = typeof a === "function" ? [
|
|
28
|
-
{},
|
|
29
|
-
a,
|
|
30
|
-
b ?? ["r0", "v3"]
|
|
31
|
-
] : [
|
|
32
|
-
a,
|
|
33
|
-
b,
|
|
34
|
-
c ?? ["r0", "v3"]
|
|
35
|
-
];
|
|
36
|
-
for (const v of versions) this.add(method, `/_matrix/client/${v}${path}`, handler, options);
|
|
11
|
+
get = (path, h) => this.add("GET", path, h);
|
|
12
|
+
post = (path, h) => this.add("POST", path, h);
|
|
13
|
+
put = (path, h) => this.add("PUT", path, h);
|
|
14
|
+
delete = (path, h) => this.add("DELETE", path, h);
|
|
15
|
+
/** Register a client-server API route under both `r0` and `v3` (or the given versions). */
|
|
16
|
+
client(method, path, handler, versions = ["r0", "v3"]) {
|
|
17
|
+
for (const v of versions) this.add(method, `/_matrix/client/${v}${path}`, handler);
|
|
37
18
|
}
|
|
38
19
|
match(method, pathname) {
|
|
39
20
|
const parts = pathname.split("/").filter(Boolean);
|
|
@@ -47,8 +28,7 @@ var Router = class {
|
|
|
47
28
|
}
|
|
48
29
|
return {
|
|
49
30
|
handler: route.handler,
|
|
50
|
-
params
|
|
51
|
-
input: route.input
|
|
31
|
+
params
|
|
52
32
|
};
|
|
53
33
|
}
|
|
54
34
|
return methodMismatch ? "method" : null;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ThreepidEmail, ThreepidOptions, ThreepidPurpose, ThreepidSms } from "./core/threepid.js";
|
|
2
|
+
import { M, UNSTABLE } from "./core/m.js";
|
|
2
3
|
import { AppServiceRegistration, loadAppServiceRegistrations, parseAppServiceRegistration } from "./core/appservices.js";
|
|
3
|
-
import { ClusterAdapter, localCluster } from "./core/cluster.js";
|
|
4
4
|
import { DatabaseAdapter, FieldDef, FindManyOptions, ModelDef, Operator, Row, Schema, WhereClause } from "./storage/types.js";
|
|
5
|
+
import { ClusterAdapter, localCluster } from "./core/cluster.js";
|
|
5
6
|
import { AdminService, ServerNoticesOptions } from "./core/admin.js";
|
|
6
7
|
import { OAuthDelegationOptions } from "./core/oauth.js";
|
|
7
8
|
import { SsoOptions, SsoProvider } from "./core/sso.js";
|
|
@@ -21,5 +22,5 @@ import { memoryAdapter } from "./storage/memory.js";
|
|
|
21
22
|
import { MysqlPoolLike, PgPoolLike, SqlAdapterOptions, SqlDialect, SqlExecutor, SqliteDatabaseLike, mysqlAdapter, postgresAdapter, sqlAdapter, sqliteAdapter } from "./storage/sql.js";
|
|
22
23
|
import { schema } from "./storage/schema.js";
|
|
23
24
|
import { S3MediaOptions, s3Media } from "./adapters/s3.js";
|
|
24
|
-
import { PgClusterPoolLike,
|
|
25
|
-
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, 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
|
|
25
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { M, UNSTABLE } from "./core/m.js";
|
|
1
2
|
import { MatrixError } from "./core/errors.js";
|
|
2
3
|
import { localCluster } from "./core/cluster.js";
|
|
3
4
|
import { loadAppServiceRegistrations, parseAppServiceRegistration } from "./core/appservices.js";
|
|
@@ -10,5 +11,5 @@ import { Homeserver, createHomeserver } from "./homeserver.js";
|
|
|
10
11
|
import { getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, isMatrixPath } from "./http/adapters.js";
|
|
11
12
|
import { mysqlAdapter, postgresAdapter, sqlAdapter, sqliteAdapter } from "./storage/sql.js";
|
|
12
13
|
import { s3Media } from "./adapters/s3.js";
|
|
13
|
-
import { postgresCluster
|
|
14
|
-
export { Homeserver, MatrixError, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster,
|
|
14
|
+
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 };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/storage/batch.ts
|
|
2
|
+
/**
|
|
3
|
+
* Run independent reads together: in one round trip when the adapter can (`findManyBatch`),
|
|
4
|
+
* otherwise concurrently. Results come in the order of the queries.
|
|
5
|
+
*/
|
|
6
|
+
function readBatch(db, queries) {
|
|
7
|
+
if (db.findManyBatch) return db.findManyBatch(queries);
|
|
8
|
+
return Promise.all(queries.map((query) => db.findMany(query.model, query.opts)));
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { readBatch };
|
package/dist/storage/memory.js
CHANGED
|
@@ -72,6 +72,7 @@ function memoryAdapter() {
|
|
|
72
72
|
rows.sort((a, b) => a[field] < b[field] ? -dir : a[field] > b[field] ? dir : 0);
|
|
73
73
|
}
|
|
74
74
|
if (opts.limit !== void 0) rows = rows.slice(0, opts.limit);
|
|
75
|
+
if (opts.fields?.length) return rows.map((r) => Object.fromEntries(opts.fields.map((f) => [f, structuredClone(r[f] ?? null)])));
|
|
75
76
|
return rows.map((r) => structuredClone(r));
|
|
76
77
|
},
|
|
77
78
|
async update(model, where, data) {
|
package/dist/storage/schema.js
CHANGED
package/dist/storage/sql.js
CHANGED
|
@@ -130,11 +130,30 @@ function sqlAdapter({ dialect, executor: rootExecutor, tablePrefix = "", transac
|
|
|
130
130
|
},
|
|
131
131
|
async findMany(model, opts = {}) {
|
|
132
132
|
const params = [];
|
|
133
|
-
let sql = `SELECT * FROM ${t(model)}${buildWhere(model, opts.where, params)}`;
|
|
133
|
+
let sql = `SELECT ${opts.fields?.length ? opts.fields.map(q).join(", ") : "*"} FROM ${t(model)}${buildWhere(model, opts.where, params)}`;
|
|
134
134
|
if (opts.sortBy) sql += ` ORDER BY ${q(opts.sortBy.field)} ${opts.sortBy.direction === "asc" ? "ASC" : "DESC"}`;
|
|
135
135
|
if (opts.limit !== void 0) sql += ` LIMIT ${Math.max(0, Math.floor(opts.limit))}`;
|
|
136
136
|
return (await executor.query(sql, params)).map((r) => fromDb(model, r));
|
|
137
137
|
},
|
|
138
|
+
...dialect === "postgres" ? { async findManyBatch(queries) {
|
|
139
|
+
if (queries.length === 0) return [];
|
|
140
|
+
const params = [];
|
|
141
|
+
const parts = queries.map(({ model, opts = {} }, i) => {
|
|
142
|
+
const fields = opts.fields?.length && opts.sortBy && !opts.fields.includes(opts.sortBy.field) ? [...opts.fields, opts.sortBy.field] : opts.fields;
|
|
143
|
+
let inner = `SELECT ${fields?.length ? fields.map(q).join(", ") : "*"} FROM ${t(model)}${buildWhere(model, opts.where, params)}`;
|
|
144
|
+
const order = opts.sortBy ? `${q(opts.sortBy.field)} ${opts.sortBy.direction === "asc" ? "ASC" : "DESC"}` : "";
|
|
145
|
+
if (order) inner += ` ORDER BY ${order}`;
|
|
146
|
+
if (opts.limit !== void 0) inner += ` LIMIT ${Math.max(0, Math.floor(opts.limit))}`;
|
|
147
|
+
return `SELECT ${i} AS i, row_number() OVER (${order ? `ORDER BY ${order.replace(/^"/, "b.\"")}` : ""}) AS n, to_jsonb(b) AS r FROM (${inner}) b`;
|
|
148
|
+
});
|
|
149
|
+
const rows = await executor.query(`${parts.join(" UNION ALL ")} ORDER BY i, n`, params);
|
|
150
|
+
const out = queries.map(() => []);
|
|
151
|
+
for (const row of rows) {
|
|
152
|
+
const r = typeof row.r === "string" ? JSON.parse(row.r) : row.r;
|
|
153
|
+
out[Number(row.i)].push(fromDb(queries[Number(row.i)].model, r));
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
} } : {},
|
|
138
157
|
async update(model, where, data) {
|
|
139
158
|
const fields = Object.keys(data).filter((k) => data[k] !== void 0);
|
|
140
159
|
if (fields.length === 0) return 0;
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -21,8 +21,18 @@ export interface WhereClause {
|
|
|
21
21
|
operator?: Operator;
|
|
22
22
|
}
|
|
23
23
|
export type Row = Record<string, any>;
|
|
24
|
+
/** One read of a `findManyBatch`. */
|
|
25
|
+
export interface BatchQuery {
|
|
26
|
+
model: string;
|
|
27
|
+
opts?: FindManyOptions;
|
|
28
|
+
}
|
|
24
29
|
export interface FindManyOptions {
|
|
25
30
|
where?: WhereClause[];
|
|
31
|
+
/**
|
|
32
|
+
* Only these fields are needed. Adapters should read just these (a smaller query and less to
|
|
33
|
+
* parse); one that ignores the option and returns whole rows is still correct.
|
|
34
|
+
*/
|
|
35
|
+
fields?: string[];
|
|
26
36
|
sortBy?: {
|
|
27
37
|
field: string;
|
|
28
38
|
direction: "asc" | "desc";
|
|
@@ -35,6 +45,12 @@ export interface FindManyOptions {
|
|
|
35
45
|
* All `where` clauses are combined with AND.
|
|
36
46
|
*/
|
|
37
47
|
export interface DatabaseAdapter {
|
|
48
|
+
/**
|
|
49
|
+
* Several independent reads in one go: the results in the same order as the queries. Optional;
|
|
50
|
+
* an adapter that talks to a database server can send them in one round trip. Without it,
|
|
51
|
+
* callers run the reads separately (see `readBatch`).
|
|
52
|
+
*/
|
|
53
|
+
findManyBatch?(queries: BatchQuery[]): Promise<Row[][]>;
|
|
38
54
|
/** Create tables / collections for the given schema. Must be idempotent. */
|
|
39
55
|
migrate?(schema: Schema): Promise<void>;
|
|
40
56
|
create(model: string, data: Row): Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Colin McDonnell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsonStringifyReplacer } from "../core/util.js";
|
|
2
|
+
import { $constructor } from "../core/core.js";
|
|
3
|
+
import { $ZodError, flattenError, formatError } from "../core/errors.js";
|
|
4
|
+
//#region node_modules/zod/v4/classic/errors.js
|
|
5
|
+
const _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
|
|
6
|
+
function _lazyMethod(proto, key, make) {
|
|
7
|
+
Object.defineProperty(proto, key, {
|
|
8
|
+
configurable: true,
|
|
9
|
+
enumerable: false,
|
|
10
|
+
get() {
|
|
11
|
+
const value = make(this);
|
|
12
|
+
Object.defineProperty(this, key, {
|
|
13
|
+
value,
|
|
14
|
+
configurable: true,
|
|
15
|
+
writable: true
|
|
16
|
+
});
|
|
17
|
+
return value;
|
|
18
|
+
},
|
|
19
|
+
set(value) {
|
|
20
|
+
Object.defineProperty(this, key, {
|
|
21
|
+
value,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const initializer = (inst, issues) => {
|
|
29
|
+
$ZodError.init(inst, issues);
|
|
30
|
+
inst.name = "ZodError";
|
|
31
|
+
const proto = Object.getPrototypeOf(inst);
|
|
32
|
+
if (_installedErrorProtos.has(proto)) return;
|
|
33
|
+
_installedErrorProtos.add(proto);
|
|
34
|
+
_lazyMethod(proto, "format", (self) => (mapper) => formatError(self, mapper));
|
|
35
|
+
_lazyMethod(proto, "flatten", (self) => (mapper) => flattenError(self, mapper));
|
|
36
|
+
_lazyMethod(proto, "addIssue", (self) => (issue) => {
|
|
37
|
+
self.issues.push(issue);
|
|
38
|
+
self.message = JSON.stringify(self.issues, jsonStringifyReplacer, 2);
|
|
39
|
+
});
|
|
40
|
+
_lazyMethod(proto, "addIssues", (self) => (issues) => {
|
|
41
|
+
self.issues.push(...issues);
|
|
42
|
+
self.message = JSON.stringify(self.issues, jsonStringifyReplacer, 2);
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(proto, "isEmpty", {
|
|
45
|
+
configurable: true,
|
|
46
|
+
enumerable: false,
|
|
47
|
+
get() {
|
|
48
|
+
return this.issues.length === 0;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
const ZodRealError = /*@__PURE__*/ $constructor("ZodError", initializer, void 0, { Parent: Error });
|
|
53
|
+
//#endregion
|
|
54
|
+
export { ZodRealError };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { _decode, _decodeAsync, _encode, _encodeAsync, _parse, _parseAsync, _safeDecode, _safeDecodeAsync, _safeEncode, _safeEncodeAsync, _safeParse, _safeParseAsync } from "../core/parse.js";
|
|
2
|
+
import { ZodRealError } from "./errors.js";
|
|
3
|
+
//#region node_modules/zod/v4/classic/parse.js
|
|
4
|
+
const parse = /* @__PURE__ */ _parse(ZodRealError);
|
|
5
|
+
const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
6
|
+
const safeParse = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
7
|
+
const safeParseAsync = /* @__PURE__ */ _safeParseAsync(ZodRealError);
|
|
8
|
+
const encode = /* @__PURE__ */ _encode(ZodRealError);
|
|
9
|
+
const decode = /* @__PURE__ */ _decode(ZodRealError);
|
|
10
|
+
const encodeAsync = /* @__PURE__ */ _encodeAsync(ZodRealError);
|
|
11
|
+
const decodeAsync = /* @__PURE__ */ _decodeAsync(ZodRealError);
|
|
12
|
+
const safeEncode = /* @__PURE__ */ _safeEncode(ZodRealError);
|
|
13
|
+
const safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);
|
|
14
|
+
const safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
|
|
15
|
+
const safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
|
|
16
|
+
//#endregion
|
|
17
|
+
export { decode, decodeAsync, encode, encodeAsync, parse, parseAsync, safeDecode, safeDecodeAsync, safeEncode, safeEncodeAsync, safeParse, safeParseAsync };
|