@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.
@@ -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 UpstashConfig, f as DatabaseConfig, u as SubId, v as SubscriptionManagerConfig, y as SupaliveServerConfig } from "../../types_server-CDu9dPnG.js";
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":"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"}
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}/<procedureName>` for
530
- * `query`/`mutation`/`action`. Subscriptions are not offered here (WS only).
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) → procedure
535
- * lookup (internal/job rejected) → auth → validate input → run → respond.
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 || path.startsWith(`${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: `Procedure "${name}" not found`,
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
- const parsed = rawBody.trim().length > 0 ? JSON.parse(rawBody) : {};
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
- await this.redisSub.subscribe(instanceChannel, schemaChannel, watermarkChannel);
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: ${instanceChannel}, ${schemaChannel}, ${watermarkChannel}`);
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 {