@rivetkit/redis
Version:
_Lightweight Libraries for Backends_
527 lines (504 loc) • 15.8 kB
JavaScript
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var _class;
var _chunkVQDBJLFCcjs = require('./chunk-VQDBJLFC.cjs');
// ../../core/dist/chunk-54HA33U4.js
var _cborx = require('cbor-x'); var cbor = _interopRequireWildcard(_cborx); var cbor2 = _interopRequireWildcard(_cborx); var cbor3 = _interopRequireWildcard(_cborx);
var _zod = require('zod');
var _streaming = require('hono/streaming');
var RUNTIME_LOGGER_NAME = "actor-runtime";
function logger2() {
return _chunkVQDBJLFCcjs.getLogger.call(void 0, RUNTIME_LOGGER_NAME);
}
function assertUnreachable(x) {
logger2().error("unreachable", { value: `${x}`, stack: new Error().stack });
throw new (0, _chunkVQDBJLFCcjs.Unreachable)(x);
}
var EncodingSchema = _zod.z.enum(["json", "cbor"]);
var CachedSerializer = class {
#data;
#cache = /* @__PURE__ */ new Map();
constructor(data) {
this.#data = data;
}
get rawData() {
return this.#data;
}
serialize(encoding) {
const cached = this.#cache.get(encoding);
if (cached) {
return cached;
} else {
const serialized = serialize(this.#data, encoding);
this.#cache.set(encoding, serialized);
return serialized;
}
}
};
function serialize(value, encoding) {
if (encoding === "json") {
return JSON.stringify(value);
} else if (encoding === "cbor") {
const cleanValue = JSON.parse(JSON.stringify(value));
return cbor.encode(cleanValue);
} else {
assertUnreachable(encoding);
}
}
var CONNECTION_CHECK_LIVENESS_SYMBOL = Symbol("checkLiveness");
var Conn = (_class = class {
__init() {this.subscriptions = /* @__PURE__ */ new Set()}
#stateEnabled;
// TODO: Remove this cyclical reference
#actor;
#status = "connected";
/**
* The proxied state that notifies of changes automatically.
*
* Any data that should be stored indefinitely should be held within this object.
*/
/**
* Driver used to manage realtime connection communication.
*
* @protected
*/
#driver;
get params() {
return this.__persist.p;
}
get auth() {
return this.__persist.a;
}
get driver() {
return this.__persist.d;
}
get _stateEnabled() {
return this.#stateEnabled;
}
/**
* Gets the current state of the connection.
*
* Throws an error if the state is not enabled.
*/
get state() {
this.#validateStateEnabled();
if (!this.__persist.s) throw new Error("state should exists");
return this.__persist.s;
}
/**
* Sets the state of the connection.
*
* Throws an error if the state is not enabled.
*/
set state(value) {
this.#validateStateEnabled();
this.__persist.s = value;
}
/**
* Unique identifier for the connection.
*/
get id() {
return this.__persist.i;
}
/**
* Token used to authenticate this request.
*/
get _token() {
return this.__persist.t;
}
/**
* Status of the connection.
*/
get status() {
return this.#status;
}
/**
* Timestamp of the last time the connection was seen, i.e. the last time the connection was active and checked for liveness.
*/
get lastSeen() {
return this.__persist.l;
}
/**
* Initializes a new instance of the Connection class.
*
* This should only be constructed by {@link Actor}.
*
* @protected
*/
constructor(actor, persist, driver, stateEnabled) {;_class.prototype.__init.call(this);
this.#actor = actor;
this.__persist = persist;
this.#driver = driver;
this.#stateEnabled = stateEnabled;
}
#validateStateEnabled() {
if (!this.#stateEnabled) {
throw new (0, _chunkVQDBJLFCcjs.ConnStateNotEnabled)();
}
}
/**
* Sends a WebSocket message to the client.
*
* @param message - The message to send.
*
* @protected
*/
_sendMessage(message) {
var _a, _b;
(_b = (_a = this.#driver).sendMessage) == null ? void 0 : _b.call(_a, this.#actor, this, this.__persist.ds, message);
}
/**
* Sends an event with arguments to the client.
*
* @param eventName - The name of the event.
* @param args - The arguments for the event.
* @see {@link https://rivet.gg/docs/events|Events Documentation}
*/
send(eventName, ...args) {
this.#actor.inspector.emitter.emit("eventFired", {
type: "event",
eventName,
args,
connId: this.id
});
this._sendMessage(
new CachedSerializer({
b: {
ev: {
n: eventName,
a: args
}
}
})
);
}
/**
* Disconnects the client with an optional reason.
*
* @param reason - The reason for disconnection.
*/
async disconnect(reason) {
this.#status = "reconnecting";
await this.#driver.disconnect(this.#actor, this, this.__persist.ds, reason);
}
/**
* This method checks the connection's liveness by querying the driver for its ready state.
* If the connection is not closed, it updates the last liveness timestamp and returns `true`.
* Otherwise, it returns `false`.
* @internal
*/
[CONNECTION_CHECK_LIVENESS_SYMBOL]() {
var _a, _b;
const readyState = (_b = (_a = this.#driver).getConnectionReadyState) == null ? void 0 : _b.call(
_a,
this.#actor,
this
);
const isConnectionClosed = readyState === 3 || readyState === 2 || readyState === void 0;
const newLastSeen = Date.now();
const newStatus = isConnectionClosed ? "reconnecting" : "connected";
logger2().debug("liveness probe for connection", {
connId: this.id,
actorId: this.#actor.id,
readyState,
status: this.#status,
newStatus,
lastSeen: this.__persist.l,
currentTs: newLastSeen
});
if (!isConnectionClosed) {
this.__persist.l = newLastSeen;
}
this.#status = newStatus;
return {
status: this.#status,
lastSeen: this.__persist.l
};
}
}, _class);
var ActionRequestSchema = _zod.z.object({
// Args
a: _zod.z.array(_zod.z.unknown())
});
var ActionResponseSchema = _zod.z.object({
// Output
o: _zod.z.unknown()
});
var ActionRequestSchema2 = _zod.z.object({
// ID
i: _zod.z.number().int(),
// Name
n: _zod.z.string(),
// Args
a: _zod.z.array(_zod.z.unknown())
});
var SubscriptionRequestSchema = _zod.z.object({
// Event name
e: _zod.z.string(),
// Subscribe
s: _zod.z.boolean()
});
var ToServerSchema = _zod.z.object({
// Body
b: _zod.z.union([
_zod.z.object({ ar: ActionRequestSchema2 }),
_zod.z.object({ sr: SubscriptionRequestSchema })
])
});
var TransportSchema = _zod.z.enum(["websocket", "sse"]);
var HEADER_ACTOR_QUERY = "X-RivetKit-Query";
// ../../core/dist/chunk-4J2FQLTP.js
var defaultTokenFn = () => {
const envToken = _chunkVQDBJLFCcjs.getEnvUniversal.call(void 0, "RIVETKIT_INSPECTOR_TOKEN");
if (envToken) {
return envToken;
}
return "";
};
var defaultEnabled = () => {
return _chunkVQDBJLFCcjs.getEnvUniversal.call(void 0, "NODE_ENV") !== "production" || !_chunkVQDBJLFCcjs.getEnvUniversal.call(void 0, "RIVETKIT_INSPECTOR_DISABLE");
};
var defaultInspectorOrigins = [
"http://localhost:43708",
"https://studio.rivet.gg"
];
var defaultCors = {
origin: (origin) => {
if (defaultInspectorOrigins.includes(origin) || origin.startsWith("https://") && origin.endsWith("rivet-gg.vercel.app")) {
return origin;
} else {
return null;
}
},
allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowHeaders: [
"Content-Type",
"Authorization",
HEADER_ACTOR_QUERY,
"last-event-id"
],
maxAge: 3600,
credentials: true
};
var InspectorConfigSchema = _zod.z.object({
enabled: _zod.z.boolean().optional().default(defaultEnabled),
/** CORS configuration for the router. Uses Hono's CORS middleware options. */
cors: _zod.z.custom().optional().default(() => defaultCors),
/**
* Token used to access the Inspector.
*/
token: _zod.z.function().returns(_zod.z.string()).optional().default(() => defaultTokenFn),
/**
* Default RivetKit server endpoint for Rivet Inspector to connect to. This should be the same endpoint as what you use for your Rivet client to connect to RivetKit.
*
* This is a convenience property just for printing out the inspector URL.
*/
defaultEndpoint: _zod.z.string().optional()
}).optional().default(() => ({
enabled: defaultEnabled(),
token: defaultTokenFn,
cors: defaultCors
}));
var DriverConfigSchema = _zod.z.object({
/** Machine-readable name to identify this driver by. */
name: _zod.z.string(),
manager: _zod.z.custom(),
actor: _zod.z.custom()
});
var RunConfigSchema = _zod.z.object({
driver: DriverConfigSchema.optional(),
/** Endpoint to connect to the Rivet engine. Can be configured via RIVET_ENGINE env var. */
engine: _zod.z.string().optional(),
// This is a function to allow for lazy configuration of upgradeWebSocket on the
// fly. This is required since the dependencies that profie upgradeWebSocket
// (specifically Node.js) can sometimes only be specified after the router is
// created or must be imported async using `await import(...)`
getUpgradeWebSocket: _zod.z.custom().optional(),
role: _zod.z.enum(["all", "server", "runner"]).optional().default("all"),
/** CORS configuration for the router. Uses Hono's CORS middleware options. */
cors: _zod.z.custom().optional(),
maxIncomingMessageSize: _zod.z.number().optional().default(65536),
inspector: InspectorConfigSchema,
/**
* Base path for the router. This is used to prefix all routes.
* For example, if the base path is `/api`, then the route `/actors` will be
* available at `/api/actors`.
*/
basePath: _zod.z.string().optional().default("/")
}).default({});
function serializeEmptyPersistData(input) {
const persistData = {
i: input,
hi: false,
s: void 0,
c: [],
e: []
};
return cbor2.encode(persistData);
}
// src/manager.ts
var _invariant = require('invariant'); var _invariant2 = _interopRequireDefault(_invariant);
// src/utils.ts
var _crypto = require('crypto'); var crypto2 = _interopRequireWildcard(_crypto);
function generateActorId(name, key) {
const jsonString = JSON.stringify([name, key]);
const hash = crypto2.createHash("sha256").update(jsonString).digest("hex").substring(0, 16);
return hash;
}
// src/manager.ts
var RedisManagerDriver = class {
#registryConfig;
#driverConfig;
#redis;
#node;
// inspector: ManagerInspector = new ManagerInspector(this, {
// getAllActors: () => this.#state.getAllActors(),
// getAllTypesOfActors: () => Object.keys(this.registry.config.actors),
// });
constructor(registryConfig, driverConfig, redis) {
this.#registryConfig = registryConfig;
this.#driverConfig = driverConfig;
this.#redis = redis;
}
get node() {
_invariant2.default.call(void 0, this.#node, "node should exist");
return this.#node;
}
set node(node) {
_invariant2.default.call(void 0, !this.#node, "node cannot be set twice");
this.#node = node;
}
async getForId({ actorId }) {
const metadataRaw = await this.#redis.getBuffer(
_chunkVQDBJLFCcjs.KEYS.ACTOR.metadata(this.#driverConfig.keyPrefix, actorId)
);
if (!metadataRaw) {
return void 0;
}
const metadata = cbor3.decode(metadataRaw);
const { name, key } = metadata;
return {
actorId,
name,
key
};
}
async getWithKey({
name,
key
}) {
const lookupKey = _chunkVQDBJLFCcjs.KEYS.actorByKey(this.#driverConfig.keyPrefix, name, key);
const actorId = await this.#redis.get(lookupKey);
if (!actorId) {
return void 0;
}
return this.getForId({ name, actorId });
}
async getOrCreateWithKey(input) {
var _a, _b, _c;
const { name, key } = input;
const actorId = generateActorId(input.name, input.key);
const pipeline = this.#redis.multi();
pipeline.setnx(
_chunkVQDBJLFCcjs.KEYS.actorByKey(this.#driverConfig.keyPrefix, name, key),
actorId
);
pipeline.setnx(
_chunkVQDBJLFCcjs.KEYS.ACTOR.metadata(this.#driverConfig.keyPrefix, actorId),
cbor3.encode({ name, key })
);
pipeline.setnx(
_chunkVQDBJLFCcjs.KEYS.ACTOR.persistedData(this.#driverConfig.keyPrefix, actorId),
Buffer.from(serializeEmptyPersistData(input.input))
);
const results = await pipeline.exec();
if (!results) {
throw new Error("redis pipeline execution failed");
}
const keyCreated = (_a = results[0]) == null ? void 0 : _a[1];
const metadataCreated = (_b = results[1]) == null ? void 0 : _b[1];
const persistedDataCreated = (_c = results[2]) == null ? void 0 : _c[1];
_invariant2.default.call(void 0,
metadataCreated === keyCreated,
"metadataCreated inconsistent with keyCreated"
);
_invariant2.default.call(void 0,
persistedDataCreated === keyCreated,
"persistedDataCreated inconsistent with keyCreated"
);
if (keyCreated === 1) {
_chunkVQDBJLFCcjs.logger.call(void 0, ).debug("actor created", { actorId });
} else {
_chunkVQDBJLFCcjs.logger.call(void 0, ).debug("actor already exists", { actorId });
}
return {
actorId,
name: input.name,
key: input.key
};
}
async createActor({ name, key, input }) {
var _a, _b, _c;
const actorId = generateActorId(name, key);
const pipeline = this.#redis.multi();
pipeline.setnx(
_chunkVQDBJLFCcjs.KEYS.actorByKey(this.#driverConfig.keyPrefix, name, key),
actorId
);
pipeline.setnx(
_chunkVQDBJLFCcjs.KEYS.ACTOR.metadata(this.#driverConfig.keyPrefix, actorId),
cbor3.encode({ name, key })
);
pipeline.setnx(
_chunkVQDBJLFCcjs.KEYS.ACTOR.persistedData(this.#driverConfig.keyPrefix, actorId),
Buffer.from(serializeEmptyPersistData(input))
);
const results = await pipeline.exec();
if (!results) {
throw new Error("redis pipeline execution failed");
}
const keyResult = (_a = results[0]) == null ? void 0 : _a[1];
const metadataResult = (_b = results[1]) == null ? void 0 : _b[1];
const persistedDataResult = (_c = results[2]) == null ? void 0 : _c[1];
_invariant2.default.call(void 0,
metadataResult === keyResult,
"metadataResult inconsistent with keyResult"
);
_invariant2.default.call(void 0,
persistedDataResult === keyResult,
"metadataResult inconsistent with keyResult"
);
if (keyResult === 0) {
throw new (0, _chunkVQDBJLFCcjs.ActorAlreadyExists)(name, key);
}
return {
actorId,
name,
key
};
}
async sendRequest(actorId, actorRequest) {
return await this.#node.sendRequest(actorId, actorRequest);
}
async openWebSocket(path, actorId, encoding, connParams) {
_chunkVQDBJLFCcjs.logger.call(void 0, ).debug("RedisManagerDriver.openWebSocket called", {
path,
actorId,
encoding
});
return await this.#node.openWebSocket(path, actorId, encoding, connParams);
}
async proxyRequest(c, actorRequest, actorId) {
return await this.#node.proxyRequest(c, actorRequest, actorId);
}
async proxyWebSocket(c, path, actorId, encoding, connParams, authData) {
return await this.#node.proxyWebSocket(
c,
path,
actorId,
encoding,
connParams,
authData
);
}
};
exports.RedisManagerDriver = RedisManagerDriver;
//# sourceMappingURL=chunk-E7IBW7FC.cjs.map