@rivetkit/engine-test-runner 0.0.0-0-0-0-preview-guard-stops.9d82529

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.
@@ -0,0 +1,17 @@
1
+
2
+ > @rivetkit/engine-test-runner@0.0.0-0-0-0-preview-guard-stops.9d82529 build /home/runner/work/actors/actors/engine/sdks/typescript/test-runner
3
+ > tsup src/index.ts
4
+
5
+ CLI Building entry: src/index.ts
6
+ CLI Using tsconfig: tsconfig.json
7
+ CLI tsup v8.5.1
8
+ CLI Using tsup config: /home/runner/work/actors/actors/engine/sdks/typescript/test-runner/tsup.config.ts
9
+ CLI Target: node16
10
+ CLI Cleaning output folder
11
+ ESM Build start
12
+ ESM dist/index.js 10.75 KB
13
+ ESM dist/index.js.map 20.97 KB
14
+ ESM ⚡️ Build success in 23ms
15
+ DTS Build start
16
+ DTS ⚡️ Build success in 1625ms
17
+ DTS dist/index.d.ts 175.00 B
@@ -0,0 +1,6 @@
1
+ import * as hono_types from 'hono/types';
2
+ import { Hono } from 'hono';
3
+
4
+ declare const app: Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
5
+
6
+ export { app as default };
package/dist/index.js ADDED
@@ -0,0 +1,358 @@
1
+ // src/index.ts
2
+ import { serve } from "@hono/node-server";
3
+ import { Runner } from "@rivetkit/engine-runner";
4
+ import { Hono } from "hono";
5
+ import { streamSSE } from "hono/streaming";
6
+
7
+ // src/log.ts
8
+ import { inspect } from "util";
9
+ import {
10
+ pino,
11
+ stdTimeFunctions
12
+ } from "pino";
13
+ var baseLogger;
14
+ var loggerCache = /* @__PURE__ */ new Map();
15
+ function getPinoLevel() {
16
+ return (process.env.LOG_LEVEL || "warn").toString().toLowerCase();
17
+ }
18
+ function getIncludeTarget() {
19
+ return process.env.LOG_TARGET === "1";
20
+ }
21
+ function customWrite(level, o) {
22
+ const entries = {};
23
+ if (process.env.LOG_TIMESTAMP === "1" && o.time) {
24
+ const date = typeof o.time === "number" ? new Date(o.time) : /* @__PURE__ */ new Date();
25
+ entries.ts = date;
26
+ }
27
+ entries.level = level.toUpperCase();
28
+ if (o.target) {
29
+ entries.target = o.target;
30
+ }
31
+ if (o.msg) {
32
+ entries.msg = o.msg;
33
+ }
34
+ for (const [key, value] of Object.entries(o)) {
35
+ if (key !== "time" && key !== "level" && key !== "target" && key !== "msg" && key !== "pid" && key !== "hostname") {
36
+ entries[key] = value;
37
+ }
38
+ }
39
+ const output = inspect(entries, {
40
+ compact: true,
41
+ breakLength: Infinity,
42
+ colors: true
43
+ });
44
+ console.log(output);
45
+ }
46
+ async function configureDefaultLogger() {
47
+ baseLogger = pino({
48
+ level: getPinoLevel(),
49
+ messageKey: "msg",
50
+ errorKey: "error",
51
+ // Do not include pid/hostname in output
52
+ base: {},
53
+ // Keep a string level in the output
54
+ formatters: {
55
+ level(_label, number) {
56
+ return { level: number };
57
+ }
58
+ },
59
+ timestamp: process.env.LOG_TIMESTAMP === "1" ? stdTimeFunctions.epochTime : false,
60
+ browser: {
61
+ write: {
62
+ fatal: customWrite.bind(null, "fatal"),
63
+ error: customWrite.bind(null, "error"),
64
+ warn: customWrite.bind(null, "warn"),
65
+ info: customWrite.bind(null, "info"),
66
+ debug: customWrite.bind(null, "debug"),
67
+ trace: customWrite.bind(null, "trace")
68
+ }
69
+ },
70
+ hooks: {
71
+ logMethod(inputArgs, _method, level) {
72
+ const levelMap = {
73
+ 10: "trace",
74
+ 20: "debug",
75
+ 30: "info",
76
+ 40: "warn",
77
+ 50: "error",
78
+ 60: "fatal"
79
+ };
80
+ const levelName = levelMap[level] || "info";
81
+ const time = process.env.LOG_TIMESTAMP === "1" ? Date.now() : void 0;
82
+ if (inputArgs.length >= 2) {
83
+ const [objOrMsg, msg] = inputArgs;
84
+ if (typeof objOrMsg === "object" && objOrMsg !== null) {
85
+ customWrite(levelName, { ...objOrMsg, msg, time });
86
+ } else {
87
+ customWrite(levelName, { msg: String(objOrMsg), time });
88
+ }
89
+ } else if (inputArgs.length === 1) {
90
+ const [objOrMsg] = inputArgs;
91
+ if (typeof objOrMsg === "object" && objOrMsg !== null) {
92
+ customWrite(levelName, { ...objOrMsg, time });
93
+ } else {
94
+ customWrite(levelName, { msg: String(objOrMsg), time });
95
+ }
96
+ }
97
+ }
98
+ }
99
+ });
100
+ loggerCache.clear();
101
+ }
102
+ function getBaseLogger() {
103
+ if (!baseLogger) {
104
+ configureDefaultLogger();
105
+ }
106
+ return baseLogger;
107
+ }
108
+ function getLogger(name = "default") {
109
+ const cached = loggerCache.get(name);
110
+ if (cached) {
111
+ return cached;
112
+ }
113
+ const base = getBaseLogger();
114
+ const child = getIncludeTarget() ? base.child({ target: name }) : base;
115
+ loggerCache.set(name, child);
116
+ return child;
117
+ }
118
+
119
+ // src/index.ts
120
+ var INTERNAL_SERVER_PORT = process.env.INTERNAL_SERVER_PORT ? Number(process.env.INTERNAL_SERVER_PORT) : 5051;
121
+ var RIVET_NAMESPACE = process.env.RIVET_NAMESPACE ?? "default";
122
+ var RIVET_RUNNER_NAME = process.env.RIVET_RUNNER_NAME ?? "test-runner";
123
+ var RIVET_RUNNER_VERSION = process.env.RIVET_RUNNER_VERSION ? Number(process.env.RIVET_RUNNER_VERSION) : 1;
124
+ var RIVET_RUNNER_TOTAL_SLOTS = parseInt(
125
+ process.env.RIVET_RUNNER_TOTAL_SLOTS ?? "1",
126
+ 10
127
+ );
128
+ var RIVET_ENDPOINT = process.env.RIVET_ENDPOINT ?? "http://127.0.0.1:6420";
129
+ var RIVET_TOKEN = process.env.RIVET_TOKEN ?? "dev";
130
+ var AUTOSTART_SERVER = (process.env.AUTOSTART_SERVER ?? "1") === "1";
131
+ var AUTOSTART_RUNNER = (process.env.AUTOSTART_RUNNER ?? "0") === "1";
132
+ var AUTOCONFIGURE_SERVERLESS = (process.env.AUTOCONFIGURE_SERVERLESS ?? "1") === "1";
133
+ var runnerStarted = Promise.withResolvers();
134
+ var runnerStopped = Promise.withResolvers();
135
+ var runner = null;
136
+ var websocketLastMsgIndexes = /* @__PURE__ */ new Map();
137
+ var app = new Hono();
138
+ function loggerMiddleware(logger) {
139
+ return async (c, next) => {
140
+ const method = c.req.method;
141
+ const path = c.req.path;
142
+ const startTime = Date.now();
143
+ await next();
144
+ const duration = Date.now() - startTime;
145
+ logger.debug({
146
+ msg: "http request",
147
+ method,
148
+ path,
149
+ status: c.res.status,
150
+ dt: `${duration}ms`,
151
+ reqSize: c.req.header("content-length"),
152
+ resSize: c.res.headers.get("content-length"),
153
+ userAgent: c.req.header("user-agent")
154
+ });
155
+ };
156
+ }
157
+ app.use("*", loggerMiddleware(getLogger()));
158
+ app.get("/wait-ready", async (c) => {
159
+ const runner2 = await runnerStarted.promise;
160
+ return c.json(runner2.runnerId);
161
+ });
162
+ app.get("/has-actor", async (c) => {
163
+ const actorIdQuery = c.req.query("actor");
164
+ const generationQuery = c.req.query("generation");
165
+ const generation = generationQuery ? Number(generationQuery) : void 0;
166
+ if (!actorIdQuery || !(runner == null ? void 0 : runner.hasActor(actorIdQuery, generation))) {
167
+ return c.text("", 404);
168
+ }
169
+ return c.text("ok");
170
+ });
171
+ app.get("/health", (c) => {
172
+ return c.text("ok");
173
+ });
174
+ app.get("/shutdown", async (c) => {
175
+ await (runner == null ? void 0 : runner.shutdown(true));
176
+ return c.text("ok");
177
+ });
178
+ app.get("/api/rivet/start", async (c) => {
179
+ return streamSSE(c, async (stream) => {
180
+ const runnerStarted2 = Promise.withResolvers();
181
+ const runnerStopped2 = Promise.withResolvers();
182
+ const runner2 = await startRunner(runnerStarted2, runnerStopped2);
183
+ c.req.raw.signal.addEventListener("abort", () => {
184
+ getLogger().debug("SSE aborted, shutting down runner");
185
+ runner2 == null ? void 0 : runner2.shutdown(true);
186
+ });
187
+ await runnerStarted2.promise;
188
+ stream.writeSSE({ data: runner2.getServerlessInitPacket() });
189
+ await runnerStopped2.promise;
190
+ });
191
+ });
192
+ app.get("/api/rivet/metadata", async (c) => {
193
+ return c.json({
194
+ // Not actually rivetkit
195
+ runtime: "rivetkit",
196
+ version: "1"
197
+ });
198
+ });
199
+ if (AUTOSTART_SERVER) {
200
+ serve({
201
+ fetch: app.fetch,
202
+ port: INTERNAL_SERVER_PORT
203
+ });
204
+ getLogger().info(
205
+ `Internal HTTP server listening on port ${INTERNAL_SERVER_PORT}`
206
+ );
207
+ }
208
+ if (AUTOSTART_RUNNER) {
209
+ runner = await startRunner(runnerStarted, runnerStopped);
210
+ } else if (AUTOCONFIGURE_SERVERLESS) {
211
+ await autoConfigureServerless();
212
+ }
213
+ process.on("SIGTERM", async () => {
214
+ getLogger().debug("received SIGTERM, force exiting in 3s");
215
+ await new Promise((res) => setTimeout(res, 3e3));
216
+ process.exit(0);
217
+ });
218
+ process.on("SIGINT", async () => {
219
+ getLogger().debug("received SIGTERM, force exiting in 3s");
220
+ await new Promise((res) => setTimeout(res, 3e3));
221
+ process.exit(0);
222
+ });
223
+ async function autoConfigureServerless() {
224
+ getLogger().info("Configuring serverless");
225
+ const res = await fetch(
226
+ `${RIVET_ENDPOINT}/runner-configs/${RIVET_RUNNER_NAME}?namespace=${RIVET_NAMESPACE}`,
227
+ {
228
+ method: "PUT",
229
+ headers: {
230
+ Authorization: `Bearer ${RIVET_TOKEN}`,
231
+ "Content-Type": "application/json"
232
+ },
233
+ body: JSON.stringify({
234
+ datacenters: {
235
+ default: {
236
+ serverless: {
237
+ url: `http://localhost:${INTERNAL_SERVER_PORT}/api/rivet`,
238
+ max_runners: 1e4,
239
+ slots_per_runner: 1,
240
+ request_lifespan: 300
241
+ }
242
+ }
243
+ }
244
+ })
245
+ }
246
+ );
247
+ if (!res.ok) {
248
+ throw new Error(
249
+ `request failed: ${res.statusText} (${res.status}):
250
+ ${await res.text()}`
251
+ );
252
+ }
253
+ }
254
+ async function startRunner(runnerStarted2, runnerStopped2) {
255
+ getLogger().info("Starting runner");
256
+ let runner2;
257
+ const config = {
258
+ logger: getLogger(),
259
+ version: RIVET_RUNNER_VERSION,
260
+ endpoint: RIVET_ENDPOINT,
261
+ token: RIVET_TOKEN,
262
+ namespace: RIVET_NAMESPACE,
263
+ runnerName: RIVET_RUNNER_NAME,
264
+ totalSlots: RIVET_RUNNER_TOTAL_SLOTS,
265
+ prepopulateActorNames: {},
266
+ onConnected: () => {
267
+ runnerStarted2.resolve(runner2);
268
+ },
269
+ onDisconnected: () => {
270
+ },
271
+ onShutdown: () => {
272
+ runnerStopped2.resolve(runner2);
273
+ },
274
+ fetch: async (runner3, actorId, _gatewayId, _requestId, request) => {
275
+ getLogger().info(
276
+ `Fetch called for actor ${actorId}, URL: ${request.url}`
277
+ );
278
+ const url = new URL(request.url);
279
+ if (url.pathname === "/ping") {
280
+ const responseData = {
281
+ actorId,
282
+ status: "ok",
283
+ timestamp: Date.now()
284
+ };
285
+ return new Response(JSON.stringify(responseData), {
286
+ status: 200,
287
+ headers: { "Content-Type": "application/json" }
288
+ });
289
+ } else if (url.pathname === "/sleep") {
290
+ runner3.sleepActor(actorId);
291
+ return new Response("ok", {
292
+ status: 200,
293
+ headers: { "Content-Type": "application/json" }
294
+ });
295
+ }
296
+ return new Response("ok", { status: 200 });
297
+ },
298
+ onActorStart: async (_actorId, _generation, _config) => {
299
+ getLogger().info(
300
+ `Actor ${_actorId} started (generation ${_generation})`
301
+ );
302
+ },
303
+ onActorStop: async (_actorId, _generation) => {
304
+ getLogger().info(
305
+ `Actor ${_actorId} stopped (generation ${_generation})`
306
+ );
307
+ },
308
+ websocket: async (runner3, actorId, ws, _gatewayId, _requestId, _request) => {
309
+ getLogger().info(`WebSocket connected for actor ${actorId}`);
310
+ ws.addEventListener("message", (event) => {
311
+ const data = event.data;
312
+ getLogger().info({
313
+ msg: `WebSocket message from actor ${actorId}`,
314
+ data,
315
+ index: event.rivetMessageIndex
316
+ });
317
+ ws.send(`Echo: ${data}`);
318
+ const websocketId = Buffer.from(
319
+ event.rivetRequestId
320
+ ).toString("base64");
321
+ websocketLastMsgIndexes.set(
322
+ websocketId,
323
+ event.rivetMessageIndex
324
+ );
325
+ runner3.sendHibernatableWebSocketMessageAck(
326
+ event.rivetGatewayId,
327
+ event.rivetRequestId,
328
+ event.rivetMessageIndex
329
+ );
330
+ });
331
+ ws.addEventListener("close", () => {
332
+ getLogger().info(`WebSocket closed for actor ${actorId}`);
333
+ });
334
+ ws.addEventListener("error", (error) => {
335
+ getLogger().error({
336
+ msg: `WebSocket error for actor ${actorId}:`,
337
+ error
338
+ });
339
+ });
340
+ },
341
+ hibernatableWebSocket: {
342
+ canHibernate() {
343
+ return true;
344
+ }
345
+ }
346
+ };
347
+ runner2 = new Runner(config);
348
+ await runner2.start();
349
+ getLogger().info("Waiting runner start...");
350
+ await runnerStarted2.promise;
351
+ getLogger().info("Runner started");
352
+ return runner2;
353
+ }
354
+ var index_default = app;
355
+ export {
356
+ index_default as default
357
+ };
358
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/log.ts"],"sourcesContent":["import { serve } from \"@hono/node-server\";\nimport type { ActorConfig, RunnerConfig } from \"@rivetkit/engine-runner\";\nimport { Runner } from \"@rivetkit/engine-runner\";\nimport { Hono, type Context as HonoContext, type Next } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\nimport type { Logger } from \"pino\";\nimport type WebSocket from \"ws\";\nimport { getLogger } from \"./log\";\n\nconst INTERNAL_SERVER_PORT = process.env.INTERNAL_SERVER_PORT\n\t? Number(process.env.INTERNAL_SERVER_PORT)\n\t: 5051;\nconst RIVET_NAMESPACE = process.env.RIVET_NAMESPACE ?? \"default\";\nconst RIVET_RUNNER_NAME = process.env.RIVET_RUNNER_NAME ?? \"test-runner\";\nconst RIVET_RUNNER_VERSION = process.env.RIVET_RUNNER_VERSION\n\t? Number(process.env.RIVET_RUNNER_VERSION)\n\t: 1;\nconst RIVET_RUNNER_TOTAL_SLOTS = parseInt(\n\tprocess.env.RIVET_RUNNER_TOTAL_SLOTS ?? \"1\",\n\t10,\n);\nconst RIVET_ENDPOINT = process.env.RIVET_ENDPOINT ?? \"http://127.0.0.1:6420\";\nconst RIVET_TOKEN = process.env.RIVET_TOKEN ?? \"dev\";\nconst AUTOSTART_SERVER = (process.env.AUTOSTART_SERVER ?? \"1\") === \"1\";\nconst AUTOSTART_RUNNER = (process.env.AUTOSTART_RUNNER ?? \"0\") === \"1\";\nconst AUTOCONFIGURE_SERVERLESS =\n\t(process.env.AUTOCONFIGURE_SERVERLESS ?? \"1\") === \"1\";\n\nconst runnerStarted = Promise.withResolvers<Runner>();\nconst runnerStopped = Promise.withResolvers<Runner>();\nlet runner: Runner | null = null;\nconst websocketLastMsgIndexes: Map<string, number> = new Map();\n\n// Create internal server\nconst app = new Hono();\n\nfunction loggerMiddleware(logger: Logger) {\n\treturn async (c: HonoContext, next: Next) => {\n\t\tconst method = c.req.method;\n\t\tconst path = c.req.path;\n\t\tconst startTime = Date.now();\n\n\t\tawait next();\n\n\t\tconst duration = Date.now() - startTime;\n\t\tlogger.debug({\n\t\t\tmsg: \"http request\",\n\t\t\tmethod,\n\t\t\tpath,\n\t\t\tstatus: c.res.status,\n\t\t\tdt: `${duration}ms`,\n\t\t\treqSize: c.req.header(\"content-length\"),\n\t\t\tresSize: c.res.headers.get(\"content-length\"),\n\t\t\tuserAgent: c.req.header(\"user-agent\"),\n\t\t});\n\t};\n}\napp.use(\"*\", loggerMiddleware(getLogger()));\n\napp.get(\"/wait-ready\", async (c) => {\n\tconst runner = await runnerStarted.promise;\n\treturn c.json(runner.runnerId);\n});\n\napp.get(\"/has-actor\", async (c) => {\n\tconst actorIdQuery = c.req.query(\"actor\");\n\tconst generationQuery = c.req.query(\"generation\");\n\tconst generation = generationQuery ? Number(generationQuery) : undefined;\n\n\tif (!actorIdQuery || !runner?.hasActor(actorIdQuery, generation)) {\n\t\treturn c.text(\"\", 404);\n\t}\n\treturn c.text(\"ok\");\n});\n\napp.get(\"/health\", (c) => {\n\treturn c.text(\"ok\");\n});\n\napp.get(\"/shutdown\", async (c) => {\n\tawait runner?.shutdown(true);\n\treturn c.text(\"ok\");\n});\n\napp.get(\"/api/rivet/start\", async (c) => {\n\treturn streamSSE(c, async (stream) => {\n\t\tconst runnerStarted = Promise.withResolvers<Runner>();\n\t\tconst runnerStopped = Promise.withResolvers<Runner>();\n\t\tconst runner = await startRunner(runnerStarted, runnerStopped);\n\n\t\tc.req.raw.signal.addEventListener(\"abort\", () => {\n\t\t\tgetLogger().debug(\"SSE aborted, shutting down runner\");\n\t\t\trunner?.shutdown(true);\n\t\t});\n\n\t\tawait runnerStarted.promise;\n\n\t\tstream.writeSSE({ data: runner.getServerlessInitPacket()! });\n\n\t\tawait runnerStopped.promise;\n\t});\n});\n\napp.get(\"/api/rivet/metadata\", async (c) => {\n\treturn c.json({\n\t\t// Not actually rivetkit\n\t\truntime: \"rivetkit\",\n\t\tversion: \"1\",\n\t});\n});\n\nif (AUTOSTART_SERVER) {\n\tserve({\n\t\tfetch: app.fetch,\n\t\tport: INTERNAL_SERVER_PORT,\n\t});\n\tgetLogger().info(\n\t\t`Internal HTTP server listening on port ${INTERNAL_SERVER_PORT}`,\n\t);\n}\n\nif (AUTOSTART_RUNNER) {\n\trunner = await startRunner(runnerStarted, runnerStopped);\n} else if (AUTOCONFIGURE_SERVERLESS) {\n\tawait autoConfigureServerless();\n}\n\nprocess.on(\"SIGTERM\", async () => {\n\tgetLogger().debug(\"received SIGTERM, force exiting in 3s\");\n\n\tawait new Promise((res) => setTimeout(res, 3000));\n\n\tprocess.exit(0);\n});\nprocess.on(\"SIGINT\", async () => {\n\tgetLogger().debug(\"received SIGTERM, force exiting in 3s\");\n\n\tawait new Promise((res) => setTimeout(res, 3000));\n\n\tprocess.exit(0);\n});\n\nasync function autoConfigureServerless() {\n\tgetLogger().info(\"Configuring serverless\");\n\n\tconst res = await fetch(\n\t\t`${RIVET_ENDPOINT}/runner-configs/${RIVET_RUNNER_NAME}?namespace=${RIVET_NAMESPACE}`,\n\t\t{\n\t\t\tmethod: \"PUT\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${RIVET_TOKEN}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tdatacenters: {\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tserverless: {\n\t\t\t\t\t\t\turl: `http://localhost:${INTERNAL_SERVER_PORT}/api/rivet`,\n\t\t\t\t\t\t\tmax_runners: 10000,\n\t\t\t\t\t\t\tslots_per_runner: 1,\n\t\t\t\t\t\t\trequest_lifespan: 300,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t);\n\n\tif (!res.ok) {\n\t\tthrow new Error(\n\t\t\t`request failed: ${res.statusText} (${res.status}):\\n${await res.text()}`,\n\t\t);\n\t}\n}\n\nasync function startRunner(\n\trunnerStarted: PromiseWithResolvers<Runner>,\n\trunnerStopped: PromiseWithResolvers<Runner>,\n): Promise<Runner> {\n\tgetLogger().info(\"Starting runner\");\n\tlet runner: Runner;\n\tconst config: RunnerConfig = {\n\t\tlogger: getLogger(),\n\t\tversion: RIVET_RUNNER_VERSION,\n\t\tendpoint: RIVET_ENDPOINT,\n\t\ttoken: RIVET_TOKEN,\n\t\tnamespace: RIVET_NAMESPACE,\n\t\trunnerName: RIVET_RUNNER_NAME,\n\t\ttotalSlots: RIVET_RUNNER_TOTAL_SLOTS,\n\t\tprepopulateActorNames: {},\n\t\tonConnected: () => {\n\t\t\trunnerStarted.resolve(runner);\n\t\t},\n\t\tonDisconnected: () => {},\n\t\tonShutdown: () => {\n\t\t\trunnerStopped.resolve(runner);\n\t\t},\n\t\tfetch: async (\n\t\t\trunner: Runner,\n\t\t\tactorId: string,\n\t\t\t_gatewayId: ArrayBuffer,\n\t\t\t_requestId: ArrayBuffer,\n\t\t\trequest: Request,\n\t\t) => {\n\t\t\tgetLogger().info(\n\t\t\t\t`Fetch called for actor ${actorId}, URL: ${request.url}`,\n\t\t\t);\n\t\t\tconst url = new URL(request.url);\n\t\t\tif (url.pathname === \"/ping\") {\n\t\t\t\t// Return the actor ID in response\n\t\t\t\tconst responseData = {\n\t\t\t\t\tactorId,\n\t\t\t\t\tstatus: \"ok\",\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t};\n\n\t\t\t\treturn new Response(JSON.stringify(responseData), {\n\t\t\t\t\tstatus: 200,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t});\n\t\t\t} else if (url.pathname === \"/sleep\") {\n\t\t\t\trunner.sleepActor(actorId);\n\n\t\t\t\treturn new Response(\"ok\", {\n\t\t\t\t\tstatus: 200,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn new Response(\"ok\", { status: 200 });\n\t\t},\n\t\tonActorStart: async (\n\t\t\t_actorId: string,\n\t\t\t_generation: number,\n\t\t\t_config: ActorConfig,\n\t\t) => {\n\t\t\tgetLogger().info(\n\t\t\t\t`Actor ${_actorId} started (generation ${_generation})`,\n\t\t\t);\n\t\t},\n\t\tonActorStop: async (_actorId: string, _generation: number) => {\n\t\t\tgetLogger().info(\n\t\t\t\t`Actor ${_actorId} stopped (generation ${_generation})`,\n\t\t\t);\n\t\t},\n\t\twebsocket: async (\n\t\t\trunner: Runner,\n\t\t\tactorId: string,\n\t\t\tws: WebSocket,\n\t\t\t_gatewayId: ArrayBuffer,\n\t\t\t_requestId: ArrayBuffer,\n\t\t\t_request: Request,\n\t\t) => {\n\t\t\tgetLogger().info(`WebSocket connected for actor ${actorId}`);\n\n\t\t\t// Echo server - send back any messages received\n\t\t\tws.addEventListener(\"message\", (event) => {\n\t\t\t\tconst data = event.data;\n\t\t\t\tgetLogger().info({\n\t\t\t\t\tmsg: `WebSocket message from actor ${actorId}`,\n\t\t\t\t\tdata,\n\t\t\t\t\tindex: (event as any).rivetMessageIndex,\n\t\t\t\t});\n\n\t\t\t\tws.send(`Echo: ${data}`);\n\n\t\t\t\t// Ack\n\t\t\t\tconst websocketId = Buffer.from(\n\t\t\t\t\t(event as any).rivetRequestId,\n\t\t\t\t).toString(\"base64\");\n\t\t\t\twebsocketLastMsgIndexes.set(\n\t\t\t\t\twebsocketId,\n\t\t\t\t\t(event as any).rivetMessageIndex,\n\t\t\t\t);\n\t\t\t\trunner.sendHibernatableWebSocketMessageAck(\n\t\t\t\t\t(event as any).rivetGatewayId,\n\t\t\t\t\t(event as any).rivetRequestId,\n\t\t\t\t\t(event as any).rivetMessageIndex,\n\t\t\t\t);\n\t\t\t});\n\n\t\t\tws.addEventListener(\"close\", () => {\n\t\t\t\tgetLogger().info(`WebSocket closed for actor ${actorId}`);\n\t\t\t});\n\n\t\t\tws.addEventListener(\"error\", (error) => {\n\t\t\t\tgetLogger().error({\n\t\t\t\t\tmsg: `WebSocket error for actor ${actorId}:`,\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\thibernatableWebSocket: {\n\t\t\tcanHibernate() {\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t};\n\n\trunner = new Runner(config);\n\n\t// Start runner\n\tawait runner.start();\n\n\t// Wait for runner to be ready\n\tgetLogger().info(\"Waiting runner start...\");\n\tawait runnerStarted.promise;\n\n\tgetLogger().info(\"Runner started\");\n\n\treturn runner;\n}\n\nexport default app;\n","import { inspect } from \"node:util\";\nimport {\n\ttype LevelWithSilent,\n\ttype Logger,\n\tpino,\n\tstdTimeFunctions,\n} from \"pino\";\n\nexport type { Logger } from \"pino\";\n\nlet baseLogger: Logger | undefined;\n\n/** Cache of child loggers by logger name. */\nconst loggerCache = new Map<string, Logger>();\n\nexport function getPinoLevel(): LevelWithSilent {\n\t// Priority: env > default\n\treturn (process.env.LOG_LEVEL || \"warn\")\n\t\t.toString()\n\t\t.toLowerCase() as LevelWithSilent;\n}\n\nexport function getIncludeTarget(): boolean {\n\treturn process.env.LOG_TARGET === \"1\";\n}\n\n/**\n * Configure a custom base logger.\n */\nexport function configureBaseLogger(logger: Logger): void {\n\tbaseLogger = logger;\n\tloggerCache.clear();\n}\n\n// TODO: This can be simplified in logfmt.ts\nfunction customWrite(level: string, o: any) {\n\tconst entries: any = {};\n\n\t// Add timestamp if enabled\n\tif (process.env.LOG_TIMESTAMP === \"1\" && o.time) {\n\t\tconst date = typeof o.time === \"number\" ? new Date(o.time) : new Date();\n\t\tentries.ts = date;\n\t}\n\n\t// Add level\n\tentries.level = level.toUpperCase();\n\n\t// Add target if present\n\tif (o.target) {\n\t\tentries.target = o.target;\n\t}\n\n\t// Add message\n\tif (o.msg) {\n\t\tentries.msg = o.msg;\n\t}\n\n\t// Add other properties\n\tfor (const [key, value] of Object.entries(o)) {\n\t\tif (\n\t\t\tkey !== \"time\" &&\n\t\t\tkey !== \"level\" &&\n\t\t\tkey !== \"target\" &&\n\t\t\tkey !== \"msg\" &&\n\t\t\tkey !== \"pid\" &&\n\t\t\tkey !== \"hostname\"\n\t\t) {\n\t\t\tentries[key] = value;\n\t\t}\n\t}\n\n\tconst output = inspect(entries, {\n\t\tcompact: true,\n\t\tbreakLength: Infinity,\n\t\tcolors: true,\n\t});\n\tconsole.log(output);\n}\n\n/**\n * Configure the default logger with optional log level.\n */\nexport async function configureDefaultLogger(): Promise<void> {\n\tbaseLogger = pino({\n\t\tlevel: getPinoLevel(),\n\t\tmessageKey: \"msg\",\n\t\terrorKey: \"error\",\n\t\t// Do not include pid/hostname in output\n\t\tbase: {},\n\t\t// Keep a string level in the output\n\t\tformatters: {\n\t\t\tlevel(_label: string, number: number) {\n\t\t\t\treturn { level: number };\n\t\t\t},\n\t\t},\n\t\ttimestamp:\n\t\t\tprocess.env.LOG_TIMESTAMP === \"1\"\n\t\t\t\t? stdTimeFunctions.epochTime\n\t\t\t\t: false,\n\t\tbrowser: {\n\t\t\twrite: {\n\t\t\t\tfatal: customWrite.bind(null, \"fatal\"),\n\t\t\t\terror: customWrite.bind(null, \"error\"),\n\t\t\t\twarn: customWrite.bind(null, \"warn\"),\n\t\t\t\tinfo: customWrite.bind(null, \"info\"),\n\t\t\t\tdebug: customWrite.bind(null, \"debug\"),\n\t\t\t\ttrace: customWrite.bind(null, \"trace\"),\n\t\t\t},\n\t\t},\n\t\thooks: {\n\t\t\tlogMethod(inputArgs, _method, level) {\n\t\t\t\t// TODO: This is a hack to not implement our own transport target. We can get better perf if we have our own transport target.\n\n\t\t\t\tconst levelMap: Record<number, string> = {\n\t\t\t\t\t10: \"trace\",\n\t\t\t\t\t20: \"debug\",\n\t\t\t\t\t30: \"info\",\n\t\t\t\t\t40: \"warn\",\n\t\t\t\t\t50: \"error\",\n\t\t\t\t\t60: \"fatal\",\n\t\t\t\t};\n\t\t\t\tconst levelName = levelMap[level] || \"info\";\n\t\t\t\tconst time =\n\t\t\t\t\tprocess.env.LOG_TIMESTAMP === \"1\" ? Date.now() : undefined;\n\t\t\t\t// TODO: This can be simplified in logfmt.ts\n\t\t\t\tif (inputArgs.length >= 2) {\n\t\t\t\t\tconst [objOrMsg, msg] = inputArgs;\n\t\t\t\t\tif (typeof objOrMsg === \"object\" && objOrMsg !== null) {\n\t\t\t\t\t\tcustomWrite(levelName, { ...objOrMsg, msg, time });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcustomWrite(levelName, { msg: String(objOrMsg), time });\n\t\t\t\t\t}\n\t\t\t\t} else if (inputArgs.length === 1) {\n\t\t\t\t\tconst [objOrMsg] = inputArgs;\n\t\t\t\t\tif (typeof objOrMsg === \"object\" && objOrMsg !== null) {\n\t\t\t\t\t\tcustomWrite(levelName, { ...objOrMsg, time });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcustomWrite(levelName, { msg: String(objOrMsg), time });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t});\n\n\tloggerCache.clear();\n}\n\n/**\n * Get or initialize the base logger.\n */\nexport function getBaseLogger(): Logger {\n\tif (!baseLogger) {\n\t\tconfigureDefaultLogger();\n\t}\n\treturn baseLogger!;\n}\n\n/**\n * Returns a child logger with `target` bound for the given name.\n */\nexport function getLogger(name = \"default\"): Logger {\n\t// Check cache first\n\tconst cached = loggerCache.get(name);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\n\t// Create\n\tconst base = getBaseLogger();\n\n\t// Add target to log if enabled\n\tconst child = getIncludeTarget() ? base.child({ target: name }) : base;\n\n\t// Cache the logger\n\tloggerCache.set(name, child);\n\n\treturn child;\n}\n"],"mappings":"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","names":["runner","runnerStarted","runnerStopped"]}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@rivetkit/engine-test-runner",
3
+ "version": "0.0.0-0-0-0-preview-guard-stops.9d82529",
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "tsx src/index.ts",
7
+ "build": "tsup src/index.ts",
8
+ "check-types": "tsc --noEmit"
9
+ },
10
+ "dependencies": {
11
+ "@hono/node-server": "^1.19.1",
12
+ "@rivetkit/engine-runner": "0.0.0-0-0-0-preview-guard-stops.9d82529",
13
+ "@rivetkit/engine-runner-protocol": "0.0.0-0-0-0-preview-guard-stops.9d82529",
14
+ "hono": "^4.7.0",
15
+ "pino": "^9.9.5",
16
+ "ws": "^8.18.3"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.18.1",
20
+ "@types/ws": "^8.18.1",
21
+ "tinybench": "^5.0.1",
22
+ "tsup": "^8.5.0",
23
+ "tsx": "^4.20.5",
24
+ "typescript": "^5.9.2",
25
+ "vitest": "^1.6.1"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/rivet-dev/actors.git",
30
+ "directory": "engine/sdks/typescript/test-runner"
31
+ }
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,314 @@
1
+ import { serve } from "@hono/node-server";
2
+ import type { ActorConfig, RunnerConfig } from "@rivetkit/engine-runner";
3
+ import { Runner } from "@rivetkit/engine-runner";
4
+ import { Hono, type Context as HonoContext, type Next } from "hono";
5
+ import { streamSSE } from "hono/streaming";
6
+ import type { Logger } from "pino";
7
+ import type WebSocket from "ws";
8
+ import { getLogger } from "./log";
9
+
10
+ const INTERNAL_SERVER_PORT = process.env.INTERNAL_SERVER_PORT
11
+ ? Number(process.env.INTERNAL_SERVER_PORT)
12
+ : 5051;
13
+ const RIVET_NAMESPACE = process.env.RIVET_NAMESPACE ?? "default";
14
+ const RIVET_RUNNER_NAME = process.env.RIVET_RUNNER_NAME ?? "test-runner";
15
+ const RIVET_RUNNER_VERSION = process.env.RIVET_RUNNER_VERSION
16
+ ? Number(process.env.RIVET_RUNNER_VERSION)
17
+ : 1;
18
+ const RIVET_RUNNER_TOTAL_SLOTS = parseInt(
19
+ process.env.RIVET_RUNNER_TOTAL_SLOTS ?? "1",
20
+ 10,
21
+ );
22
+ const RIVET_ENDPOINT = process.env.RIVET_ENDPOINT ?? "http://127.0.0.1:6420";
23
+ const RIVET_TOKEN = process.env.RIVET_TOKEN ?? "dev";
24
+ const AUTOSTART_SERVER = (process.env.AUTOSTART_SERVER ?? "1") === "1";
25
+ const AUTOSTART_RUNNER = (process.env.AUTOSTART_RUNNER ?? "0") === "1";
26
+ const AUTOCONFIGURE_SERVERLESS =
27
+ (process.env.AUTOCONFIGURE_SERVERLESS ?? "1") === "1";
28
+
29
+ const runnerStarted = Promise.withResolvers<Runner>();
30
+ const runnerStopped = Promise.withResolvers<Runner>();
31
+ let runner: Runner | null = null;
32
+ const websocketLastMsgIndexes: Map<string, number> = new Map();
33
+
34
+ // Create internal server
35
+ const app = new Hono();
36
+
37
+ function loggerMiddleware(logger: Logger) {
38
+ return async (c: HonoContext, next: Next) => {
39
+ const method = c.req.method;
40
+ const path = c.req.path;
41
+ const startTime = Date.now();
42
+
43
+ await next();
44
+
45
+ const duration = Date.now() - startTime;
46
+ logger.debug({
47
+ msg: "http request",
48
+ method,
49
+ path,
50
+ status: c.res.status,
51
+ dt: `${duration}ms`,
52
+ reqSize: c.req.header("content-length"),
53
+ resSize: c.res.headers.get("content-length"),
54
+ userAgent: c.req.header("user-agent"),
55
+ });
56
+ };
57
+ }
58
+ app.use("*", loggerMiddleware(getLogger()));
59
+
60
+ app.get("/wait-ready", async (c) => {
61
+ const runner = await runnerStarted.promise;
62
+ return c.json(runner.runnerId);
63
+ });
64
+
65
+ app.get("/has-actor", async (c) => {
66
+ const actorIdQuery = c.req.query("actor");
67
+ const generationQuery = c.req.query("generation");
68
+ const generation = generationQuery ? Number(generationQuery) : undefined;
69
+
70
+ if (!actorIdQuery || !runner?.hasActor(actorIdQuery, generation)) {
71
+ return c.text("", 404);
72
+ }
73
+ return c.text("ok");
74
+ });
75
+
76
+ app.get("/health", (c) => {
77
+ return c.text("ok");
78
+ });
79
+
80
+ app.get("/shutdown", async (c) => {
81
+ await runner?.shutdown(true);
82
+ return c.text("ok");
83
+ });
84
+
85
+ app.get("/api/rivet/start", async (c) => {
86
+ return streamSSE(c, async (stream) => {
87
+ const runnerStarted = Promise.withResolvers<Runner>();
88
+ const runnerStopped = Promise.withResolvers<Runner>();
89
+ const runner = await startRunner(runnerStarted, runnerStopped);
90
+
91
+ c.req.raw.signal.addEventListener("abort", () => {
92
+ getLogger().debug("SSE aborted, shutting down runner");
93
+ runner?.shutdown(true);
94
+ });
95
+
96
+ await runnerStarted.promise;
97
+
98
+ stream.writeSSE({ data: runner.getServerlessInitPacket()! });
99
+
100
+ await runnerStopped.promise;
101
+ });
102
+ });
103
+
104
+ app.get("/api/rivet/metadata", async (c) => {
105
+ return c.json({
106
+ // Not actually rivetkit
107
+ runtime: "rivetkit",
108
+ version: "1",
109
+ });
110
+ });
111
+
112
+ if (AUTOSTART_SERVER) {
113
+ serve({
114
+ fetch: app.fetch,
115
+ port: INTERNAL_SERVER_PORT,
116
+ });
117
+ getLogger().info(
118
+ `Internal HTTP server listening on port ${INTERNAL_SERVER_PORT}`,
119
+ );
120
+ }
121
+
122
+ if (AUTOSTART_RUNNER) {
123
+ runner = await startRunner(runnerStarted, runnerStopped);
124
+ } else if (AUTOCONFIGURE_SERVERLESS) {
125
+ await autoConfigureServerless();
126
+ }
127
+
128
+ process.on("SIGTERM", async () => {
129
+ getLogger().debug("received SIGTERM, force exiting in 3s");
130
+
131
+ await new Promise((res) => setTimeout(res, 3000));
132
+
133
+ process.exit(0);
134
+ });
135
+ process.on("SIGINT", async () => {
136
+ getLogger().debug("received SIGTERM, force exiting in 3s");
137
+
138
+ await new Promise((res) => setTimeout(res, 3000));
139
+
140
+ process.exit(0);
141
+ });
142
+
143
+ async function autoConfigureServerless() {
144
+ getLogger().info("Configuring serverless");
145
+
146
+ const res = await fetch(
147
+ `${RIVET_ENDPOINT}/runner-configs/${RIVET_RUNNER_NAME}?namespace=${RIVET_NAMESPACE}`,
148
+ {
149
+ method: "PUT",
150
+ headers: {
151
+ Authorization: `Bearer ${RIVET_TOKEN}`,
152
+ "Content-Type": "application/json",
153
+ },
154
+ body: JSON.stringify({
155
+ datacenters: {
156
+ default: {
157
+ serverless: {
158
+ url: `http://localhost:${INTERNAL_SERVER_PORT}/api/rivet`,
159
+ max_runners: 10000,
160
+ slots_per_runner: 1,
161
+ request_lifespan: 300,
162
+ },
163
+ },
164
+ },
165
+ }),
166
+ },
167
+ );
168
+
169
+ if (!res.ok) {
170
+ throw new Error(
171
+ `request failed: ${res.statusText} (${res.status}):\n${await res.text()}`,
172
+ );
173
+ }
174
+ }
175
+
176
+ async function startRunner(
177
+ runnerStarted: PromiseWithResolvers<Runner>,
178
+ runnerStopped: PromiseWithResolvers<Runner>,
179
+ ): Promise<Runner> {
180
+ getLogger().info("Starting runner");
181
+ let runner: Runner;
182
+ const config: RunnerConfig = {
183
+ logger: getLogger(),
184
+ version: RIVET_RUNNER_VERSION,
185
+ endpoint: RIVET_ENDPOINT,
186
+ token: RIVET_TOKEN,
187
+ namespace: RIVET_NAMESPACE,
188
+ runnerName: RIVET_RUNNER_NAME,
189
+ totalSlots: RIVET_RUNNER_TOTAL_SLOTS,
190
+ prepopulateActorNames: {},
191
+ onConnected: () => {
192
+ runnerStarted.resolve(runner);
193
+ },
194
+ onDisconnected: () => {},
195
+ onShutdown: () => {
196
+ runnerStopped.resolve(runner);
197
+ },
198
+ fetch: async (
199
+ runner: Runner,
200
+ actorId: string,
201
+ _gatewayId: ArrayBuffer,
202
+ _requestId: ArrayBuffer,
203
+ request: Request,
204
+ ) => {
205
+ getLogger().info(
206
+ `Fetch called for actor ${actorId}, URL: ${request.url}`,
207
+ );
208
+ const url = new URL(request.url);
209
+ if (url.pathname === "/ping") {
210
+ // Return the actor ID in response
211
+ const responseData = {
212
+ actorId,
213
+ status: "ok",
214
+ timestamp: Date.now(),
215
+ };
216
+
217
+ return new Response(JSON.stringify(responseData), {
218
+ status: 200,
219
+ headers: { "Content-Type": "application/json" },
220
+ });
221
+ } else if (url.pathname === "/sleep") {
222
+ runner.sleepActor(actorId);
223
+
224
+ return new Response("ok", {
225
+ status: 200,
226
+ headers: { "Content-Type": "application/json" },
227
+ });
228
+ }
229
+
230
+ return new Response("ok", { status: 200 });
231
+ },
232
+ onActorStart: async (
233
+ _actorId: string,
234
+ _generation: number,
235
+ _config: ActorConfig,
236
+ ) => {
237
+ getLogger().info(
238
+ `Actor ${_actorId} started (generation ${_generation})`,
239
+ );
240
+ },
241
+ onActorStop: async (_actorId: string, _generation: number) => {
242
+ getLogger().info(
243
+ `Actor ${_actorId} stopped (generation ${_generation})`,
244
+ );
245
+ },
246
+ websocket: async (
247
+ runner: Runner,
248
+ actorId: string,
249
+ ws: WebSocket,
250
+ _gatewayId: ArrayBuffer,
251
+ _requestId: ArrayBuffer,
252
+ _request: Request,
253
+ ) => {
254
+ getLogger().info(`WebSocket connected for actor ${actorId}`);
255
+
256
+ // Echo server - send back any messages received
257
+ ws.addEventListener("message", (event) => {
258
+ const data = event.data;
259
+ getLogger().info({
260
+ msg: `WebSocket message from actor ${actorId}`,
261
+ data,
262
+ index: (event as any).rivetMessageIndex,
263
+ });
264
+
265
+ ws.send(`Echo: ${data}`);
266
+
267
+ // Ack
268
+ const websocketId = Buffer.from(
269
+ (event as any).rivetRequestId,
270
+ ).toString("base64");
271
+ websocketLastMsgIndexes.set(
272
+ websocketId,
273
+ (event as any).rivetMessageIndex,
274
+ );
275
+ runner.sendHibernatableWebSocketMessageAck(
276
+ (event as any).rivetGatewayId,
277
+ (event as any).rivetRequestId,
278
+ (event as any).rivetMessageIndex,
279
+ );
280
+ });
281
+
282
+ ws.addEventListener("close", () => {
283
+ getLogger().info(`WebSocket closed for actor ${actorId}`);
284
+ });
285
+
286
+ ws.addEventListener("error", (error) => {
287
+ getLogger().error({
288
+ msg: `WebSocket error for actor ${actorId}:`,
289
+ error,
290
+ });
291
+ });
292
+ },
293
+ hibernatableWebSocket: {
294
+ canHibernate() {
295
+ return true;
296
+ },
297
+ },
298
+ };
299
+
300
+ runner = new Runner(config);
301
+
302
+ // Start runner
303
+ await runner.start();
304
+
305
+ // Wait for runner to be ready
306
+ getLogger().info("Waiting runner start...");
307
+ await runnerStarted.promise;
308
+
309
+ getLogger().info("Runner started");
310
+
311
+ return runner;
312
+ }
313
+
314
+ export default app;
package/src/log.ts ADDED
@@ -0,0 +1,178 @@
1
+ import { inspect } from "node:util";
2
+ import {
3
+ type LevelWithSilent,
4
+ type Logger,
5
+ pino,
6
+ stdTimeFunctions,
7
+ } from "pino";
8
+
9
+ export type { Logger } from "pino";
10
+
11
+ let baseLogger: Logger | undefined;
12
+
13
+ /** Cache of child loggers by logger name. */
14
+ const loggerCache = new Map<string, Logger>();
15
+
16
+ export function getPinoLevel(): LevelWithSilent {
17
+ // Priority: env > default
18
+ return (process.env.LOG_LEVEL || "warn")
19
+ .toString()
20
+ .toLowerCase() as LevelWithSilent;
21
+ }
22
+
23
+ export function getIncludeTarget(): boolean {
24
+ return process.env.LOG_TARGET === "1";
25
+ }
26
+
27
+ /**
28
+ * Configure a custom base logger.
29
+ */
30
+ export function configureBaseLogger(logger: Logger): void {
31
+ baseLogger = logger;
32
+ loggerCache.clear();
33
+ }
34
+
35
+ // TODO: This can be simplified in logfmt.ts
36
+ function customWrite(level: string, o: any) {
37
+ const entries: any = {};
38
+
39
+ // Add timestamp if enabled
40
+ if (process.env.LOG_TIMESTAMP === "1" && o.time) {
41
+ const date = typeof o.time === "number" ? new Date(o.time) : new Date();
42
+ entries.ts = date;
43
+ }
44
+
45
+ // Add level
46
+ entries.level = level.toUpperCase();
47
+
48
+ // Add target if present
49
+ if (o.target) {
50
+ entries.target = o.target;
51
+ }
52
+
53
+ // Add message
54
+ if (o.msg) {
55
+ entries.msg = o.msg;
56
+ }
57
+
58
+ // Add other properties
59
+ for (const [key, value] of Object.entries(o)) {
60
+ if (
61
+ key !== "time" &&
62
+ key !== "level" &&
63
+ key !== "target" &&
64
+ key !== "msg" &&
65
+ key !== "pid" &&
66
+ key !== "hostname"
67
+ ) {
68
+ entries[key] = value;
69
+ }
70
+ }
71
+
72
+ const output = inspect(entries, {
73
+ compact: true,
74
+ breakLength: Infinity,
75
+ colors: true,
76
+ });
77
+ console.log(output);
78
+ }
79
+
80
+ /**
81
+ * Configure the default logger with optional log level.
82
+ */
83
+ export async function configureDefaultLogger(): Promise<void> {
84
+ baseLogger = pino({
85
+ level: getPinoLevel(),
86
+ messageKey: "msg",
87
+ errorKey: "error",
88
+ // Do not include pid/hostname in output
89
+ base: {},
90
+ // Keep a string level in the output
91
+ formatters: {
92
+ level(_label: string, number: number) {
93
+ return { level: number };
94
+ },
95
+ },
96
+ timestamp:
97
+ process.env.LOG_TIMESTAMP === "1"
98
+ ? stdTimeFunctions.epochTime
99
+ : false,
100
+ browser: {
101
+ write: {
102
+ fatal: customWrite.bind(null, "fatal"),
103
+ error: customWrite.bind(null, "error"),
104
+ warn: customWrite.bind(null, "warn"),
105
+ info: customWrite.bind(null, "info"),
106
+ debug: customWrite.bind(null, "debug"),
107
+ trace: customWrite.bind(null, "trace"),
108
+ },
109
+ },
110
+ hooks: {
111
+ logMethod(inputArgs, _method, level) {
112
+ // TODO: This is a hack to not implement our own transport target. We can get better perf if we have our own transport target.
113
+
114
+ const levelMap: Record<number, string> = {
115
+ 10: "trace",
116
+ 20: "debug",
117
+ 30: "info",
118
+ 40: "warn",
119
+ 50: "error",
120
+ 60: "fatal",
121
+ };
122
+ const levelName = levelMap[level] || "info";
123
+ const time =
124
+ process.env.LOG_TIMESTAMP === "1" ? Date.now() : undefined;
125
+ // TODO: This can be simplified in logfmt.ts
126
+ if (inputArgs.length >= 2) {
127
+ const [objOrMsg, msg] = inputArgs;
128
+ if (typeof objOrMsg === "object" && objOrMsg !== null) {
129
+ customWrite(levelName, { ...objOrMsg, msg, time });
130
+ } else {
131
+ customWrite(levelName, { msg: String(objOrMsg), time });
132
+ }
133
+ } else if (inputArgs.length === 1) {
134
+ const [objOrMsg] = inputArgs;
135
+ if (typeof objOrMsg === "object" && objOrMsg !== null) {
136
+ customWrite(levelName, { ...objOrMsg, time });
137
+ } else {
138
+ customWrite(levelName, { msg: String(objOrMsg), time });
139
+ }
140
+ }
141
+ },
142
+ },
143
+ });
144
+
145
+ loggerCache.clear();
146
+ }
147
+
148
+ /**
149
+ * Get or initialize the base logger.
150
+ */
151
+ export function getBaseLogger(): Logger {
152
+ if (!baseLogger) {
153
+ configureDefaultLogger();
154
+ }
155
+ return baseLogger!;
156
+ }
157
+
158
+ /**
159
+ * Returns a child logger with `target` bound for the given name.
160
+ */
161
+ export function getLogger(name = "default"): Logger {
162
+ // Check cache first
163
+ const cached = loggerCache.get(name);
164
+ if (cached) {
165
+ return cached;
166
+ }
167
+
168
+ // Create
169
+ const base = getBaseLogger();
170
+
171
+ // Add target to log if enabled
172
+ const child = getIncludeTarget() ? base.child({ target: name }) : base;
173
+
174
+ // Cache the logger
175
+ loggerCache.set(name, child);
176
+
177
+ return child;
178
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "paths": {
5
+ "@/*": ["./src/*"]
6
+ },
7
+ "lib": ["ESNext", "DOM"]
8
+ },
9
+ "include": ["src/**/*", "tests/**/*", "benches/**/*"],
10
+ "exclude": ["node_modules"]
11
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "tsup";
2
+ import defaultConfig from "../../../../tsup.base.ts";
3
+
4
+ export default defineConfig({
5
+ ...defaultConfig,
6
+ format: "esm",
7
+ });
package/turbo.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://turbo.build/schema.json",
3
+ "extends": ["//"]
4
+ }
@@ -0,0 +1,16 @@
1
+ import { resolve } from "node:path";
2
+ import { defineConfig } from "vitest/config";
3
+ import defaultConfig from "../../../../vitest.base.ts";
4
+
5
+ export default defineConfig({
6
+ ...defaultConfig,
7
+ resolve: {
8
+ alias: {
9
+ "@": resolve(__dirname, "./src"),
10
+ },
11
+ },
12
+ test: {
13
+ ...defaultConfig.test,
14
+ include: ["tests/**/*.test.ts"],
15
+ },
16
+ });