@supalive/core 1.5.1 → 1.7.0
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/dist/src/exports/server.d.ts +12 -1
- package/dist/src/exports/server.d.ts.map +1 -1
- package/dist/src/exports/server.js +51 -19
- package/dist/src/exports/server.js.map +1 -1
- package/dist/src/exports/types.d.ts +2 -2
- package/dist/types_server-DCD3Kqln.d.ts +328 -0
- package/dist/types_server-DCD3Kqln.d.ts.map +1 -0
- package/dist/types_server-eJnBeRoI.d.ts +326 -0
- package/dist/types_server-eJnBeRoI.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { $n as RegisterSubscriptionParamsSchema, At as DevScheduler, Bt as SupaliveDb, E as Context, Ft as JobScheduler, It as QStashScheduler, Jn as AffectedSubscription, Lt as QStashSchedulerOptions, Mt as EnqueueOptions, Nt as IncomingJobRequest, Pt as JobClient, Qn as RegisterSubscriptionParams, Rn as Database, Rt as ScheduleHandle, Xn as InvalidateWritesetParamsSchema, Yn as InvalidateWritesetParams, Zn as InvalidateWritesetResult, ar as UnregisterSubscriptionsResult, cr as UpdateSubscriptionReadSetResult, er as RegisterSubscriptionResult, ir as UnregisterSubscriptionsParams, jt as DevSchedulerOptions, nr as UnregisterSubscriptionParamsSchema, or as UpdateSubscriptionReadSetParams, qn as CacheLayer, rr as UnregisterSubscriptionResult, rt as Router, sr as UpdateSubscriptionReadSetParamsSchema, tr as UnregisterSubscriptionParams, zt as ScheduledJobDef } from "../../index-DTNBHQPY.js";
|
|
2
2
|
import { t as MySqlDatabase } from "../../mysql-BRyidTZD.js";
|
|
3
3
|
import { t as PgDatabase } from "../../postgres-Dh4ATweM.js";
|
|
4
|
-
import { b as
|
|
4
|
+
import { b as SupaliveServerConfig, p as DatabaseConfig, u as SubId, x as UpstashConfig, y as SubscriptionManagerConfig } from "../../types_server-DCD3Kqln.js";
|
|
5
5
|
import pino, { Level } from "pino";
|
|
6
6
|
import Redis$1 from "ioredis";
|
|
7
7
|
|
|
@@ -16,6 +16,8 @@ declare class SupaliveWebSocketServer<TContext extends Context> {
|
|
|
16
16
|
private wss;
|
|
17
17
|
private cache;
|
|
18
18
|
private redisSub;
|
|
19
|
+
/** App-level pub/sub channels this instance listens on (see config.appPubSub). */
|
|
20
|
+
private readonly appPubSubChannels;
|
|
19
21
|
private db;
|
|
20
22
|
private contextRegistry;
|
|
21
23
|
private router?;
|
|
@@ -167,6 +169,15 @@ declare class SupaliveWebSocketServer<TContext extends Context> {
|
|
|
167
169
|
* omit to flush everyone's cache.
|
|
168
170
|
*/
|
|
169
171
|
publishSchemaReload(tables?: string[]): Promise<void>;
|
|
172
|
+
/**
|
|
173
|
+
* Publish an app-level message (see {@link SupaliveServerConfig.appPubSub}) to
|
|
174
|
+
* a Redis channel. Rides the shared command connection and is delivered to
|
|
175
|
+
* every instance subscribed to `channel`, INCLUDING this one — so a handler
|
|
176
|
+
* should tolerate/skip self-originated messages if that matters. The message
|
|
177
|
+
* is an opaque string; the app owns encoding. Uses the standard Redis retry
|
|
178
|
+
* policy; callers typically fire-and-forget.
|
|
179
|
+
*/
|
|
180
|
+
publishAppMessage(channel: string, message: string): Promise<void>;
|
|
170
181
|
private handleRemoteInstanceMessage;
|
|
171
182
|
private handleRemoteRecomputeRace;
|
|
172
183
|
private publishSubscriptionUpdate;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","names":[],"sources":["../../../src/server/supalive-server.ts","../../../src/logger.ts","../../../src/server/subscription-manager.ts","../../../src/server/subscription-manager-client.ts","../../../src/db/init_db.ts","../../../src/server/sub_hash.ts"],"mappings":";;;;;;;;;;;cA4Ha,uBAAA,kBAAyC,OAAA;EAAA,QAC5C,MAAA;EAAA,QACA,UAAA;EAAA,QAEA,UAAA;EAAA,QACA,GAAA;EAAA,QAEA,KAAA;EAAA,QACA,QAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../../../src/server/supalive-server.ts","../../../src/logger.ts","../../../src/server/subscription-manager.ts","../../../src/server/subscription-manager-client.ts","../../../src/db/init_db.ts","../../../src/server/sub_hash.ts"],"mappings":"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"}
|
|
@@ -526,13 +526,17 @@ var FixedWindowRateLimiter = class {
|
|
|
526
526
|
//#region src/server/http-rpc.ts
|
|
527
527
|
const logger$3 = logger.child({ name: "HTTP_RPC" }, { level: logLevelOf("SERVER_LOG_LEVEL") });
|
|
528
528
|
/**
|
|
529
|
-
* Handles the one-shot HTTP RPC transport: `POST {path}
|
|
530
|
-
* `query`/`mutation`/`action`.
|
|
529
|
+
* Handles the one-shot HTTP RPC transport: a single `POST {path}` endpoint for
|
|
530
|
+
* `query`/`mutation`/`action`. The procedure name travels in the request body
|
|
531
|
+
* (`{ name, input }`), not the URL — so the wire never reveals which procedures
|
|
532
|
+
* exist and a transport-level error (which typically echoes the failing URL)
|
|
533
|
+
* can't leak a procedure name. Subscriptions are not offered here (WS only).
|
|
531
534
|
* Kept independent of the WebSocket server so it can be unit-tested against a
|
|
532
535
|
* plain `http.Server` with a stub db/registry.
|
|
533
536
|
*
|
|
534
|
-
* Request flow: CORS → (OPTIONS preflight) → rate limit (pre-auth) →
|
|
535
|
-
* lookup (internal/job rejected) → auth →
|
|
537
|
+
* Request flow: CORS → (OPTIONS preflight) → rate limit (pre-auth) → read body →
|
|
538
|
+
* parse `{ name, input }` → procedure lookup (internal/job rejected) → auth →
|
|
539
|
+
* validate input → run → respond.
|
|
536
540
|
*/
|
|
537
541
|
var HttpRpcHandler = class {
|
|
538
542
|
deps;
|
|
@@ -548,9 +552,10 @@ var HttpRpcHandler = class {
|
|
|
548
552
|
this.rateLimiter = new FixedWindowRateLimiter(rl.windowMs ?? 6e4, rl.max ?? 120);
|
|
549
553
|
}
|
|
550
554
|
}
|
|
551
|
-
/** True when this handler owns the given request path.
|
|
555
|
+
/** True when this handler owns the given request path. The procedure name is
|
|
556
|
+
* carried in the body now, so a single exact path is the whole surface. */
|
|
552
557
|
handlesPath(path) {
|
|
553
|
-
return path === this.path
|
|
558
|
+
return path === this.path;
|
|
554
559
|
}
|
|
555
560
|
stop() {
|
|
556
561
|
this.rateLimiter?.stop();
|
|
@@ -567,13 +572,6 @@ var HttpRpcHandler = class {
|
|
|
567
572
|
error: "method not allowed",
|
|
568
573
|
code: "METHOD_NOT_ALLOWED"
|
|
569
574
|
});
|
|
570
|
-
const prefix = `${this.path}/`;
|
|
571
|
-
const name = path.startsWith(prefix) ? decodeURIComponent(path.slice(prefix.length)) : "";
|
|
572
|
-
if (!name || name.includes("/")) return this.send(res, 404, {
|
|
573
|
-
success: false,
|
|
574
|
-
error: "unknown procedure",
|
|
575
|
-
code: "NOT_FOUND"
|
|
576
|
-
});
|
|
577
575
|
if (this.rateLimiter) {
|
|
578
576
|
const keyBy = this.rpc.rateLimit ? this.rpc.rateLimit.keyBy : void 0;
|
|
579
577
|
const key = keyBy ? keyBy(req) : clientIp(req);
|
|
@@ -587,10 +585,25 @@ var HttpRpcHandler = class {
|
|
|
587
585
|
});
|
|
588
586
|
}
|
|
589
587
|
}
|
|
588
|
+
const rawBody = await readBody(req);
|
|
589
|
+
let name;
|
|
590
|
+
let rawInput;
|
|
591
|
+
try {
|
|
592
|
+
const envelope = rawBody.trim().length > 0 ? JSON.parse(rawBody) : {};
|
|
593
|
+
if (!envelope || typeof envelope !== "object" || typeof envelope.name !== "string") throw new Error("missing procedure name");
|
|
594
|
+
name = envelope.name;
|
|
595
|
+
rawInput = envelope.input ?? {};
|
|
596
|
+
} catch {
|
|
597
|
+
return this.send(res, 400, {
|
|
598
|
+
success: false,
|
|
599
|
+
error: "invalid request",
|
|
600
|
+
code: "BAD_REQUEST"
|
|
601
|
+
});
|
|
602
|
+
}
|
|
590
603
|
const procedure = this.deps.getRegistry()?.getProcedure(name);
|
|
591
604
|
if (!procedure || procedure.internal || procedure.type === "job") return this.send(res, 404, {
|
|
592
605
|
success: false,
|
|
593
|
-
error:
|
|
606
|
+
error: "not found",
|
|
594
607
|
code: "NOT_FOUND"
|
|
595
608
|
});
|
|
596
609
|
const auth = await this.resolveAuth(req);
|
|
@@ -599,11 +612,9 @@ var HttpRpcHandler = class {
|
|
|
599
612
|
error: auth.message,
|
|
600
613
|
code: "UNAUTHENTICATED"
|
|
601
614
|
});
|
|
602
|
-
const rawBody = await readBody(req);
|
|
603
615
|
let input;
|
|
604
616
|
try {
|
|
605
|
-
|
|
606
|
-
input = procedure.inputSchema.parse(parsed);
|
|
617
|
+
input = procedure.inputSchema.parse(rawInput);
|
|
607
618
|
} catch (err) {
|
|
608
619
|
logger$3.warn(err, `RPC "${name}" received invalid input`);
|
|
609
620
|
return this.send(res, 400, {
|
|
@@ -787,6 +798,8 @@ var SupaliveWebSocketServer = class {
|
|
|
787
798
|
wss;
|
|
788
799
|
cache;
|
|
789
800
|
redisSub;
|
|
801
|
+
/** App-level pub/sub channels this instance listens on (see config.appPubSub). */
|
|
802
|
+
appPubSubChannels;
|
|
790
803
|
db;
|
|
791
804
|
contextRegistry;
|
|
792
805
|
router;
|
|
@@ -819,6 +832,7 @@ var SupaliveWebSocketServer = class {
|
|
|
819
832
|
this.subManagerClient = new SubManagerClient(config.subManagerUrl);
|
|
820
833
|
this.cache = config.cacheLayer;
|
|
821
834
|
this.redisSub = config.redisSubClient;
|
|
835
|
+
this.appPubSubChannels = new Set(config.appPubSub?.channels ?? []);
|
|
822
836
|
this.db = config.database;
|
|
823
837
|
this.storage = config.storage;
|
|
824
838
|
this.db.setOnAfterCommit((writeSet, commitTs) => {
|
|
@@ -1615,9 +1629,15 @@ var SupaliveWebSocketServer = class {
|
|
|
1615
1629
|
const instanceChannel = `sub:instance:${this.instanceId}`;
|
|
1616
1630
|
const schemaChannel = SCHEMA_RELOAD_CHANNEL;
|
|
1617
1631
|
const watermarkChannel = RETENTION_WATERMARK_CHANNEL;
|
|
1618
|
-
|
|
1632
|
+
const appChannels = [...this.appPubSubChannels];
|
|
1633
|
+
await this.redisSub.subscribe(instanceChannel, schemaChannel, watermarkChannel, ...appChannels);
|
|
1619
1634
|
this.redisSub.on("connect", () => {
|
|
1620
|
-
logger$2.info(`Subscribed to Redis channels: ${
|
|
1635
|
+
logger$2.info(`Subscribed to Redis channels: ${[
|
|
1636
|
+
instanceChannel,
|
|
1637
|
+
schemaChannel,
|
|
1638
|
+
watermarkChannel,
|
|
1639
|
+
...appChannels
|
|
1640
|
+
].join(", ")}`);
|
|
1621
1641
|
});
|
|
1622
1642
|
this.redisSub.on("message", (channel, message) => {
|
|
1623
1643
|
if (channel === instanceChannel) this.handleRemoteInstanceMessage(message).catch((err) => {
|
|
@@ -1631,6 +1651,7 @@ var SupaliveWebSocketServer = class {
|
|
|
1631
1651
|
} catch (err) {
|
|
1632
1652
|
logger$2.error(err, `Bad retention watermark message: ${message}`);
|
|
1633
1653
|
}
|
|
1654
|
+
else if (this.appPubSubChannels.has(channel)) Promise.resolve(this.config.appPubSub?.onMessage(channel, message)).catch((err) => logger$2.error(err, `Error in appPubSub handler for channel ${channel}`));
|
|
1634
1655
|
});
|
|
1635
1656
|
this.redisSub.on("error", (err) => {
|
|
1636
1657
|
logger$2.error(err, `Subscriber error for channels: ${instanceChannel}, ${schemaChannel}`);
|
|
@@ -1667,6 +1688,17 @@ var SupaliveWebSocketServer = class {
|
|
|
1667
1688
|
});
|
|
1668
1689
|
await redisRetryPolicy.execute(() => this.cache.redis.publish(SCHEMA_RELOAD_CHANNEL, payload));
|
|
1669
1690
|
}
|
|
1691
|
+
/**
|
|
1692
|
+
* Publish an app-level message (see {@link SupaliveServerConfig.appPubSub}) to
|
|
1693
|
+
* a Redis channel. Rides the shared command connection and is delivered to
|
|
1694
|
+
* every instance subscribed to `channel`, INCLUDING this one — so a handler
|
|
1695
|
+
* should tolerate/skip self-originated messages if that matters. The message
|
|
1696
|
+
* is an opaque string; the app owns encoding. Uses the standard Redis retry
|
|
1697
|
+
* policy; callers typically fire-and-forget.
|
|
1698
|
+
*/
|
|
1699
|
+
async publishAppMessage(channel, message) {
|
|
1700
|
+
await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, message));
|
|
1701
|
+
}
|
|
1670
1702
|
async handleRemoteInstanceMessage(message) {
|
|
1671
1703
|
let parsed;
|
|
1672
1704
|
try {
|