@rivetkit/next-js 2.0.33 → 2.0.34-rc.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/dist/mod.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { Registry, RunConfigInput } from 'rivetkit';
1
+ import { Registry } from 'rivetkit';
2
2
 
3
- declare const toNextHandler: (registry: Registry<any>, inputConfig?: RunConfigInput) => {
3
+ declare const toNextHandler: (registry: Registry<any>) => {
4
4
  GET: (request: Request, { params }: {
5
5
  params: Promise<{
6
6
  all: string[];
package/dist/mod.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { Registry, RunConfigInput } from 'rivetkit';
1
+ import { Registry } from 'rivetkit';
2
2
 
3
- declare const toNextHandler: (registry: Registry<any>, inputConfig?: RunConfigInput) => {
3
+ declare const toNextHandler: (registry: Registry<any>) => {
4
4
  GET: (request: Request, { params }: {
5
5
  params: Promise<{
6
6
  all: string[];
package/dist/mod.js CHANGED
@@ -10,16 +10,16 @@ function logger() {
10
10
  }
11
11
 
12
12
  // src/mod.ts
13
- var toNextHandler = (registry, inputConfig = {}) => {
14
- inputConfig.disableDefaultServer = true;
15
- inputConfig.runnerKind = "serverless";
13
+ var toNextHandler = (registry) => {
14
+ registry.config.serveManager = false;
15
+ registry.config.serverless = { ...registry.config.serverless, basePath: "/" };
16
16
  if (process.env.NODE_ENV !== "production") {
17
17
  logger().debug(
18
18
  "detected development environment, auto-starting engine and auto-configuring serverless"
19
19
  );
20
20
  const publicUrl = _nullishCoalesce(_nullishCoalesce(process.env.NEXT_PUBLIC_SITE_URL, () => ( process.env.NEXT_PUBLIC_VERCEL_URL)), () => ( `http://127.0.0.1:${_nullishCoalesce(process.env.PORT, () => ( 3e3))}`));
21
- inputConfig.runEngine = true;
22
- inputConfig.autoConfigureServerless = {
21
+ registry.config.serverless.spawnEngine = true;
22
+ registry.config.serverless.configureRunnerPool = {
23
23
  url: `${publicUrl}/api/rivet`,
24
24
  minRunners: 0,
25
25
  maxRunners: 1e5,
@@ -32,17 +32,16 @@ var toNextHandler = (registry, inputConfig = {}) => {
32
32
  "detected production environment, will not auto-start engine and auto-configure serverless"
33
33
  );
34
34
  }
35
- inputConfig.noWelcome = true;
36
- const { fetch } = registry.start(inputConfig);
35
+ registry.config.noWelcome = true;
37
36
  const fetchWrapper = async (request, { params }) => {
38
37
  const { all } = await params;
39
38
  const newUrl = new URL(request.url);
40
- newUrl.pathname = all.join("/");
41
- if (process.env.NODE_ENV !== "development") {
42
- const newReq = new Request(newUrl, request);
43
- return await fetch(newReq);
44
- } else {
39
+ newUrl.pathname = `/${all.join("/")}`;
40
+ if (false) {
45
41
  return await handleRequestWithFileWatcher(request, newUrl, fetch);
42
+ } else {
43
+ const newReq = new Request(newUrl, request);
44
+ return await registry.handler(newReq);
46
45
  }
47
46
  };
48
47
  return {
@@ -54,106 +53,6 @@ var toNextHandler = (registry, inputConfig = {}) => {
54
53
  OPTIONS: fetchWrapper
55
54
  };
56
55
  };
57
- async function handleRequestWithFileWatcher(request, newUrl, fetch) {
58
- var _a;
59
- const mergedController = new AbortController();
60
- const abortMerged = () => mergedController.abort();
61
- (_a = request.signal) == null ? void 0 : _a.addEventListener("abort", abortMerged);
62
- const watchIntervalId = watchRouteFile(mergedController);
63
- request.signal.addEventListener("abort", () => {
64
- logger().debug("clearing file watcher interval: request aborted");
65
- clearInterval(watchIntervalId);
66
- });
67
- const newReq = new Request(newUrl, {
68
- // Copy old request properties
69
- method: request.method,
70
- headers: request.headers,
71
- body: request.body,
72
- credentials: request.credentials,
73
- cache: request.cache,
74
- redirect: request.redirect,
75
- referrer: request.referrer,
76
- integrity: request.integrity,
77
- // Override with new signal
78
- signal: mergedController.signal,
79
- // Required for streaming body
80
- duplex: "half"
81
- });
82
- const response = await fetch(newReq);
83
- if (response.body) {
84
- const wrappedStream = waitForStreamFinish(response.body, () => {
85
- logger().debug("clearing file watcher interval: stream finished");
86
- clearInterval(watchIntervalId);
87
- });
88
- return new Response(wrappedStream, {
89
- status: response.status,
90
- statusText: response.statusText,
91
- headers: response.headers
92
- });
93
- } else {
94
- logger().debug("clearing file watcher interval: no response body");
95
- clearInterval(watchIntervalId);
96
- return response;
97
- }
98
- }
99
- function watchRouteFile(abortController) {
100
- logger().debug("starting file watcher");
101
- const routePath = _path.join.call(void 0,
102
- process.cwd(),
103
- ".next/server/app/api/rivet/[...all]/route.js"
104
- );
105
- let lastMtime = null;
106
- const checkFile = () => {
107
- logger().debug({ msg: "checking for file changes", routePath });
108
- try {
109
- if (!_fs.existsSync.call(void 0, routePath)) {
110
- return;
111
- }
112
- const stats = _fs.statSync.call(void 0, routePath);
113
- const mtime = stats.mtimeMs;
114
- if (lastMtime !== null && mtime !== lastMtime) {
115
- logger().info({ msg: "route file changed", routePath });
116
- abortController.abort();
117
- }
118
- lastMtime = mtime;
119
- } catch (err) {
120
- logger().info({
121
- msg: "failed to check for route file change",
122
- err: _utils.stringifyError.call(void 0, err)
123
- });
124
- }
125
- };
126
- checkFile();
127
- return setInterval(checkFile, 1e3);
128
- }
129
- function waitForStreamFinish(body, onFinish) {
130
- const reader = body.getReader();
131
- return new ReadableStream({
132
- async start(controller) {
133
- try {
134
- while (true) {
135
- const { done, value } = await reader.read();
136
- if (done) {
137
- logger().debug("stream completed");
138
- onFinish();
139
- controller.close();
140
- break;
141
- }
142
- controller.enqueue(value);
143
- }
144
- } catch (err) {
145
- logger().debug("stream errored");
146
- onFinish();
147
- controller.error(err);
148
- }
149
- },
150
- cancel() {
151
- logger().debug("stream cancelled");
152
- onFinish();
153
- reader.cancel();
154
- }
155
- });
156
- }
157
56
 
158
57
 
159
58
  exports.toNextHandler = toNextHandler;
package/dist/mod.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/rivet/rivet/rivetkit-typescript/packages/next-js/dist/mod.js","../src/mod.ts","../src/log.ts"],"names":[],"mappings":"AAAA;ACAA,wBAAqC;AACrC,4BAAqB;AAErB,uCAA+B;ADC/B;AACA;AELA,mCAA0B;AAEnB,SAAS,MAAA,CAAA,EAAS;AACxB,EAAA,OAAO,4BAAA,gBAA0B,CAAA;AAClC;AFMA;AACA;ACLO,IAAM,cAAA,EAAgB,CAC5B,QAAA,EACA,YAAA,EAA8B,CAAC,CAAA,EAAA,GAC3B;AAEJ,EAAA,WAAA,CAAY,qBAAA,EAAuB,IAAA;AAGnC,EAAA,WAAA,CAAY,WAAA,EAAa,YAAA;AAEzB,EAAA,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,SAAA,IAAa,YAAA,EAAc;AAE1C,IAAA,MAAA,CAAO,CAAA,CAAE,KAAA;AAAA,MACR;AAAA,IACD,CAAA;AAEA,IAAA,MAAM,UAAA,oCACL,OAAA,CAAQ,GAAA,CAAI,oBAAA,UACZ,OAAA,CAAQ,GAAA,CAAI,wBAAA,UACZ,CAAA,iBAAA,mBAAoB,OAAA,CAAQ,GAAA,CAAI,IAAA,UAAQ,KAAI,CAAA,GAAA;AAErB,IAAA;AACc,IAAA;AACpB,MAAA;AACL,MAAA;AACA,MAAA;AACK,MAAA;AACD,MAAA;AACgB,MAAA;AACjC,IAAA;AACM,EAAA;AACG,IAAA;AACR,MAAA;AACD,IAAA;AACD,EAAA;AAGwB,EAAA;AAEoB,EAAA;AAMpB,EAAA;AACD,IAAA;AAEY,IAAA;AACJ,IAAA;AAEc,IAAA;AAED,MAAA;AACjB,MAAA;AACnB,IAAA;AAE0D,MAAA;AACjE,IAAA;AACD,EAAA;AAEO,EAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACE,IAAA;AACD,IAAA;AACG,IAAA;AACV,EAAA;AACD;AAYqB;AAvFrB,EAAA;AA0F8C,EAAA;AACI,EAAA;AACP,EAAA;AAMa,EAAA;AAGR,EAAA;AACkB,IAAA;AACnC,IAAA;AAC7B,EAAA;AAGkC,EAAA;AAAA;AAElB,IAAA;AACC,IAAA;AACH,IAAA;AACO,IAAA;AACN,IAAA;AACG,IAAA;AACA,IAAA;AACC,IAAA;AAAA;AAEM,IAAA;AAAA;AAEjB,IAAA;AACO,EAAA;AAGmB,EAAA;AAOhB,EAAA;AAC6C,IAAA;AACE,MAAA;AACnC,MAAA;AAC7B,IAAA;AACkC,IAAA;AACjB,MAAA;AACI,MAAA;AACH,MAAA;AAClB,IAAA;AACK,EAAA;AAE2D,IAAA;AACpC,IAAA;AACtB,IAAA;AACR,EAAA;AACD;AAW0E;AACnC,EAAA;AAEpB,EAAA;AACL,IAAA;AACZ,IAAA;AACD,EAAA;AAE+B,EAAA;AACP,EAAA;AACuC,IAAA;AAC1D,IAAA;AACyB,MAAA;AAC3B,QAAA;AACD,MAAA;AAEgC,MAAA;AACZ,MAAA;AAE2B,MAAA;AACQ,QAAA;AAChC,QAAA;AACvB,MAAA;AAEY,MAAA;AACC,IAAA;AACC,MAAA;AACR,QAAA;AACkB,QAAA;AACvB,MAAA;AACF,IAAA;AACD,EAAA;AAEU,EAAA;AAEwB,EAAA;AACnC;AAUkB;AACa,EAAA;AACJ,EAAA;AACD,IAAA;AACnB,MAAA;AACU,QAAA;AAC8B,UAAA;AAChC,UAAA;AACwB,YAAA;AACxB,YAAA;AACQ,YAAA;AACjB,YAAA;AACD,UAAA;AACwB,UAAA;AACzB,QAAA;AACa,MAAA;AACkB,QAAA;AACtB,QAAA;AACW,QAAA;AACrB,MAAA;AACD,IAAA;AACS,IAAA;AACyB,MAAA;AACxB,MAAA;AACK,MAAA;AACf,IAAA;AACA,EAAA;AACF;AD3E4I;AACA;AACA","file":"/home/runner/work/rivet/rivet/rivetkit-typescript/packages/next-js/dist/mod.js","sourcesContent":[null,"import { existsSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Registry, RunConfigInput } from \"rivetkit\";\nimport { stringifyError } from \"rivetkit/utils\";\nimport { logger } from \"./log\";\n\nexport const toNextHandler = (\n\tregistry: Registry<any>,\n\tinputConfig: RunConfigInput = {},\n) => {\n\t// Don't run server locally since we're using the fetch handler directly\n\tinputConfig.disableDefaultServer = true;\n\n\t// Configure serverless\n\tinputConfig.runnerKind = \"serverless\";\n\n\tif (process.env.NODE_ENV !== \"production\") {\n\t\t// Auto-configure serverless runner if not in prod\n\t\tlogger().debug(\n\t\t\t\"detected development environment, auto-starting engine and auto-configuring serverless\",\n\t\t);\n\n\t\tconst publicUrl =\n\t\t\tprocess.env.NEXT_PUBLIC_SITE_URL ??\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_URL ??\n\t\t\t`http://127.0.0.1:${process.env.PORT ?? 3000}`;\n\n\t\tinputConfig.runEngine = true;\n\t\tinputConfig.autoConfigureServerless = {\n\t\t\turl: `${publicUrl}/api/rivet`,\n\t\t\tminRunners: 0,\n\t\t\tmaxRunners: 100_000,\n\t\t\trequestLifespan: 300,\n\t\t\tslotsPerRunner: 1,\n\t\t\tmetadata: { provider: \"next-js\" },\n\t\t};\n\t} else {\n\t\tlogger().debug(\n\t\t\t\"detected production environment, will not auto-start engine and auto-configure serverless\",\n\t\t);\n\t}\n\n\t// Next logs this on every request\n\tinputConfig.noWelcome = true;\n\n\tconst { fetch } = registry.start(inputConfig);\n\n\t// Function that Next will call when handling requests\n\tconst fetchWrapper = async (\n\t\trequest: Request,\n\t\t{ params }: { params: Promise<{ all: string[] }> },\n\t): Promise<Response> => {\n\t\tconst { all } = await params;\n\n\t\tconst newUrl = new URL(request.url);\n\t\tnewUrl.pathname = all.join(\"/\");\n\n\t\tif (process.env.NODE_ENV !== \"development\") {\n\t\t\t// Handle request\n\t\t\tconst newReq = new Request(newUrl, request);\n\t\t\treturn await fetch(newReq);\n\t\t} else {\n\t\t\t// Special request handling for file watching\n\t\t\treturn await handleRequestWithFileWatcher(request, newUrl, fetch);\n\t\t}\n\t};\n\n\treturn {\n\t\tGET: fetchWrapper,\n\t\tPOST: fetchWrapper,\n\t\tPUT: fetchWrapper,\n\t\tPATCH: fetchWrapper,\n\t\tHEAD: fetchWrapper,\n\t\tOPTIONS: fetchWrapper,\n\t};\n};\n\n/**\n * Special request handler that will watch the source file to terminate this\n * request once complete.\n *\n * See docs on watchRouteFile for more information.\n */\nasync function handleRequestWithFileWatcher(\n\trequest: Request,\n\tnewUrl: URL,\n\tfetch: (request: Request, ...args: any) => Response | Promise<Response>,\n): Promise<Response> {\n\t// Create a new abort controller that we can abort, since the signal on\n\t// the request we cannot control\n\tconst mergedController = new AbortController();\n\tconst abortMerged = () => mergedController.abort();\n\trequest.signal?.addEventListener(\"abort\", abortMerged);\n\n\t// Watch for file changes in dev\n\t//\n\t// We spawn one watcher per-request since there is not a clean way of\n\t// cleaning up global watchers when hot reloading in Next\n\tconst watchIntervalId = watchRouteFile(mergedController);\n\n\t// Clear interval if request is aborted\n\trequest.signal.addEventListener(\"abort\", () => {\n\t\tlogger().debug(\"clearing file watcher interval: request aborted\");\n\t\tclearInterval(watchIntervalId);\n\t});\n\n\t// Replace URL and abort signal\n\tconst newReq = new Request(newUrl, {\n\t\t// Copy old request properties\n\t\tmethod: request.method,\n\t\theaders: request.headers,\n\t\tbody: request.body,\n\t\tcredentials: request.credentials,\n\t\tcache: request.cache,\n\t\tredirect: request.redirect,\n\t\treferrer: request.referrer,\n\t\tintegrity: request.integrity,\n\t\t// Override with new signal\n\t\tsignal: mergedController.signal,\n\t\t// Required for streaming body\n\t\tduplex: \"half\",\n\t} as RequestInit);\n\n\t// Handle request\n\tconst response = await fetch(newReq);\n\n\t// HACK: Next.js does not provide a way to detect when a request\n\t// finishes, so we need to tap the response stream\n\t//\n\t// We can't just wait for `await fetch` to finish since SSE streams run\n\t// for longer\n\tif (response.body) {\n\t\tconst wrappedStream = waitForStreamFinish(response.body, () => {\n\t\t\tlogger().debug(\"clearing file watcher interval: stream finished\");\n\t\t\tclearInterval(watchIntervalId);\n\t\t});\n\t\treturn new Response(wrappedStream, {\n\t\t\tstatus: response.status,\n\t\t\tstatusText: response.statusText,\n\t\t\theaders: response.headers,\n\t\t});\n\t} else {\n\t\t// No response body, clear interval immediately\n\t\tlogger().debug(\"clearing file watcher interval: no response body\");\n\t\tclearInterval(watchIntervalId);\n\t\treturn response;\n\t}\n}\n\n/**\n * HACK: Watch for file changes on this route in order to shut down the runner.\n * We do this because Next.js does not terminate long-running requests on file\n * change, so we need to manually shut down the runner in order to trigger a\n * new `/start` request with the new code.\n *\n * We don't use file watchers since those are frequently buggy x-platform and\n * subject to misconfigured inotify limits.\n */\nfunction watchRouteFile(abortController: AbortController): NodeJS.Timeout {\n\tlogger().debug(\"starting file watcher\");\n\n\tconst routePath = join(\n\t\tprocess.cwd(),\n\t\t\".next/server/app/api/rivet/[...all]/route.js\",\n\t);\n\n\tlet lastMtime: number | null = null;\n\tconst checkFile = () => {\n\t\tlogger().debug({ msg: \"checking for file changes\", routePath });\n\t\ttry {\n\t\t\tif (!existsSync(routePath)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst stats = statSync(routePath);\n\t\t\tconst mtime = stats.mtimeMs;\n\n\t\t\tif (lastMtime !== null && mtime !== lastMtime) {\n\t\t\t\tlogger().info({ msg: \"route file changed\", routePath });\n\t\t\t\tabortController.abort();\n\t\t\t}\n\n\t\t\tlastMtime = mtime;\n\t\t} catch (err) {\n\t\t\tlogger().info({\n\t\t\t\tmsg: \"failed to check for route file change\",\n\t\t\t\terr: stringifyError(err),\n\t\t\t});\n\t\t}\n\t};\n\n\tcheckFile();\n\n\treturn setInterval(checkFile, 1000);\n}\n\n/**\n * Waits for a stream to finish and calls onFinish on complete.\n *\n * Used for cancelling the file watcher.\n */\nfunction waitForStreamFinish(\n\tbody: ReadableStream<Uint8Array>,\n\tonFinish: () => void,\n): ReadableStream {\n\tconst reader = body.getReader();\n\treturn new ReadableStream({\n\t\tasync start(controller) {\n\t\t\ttry {\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) {\n\t\t\t\t\t\tlogger().debug(\"stream completed\");\n\t\t\t\t\t\tonFinish();\n\t\t\t\t\t\tcontroller.close();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcontroller.enqueue(value);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tlogger().debug(\"stream errored\");\n\t\t\t\tonFinish();\n\t\t\t\tcontroller.error(err);\n\t\t\t}\n\t\t},\n\t\tcancel() {\n\t\t\tlogger().debug(\"stream cancelled\");\n\t\t\tonFinish();\n\t\t\treader.cancel();\n\t\t},\n\t});\n}\n","import { getLogger } from \"rivetkit/log\";\n\nexport function logger() {\n\treturn getLogger(\"driver-next-js\");\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/rivet/rivet/rivetkit-typescript/packages/next-js/dist/mod.js","../src/mod.ts","../src/log.ts"],"names":[],"mappings":"AAAA;ACAA,wBAAqC;AACrC,4BAAqB;AAErB,uCAA+B;ADC/B;AACA;AELA,mCAA0B;AAEnB,SAAS,MAAA,CAAA,EAAS;AACxB,EAAA,OAAO,4BAAA,gBAA0B,CAAA;AAClC;AFMA;AACA;ACLO,IAAM,cAAA,EAAgB,CAAC,QAAA,EAAA,GAA4B;AAEzD,EAAA,QAAA,CAAS,MAAA,CAAO,aAAA,EAAe,KAAA;AAG/B,EAAA,QAAA,CAAS,MAAA,CAAO,WAAA,EAAa,EAAE,GAAG,QAAA,CAAS,MAAA,CAAO,UAAA,EAAY,QAAA,EAAU,IAAI,CAAA;AAE5E,EAAA,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,SAAA,IAAa,YAAA,EAAc;AAE1C,IAAA,MAAA,CAAO,CAAA,CAAE,KAAA;AAAA,MACR;AAAA,IACD,CAAA;AAEA,IAAA,MAAM,UAAA,oCACL,OAAA,CAAQ,GAAA,CAAI,oBAAA,UACZ,OAAA,CAAQ,GAAA,CAAI,wBAAA,UACZ,CAAA,iBAAA,mBAAoB,OAAA,CAAQ,GAAA,CAAI,IAAA,UAAQ,KAAI,CAAA,GAAA;AAIJ,IAAA;AACQ,IAAA;AAC/B,MAAA;AACL,MAAA;AACA,MAAA;AACK,MAAA;AACD,MAAA;AACgB,MAAA;AACjC,IAAA;AACM,EAAA;AACG,IAAA;AACR,MAAA;AACD,IAAA;AACD,EAAA;AAG4B,EAAA;AAMJ,EAAA;AACD,IAAA;AAEY,IAAA;AACC,IAAA;AAGxB,IAAA;AAEsD,MAAA;AAC1D,IAAA;AAEoC,MAAA;AACN,MAAA;AACrC,IAAA;AACD,EAAA;AAEO,EAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACE,IAAA;AACD,IAAA;AACG,IAAA;AACV,EAAA;AACD;ADlB4I;AACA;AACA","file":"/home/runner/work/rivet/rivet/rivetkit-typescript/packages/next-js/dist/mod.js","sourcesContent":[null,"import { existsSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Registry } from \"rivetkit\";\nimport { stringifyError } from \"rivetkit/utils\";\nimport { logger } from \"./log\";\n\nexport const toNextHandler = (registry: Registry<any>) => {\n\t// Don't run server locally since we're using the fetch handler directly\n\tregistry.config.serveManager = false;\n\n\t// Set basePath to \"/\" since Next.js route strips the /api/rivet prefix\n\tregistry.config.serverless = { ...registry.config.serverless, basePath: \"/\" };\n\n\tif (process.env.NODE_ENV !== \"production\") {\n\t\t// Auto-configure serverless runner if not in prod\n\t\tlogger().debug(\n\t\t\t\"detected development environment, auto-starting engine and auto-configuring serverless\",\n\t\t);\n\n\t\tconst publicUrl =\n\t\t\tprocess.env.NEXT_PUBLIC_SITE_URL ??\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_URL ??\n\t\t\t`http://127.0.0.1:${process.env.PORT ?? 3000}`;\n\n\t\t// Set these on the registry's config directly since the legacy inputConfig\n\t\t// isn't used by the serverless router\n\t\tregistry.config.serverless.spawnEngine = true;\n\t\tregistry.config.serverless.configureRunnerPool = {\n\t\t\turl: `${publicUrl}/api/rivet`,\n\t\t\tminRunners: 0,\n\t\t\tmaxRunners: 100_000,\n\t\t\trequestLifespan: 300,\n\t\t\tslotsPerRunner: 1,\n\t\t\tmetadata: { provider: \"next-js\" },\n\t\t};\n\t} else {\n\t\tlogger().debug(\n\t\t\t\"detected production environment, will not auto-start engine and auto-configure serverless\",\n\t\t);\n\t}\n\n\t// Next logs this on every request\n\tregistry.config.noWelcome = true;\n\n\t// Function that Next will call when handling requests\n\tconst fetchWrapper = async (\n\t\trequest: Request,\n\t\t{ params }: { params: Promise<{ all: string[] }> },\n\t): Promise<Response> => {\n\t\tconst { all } = await params;\n\n\t\tconst newUrl = new URL(request.url);\n\t\tnewUrl.pathname = `/${all.join(\"/\")}`;\n\n\t\t// if (process.env.NODE_ENV === \"development\") {\n\t\tif (false) {\n\t\t\t// Special request handling for file watching\n\t\t\treturn await handleRequestWithFileWatcher(request, newUrl, fetch);\n\t\t} else {\n\t\t\t// Handle request\n\t\t\tconst newReq = new Request(newUrl, request);\n\t\t\treturn await registry.handler(newReq);\n\t\t}\n\t};\n\n\treturn {\n\t\tGET: fetchWrapper,\n\t\tPOST: fetchWrapper,\n\t\tPUT: fetchWrapper,\n\t\tPATCH: fetchWrapper,\n\t\tHEAD: fetchWrapper,\n\t\tOPTIONS: fetchWrapper,\n\t};\n};\n\n/**\n * Special request handler that will watch the source file to terminate this\n * request once complete.\n *\n * See docs on watchRouteFile for more information.\n */\nasync function handleRequestWithFileWatcher(\n\trequest: Request,\n\tnewUrl: URL,\n\tfetch: (request: Request, ...args: any) => Response | Promise<Response>,\n): Promise<Response> {\n\t// Create a new abort controller that we can abort, since the signal on\n\t// the request we cannot control\n\tconst mergedController = new AbortController();\n\tconst abortMerged = () => mergedController.abort();\n\trequest.signal?.addEventListener(\"abort\", abortMerged);\n\n\t// Watch for file changes in dev\n\t//\n\t// We spawn one watcher per-request since there is not a clean way of\n\t// cleaning up global watchers when hot reloading in Next\n\tconst watchIntervalId = watchRouteFile(mergedController);\n\n\t// Clear interval if request is aborted\n\trequest.signal.addEventListener(\"abort\", () => {\n\t\tlogger().debug(\"clearing file watcher interval: request aborted\");\n\t\tclearInterval(watchIntervalId);\n\t});\n\n\t// Replace URL and abort signal\n\tconst newReq = new Request(newUrl, {\n\t\t// Copy old request properties\n\t\tmethod: request.method,\n\t\theaders: request.headers,\n\t\tbody: request.body,\n\t\tcredentials: request.credentials,\n\t\tcache: request.cache,\n\t\tredirect: request.redirect,\n\t\treferrer: request.referrer,\n\t\tintegrity: request.integrity,\n\t\t// Override with new signal\n\t\tsignal: mergedController.signal,\n\t\t// Required for streaming body\n\t\tduplex: \"half\",\n\t} as RequestInit);\n\n\t// Handle request\n\tconst response = await fetch(newReq);\n\n\t// HACK: Next.js does not provide a way to detect when a request\n\t// finishes, so we need to tap the response stream\n\t//\n\t// We can't just wait for `await fetch` to finish since SSE streams run\n\t// for longer\n\tif (response.body) {\n\t\tconst wrappedStream = waitForStreamFinish(response.body, () => {\n\t\t\tlogger().debug(\"clearing file watcher interval: stream finished\");\n\t\t\tclearInterval(watchIntervalId);\n\t\t});\n\t\treturn new Response(wrappedStream, {\n\t\t\tstatus: response.status,\n\t\t\tstatusText: response.statusText,\n\t\t\theaders: response.headers,\n\t\t});\n\t} else {\n\t\t// No response body, clear interval immediately\n\t\tlogger().debug(\"clearing file watcher interval: no response body\");\n\t\tclearInterval(watchIntervalId);\n\t\treturn response;\n\t}\n}\n\n/**\n * HACK: Watch for file changes on this route in order to shut down the runner.\n * We do this because Next.js does not terminate long-running requests on file\n * change, so we need to manually shut down the runner in order to trigger a\n * new `/start` request with the new code.\n *\n * We don't use file watchers since those are frequently buggy x-platform and\n * subject to misconfigured inotify limits.\n */\nfunction watchRouteFile(abortController: AbortController): NodeJS.Timeout {\n\tlogger().debug(\"starting file watcher\");\n\n\tconst routePath = join(\n\t\tprocess.cwd(),\n\t\t\".next/server/app/api/rivet/[...all]/route.js\",\n\t);\n\n\tlet lastMtime: number | null = null;\n\tconst checkFile = () => {\n\t\tlogger().debug({ msg: \"checking for file changes\", routePath });\n\t\ttry {\n\t\t\tif (!existsSync(routePath)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst stats = statSync(routePath);\n\t\t\tconst mtime = stats.mtimeMs;\n\n\t\t\tif (lastMtime !== null && mtime !== lastMtime) {\n\t\t\t\tlogger().info({ msg: \"route file changed\", routePath });\n\t\t\t\tabortController.abort();\n\t\t\t}\n\n\t\t\tlastMtime = mtime;\n\t\t} catch (err) {\n\t\t\tlogger().info({\n\t\t\t\tmsg: \"failed to check for route file change\",\n\t\t\t\terr: stringifyError(err),\n\t\t\t});\n\t\t}\n\t};\n\n\tcheckFile();\n\n\treturn setInterval(checkFile, 1000);\n}\n\n/**\n * Waits for a stream to finish and calls onFinish on complete.\n *\n * Used for cancelling the file watcher.\n */\nfunction waitForStreamFinish(\n\tbody: ReadableStream<Uint8Array>,\n\tonFinish: () => void,\n): ReadableStream {\n\tconst reader = body.getReader();\n\treturn new ReadableStream({\n\t\tasync start(controller) {\n\t\t\ttry {\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) {\n\t\t\t\t\t\tlogger().debug(\"stream completed\");\n\t\t\t\t\t\tonFinish();\n\t\t\t\t\t\tcontroller.close();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcontroller.enqueue(value);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tlogger().debug(\"stream errored\");\n\t\t\t\tonFinish();\n\t\t\t\tcontroller.error(err);\n\t\t\t}\n\t\t},\n\t\tcancel() {\n\t\t\tlogger().debug(\"stream cancelled\");\n\t\t\tonFinish();\n\t\t\treader.cancel();\n\t\t},\n\t});\n}\n","import { getLogger } from \"rivetkit/log\";\n\nexport function logger() {\n\treturn getLogger(\"driver-next-js\");\n}\n"]}
package/dist/mod.mjs CHANGED
@@ -10,16 +10,16 @@ function logger() {
10
10
  }
11
11
 
12
12
  // src/mod.ts
13
- var toNextHandler = (registry, inputConfig = {}) => {
14
- inputConfig.disableDefaultServer = true;
15
- inputConfig.runnerKind = "serverless";
13
+ var toNextHandler = (registry) => {
14
+ registry.config.serveManager = false;
15
+ registry.config.serverless = { ...registry.config.serverless, basePath: "/" };
16
16
  if (process.env.NODE_ENV !== "production") {
17
17
  logger().debug(
18
18
  "detected development environment, auto-starting engine and auto-configuring serverless"
19
19
  );
20
20
  const publicUrl = process.env.NEXT_PUBLIC_SITE_URL ?? process.env.NEXT_PUBLIC_VERCEL_URL ?? `http://127.0.0.1:${process.env.PORT ?? 3e3}`;
21
- inputConfig.runEngine = true;
22
- inputConfig.autoConfigureServerless = {
21
+ registry.config.serverless.spawnEngine = true;
22
+ registry.config.serverless.configureRunnerPool = {
23
23
  url: `${publicUrl}/api/rivet`,
24
24
  minRunners: 0,
25
25
  maxRunners: 1e5,
@@ -32,17 +32,16 @@ var toNextHandler = (registry, inputConfig = {}) => {
32
32
  "detected production environment, will not auto-start engine and auto-configure serverless"
33
33
  );
34
34
  }
35
- inputConfig.noWelcome = true;
36
- const { fetch } = registry.start(inputConfig);
35
+ registry.config.noWelcome = true;
37
36
  const fetchWrapper = async (request, { params }) => {
38
37
  const { all } = await params;
39
38
  const newUrl = new URL(request.url);
40
- newUrl.pathname = all.join("/");
41
- if (process.env.NODE_ENV !== "development") {
42
- const newReq = new Request(newUrl, request);
43
- return await fetch(newReq);
44
- } else {
39
+ newUrl.pathname = `/${all.join("/")}`;
40
+ if (false) {
45
41
  return await handleRequestWithFileWatcher(request, newUrl, fetch);
42
+ } else {
43
+ const newReq = new Request(newUrl, request);
44
+ return await registry.handler(newReq);
46
45
  }
47
46
  };
48
47
  return {
@@ -54,106 +53,6 @@ var toNextHandler = (registry, inputConfig = {}) => {
54
53
  OPTIONS: fetchWrapper
55
54
  };
56
55
  };
57
- async function handleRequestWithFileWatcher(request, newUrl, fetch) {
58
- var _a;
59
- const mergedController = new AbortController();
60
- const abortMerged = () => mergedController.abort();
61
- (_a = request.signal) == null ? void 0 : _a.addEventListener("abort", abortMerged);
62
- const watchIntervalId = watchRouteFile(mergedController);
63
- request.signal.addEventListener("abort", () => {
64
- logger().debug("clearing file watcher interval: request aborted");
65
- clearInterval(watchIntervalId);
66
- });
67
- const newReq = new Request(newUrl, {
68
- // Copy old request properties
69
- method: request.method,
70
- headers: request.headers,
71
- body: request.body,
72
- credentials: request.credentials,
73
- cache: request.cache,
74
- redirect: request.redirect,
75
- referrer: request.referrer,
76
- integrity: request.integrity,
77
- // Override with new signal
78
- signal: mergedController.signal,
79
- // Required for streaming body
80
- duplex: "half"
81
- });
82
- const response = await fetch(newReq);
83
- if (response.body) {
84
- const wrappedStream = waitForStreamFinish(response.body, () => {
85
- logger().debug("clearing file watcher interval: stream finished");
86
- clearInterval(watchIntervalId);
87
- });
88
- return new Response(wrappedStream, {
89
- status: response.status,
90
- statusText: response.statusText,
91
- headers: response.headers
92
- });
93
- } else {
94
- logger().debug("clearing file watcher interval: no response body");
95
- clearInterval(watchIntervalId);
96
- return response;
97
- }
98
- }
99
- function watchRouteFile(abortController) {
100
- logger().debug("starting file watcher");
101
- const routePath = join(
102
- process.cwd(),
103
- ".next/server/app/api/rivet/[...all]/route.js"
104
- );
105
- let lastMtime = null;
106
- const checkFile = () => {
107
- logger().debug({ msg: "checking for file changes", routePath });
108
- try {
109
- if (!existsSync(routePath)) {
110
- return;
111
- }
112
- const stats = statSync(routePath);
113
- const mtime = stats.mtimeMs;
114
- if (lastMtime !== null && mtime !== lastMtime) {
115
- logger().info({ msg: "route file changed", routePath });
116
- abortController.abort();
117
- }
118
- lastMtime = mtime;
119
- } catch (err) {
120
- logger().info({
121
- msg: "failed to check for route file change",
122
- err: stringifyError(err)
123
- });
124
- }
125
- };
126
- checkFile();
127
- return setInterval(checkFile, 1e3);
128
- }
129
- function waitForStreamFinish(body, onFinish) {
130
- const reader = body.getReader();
131
- return new ReadableStream({
132
- async start(controller) {
133
- try {
134
- while (true) {
135
- const { done, value } = await reader.read();
136
- if (done) {
137
- logger().debug("stream completed");
138
- onFinish();
139
- controller.close();
140
- break;
141
- }
142
- controller.enqueue(value);
143
- }
144
- } catch (err) {
145
- logger().debug("stream errored");
146
- onFinish();
147
- controller.error(err);
148
- }
149
- },
150
- cancel() {
151
- logger().debug("stream cancelled");
152
- onFinish();
153
- reader.cancel();
154
- }
155
- });
156
- }
157
56
  export {
158
57
  toNextHandler
159
58
  };
package/dist/mod.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mod.ts","../src/log.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Registry, RunConfigInput } from \"rivetkit\";\nimport { stringifyError } from \"rivetkit/utils\";\nimport { logger } from \"./log\";\n\nexport const toNextHandler = (\n\tregistry: Registry<any>,\n\tinputConfig: RunConfigInput = {},\n) => {\n\t// Don't run server locally since we're using the fetch handler directly\n\tinputConfig.disableDefaultServer = true;\n\n\t// Configure serverless\n\tinputConfig.runnerKind = \"serverless\";\n\n\tif (process.env.NODE_ENV !== \"production\") {\n\t\t// Auto-configure serverless runner if not in prod\n\t\tlogger().debug(\n\t\t\t\"detected development environment, auto-starting engine and auto-configuring serverless\",\n\t\t);\n\n\t\tconst publicUrl =\n\t\t\tprocess.env.NEXT_PUBLIC_SITE_URL ??\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_URL ??\n\t\t\t`http://127.0.0.1:${process.env.PORT ?? 3000}`;\n\n\t\tinputConfig.runEngine = true;\n\t\tinputConfig.autoConfigureServerless = {\n\t\t\turl: `${publicUrl}/api/rivet`,\n\t\t\tminRunners: 0,\n\t\t\tmaxRunners: 100_000,\n\t\t\trequestLifespan: 300,\n\t\t\tslotsPerRunner: 1,\n\t\t\tmetadata: { provider: \"next-js\" },\n\t\t};\n\t} else {\n\t\tlogger().debug(\n\t\t\t\"detected production environment, will not auto-start engine and auto-configure serverless\",\n\t\t);\n\t}\n\n\t// Next logs this on every request\n\tinputConfig.noWelcome = true;\n\n\tconst { fetch } = registry.start(inputConfig);\n\n\t// Function that Next will call when handling requests\n\tconst fetchWrapper = async (\n\t\trequest: Request,\n\t\t{ params }: { params: Promise<{ all: string[] }> },\n\t): Promise<Response> => {\n\t\tconst { all } = await params;\n\n\t\tconst newUrl = new URL(request.url);\n\t\tnewUrl.pathname = all.join(\"/\");\n\n\t\tif (process.env.NODE_ENV !== \"development\") {\n\t\t\t// Handle request\n\t\t\tconst newReq = new Request(newUrl, request);\n\t\t\treturn await fetch(newReq);\n\t\t} else {\n\t\t\t// Special request handling for file watching\n\t\t\treturn await handleRequestWithFileWatcher(request, newUrl, fetch);\n\t\t}\n\t};\n\n\treturn {\n\t\tGET: fetchWrapper,\n\t\tPOST: fetchWrapper,\n\t\tPUT: fetchWrapper,\n\t\tPATCH: fetchWrapper,\n\t\tHEAD: fetchWrapper,\n\t\tOPTIONS: fetchWrapper,\n\t};\n};\n\n/**\n * Special request handler that will watch the source file to terminate this\n * request once complete.\n *\n * See docs on watchRouteFile for more information.\n */\nasync function handleRequestWithFileWatcher(\n\trequest: Request,\n\tnewUrl: URL,\n\tfetch: (request: Request, ...args: any) => Response | Promise<Response>,\n): Promise<Response> {\n\t// Create a new abort controller that we can abort, since the signal on\n\t// the request we cannot control\n\tconst mergedController = new AbortController();\n\tconst abortMerged = () => mergedController.abort();\n\trequest.signal?.addEventListener(\"abort\", abortMerged);\n\n\t// Watch for file changes in dev\n\t//\n\t// We spawn one watcher per-request since there is not a clean way of\n\t// cleaning up global watchers when hot reloading in Next\n\tconst watchIntervalId = watchRouteFile(mergedController);\n\n\t// Clear interval if request is aborted\n\trequest.signal.addEventListener(\"abort\", () => {\n\t\tlogger().debug(\"clearing file watcher interval: request aborted\");\n\t\tclearInterval(watchIntervalId);\n\t});\n\n\t// Replace URL and abort signal\n\tconst newReq = new Request(newUrl, {\n\t\t// Copy old request properties\n\t\tmethod: request.method,\n\t\theaders: request.headers,\n\t\tbody: request.body,\n\t\tcredentials: request.credentials,\n\t\tcache: request.cache,\n\t\tredirect: request.redirect,\n\t\treferrer: request.referrer,\n\t\tintegrity: request.integrity,\n\t\t// Override with new signal\n\t\tsignal: mergedController.signal,\n\t\t// Required for streaming body\n\t\tduplex: \"half\",\n\t} as RequestInit);\n\n\t// Handle request\n\tconst response = await fetch(newReq);\n\n\t// HACK: Next.js does not provide a way to detect when a request\n\t// finishes, so we need to tap the response stream\n\t//\n\t// We can't just wait for `await fetch` to finish since SSE streams run\n\t// for longer\n\tif (response.body) {\n\t\tconst wrappedStream = waitForStreamFinish(response.body, () => {\n\t\t\tlogger().debug(\"clearing file watcher interval: stream finished\");\n\t\t\tclearInterval(watchIntervalId);\n\t\t});\n\t\treturn new Response(wrappedStream, {\n\t\t\tstatus: response.status,\n\t\t\tstatusText: response.statusText,\n\t\t\theaders: response.headers,\n\t\t});\n\t} else {\n\t\t// No response body, clear interval immediately\n\t\tlogger().debug(\"clearing file watcher interval: no response body\");\n\t\tclearInterval(watchIntervalId);\n\t\treturn response;\n\t}\n}\n\n/**\n * HACK: Watch for file changes on this route in order to shut down the runner.\n * We do this because Next.js does not terminate long-running requests on file\n * change, so we need to manually shut down the runner in order to trigger a\n * new `/start` request with the new code.\n *\n * We don't use file watchers since those are frequently buggy x-platform and\n * subject to misconfigured inotify limits.\n */\nfunction watchRouteFile(abortController: AbortController): NodeJS.Timeout {\n\tlogger().debug(\"starting file watcher\");\n\n\tconst routePath = join(\n\t\tprocess.cwd(),\n\t\t\".next/server/app/api/rivet/[...all]/route.js\",\n\t);\n\n\tlet lastMtime: number | null = null;\n\tconst checkFile = () => {\n\t\tlogger().debug({ msg: \"checking for file changes\", routePath });\n\t\ttry {\n\t\t\tif (!existsSync(routePath)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst stats = statSync(routePath);\n\t\t\tconst mtime = stats.mtimeMs;\n\n\t\t\tif (lastMtime !== null && mtime !== lastMtime) {\n\t\t\t\tlogger().info({ msg: \"route file changed\", routePath });\n\t\t\t\tabortController.abort();\n\t\t\t}\n\n\t\t\tlastMtime = mtime;\n\t\t} catch (err) {\n\t\t\tlogger().info({\n\t\t\t\tmsg: \"failed to check for route file change\",\n\t\t\t\terr: stringifyError(err),\n\t\t\t});\n\t\t}\n\t};\n\n\tcheckFile();\n\n\treturn setInterval(checkFile, 1000);\n}\n\n/**\n * Waits for a stream to finish and calls onFinish on complete.\n *\n * Used for cancelling the file watcher.\n */\nfunction waitForStreamFinish(\n\tbody: ReadableStream<Uint8Array>,\n\tonFinish: () => void,\n): ReadableStream {\n\tconst reader = body.getReader();\n\treturn new ReadableStream({\n\t\tasync start(controller) {\n\t\t\ttry {\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) {\n\t\t\t\t\t\tlogger().debug(\"stream completed\");\n\t\t\t\t\t\tonFinish();\n\t\t\t\t\t\tcontroller.close();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcontroller.enqueue(value);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tlogger().debug(\"stream errored\");\n\t\t\t\tonFinish();\n\t\t\t\tcontroller.error(err);\n\t\t\t}\n\t\t},\n\t\tcancel() {\n\t\t\tlogger().debug(\"stream cancelled\");\n\t\t\tonFinish();\n\t\t\treader.cancel();\n\t\t},\n\t});\n}\n","import { getLogger } from \"rivetkit/log\";\n\nexport function logger() {\n\treturn getLogger(\"driver-next-js\");\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/mod.ts","../src/log.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Registry } from \"rivetkit\";\nimport { stringifyError } from \"rivetkit/utils\";\nimport { logger } from \"./log\";\n\nexport const toNextHandler = (registry: Registry<any>) => {\n\t// Don't run server locally since we're using the fetch handler directly\n\tregistry.config.serveManager = false;\n\n\t// Set basePath to \"/\" since Next.js route strips the /api/rivet prefix\n\tregistry.config.serverless = { ...registry.config.serverless, basePath: \"/\" };\n\n\tif (process.env.NODE_ENV !== \"production\") {\n\t\t// Auto-configure serverless runner if not in prod\n\t\tlogger().debug(\n\t\t\t\"detected development environment, auto-starting engine and auto-configuring serverless\",\n\t\t);\n\n\t\tconst publicUrl =\n\t\t\tprocess.env.NEXT_PUBLIC_SITE_URL ??\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_URL ??\n\t\t\t`http://127.0.0.1:${process.env.PORT ?? 3000}`;\n\n\t\t// Set these on the registry's config directly since the legacy inputConfig\n\t\t// isn't used by the serverless router\n\t\tregistry.config.serverless.spawnEngine = true;\n\t\tregistry.config.serverless.configureRunnerPool = {\n\t\t\turl: `${publicUrl}/api/rivet`,\n\t\t\tminRunners: 0,\n\t\t\tmaxRunners: 100_000,\n\t\t\trequestLifespan: 300,\n\t\t\tslotsPerRunner: 1,\n\t\t\tmetadata: { provider: \"next-js\" },\n\t\t};\n\t} else {\n\t\tlogger().debug(\n\t\t\t\"detected production environment, will not auto-start engine and auto-configure serverless\",\n\t\t);\n\t}\n\n\t// Next logs this on every request\n\tregistry.config.noWelcome = true;\n\n\t// Function that Next will call when handling requests\n\tconst fetchWrapper = async (\n\t\trequest: Request,\n\t\t{ params }: { params: Promise<{ all: string[] }> },\n\t): Promise<Response> => {\n\t\tconst { all } = await params;\n\n\t\tconst newUrl = new URL(request.url);\n\t\tnewUrl.pathname = `/${all.join(\"/\")}`;\n\n\t\t// if (process.env.NODE_ENV === \"development\") {\n\t\tif (false) {\n\t\t\t// Special request handling for file watching\n\t\t\treturn await handleRequestWithFileWatcher(request, newUrl, fetch);\n\t\t} else {\n\t\t\t// Handle request\n\t\t\tconst newReq = new Request(newUrl, request);\n\t\t\treturn await registry.handler(newReq);\n\t\t}\n\t};\n\n\treturn {\n\t\tGET: fetchWrapper,\n\t\tPOST: fetchWrapper,\n\t\tPUT: fetchWrapper,\n\t\tPATCH: fetchWrapper,\n\t\tHEAD: fetchWrapper,\n\t\tOPTIONS: fetchWrapper,\n\t};\n};\n\n/**\n * Special request handler that will watch the source file to terminate this\n * request once complete.\n *\n * See docs on watchRouteFile for more information.\n */\nasync function handleRequestWithFileWatcher(\n\trequest: Request,\n\tnewUrl: URL,\n\tfetch: (request: Request, ...args: any) => Response | Promise<Response>,\n): Promise<Response> {\n\t// Create a new abort controller that we can abort, since the signal on\n\t// the request we cannot control\n\tconst mergedController = new AbortController();\n\tconst abortMerged = () => mergedController.abort();\n\trequest.signal?.addEventListener(\"abort\", abortMerged);\n\n\t// Watch for file changes in dev\n\t//\n\t// We spawn one watcher per-request since there is not a clean way of\n\t// cleaning up global watchers when hot reloading in Next\n\tconst watchIntervalId = watchRouteFile(mergedController);\n\n\t// Clear interval if request is aborted\n\trequest.signal.addEventListener(\"abort\", () => {\n\t\tlogger().debug(\"clearing file watcher interval: request aborted\");\n\t\tclearInterval(watchIntervalId);\n\t});\n\n\t// Replace URL and abort signal\n\tconst newReq = new Request(newUrl, {\n\t\t// Copy old request properties\n\t\tmethod: request.method,\n\t\theaders: request.headers,\n\t\tbody: request.body,\n\t\tcredentials: request.credentials,\n\t\tcache: request.cache,\n\t\tredirect: request.redirect,\n\t\treferrer: request.referrer,\n\t\tintegrity: request.integrity,\n\t\t// Override with new signal\n\t\tsignal: mergedController.signal,\n\t\t// Required for streaming body\n\t\tduplex: \"half\",\n\t} as RequestInit);\n\n\t// Handle request\n\tconst response = await fetch(newReq);\n\n\t// HACK: Next.js does not provide a way to detect when a request\n\t// finishes, so we need to tap the response stream\n\t//\n\t// We can't just wait for `await fetch` to finish since SSE streams run\n\t// for longer\n\tif (response.body) {\n\t\tconst wrappedStream = waitForStreamFinish(response.body, () => {\n\t\t\tlogger().debug(\"clearing file watcher interval: stream finished\");\n\t\t\tclearInterval(watchIntervalId);\n\t\t});\n\t\treturn new Response(wrappedStream, {\n\t\t\tstatus: response.status,\n\t\t\tstatusText: response.statusText,\n\t\t\theaders: response.headers,\n\t\t});\n\t} else {\n\t\t// No response body, clear interval immediately\n\t\tlogger().debug(\"clearing file watcher interval: no response body\");\n\t\tclearInterval(watchIntervalId);\n\t\treturn response;\n\t}\n}\n\n/**\n * HACK: Watch for file changes on this route in order to shut down the runner.\n * We do this because Next.js does not terminate long-running requests on file\n * change, so we need to manually shut down the runner in order to trigger a\n * new `/start` request with the new code.\n *\n * We don't use file watchers since those are frequently buggy x-platform and\n * subject to misconfigured inotify limits.\n */\nfunction watchRouteFile(abortController: AbortController): NodeJS.Timeout {\n\tlogger().debug(\"starting file watcher\");\n\n\tconst routePath = join(\n\t\tprocess.cwd(),\n\t\t\".next/server/app/api/rivet/[...all]/route.js\",\n\t);\n\n\tlet lastMtime: number | null = null;\n\tconst checkFile = () => {\n\t\tlogger().debug({ msg: \"checking for file changes\", routePath });\n\t\ttry {\n\t\t\tif (!existsSync(routePath)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst stats = statSync(routePath);\n\t\t\tconst mtime = stats.mtimeMs;\n\n\t\t\tif (lastMtime !== null && mtime !== lastMtime) {\n\t\t\t\tlogger().info({ msg: \"route file changed\", routePath });\n\t\t\t\tabortController.abort();\n\t\t\t}\n\n\t\t\tlastMtime = mtime;\n\t\t} catch (err) {\n\t\t\tlogger().info({\n\t\t\t\tmsg: \"failed to check for route file change\",\n\t\t\t\terr: stringifyError(err),\n\t\t\t});\n\t\t}\n\t};\n\n\tcheckFile();\n\n\treturn setInterval(checkFile, 1000);\n}\n\n/**\n * Waits for a stream to finish and calls onFinish on complete.\n *\n * Used for cancelling the file watcher.\n */\nfunction waitForStreamFinish(\n\tbody: ReadableStream<Uint8Array>,\n\tonFinish: () => void,\n): ReadableStream {\n\tconst reader = body.getReader();\n\treturn new ReadableStream({\n\t\tasync start(controller) {\n\t\t\ttry {\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) {\n\t\t\t\t\t\tlogger().debug(\"stream completed\");\n\t\t\t\t\t\tonFinish();\n\t\t\t\t\t\tcontroller.close();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcontroller.enqueue(value);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tlogger().debug(\"stream errored\");\n\t\t\t\tonFinish();\n\t\t\t\tcontroller.error(err);\n\t\t\t}\n\t\t},\n\t\tcancel() {\n\t\t\tlogger().debug(\"stream cancelled\");\n\t\t\tonFinish();\n\t\t\treader.cancel();\n\t\t},\n\t});\n}\n","import { getLogger } from \"rivetkit/log\";\n\nexport function logger() {\n\treturn getLogger(\"driver-next-js\");\n}\n"],"mappings":";AAAA,SAAS,YAAY,gBAAgB;AACrC,SAAS,YAAY;AAErB,SAAS,sBAAsB;;;ACH/B,SAAS,iBAAiB;AAEnB,SAAS,SAAS;AACxB,SAAO,UAAU,gBAAgB;AAClC;;;ADEO,IAAM,gBAAgB,CAAC,aAA4B;AAEzD,WAAS,OAAO,eAAe;AAG/B,WAAS,OAAO,aAAa,EAAE,GAAG,SAAS,OAAO,YAAY,UAAU,IAAI;AAE5E,MAAI,QAAQ,IAAI,aAAa,cAAc;AAE1C,WAAO,EAAE;AAAA,MACR;AAAA,IACD;AAEA,UAAM,YACL,QAAQ,IAAI,wBACZ,QAAQ,IAAI,0BACZ,oBAAoB,QAAQ,IAAI,QAAQ,GAAI;AAI7C,aAAS,OAAO,WAAW,cAAc;AACzC,aAAS,OAAO,WAAW,sBAAsB;AAAA,MAChD,KAAK,GAAG,SAAS;AAAA,MACjB,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,UAAU,EAAE,UAAU,UAAU;AAAA,IACjC;AAAA,EACD,OAAO;AACN,WAAO,EAAE;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAGA,WAAS,OAAO,YAAY;AAG5B,QAAM,eAAe,OACpB,SACA,EAAE,OAAO,MACc;AACvB,UAAM,EAAE,IAAI,IAAI,MAAM;AAEtB,UAAM,SAAS,IAAI,IAAI,QAAQ,GAAG;AAClC,WAAO,WAAW,IAAI,IAAI,KAAK,GAAG,CAAC;AAGnC,QAAI,OAAO;AAEV,aAAO,MAAM,6BAA6B,SAAS,QAAQ,KAAK;AAAA,IACjE,OAAO;AAEN,YAAM,SAAS,IAAI,QAAQ,QAAQ,OAAO;AAC1C,aAAO,MAAM,SAAS,QAAQ,MAAM;AAAA,IACrC;AAAA,EACD;AAEA,SAAO;AAAA,IACN,KAAK;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS;AAAA,EACV;AACD;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rivetkit/next-js",
3
- "version": "2.0.33",
3
+ "version": "2.0.34-rc.2",
4
4
  "description": "Next.js integration for RivetKit actors and client",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -49,8 +49,8 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "hono": "^4.8.3",
52
- "@rivetkit/react": "2.0.33",
53
- "rivetkit": "^2.0.33"
52
+ "@rivetkit/react": "2.0.34-rc.2",
53
+ "rivetkit": "^2.0.34-rc.2"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": "^18 || ^19",