rwsdk 1.0.0-beta.42 → 1.0.0-beta.44
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/runtime/client/client.d.ts +22 -1
- package/dist/runtime/client/client.js +23 -2
- package/dist/runtime/entries/no-react-server-ssr-bridge.js +1 -1
- package/dist/runtime/entries/no-react-server.js +3 -1
- package/dist/runtime/entries/react-server-only.js +1 -1
- package/dist/runtime/entries/worker.d.ts +1 -0
- package/dist/runtime/entries/worker.js +1 -0
- package/dist/runtime/lib/links.js +6 -6
- package/dist/runtime/lib/router.d.ts +19 -17
- package/dist/runtime/lib/router.js +343 -108
- package/dist/runtime/lib/router.test.js +343 -1
- package/dist/runtime/requestInfo/types.d.ts +1 -0
- package/dist/runtime/requestInfo/utils.js +1 -0
- package/dist/runtime/requestInfo/worker.js +2 -1
- package/dist/runtime/worker.js +6 -1
- package/dist/use-synced-state/SyncedStateServer.d.mts +19 -4
- package/dist/use-synced-state/SyncedStateServer.mjs +76 -8
- package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +18 -11
- package/dist/use-synced-state/__tests__/worker.test.mjs +13 -12
- package/dist/use-synced-state/client-core.d.ts +3 -0
- package/dist/use-synced-state/client-core.js +77 -13
- package/dist/use-synced-state/useSyncedState.d.ts +3 -2
- package/dist/use-synced-state/useSyncedState.js +9 -3
- package/dist/use-synced-state/worker.d.mts +2 -1
- package/dist/use-synced-state/worker.mjs +82 -16
- package/dist/vite/runDirectivesScan.mjs +3 -1
- package/dist/vite/transformClientComponents.test.mjs +32 -0
- package/package.json +7 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { defineRoutes, layout, matchPath, prefix, render, route, } from "./router";
|
|
3
|
+
import { defineRoutes, except, layout, matchPath, prefix, render, route, } from "./router";
|
|
4
4
|
describe("matchPath", () => {
|
|
5
5
|
// Test case 1: Static paths
|
|
6
6
|
it("should match static paths", () => {
|
|
@@ -62,6 +62,7 @@ describe("defineRoutes - Request Handling Behavior", () => {
|
|
|
62
62
|
const createMockDependencies = () => {
|
|
63
63
|
const mockRequestInfo = {
|
|
64
64
|
request: new Request("http://localhost:3000/"),
|
|
65
|
+
path: "/",
|
|
65
66
|
params: {},
|
|
66
67
|
ctx: {},
|
|
67
68
|
rw: {
|
|
@@ -293,6 +294,109 @@ describe("defineRoutes - Request Handling Behavior", () => {
|
|
|
293
294
|
expect(executionOrder).toEqual(["prefixedMiddleware"]);
|
|
294
295
|
expect(await response.text()).toBe("From prefixed middleware");
|
|
295
296
|
});
|
|
297
|
+
it("should pass prefix parameters to route handlers", async () => {
|
|
298
|
+
let capturedParams = null;
|
|
299
|
+
const TaskDetailPage = (requestInfo) => {
|
|
300
|
+
capturedParams = requestInfo.params;
|
|
301
|
+
return React.createElement("div", {}, "Task Detail");
|
|
302
|
+
};
|
|
303
|
+
const router = defineRoutes([
|
|
304
|
+
...prefix("/tasks/:containerId", [route("/", TaskDetailPage)]),
|
|
305
|
+
]);
|
|
306
|
+
const deps = createMockDependencies();
|
|
307
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/tasks/123/");
|
|
308
|
+
const request = new Request("http://localhost:3000/tasks/123/");
|
|
309
|
+
await router.handle({
|
|
310
|
+
request,
|
|
311
|
+
renderPage: deps.mockRenderPage,
|
|
312
|
+
getRequestInfo: deps.getRequestInfo,
|
|
313
|
+
onError: deps.onError,
|
|
314
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
315
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
316
|
+
});
|
|
317
|
+
expect(capturedParams).toEqual({ containerId: "123" });
|
|
318
|
+
});
|
|
319
|
+
it("should pass prefix parameters to middlewares within a parameterized prefix", async () => {
|
|
320
|
+
const executionOrder = [];
|
|
321
|
+
let capturedParams = null;
|
|
322
|
+
const prefixedMiddleware = (requestInfo) => {
|
|
323
|
+
executionOrder.push("prefixedMiddleware");
|
|
324
|
+
capturedParams = requestInfo.params;
|
|
325
|
+
};
|
|
326
|
+
const PageComponent = () => {
|
|
327
|
+
executionOrder.push("PageComponent");
|
|
328
|
+
return React.createElement("div", {}, "Page");
|
|
329
|
+
};
|
|
330
|
+
const router = defineRoutes([
|
|
331
|
+
...prefix("/tasks/:containerId", [
|
|
332
|
+
prefixedMiddleware,
|
|
333
|
+
route("/", PageComponent),
|
|
334
|
+
]),
|
|
335
|
+
]);
|
|
336
|
+
const deps = createMockDependencies();
|
|
337
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/tasks/123/");
|
|
338
|
+
const request = new Request("http://localhost:3000/tasks/123/");
|
|
339
|
+
await router.handle({
|
|
340
|
+
request,
|
|
341
|
+
renderPage: deps.mockRenderPage,
|
|
342
|
+
getRequestInfo: deps.getRequestInfo,
|
|
343
|
+
onError: deps.onError,
|
|
344
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
345
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
346
|
+
});
|
|
347
|
+
expect(executionOrder).toEqual(["prefixedMiddleware", "PageComponent"]);
|
|
348
|
+
// The wildcard captures the trailing slash as an empty string
|
|
349
|
+
expect(capturedParams).toEqual({ containerId: "123", $0: "" });
|
|
350
|
+
});
|
|
351
|
+
it("should pass prefix parameters to route handlers (array)", async () => {
|
|
352
|
+
let capturedParamsInMiddleware = null;
|
|
353
|
+
let capturedParamsInComponent = null;
|
|
354
|
+
const middleware = (requestInfo) => {
|
|
355
|
+
capturedParamsInMiddleware = requestInfo.params;
|
|
356
|
+
};
|
|
357
|
+
const Component = (requestInfo) => {
|
|
358
|
+
capturedParamsInComponent = requestInfo.params;
|
|
359
|
+
return React.createElement("div", {}, "Component");
|
|
360
|
+
};
|
|
361
|
+
const router = defineRoutes([
|
|
362
|
+
...prefix("/tasks/:containerId", [route("/", [middleware, Component])]),
|
|
363
|
+
]);
|
|
364
|
+
const deps = createMockDependencies();
|
|
365
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/tasks/123/");
|
|
366
|
+
const request = new Request("http://localhost:3000/tasks/123/");
|
|
367
|
+
await router.handle({
|
|
368
|
+
request,
|
|
369
|
+
renderPage: deps.mockRenderPage,
|
|
370
|
+
getRequestInfo: deps.getRequestInfo,
|
|
371
|
+
onError: deps.onError,
|
|
372
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
373
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
374
|
+
});
|
|
375
|
+
expect(capturedParamsInMiddleware).toEqual({ containerId: "123" });
|
|
376
|
+
expect(capturedParamsInComponent).toEqual({ containerId: "123" });
|
|
377
|
+
});
|
|
378
|
+
it("should match even if prefix has a trailing slash", async () => {
|
|
379
|
+
let capturedParams = null;
|
|
380
|
+
const TaskDetailPage = (requestInfo) => {
|
|
381
|
+
capturedParams = requestInfo.params;
|
|
382
|
+
return React.createElement("div", {}, "Task Detail");
|
|
383
|
+
};
|
|
384
|
+
const router = defineRoutes([
|
|
385
|
+
...prefix("/tasks/:containerId/", [route("/", TaskDetailPage)]),
|
|
386
|
+
]);
|
|
387
|
+
const deps = createMockDependencies();
|
|
388
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/tasks/123/");
|
|
389
|
+
const request = new Request("http://localhost:3000/tasks/123/");
|
|
390
|
+
await router.handle({
|
|
391
|
+
request,
|
|
392
|
+
renderPage: deps.mockRenderPage,
|
|
393
|
+
getRequestInfo: deps.getRequestInfo,
|
|
394
|
+
onError: deps.onError,
|
|
395
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
396
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
397
|
+
});
|
|
398
|
+
expect(capturedParams).toEqual({ containerId: "123" });
|
|
399
|
+
});
|
|
296
400
|
});
|
|
297
401
|
describe("RSC Action Handling", () => {
|
|
298
402
|
it("should handle RSC actions before the first route definition", async () => {
|
|
@@ -884,4 +988,242 @@ describe("defineRoutes - Request Handling Behavior", () => {
|
|
|
884
988
|
expect(await response.text()).toBe("Rendered: Element");
|
|
885
989
|
});
|
|
886
990
|
});
|
|
991
|
+
describe("except - Error Handling", () => {
|
|
992
|
+
it("should catch errors from global middleware", async () => {
|
|
993
|
+
const errorMessage = "Middleware error";
|
|
994
|
+
const middleware = () => {
|
|
995
|
+
throw new Error(errorMessage);
|
|
996
|
+
};
|
|
997
|
+
const errorHandler = except((error) => {
|
|
998
|
+
return new Response(`Caught: ${error instanceof Error ? error.message : String(error)}`, {
|
|
999
|
+
status: 500,
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
const router = defineRoutes([
|
|
1003
|
+
middleware,
|
|
1004
|
+
errorHandler,
|
|
1005
|
+
route("/test/", () => React.createElement("div")),
|
|
1006
|
+
]);
|
|
1007
|
+
const deps = createMockDependencies();
|
|
1008
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
1009
|
+
const request = new Request("http://localhost:3000/test/");
|
|
1010
|
+
const response = await router.handle({
|
|
1011
|
+
request,
|
|
1012
|
+
renderPage: deps.mockRenderPage,
|
|
1013
|
+
getRequestInfo: deps.getRequestInfo,
|
|
1014
|
+
onError: deps.onError,
|
|
1015
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
1016
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
1017
|
+
});
|
|
1018
|
+
expect(response.status).toBe(500);
|
|
1019
|
+
expect(await response.text()).toBe(`Caught: ${errorMessage}`);
|
|
1020
|
+
});
|
|
1021
|
+
it("should catch errors from route handlers (components)", async () => {
|
|
1022
|
+
const errorMessage = "Component error";
|
|
1023
|
+
const PageComponent = () => {
|
|
1024
|
+
throw new Error(errorMessage);
|
|
1025
|
+
};
|
|
1026
|
+
const errorHandler = except((error) => {
|
|
1027
|
+
return new Response(`Caught: ${error instanceof Error ? error.message : String(error)}`, {
|
|
1028
|
+
status: 500,
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
const router = defineRoutes([
|
|
1032
|
+
errorHandler,
|
|
1033
|
+
route("/test/", PageComponent),
|
|
1034
|
+
]);
|
|
1035
|
+
const deps = createMockDependencies();
|
|
1036
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
1037
|
+
const request = new Request("http://localhost:3000/test/");
|
|
1038
|
+
const response = await router.handle({
|
|
1039
|
+
request,
|
|
1040
|
+
renderPage: deps.mockRenderPage,
|
|
1041
|
+
getRequestInfo: deps.getRequestInfo,
|
|
1042
|
+
onError: deps.onError,
|
|
1043
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
1044
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
1045
|
+
});
|
|
1046
|
+
expect(response.status).toBe(500);
|
|
1047
|
+
expect(await response.text()).toBe(`Caught: ${errorMessage}`);
|
|
1048
|
+
});
|
|
1049
|
+
it("should catch errors from route handlers (functions)", async () => {
|
|
1050
|
+
const errorMessage = "Handler error";
|
|
1051
|
+
const routeHandler = () => {
|
|
1052
|
+
throw new Error(errorMessage);
|
|
1053
|
+
};
|
|
1054
|
+
const errorHandler = except((error) => {
|
|
1055
|
+
return new Response(`Caught: ${error instanceof Error ? error.message : String(error)}`, {
|
|
1056
|
+
status: 500,
|
|
1057
|
+
});
|
|
1058
|
+
});
|
|
1059
|
+
const router = defineRoutes([
|
|
1060
|
+
errorHandler,
|
|
1061
|
+
route("/test/", routeHandler),
|
|
1062
|
+
]);
|
|
1063
|
+
const deps = createMockDependencies();
|
|
1064
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
1065
|
+
const request = new Request("http://localhost:3000/test/");
|
|
1066
|
+
const response = await router.handle({
|
|
1067
|
+
request,
|
|
1068
|
+
renderPage: deps.mockRenderPage,
|
|
1069
|
+
getRequestInfo: deps.getRequestInfo,
|
|
1070
|
+
onError: deps.onError,
|
|
1071
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
1072
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
1073
|
+
});
|
|
1074
|
+
expect(response.status).toBe(500);
|
|
1075
|
+
expect(await response.text()).toBe(`Caught: ${errorMessage}`);
|
|
1076
|
+
});
|
|
1077
|
+
it("should catch errors from RSC actions", async () => {
|
|
1078
|
+
const errorMessage = "RSC action error";
|
|
1079
|
+
const errorHandler = except((error) => {
|
|
1080
|
+
return new Response(`Caught: ${error instanceof Error ? error.message : String(error)}`, {
|
|
1081
|
+
status: 500,
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
1084
|
+
const router = defineRoutes([
|
|
1085
|
+
errorHandler,
|
|
1086
|
+
route("/test/", () => React.createElement("div")),
|
|
1087
|
+
]);
|
|
1088
|
+
const deps = createMockDependencies();
|
|
1089
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
|
|
1090
|
+
deps.mockRscActionHandler = async () => {
|
|
1091
|
+
throw new Error(errorMessage);
|
|
1092
|
+
};
|
|
1093
|
+
const request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
|
|
1094
|
+
const response = await router.handle({
|
|
1095
|
+
request,
|
|
1096
|
+
renderPage: deps.mockRenderPage,
|
|
1097
|
+
getRequestInfo: deps.getRequestInfo,
|
|
1098
|
+
onError: deps.onError,
|
|
1099
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
1100
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
1101
|
+
});
|
|
1102
|
+
expect(response.status).toBe(500);
|
|
1103
|
+
expect(await response.text()).toBe(`Caught: ${errorMessage}`);
|
|
1104
|
+
});
|
|
1105
|
+
it("should use the most recent except handler before the error", async () => {
|
|
1106
|
+
const errorMessage = "Route error";
|
|
1107
|
+
const firstHandler = except((error) => {
|
|
1108
|
+
return new Response("First handler", { status: 500 });
|
|
1109
|
+
});
|
|
1110
|
+
const secondHandler = except((error) => {
|
|
1111
|
+
return new Response("Second handler", { status: 500 });
|
|
1112
|
+
});
|
|
1113
|
+
const PageComponent = () => {
|
|
1114
|
+
throw new Error(errorMessage);
|
|
1115
|
+
};
|
|
1116
|
+
const router = defineRoutes([
|
|
1117
|
+
firstHandler,
|
|
1118
|
+
secondHandler,
|
|
1119
|
+
route("/test/", PageComponent),
|
|
1120
|
+
]);
|
|
1121
|
+
const deps = createMockDependencies();
|
|
1122
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
1123
|
+
const request = new Request("http://localhost:3000/test/");
|
|
1124
|
+
const response = await router.handle({
|
|
1125
|
+
request,
|
|
1126
|
+
renderPage: deps.mockRenderPage,
|
|
1127
|
+
getRequestInfo: deps.getRequestInfo,
|
|
1128
|
+
onError: deps.onError,
|
|
1129
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
1130
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
1131
|
+
});
|
|
1132
|
+
// Should use the second handler (most recent before the route)
|
|
1133
|
+
expect(response.status).toBe(500);
|
|
1134
|
+
expect(await response.text()).toBe("Second handler");
|
|
1135
|
+
});
|
|
1136
|
+
it("should try next except handler if current one throws", async () => {
|
|
1137
|
+
const errorMessage = "Route error";
|
|
1138
|
+
const firstHandler = except(() => {
|
|
1139
|
+
throw new Error("First handler error");
|
|
1140
|
+
});
|
|
1141
|
+
const secondHandler = except((error) => {
|
|
1142
|
+
return new Response(`Caught by second: ${error instanceof Error ? error.message : String(error)}`, {
|
|
1143
|
+
status: 500,
|
|
1144
|
+
});
|
|
1145
|
+
});
|
|
1146
|
+
const PageComponent = () => {
|
|
1147
|
+
throw new Error(errorMessage);
|
|
1148
|
+
};
|
|
1149
|
+
const router = defineRoutes([
|
|
1150
|
+
secondHandler, // Outer handler
|
|
1151
|
+
firstHandler, // Inner handler (closer to route)
|
|
1152
|
+
route("/test/", PageComponent),
|
|
1153
|
+
]);
|
|
1154
|
+
const deps = createMockDependencies();
|
|
1155
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
1156
|
+
const request = new Request("http://localhost:3000/test/");
|
|
1157
|
+
const response = await router.handle({
|
|
1158
|
+
request,
|
|
1159
|
+
renderPage: deps.mockRenderPage,
|
|
1160
|
+
getRequestInfo: deps.getRequestInfo,
|
|
1161
|
+
onError: deps.onError,
|
|
1162
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
1163
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
1164
|
+
});
|
|
1165
|
+
// Should catch the error from the first handler with the second handler
|
|
1166
|
+
expect(response.status).toBe(500);
|
|
1167
|
+
expect(await response.text()).toBe("Caught by second: First handler error");
|
|
1168
|
+
});
|
|
1169
|
+
it("should return JSX element from except handler", async () => {
|
|
1170
|
+
const errorMessage = "Route error";
|
|
1171
|
+
function ErrorComponent() {
|
|
1172
|
+
return React.createElement("div", {}, "Error Page");
|
|
1173
|
+
}
|
|
1174
|
+
const errorHandler = except(() => {
|
|
1175
|
+
return React.createElement(ErrorComponent);
|
|
1176
|
+
});
|
|
1177
|
+
const PageComponent = () => {
|
|
1178
|
+
throw new Error(errorMessage);
|
|
1179
|
+
};
|
|
1180
|
+
const router = defineRoutes([
|
|
1181
|
+
errorHandler,
|
|
1182
|
+
route("/test/", PageComponent),
|
|
1183
|
+
]);
|
|
1184
|
+
const deps = createMockDependencies();
|
|
1185
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
1186
|
+
const request = new Request("http://localhost:3000/test/");
|
|
1187
|
+
const response = await router.handle({
|
|
1188
|
+
request,
|
|
1189
|
+
renderPage: deps.mockRenderPage,
|
|
1190
|
+
getRequestInfo: deps.getRequestInfo,
|
|
1191
|
+
onError: deps.onError,
|
|
1192
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
1193
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
1194
|
+
});
|
|
1195
|
+
expect(await response.text()).toBe("Rendered: ErrorComponent");
|
|
1196
|
+
});
|
|
1197
|
+
it("should work with prefix and layout", async () => {
|
|
1198
|
+
const errorMessage = "Route error";
|
|
1199
|
+
const errorHandler = except((error) => {
|
|
1200
|
+
return new Response(`Caught: ${error instanceof Error ? error.message : String(error)}`, {
|
|
1201
|
+
status: 500,
|
|
1202
|
+
});
|
|
1203
|
+
});
|
|
1204
|
+
const PageComponent = () => {
|
|
1205
|
+
throw new Error(errorMessage);
|
|
1206
|
+
};
|
|
1207
|
+
const Layout = ({ children }) => {
|
|
1208
|
+
return React.createElement("div", {}, children);
|
|
1209
|
+
};
|
|
1210
|
+
const router = defineRoutes([
|
|
1211
|
+
errorHandler,
|
|
1212
|
+
layout(Layout, [prefix("/api", [route("/test/", PageComponent)])]),
|
|
1213
|
+
]);
|
|
1214
|
+
const deps = createMockDependencies();
|
|
1215
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/api/test/");
|
|
1216
|
+
const request = new Request("http://localhost:3000/api/test/");
|
|
1217
|
+
const response = await router.handle({
|
|
1218
|
+
request,
|
|
1219
|
+
renderPage: deps.mockRenderPage,
|
|
1220
|
+
getRequestInfo: deps.getRequestInfo,
|
|
1221
|
+
onError: deps.onError,
|
|
1222
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
1223
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
1224
|
+
});
|
|
1225
|
+
expect(response.status).toBe(500);
|
|
1226
|
+
expect(await response.text()).toBe(`Caught: ${errorMessage}`);
|
|
1227
|
+
});
|
|
1228
|
+
});
|
|
887
1229
|
});
|
|
@@ -9,6 +9,7 @@ export const constructWithDefaultRequestInfo = (overrides = {}) => {
|
|
|
9
9
|
const { rw: rwOverrides, ...otherRequestInfoOverrides } = overrides;
|
|
10
10
|
const defaultRequestInfo = {
|
|
11
11
|
request: new Request("http://localhost/"),
|
|
12
|
+
path: "/",
|
|
12
13
|
params: {},
|
|
13
14
|
ctx: {},
|
|
14
15
|
cf: {
|
|
@@ -17,7 +17,8 @@ export const requestInfo = Object.freeze(requestInfoBase);
|
|
|
17
17
|
export function getRequestInfo() {
|
|
18
18
|
const store = requestInfoStore.getStore();
|
|
19
19
|
if (!store) {
|
|
20
|
-
throw new Error("Request context not found"
|
|
20
|
+
throw new Error("RedwoodSDK: Request context not found. getRequestInfo() can only be called within the request lifecycle (e.g., in a route handler, middleware, or server action).\n\n" +
|
|
21
|
+
"For detailed troubleshooting steps, see: https://docs.rwsdk.com/guides/troubleshooting#request-context-errors");
|
|
21
22
|
}
|
|
22
23
|
return store;
|
|
23
24
|
}
|
package/dist/runtime/worker.js
CHANGED
|
@@ -11,11 +11,11 @@ import { defineRoutes } from "./lib/router";
|
|
|
11
11
|
import { generateNonce } from "./lib/utils";
|
|
12
12
|
export * from "./requestInfo/types";
|
|
13
13
|
export const defineApp = (routes) => {
|
|
14
|
+
const router = defineRoutes(routes);
|
|
14
15
|
return {
|
|
15
16
|
__rwRoutes: routes,
|
|
16
17
|
fetch: async (request, env, cf) => {
|
|
17
18
|
globalThis.__webpack_require__ = ssrWebpackRequire;
|
|
18
|
-
const router = defineRoutes(routes);
|
|
19
19
|
// context(justinvdm, 5 Feb 2025): Serve assets requests using the assets service binding
|
|
20
20
|
// todo(justinvdm, 5 Feb 2025): Find a way to avoid this so asset requests are served directly
|
|
21
21
|
// rather than first needing to go through the worker
|
|
@@ -63,6 +63,10 @@ export const defineApp = (routes) => {
|
|
|
63
63
|
}
|
|
64
64
|
try {
|
|
65
65
|
const url = new URL(request.url);
|
|
66
|
+
let path = url.pathname;
|
|
67
|
+
if (path !== "/" && !path.endsWith("/")) {
|
|
68
|
+
path = path + "/";
|
|
69
|
+
}
|
|
66
70
|
const isRSCRequest = url.searchParams.has("__rsc") ||
|
|
67
71
|
request.headers.get("accept")?.includes("text/x-component");
|
|
68
72
|
const isAction = url.searchParams.has("__rsc_action_id");
|
|
@@ -83,6 +87,7 @@ export const defineApp = (routes) => {
|
|
|
83
87
|
};
|
|
84
88
|
const outerRequestInfo = {
|
|
85
89
|
request,
|
|
90
|
+
path,
|
|
86
91
|
cf,
|
|
87
92
|
params: {},
|
|
88
93
|
ctx: {},
|
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
import { RpcStub } from "capnweb";
|
|
2
2
|
import { DurableObject } from "cloudflare:workers";
|
|
3
|
+
import type { RequestInfo } from "../runtime/requestInfo/types";
|
|
3
4
|
export type SyncedStateValue = unknown;
|
|
4
|
-
type OnSetHandler = (key: string, value: SyncedStateValue) => void;
|
|
5
|
-
type OnGetHandler = (key: string, value: SyncedStateValue | undefined) => void;
|
|
5
|
+
type OnSetHandler = (key: string, value: SyncedStateValue, stub: DurableObjectStub<SyncedStateServer>) => void;
|
|
6
|
+
type OnGetHandler = (key: string, value: SyncedStateValue | undefined, stub: DurableObjectStub<SyncedStateServer>) => void;
|
|
7
|
+
type OnKeyHandler = (key: string, stub: DurableObjectStub<SyncedStateServer>) => Promise<string>;
|
|
8
|
+
type OnRoomHandler = (roomId: string | undefined, requestInfo: RequestInfo | null) => Promise<string>;
|
|
9
|
+
type OnSubscribeHandler = (key: string, stub: DurableObjectStub<SyncedStateServer>) => void;
|
|
10
|
+
type OnUnsubscribeHandler = (key: string, stub: DurableObjectStub<SyncedStateServer>) => void;
|
|
6
11
|
/**
|
|
7
12
|
* Durable Object that keeps shared state for multiple clients and notifies subscribers.
|
|
8
13
|
*/
|
|
9
14
|
export declare class SyncedStateServer extends DurableObject {
|
|
10
15
|
#private;
|
|
11
|
-
static registerKeyHandler(handler:
|
|
12
|
-
static getKeyHandler():
|
|
16
|
+
static registerKeyHandler(handler: OnKeyHandler | null): void;
|
|
17
|
+
static getKeyHandler(): OnKeyHandler | null;
|
|
18
|
+
static registerRoomHandler(handler: OnRoomHandler | null): void;
|
|
19
|
+
static getRoomHandler(): OnRoomHandler | null;
|
|
20
|
+
static registerNamespace(namespace: DurableObjectNamespace<SyncedStateServer>, durableObjectName?: string): void;
|
|
21
|
+
static getNamespace(): DurableObjectNamespace<SyncedStateServer> | null;
|
|
22
|
+
static getDurableObjectName(): string;
|
|
23
|
+
setStub(stub: DurableObjectStub<SyncedStateServer>): void;
|
|
13
24
|
static registerSetStateHandler(handler: OnSetHandler | null): void;
|
|
14
25
|
static registerGetStateHandler(handler: OnGetHandler | null): void;
|
|
26
|
+
static registerSubscribeHandler(handler: OnSubscribeHandler | null): void;
|
|
27
|
+
static registerUnsubscribeHandler(handler: OnUnsubscribeHandler | null): void;
|
|
28
|
+
static getSubscribeHandler(): OnSubscribeHandler | null;
|
|
29
|
+
static getUnsubscribeHandler(): OnUnsubscribeHandler | null;
|
|
15
30
|
getState(key: string): SyncedStateValue;
|
|
16
31
|
setState(value: SyncedStateValue, key: string): void;
|
|
17
32
|
subscribe(key: string, client: RpcStub<(value: SyncedStateValue) => void>): void;
|
|
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _a, _SyncedStateServer_keyHandler, _SyncedStateServer_setStateHandler, _SyncedStateServer_getStateHandler, _SyncedStateServer_stateStore, _SyncedStateServer_subscriptions, _SyncedStateServer_subscriptionRefs, _CoordinatorApi_coordinator;
|
|
12
|
+
var _SyncedStateServer_instances, _a, _SyncedStateServer_keyHandler, _SyncedStateServer_roomHandler, _SyncedStateServer_setStateHandler, _SyncedStateServer_getStateHandler, _SyncedStateServer_subscribeHandler, _SyncedStateServer_unsubscribeHandler, _SyncedStateServer_namespace, _SyncedStateServer_durableObjectName, _SyncedStateServer_stub, _SyncedStateServer_stateStore, _SyncedStateServer_subscriptions, _SyncedStateServer_subscriptionRefs, _SyncedStateServer_getStubForHandlers, _CoordinatorApi_coordinator, _CoordinatorApi_stub;
|
|
13
13
|
import { RpcTarget, newWorkersRpcResponse } from "capnweb";
|
|
14
14
|
import { DurableObject } from "cloudflare:workers";
|
|
15
15
|
/**
|
|
@@ -18,6 +18,8 @@ import { DurableObject } from "cloudflare:workers";
|
|
|
18
18
|
export class SyncedStateServer extends DurableObject {
|
|
19
19
|
constructor() {
|
|
20
20
|
super(...arguments);
|
|
21
|
+
_SyncedStateServer_instances.add(this);
|
|
22
|
+
_SyncedStateServer_stub.set(this, null);
|
|
21
23
|
_SyncedStateServer_stateStore.set(this, new Map());
|
|
22
24
|
_SyncedStateServer_subscriptions.set(this, new Map());
|
|
23
25
|
_SyncedStateServer_subscriptionRefs.set(this, new Map());
|
|
@@ -28,23 +30,62 @@ export class SyncedStateServer extends DurableObject {
|
|
|
28
30
|
static getKeyHandler() {
|
|
29
31
|
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_keyHandler);
|
|
30
32
|
}
|
|
33
|
+
static registerRoomHandler(handler) {
|
|
34
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_roomHandler);
|
|
35
|
+
}
|
|
36
|
+
static getRoomHandler() {
|
|
37
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_roomHandler);
|
|
38
|
+
}
|
|
39
|
+
static registerNamespace(namespace, durableObjectName) {
|
|
40
|
+
__classPrivateFieldSet(_a, _a, namespace, "f", _SyncedStateServer_namespace);
|
|
41
|
+
if (durableObjectName) {
|
|
42
|
+
__classPrivateFieldSet(_a, _a, durableObjectName, "f", _SyncedStateServer_durableObjectName);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
static getNamespace() {
|
|
46
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_namespace);
|
|
47
|
+
}
|
|
48
|
+
static getDurableObjectName() {
|
|
49
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_durableObjectName);
|
|
50
|
+
}
|
|
51
|
+
setStub(stub) {
|
|
52
|
+
__classPrivateFieldSet(this, _SyncedStateServer_stub, stub, "f");
|
|
53
|
+
}
|
|
31
54
|
static registerSetStateHandler(handler) {
|
|
32
55
|
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_setStateHandler);
|
|
33
56
|
}
|
|
34
57
|
static registerGetStateHandler(handler) {
|
|
35
58
|
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_getStateHandler);
|
|
36
59
|
}
|
|
60
|
+
static registerSubscribeHandler(handler) {
|
|
61
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_subscribeHandler);
|
|
62
|
+
}
|
|
63
|
+
static registerUnsubscribeHandler(handler) {
|
|
64
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_unsubscribeHandler);
|
|
65
|
+
}
|
|
66
|
+
static getSubscribeHandler() {
|
|
67
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_subscribeHandler);
|
|
68
|
+
}
|
|
69
|
+
static getUnsubscribeHandler() {
|
|
70
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_unsubscribeHandler);
|
|
71
|
+
}
|
|
37
72
|
getState(key) {
|
|
38
73
|
const value = __classPrivateFieldGet(this, _SyncedStateServer_stateStore, "f").get(key);
|
|
39
74
|
if (__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_getStateHandler)) {
|
|
40
|
-
__classPrivateFieldGet(
|
|
75
|
+
const stub = __classPrivateFieldGet(this, _SyncedStateServer_instances, "m", _SyncedStateServer_getStubForHandlers).call(this);
|
|
76
|
+
if (stub) {
|
|
77
|
+
__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_getStateHandler).call(_a, key, value, stub);
|
|
78
|
+
}
|
|
41
79
|
}
|
|
42
80
|
return value;
|
|
43
81
|
}
|
|
44
82
|
setState(value, key) {
|
|
45
83
|
__classPrivateFieldGet(this, _SyncedStateServer_stateStore, "f").set(key, value);
|
|
46
84
|
if (__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_setStateHandler)) {
|
|
47
|
-
__classPrivateFieldGet(
|
|
85
|
+
const stub = __classPrivateFieldGet(this, _SyncedStateServer_instances, "m", _SyncedStateServer_getStubForHandlers).call(this);
|
|
86
|
+
if (stub) {
|
|
87
|
+
__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_setStateHandler).call(_a, key, value, stub);
|
|
88
|
+
}
|
|
48
89
|
}
|
|
49
90
|
const subscribers = __classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").get(key);
|
|
50
91
|
if (!subscribers) {
|
|
@@ -78,7 +119,9 @@ export class SyncedStateServer extends DurableObject {
|
|
|
78
119
|
if (!__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").has(key)) {
|
|
79
120
|
__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").set(key, new Map());
|
|
80
121
|
}
|
|
81
|
-
const duplicate = client.dup
|
|
122
|
+
const duplicate = typeof client.dup === "function"
|
|
123
|
+
? client.dup()
|
|
124
|
+
: client;
|
|
82
125
|
__classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").get(key).add(duplicate);
|
|
83
126
|
__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").get(key).set(client, duplicate);
|
|
84
127
|
}
|
|
@@ -98,19 +141,44 @@ export class SyncedStateServer extends DurableObject {
|
|
|
98
141
|
}
|
|
99
142
|
}
|
|
100
143
|
async fetch(request) {
|
|
101
|
-
|
|
144
|
+
// Create a placeholder stub - it will be set by the worker via _setStub
|
|
145
|
+
const api = new CoordinatorApi(this, __classPrivateFieldGet(this, _SyncedStateServer_stub, "f") || {});
|
|
102
146
|
return newWorkersRpcResponse(request, api);
|
|
103
147
|
}
|
|
104
148
|
}
|
|
105
|
-
_a = SyncedStateServer, _SyncedStateServer_stateStore = new WeakMap(), _SyncedStateServer_subscriptions = new WeakMap(), _SyncedStateServer_subscriptionRefs = new WeakMap()
|
|
149
|
+
_a = SyncedStateServer, _SyncedStateServer_stub = new WeakMap(), _SyncedStateServer_stateStore = new WeakMap(), _SyncedStateServer_subscriptions = new WeakMap(), _SyncedStateServer_subscriptionRefs = new WeakMap(), _SyncedStateServer_instances = new WeakSet(), _SyncedStateServer_getStubForHandlers = function _SyncedStateServer_getStubForHandlers() {
|
|
150
|
+
// If we have a stub already, use it
|
|
151
|
+
if (__classPrivateFieldGet(this, _SyncedStateServer_stub, "f")) {
|
|
152
|
+
return __classPrivateFieldGet(this, _SyncedStateServer_stub, "f");
|
|
153
|
+
}
|
|
154
|
+
// Otherwise, try to get a stub from the registered namespace using our own ID
|
|
155
|
+
const namespace = __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_namespace);
|
|
156
|
+
if (namespace) {
|
|
157
|
+
return namespace.get(this.ctx.id);
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
};
|
|
106
161
|
_SyncedStateServer_keyHandler = { value: null };
|
|
162
|
+
_SyncedStateServer_roomHandler = { value: null };
|
|
107
163
|
_SyncedStateServer_setStateHandler = { value: null };
|
|
108
164
|
_SyncedStateServer_getStateHandler = { value: null };
|
|
165
|
+
_SyncedStateServer_subscribeHandler = { value: null };
|
|
166
|
+
_SyncedStateServer_unsubscribeHandler = { value: null };
|
|
167
|
+
_SyncedStateServer_namespace = { value: null };
|
|
168
|
+
_SyncedStateServer_durableObjectName = { value: "syncedState" };
|
|
109
169
|
class CoordinatorApi extends RpcTarget {
|
|
110
|
-
constructor(coordinator) {
|
|
170
|
+
constructor(coordinator, stub) {
|
|
111
171
|
super();
|
|
112
172
|
_CoordinatorApi_coordinator.set(this, void 0);
|
|
173
|
+
_CoordinatorApi_stub.set(this, void 0);
|
|
113
174
|
__classPrivateFieldSet(this, _CoordinatorApi_coordinator, coordinator, "f");
|
|
175
|
+
__classPrivateFieldSet(this, _CoordinatorApi_stub, stub, "f");
|
|
176
|
+
coordinator.setStub(stub);
|
|
177
|
+
}
|
|
178
|
+
// Internal method to set the stub - called from worker
|
|
179
|
+
_setStub(stub) {
|
|
180
|
+
__classPrivateFieldSet(this, _CoordinatorApi_stub, stub, "f");
|
|
181
|
+
__classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").setStub(stub);
|
|
114
182
|
}
|
|
115
183
|
getState(key) {
|
|
116
184
|
return __classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").getState(key);
|
|
@@ -125,4 +193,4 @@ class CoordinatorApi extends RpcTarget {
|
|
|
125
193
|
__classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").unsubscribe(key, client);
|
|
126
194
|
}
|
|
127
195
|
}
|
|
128
|
-
_CoordinatorApi_coordinator = new WeakMap();
|
|
196
|
+
_CoordinatorApi_coordinator = new WeakMap(), _CoordinatorApi_stub = new WeakMap();
|