rwsdk 0.1.23 → 0.1.24
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.d.ts +3 -1
- package/dist/runtime/client.js +23 -3
- package/dist/runtime/clientNavigation.d.ts +3 -1
- package/dist/runtime/clientNavigation.js +11 -0
- package/dist/runtime/lib/realtime/client.d.ts +4 -2
- package/dist/runtime/lib/realtime/client.js +133 -100
- package/dist/runtime/lib/realtime/durableObject.d.ts +1 -0
- package/dist/runtime/lib/realtime/durableObject.js +42 -34
- package/dist/runtime/lib/realtime/protocol.d.ts +50 -0
- package/dist/runtime/lib/realtime/protocol.js +141 -0
- package/dist/runtime/lib/realtime/shared.d.ts +8 -8
- package/dist/runtime/lib/realtime/shared.js +4 -4
- package/dist/scripts/debug-sync.mjs +90 -40
- package/package.json +1 -1
package/dist/runtime/client.d.ts
CHANGED
|
@@ -6,12 +6,14 @@ export type ActionResponse<Result> = {
|
|
|
6
6
|
};
|
|
7
7
|
type TransportContext = {
|
|
8
8
|
setRscPayload: <Result>(v: Promise<ActionResponse<Result>>) => void;
|
|
9
|
+
handleResponse?: (response: Response) => boolean;
|
|
9
10
|
};
|
|
10
11
|
export type Transport = (context: TransportContext) => CallServerCallback;
|
|
11
12
|
export type CreateCallServer = (context: TransportContext) => <Result>(id: null | string, args: null | unknown[]) => Promise<Result>;
|
|
12
13
|
export declare const fetchTransport: Transport;
|
|
13
|
-
export declare const initClient: ({ transport, hydrateRootOptions, }?: {
|
|
14
|
+
export declare const initClient: ({ transport, hydrateRootOptions, handleResponse, }?: {
|
|
14
15
|
transport?: Transport;
|
|
15
16
|
hydrateRootOptions?: HydrationOptions;
|
|
17
|
+
handleResponse?: (response: Response) => boolean;
|
|
16
18
|
}) => Promise<void>;
|
|
17
19
|
export {};
|
package/dist/runtime/client.js
CHANGED
|
@@ -11,21 +11,41 @@ export const fetchTransport = (transportContext) => {
|
|
|
11
11
|
if (id != null) {
|
|
12
12
|
url.searchParams.set("__rsc_action_id", id);
|
|
13
13
|
}
|
|
14
|
-
const
|
|
14
|
+
const fetchPromise = fetch(url, {
|
|
15
15
|
method: "POST",
|
|
16
16
|
body: args != null ? await encodeReply(args) : null,
|
|
17
|
-
})
|
|
17
|
+
});
|
|
18
|
+
// If there's a response handler, check the response first
|
|
19
|
+
if (transportContext.handleResponse) {
|
|
20
|
+
const response = await fetchPromise;
|
|
21
|
+
const shouldContinue = transportContext.handleResponse(response);
|
|
22
|
+
if (!shouldContinue) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Continue with the response if handler returned true
|
|
26
|
+
const streamData = createFromFetch(Promise.resolve(response), {
|
|
27
|
+
callServer: fetchCallServer,
|
|
28
|
+
});
|
|
29
|
+
transportContext.setRscPayload(streamData);
|
|
30
|
+
const result = await streamData;
|
|
31
|
+
return result.actionResult;
|
|
32
|
+
}
|
|
33
|
+
// Original behavior when no handler is present
|
|
34
|
+
const streamData = createFromFetch(fetchPromise, {
|
|
35
|
+
callServer: fetchCallServer,
|
|
36
|
+
});
|
|
18
37
|
transportContext.setRscPayload(streamData);
|
|
19
38
|
const result = await streamData;
|
|
20
39
|
return result.actionResult;
|
|
21
40
|
};
|
|
22
41
|
return fetchCallServer;
|
|
23
42
|
};
|
|
24
|
-
export const initClient = async ({ transport = fetchTransport, hydrateRootOptions, } = {}) => {
|
|
43
|
+
export const initClient = async ({ transport = fetchTransport, hydrateRootOptions, handleResponse, } = {}) => {
|
|
25
44
|
const React = await import("react");
|
|
26
45
|
const { hydrateRoot } = await import("react-dom/client");
|
|
27
46
|
const transportContext = {
|
|
28
47
|
setRscPayload: () => { },
|
|
48
|
+
handleResponse,
|
|
29
49
|
};
|
|
30
50
|
let transportCallServer = transport(transportContext);
|
|
31
51
|
const callServer = (id, args) => transportCallServer(id, args);
|
|
@@ -50,4 +50,15 @@ export function initClientNavigation(opts = {
|
|
|
50
50
|
window.addEventListener("popstate", async function handlePopState() {
|
|
51
51
|
await opts.onNavigate();
|
|
52
52
|
});
|
|
53
|
+
// Return a handleResponse function for use with initClient
|
|
54
|
+
return {
|
|
55
|
+
handleResponse: function handleResponse(response) {
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
// Redirect to the current page (window.location) to show the error
|
|
58
|
+
window.location.href = window.location.href;
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
53
64
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type Transport } from "../../client";
|
|
2
|
-
export declare const initRealtimeClient: ({ key, }?: {
|
|
2
|
+
export declare const initRealtimeClient: ({ key, handleResponse, }?: {
|
|
3
3
|
key?: string;
|
|
4
|
+
handleResponse?: (response: Response) => boolean;
|
|
4
5
|
}) => Promise<void>;
|
|
5
|
-
export declare const realtimeTransport: ({ key }: {
|
|
6
|
+
export declare const realtimeTransport: ({ key, handleResponse, }: {
|
|
6
7
|
key?: string;
|
|
8
|
+
handleResponse?: (response: Response) => boolean;
|
|
7
9
|
}) => Transport;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { initClient } from "../../client";
|
|
2
2
|
import { createFromReadableStream } from "react-server-dom-webpack/client.browser";
|
|
3
3
|
import { MESSAGE_TYPE } from "./shared";
|
|
4
|
+
import { packMessage, unpackMessage, } from "./protocol";
|
|
4
5
|
const DEFAULT_KEY = "default";
|
|
5
|
-
export const initRealtimeClient = ({ key = DEFAULT_KEY, } = {}) => {
|
|
6
|
-
const transport = realtimeTransport({ key });
|
|
7
|
-
return initClient({ transport });
|
|
6
|
+
export const initRealtimeClient = ({ key = DEFAULT_KEY, handleResponse, } = {}) => {
|
|
7
|
+
const transport = realtimeTransport({ key, handleResponse });
|
|
8
|
+
return initClient({ transport, handleResponse });
|
|
8
9
|
};
|
|
9
|
-
export const realtimeTransport = ({ key = DEFAULT_KEY }) => (transportContext) => {
|
|
10
|
+
export const realtimeTransport = ({ key = DEFAULT_KEY, handleResponse, }) => (transportContext) => {
|
|
10
11
|
let ws = null;
|
|
11
12
|
let isConnected = false;
|
|
12
13
|
const clientId = crypto.randomUUID();
|
|
@@ -14,14 +15,15 @@ export const realtimeTransport = ({ key = DEFAULT_KEY }) => (transportContext) =
|
|
|
14
15
|
const isHttps = clientUrl.protocol === "https:";
|
|
15
16
|
clientUrl.protocol = "";
|
|
16
17
|
clientUrl.host = "";
|
|
17
|
-
const setupWebSocket = () => {
|
|
18
|
+
const setupWebSocket = async () => {
|
|
18
19
|
if (ws)
|
|
19
20
|
return;
|
|
20
21
|
const protocol = isHttps ? "wss" : "ws";
|
|
21
22
|
ws = new WebSocket(`${protocol}://${window.location.host}/__realtime?` +
|
|
22
23
|
`key=${encodeURIComponent(key)}&` +
|
|
23
24
|
`url=${encodeURIComponent(clientUrl.toString())}&` +
|
|
24
|
-
`clientId=${encodeURIComponent(clientId)}
|
|
25
|
+
`clientId=${encodeURIComponent(clientId)}&` +
|
|
26
|
+
`shouldForwardResponses=${encodeURIComponent(handleResponse ? "true" : "false")}`);
|
|
25
27
|
ws.binaryType = "arraybuffer";
|
|
26
28
|
ws.addEventListener("open", () => {
|
|
27
29
|
isConnected = true;
|
|
@@ -29,45 +31,15 @@ export const realtimeTransport = ({ key = DEFAULT_KEY }) => (transportContext) =
|
|
|
29
31
|
ws.addEventListener("error", (event) => {
|
|
30
32
|
console.error("[Realtime] WebSocket error", event);
|
|
31
33
|
});
|
|
32
|
-
ws.addEventListener("message", (event) => {
|
|
33
|
-
const data = new Uint8Array(event.data);
|
|
34
|
-
const messageType = data[0];
|
|
35
|
-
if (messageType === MESSAGE_TYPE.RSC_START) {
|
|
36
|
-
const decoder = new TextDecoder();
|
|
37
|
-
const rscId = decoder.decode(data.slice(1, 37)); // Extract RSC stream ID
|
|
38
|
-
const stream = new ReadableStream({
|
|
39
|
-
start(controller) {
|
|
40
|
-
ws.addEventListener("message", function streamHandler(event) {
|
|
41
|
-
const data = new Uint8Array(event.data);
|
|
42
|
-
const messageType = data[0];
|
|
43
|
-
// Extract the RSC stream ID and verify it matches
|
|
44
|
-
const responseId = decoder.decode(data.slice(1, 37));
|
|
45
|
-
if (responseId !== rscId) {
|
|
46
|
-
return; // Not for this stream
|
|
47
|
-
}
|
|
48
|
-
const payload = data.slice(37);
|
|
49
|
-
if (messageType === MESSAGE_TYPE.RSC_CHUNK) {
|
|
50
|
-
controller.enqueue(payload);
|
|
51
|
-
}
|
|
52
|
-
else if (messageType === MESSAGE_TYPE.RSC_END) {
|
|
53
|
-
controller.close();
|
|
54
|
-
ws.removeEventListener("message", streamHandler);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
const rscPayload = createFromReadableStream(stream, {
|
|
60
|
-
callServer: realtimeCallServer,
|
|
61
|
-
});
|
|
62
|
-
transportContext.setRscPayload(rscPayload);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
34
|
ws.addEventListener("close", () => {
|
|
66
35
|
console.warn("[Realtime] WebSocket closed, attempting to reconnect...");
|
|
67
36
|
ws = null;
|
|
68
37
|
isConnected = false;
|
|
69
38
|
setTimeout(setupWebSocket, 5000);
|
|
70
39
|
});
|
|
40
|
+
listenForUpdates(ws, (response) => {
|
|
41
|
+
processResponse(response);
|
|
42
|
+
});
|
|
71
43
|
};
|
|
72
44
|
const ensureWs = () => {
|
|
73
45
|
if (!ws && isConnected) {
|
|
@@ -89,78 +61,139 @@ export const realtimeTransport = ({ key = DEFAULT_KEY }) => (transportContext) =
|
|
|
89
61
|
clientUrl.host = "";
|
|
90
62
|
const encodedArgs = args != null ? await encodeReply(args) : null;
|
|
91
63
|
const requestId = crypto.randomUUID();
|
|
92
|
-
const
|
|
64
|
+
const message = packMessage({
|
|
65
|
+
type: MESSAGE_TYPE.ACTION_REQUEST,
|
|
93
66
|
id,
|
|
94
67
|
args: encodedArgs,
|
|
95
68
|
requestId,
|
|
96
|
-
clientUrl,
|
|
69
|
+
clientUrl: clientUrl.toString(),
|
|
97
70
|
});
|
|
98
|
-
const
|
|
99
|
-
const messageBytes = encoder.encode(messageData);
|
|
100
|
-
const message = new Uint8Array(messageBytes.length + 1);
|
|
101
|
-
message[0] = MESSAGE_TYPE.ACTION_REQUEST;
|
|
102
|
-
message.set(messageBytes, 1);
|
|
71
|
+
const promisedResponse = respondToRequest(requestId, socket);
|
|
103
72
|
socket.send(message);
|
|
104
|
-
return
|
|
105
|
-
const stream = new ReadableStream({
|
|
106
|
-
start(controller) {
|
|
107
|
-
const messageHandler = (event) => {
|
|
108
|
-
const data = new Uint8Array(event.data);
|
|
109
|
-
const messageType = data[0];
|
|
110
|
-
// First byte is message type
|
|
111
|
-
// Next 36 bytes (or fixed size) should be UUID as requestId
|
|
112
|
-
// Remaining bytes are the payload
|
|
113
|
-
// Extract the requestId from the message
|
|
114
|
-
const decoder = new TextDecoder();
|
|
115
|
-
const responseId = decoder.decode(data.slice(1, 37)); // Assuming UUID is 36 chars
|
|
116
|
-
// Only process messages meant for this request
|
|
117
|
-
if (responseId !== requestId) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
// The actual payload starts after the requestId
|
|
121
|
-
const payload = data.slice(37);
|
|
122
|
-
if (messageType === MESSAGE_TYPE.ACTION_CHUNK) {
|
|
123
|
-
controller.enqueue(payload);
|
|
124
|
-
}
|
|
125
|
-
else if (messageType === MESSAGE_TYPE.ACTION_END) {
|
|
126
|
-
controller.close();
|
|
127
|
-
socket.removeEventListener("message", messageHandler);
|
|
128
|
-
}
|
|
129
|
-
else if (messageType === MESSAGE_TYPE.ACTION_ERROR) {
|
|
130
|
-
const errorJson = decoder.decode(payload);
|
|
131
|
-
let errorMsg = "Unknown error";
|
|
132
|
-
try {
|
|
133
|
-
const errorObj = JSON.parse(errorJson);
|
|
134
|
-
errorMsg = errorObj.error || errorMsg;
|
|
135
|
-
}
|
|
136
|
-
catch (e) {
|
|
137
|
-
// Use default error message
|
|
138
|
-
}
|
|
139
|
-
controller.error(new Error(errorMsg));
|
|
140
|
-
socket.removeEventListener("message", messageHandler);
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
socket.addEventListener("message", messageHandler);
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
const rscPayload = createFromReadableStream(stream, {
|
|
147
|
-
callServer: realtimeCallServer,
|
|
148
|
-
});
|
|
149
|
-
transportContext.setRscPayload(rscPayload);
|
|
150
|
-
try {
|
|
151
|
-
const result = await rscPayload;
|
|
152
|
-
resolve(result.actionResult);
|
|
153
|
-
}
|
|
154
|
-
catch (rscPayloadError) {
|
|
155
|
-
reject(rscPayloadError);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
73
|
+
return await processResponse(await promisedResponse);
|
|
158
74
|
}
|
|
159
75
|
catch (e) {
|
|
160
76
|
console.error("[Realtime] Error calling server", e);
|
|
161
77
|
return null;
|
|
162
78
|
}
|
|
163
79
|
};
|
|
80
|
+
const processResponse = async (response) => {
|
|
81
|
+
try {
|
|
82
|
+
let streamForRsc;
|
|
83
|
+
let shouldContinue = true;
|
|
84
|
+
if (transportContext.handleResponse) {
|
|
85
|
+
const [stream1, stream2] = response.body.tee();
|
|
86
|
+
const clonedResponse = new Response(stream1, response);
|
|
87
|
+
streamForRsc = stream2;
|
|
88
|
+
shouldContinue = transportContext.handleResponse(clonedResponse);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
streamForRsc = response.body;
|
|
92
|
+
}
|
|
93
|
+
if (!shouldContinue) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const rscPayload = createFromReadableStream(streamForRsc, {
|
|
97
|
+
callServer: realtimeCallServer,
|
|
98
|
+
});
|
|
99
|
+
transportContext.setRscPayload(rscPayload);
|
|
100
|
+
return (await rscPayload).actionResult;
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
164
106
|
setupWebSocket();
|
|
165
107
|
return realtimeCallServer;
|
|
166
108
|
};
|
|
109
|
+
function respondToRequest(requestId, socket) {
|
|
110
|
+
const messageTypes = {
|
|
111
|
+
start: MESSAGE_TYPE.ACTION_START,
|
|
112
|
+
chunk: MESSAGE_TYPE.ACTION_CHUNK,
|
|
113
|
+
end: MESSAGE_TYPE.ACTION_END,
|
|
114
|
+
error: MESSAGE_TYPE.ACTION_ERROR,
|
|
115
|
+
};
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const handler = (event) => {
|
|
118
|
+
const unpacked = unpackMessage(new Uint8Array(event.data));
|
|
119
|
+
if (unpacked.type === MESSAGE_TYPE.ACTION_REQUEST) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (unpacked.id !== requestId) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (unpacked.type === messageTypes.start) {
|
|
126
|
+
const message = unpacked;
|
|
127
|
+
socket.removeEventListener("message", handler);
|
|
128
|
+
const stream = createUpdateStreamFromSocket(requestId, socket, messageTypes, reject);
|
|
129
|
+
const response = new Response(stream, {
|
|
130
|
+
status: message.status,
|
|
131
|
+
headers: { "Content-Type": "text/plain" },
|
|
132
|
+
});
|
|
133
|
+
resolve(response);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
socket.addEventListener("message", handler);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function listenForUpdates(socket, onUpdate) {
|
|
140
|
+
const messageTypes = {
|
|
141
|
+
start: MESSAGE_TYPE.RSC_START,
|
|
142
|
+
chunk: MESSAGE_TYPE.RSC_CHUNK,
|
|
143
|
+
end: MESSAGE_TYPE.RSC_END,
|
|
144
|
+
};
|
|
145
|
+
const handler = async (event) => {
|
|
146
|
+
const unpacked = unpackMessage(new Uint8Array(event.data));
|
|
147
|
+
if (unpacked.type === MESSAGE_TYPE.ACTION_REQUEST ||
|
|
148
|
+
unpacked.type === MESSAGE_TYPE.ACTION_CHUNK ||
|
|
149
|
+
unpacked.type === MESSAGE_TYPE.ACTION_END ||
|
|
150
|
+
unpacked.type === MESSAGE_TYPE.ACTION_ERROR) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (unpacked.type === messageTypes.start) {
|
|
154
|
+
const message = unpacked;
|
|
155
|
+
const stream = createUpdateStreamFromSocket(message.id, socket, messageTypes, (error) => {
|
|
156
|
+
console.error("[Realtime] Error creating update stream", error);
|
|
157
|
+
});
|
|
158
|
+
const response = new Response(stream, {
|
|
159
|
+
status: message.status,
|
|
160
|
+
headers: { "Content-Type": "text/plain" },
|
|
161
|
+
});
|
|
162
|
+
onUpdate(response);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
socket.addEventListener("message", handler);
|
|
166
|
+
}
|
|
167
|
+
const createUpdateStreamFromSocket = (id, socket, messageTypes, onError) => {
|
|
168
|
+
let deferredStreamController = Promise.withResolvers();
|
|
169
|
+
const stream = new ReadableStream({
|
|
170
|
+
start(controller) {
|
|
171
|
+
deferredStreamController.resolve(controller);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
const handler = async (event) => {
|
|
175
|
+
const unpacked = unpackMessage(new Uint8Array(event.data));
|
|
176
|
+
if (unpacked.type === MESSAGE_TYPE.ACTION_REQUEST) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (unpacked.id !== id) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const streamController = await deferredStreamController.promise;
|
|
183
|
+
if (unpacked.type === messageTypes.chunk) {
|
|
184
|
+
const message = unpacked;
|
|
185
|
+
streamController.enqueue(message.payload);
|
|
186
|
+
}
|
|
187
|
+
else if (unpacked.type === messageTypes.end) {
|
|
188
|
+
streamController.close();
|
|
189
|
+
socket.removeEventListener("message", handler);
|
|
190
|
+
}
|
|
191
|
+
else if (messageTypes.error && unpacked.type === messageTypes.error) {
|
|
192
|
+
const message = unpacked;
|
|
193
|
+
onError(new Error(message.error));
|
|
194
|
+
socket.removeEventListener("message", handler);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
socket.addEventListener("message", handler);
|
|
198
|
+
return stream;
|
|
199
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DurableObject } from "cloudflare:workers";
|
|
2
2
|
import { MESSAGE_TYPE } from "./shared";
|
|
3
3
|
import { validateUpgradeRequest } from "./validateUpgradeRequest";
|
|
4
|
+
import { packMessage, unpackMessage, } from "./protocol";
|
|
4
5
|
export class RealtimeDurableObject extends DurableObject {
|
|
5
6
|
constructor(state, env) {
|
|
6
7
|
super(state, env);
|
|
@@ -23,6 +24,7 @@ export class RealtimeDurableObject extends DurableObject {
|
|
|
23
24
|
url: url.searchParams.get("url"),
|
|
24
25
|
clientId: url.searchParams.get("clientId"),
|
|
25
26
|
cookieHeaders: request.headers.get("Cookie") || "",
|
|
27
|
+
shouldForwardResponses: url.searchParams.get("shouldForwardResponses") === "true",
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
30
|
async storeClientInfo(clientInfo) {
|
|
@@ -51,54 +53,62 @@ export class RealtimeDurableObject extends DurableObject {
|
|
|
51
53
|
async webSocketMessage(ws, data) {
|
|
52
54
|
const clientId = ws.deserializeAttachment();
|
|
53
55
|
let clientInfo = await this.getClientInfo(clientId);
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const decoder = new TextDecoder();
|
|
58
|
-
const jsonData = decoder.decode(message.slice(1));
|
|
59
|
-
const { id, args, requestId, clientUrl } = JSON.parse(jsonData);
|
|
56
|
+
const unpacked = unpackMessage(new Uint8Array(data));
|
|
57
|
+
if (unpacked.type === MESSAGE_TYPE.ACTION_REQUEST) {
|
|
58
|
+
const message = unpacked;
|
|
60
59
|
clientInfo = {
|
|
61
60
|
...clientInfo,
|
|
62
|
-
url: clientUrl,
|
|
61
|
+
url: message.clientUrl,
|
|
63
62
|
};
|
|
64
63
|
await this.storeClientInfo(clientInfo);
|
|
65
64
|
try {
|
|
66
|
-
await this.handleAction(ws, id, args, clientInfo, requestId, clientUrl);
|
|
65
|
+
await this.handleAction(ws, message.id, message.args, clientInfo, message.requestId, message.clientUrl);
|
|
67
66
|
}
|
|
68
67
|
catch (error) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
ws.send(packMessage({
|
|
69
|
+
type: MESSAGE_TYPE.ACTION_ERROR,
|
|
70
|
+
id: message.requestId,
|
|
72
71
|
error: error instanceof Error ? error.message : String(error),
|
|
73
|
-
});
|
|
74
|
-
const errorBytes = encoder.encode(errorData);
|
|
75
|
-
const errorResponse = new Uint8Array(1 + requestIdBytes.length + errorBytes.length);
|
|
76
|
-
errorResponse[0] = MESSAGE_TYPE.ACTION_ERROR;
|
|
77
|
-
errorResponse.set(requestIdBytes, 1);
|
|
78
|
-
errorResponse.set(errorBytes, 1 + requestIdBytes.length);
|
|
79
|
-
ws.send(errorResponse);
|
|
72
|
+
}));
|
|
80
73
|
}
|
|
81
74
|
}
|
|
82
75
|
}
|
|
83
76
|
async streamResponse(response, ws, messageTypes, streamId) {
|
|
77
|
+
const startMessage = messageTypes.start === MESSAGE_TYPE.ACTION_START
|
|
78
|
+
? {
|
|
79
|
+
type: MESSAGE_TYPE.ACTION_START,
|
|
80
|
+
id: streamId,
|
|
81
|
+
status: response.status,
|
|
82
|
+
}
|
|
83
|
+
: {
|
|
84
|
+
type: MESSAGE_TYPE.RSC_START,
|
|
85
|
+
id: streamId,
|
|
86
|
+
status: response.status,
|
|
87
|
+
};
|
|
88
|
+
ws.send(packMessage(startMessage));
|
|
84
89
|
const reader = response.body.getReader();
|
|
85
|
-
const encoder = new TextEncoder();
|
|
86
|
-
const streamIdBytes = encoder.encode(streamId);
|
|
87
90
|
try {
|
|
88
91
|
while (true) {
|
|
89
92
|
const { done, value } = await reader.read();
|
|
90
93
|
if (done) {
|
|
91
|
-
const endMessage =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
ws.send(endMessage);
|
|
94
|
+
const endMessage = messageTypes.end === MESSAGE_TYPE.ACTION_END
|
|
95
|
+
? { type: MESSAGE_TYPE.ACTION_END, id: streamId }
|
|
96
|
+
: { type: MESSAGE_TYPE.RSC_END, id: streamId };
|
|
97
|
+
ws.send(packMessage(endMessage));
|
|
95
98
|
break;
|
|
96
99
|
}
|
|
97
|
-
const chunkMessage =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
const chunkMessage = messageTypes.chunk === MESSAGE_TYPE.ACTION_CHUNK
|
|
101
|
+
? {
|
|
102
|
+
type: MESSAGE_TYPE.ACTION_CHUNK,
|
|
103
|
+
id: streamId,
|
|
104
|
+
payload: value,
|
|
105
|
+
}
|
|
106
|
+
: {
|
|
107
|
+
type: MESSAGE_TYPE.RSC_CHUNK,
|
|
108
|
+
id: streamId,
|
|
109
|
+
payload: value,
|
|
110
|
+
};
|
|
111
|
+
ws.send(packMessage(chunkMessage));
|
|
102
112
|
}
|
|
103
113
|
}
|
|
104
114
|
finally {
|
|
@@ -119,13 +129,14 @@ export class RealtimeDurableObject extends DurableObject {
|
|
|
119
129
|
Cookie: clientInfo.cookieHeaders,
|
|
120
130
|
},
|
|
121
131
|
});
|
|
122
|
-
if (!response.ok) {
|
|
132
|
+
if (!response.ok && !clientInfo.shouldForwardResponses) {
|
|
123
133
|
throw new Error(`Action failed: ${response.statusText}`);
|
|
124
134
|
}
|
|
125
135
|
this.render({
|
|
126
136
|
exclude: [clientInfo.clientId],
|
|
127
137
|
});
|
|
128
138
|
await this.streamResponse(response, ws, {
|
|
139
|
+
start: MESSAGE_TYPE.ACTION_START,
|
|
129
140
|
chunk: MESSAGE_TYPE.ACTION_CHUNK,
|
|
130
141
|
end: MESSAGE_TYPE.ACTION_END,
|
|
131
142
|
}, requestId);
|
|
@@ -167,11 +178,8 @@ export class RealtimeDurableObject extends DurableObject {
|
|
|
167
178
|
return;
|
|
168
179
|
}
|
|
169
180
|
const rscId = crypto.randomUUID();
|
|
170
|
-
const startMessage = new Uint8Array(1 + 36);
|
|
171
|
-
startMessage[0] = MESSAGE_TYPE.RSC_START;
|
|
172
|
-
startMessage.set(new TextEncoder().encode(rscId), 1);
|
|
173
|
-
socket.send(startMessage);
|
|
174
181
|
await this.streamResponse(response, socket, {
|
|
182
|
+
start: MESSAGE_TYPE.RSC_START,
|
|
175
183
|
chunk: MESSAGE_TYPE.RSC_CHUNK,
|
|
176
184
|
end: MESSAGE_TYPE.RSC_END,
|
|
177
185
|
}, rscId);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { MESSAGE_TYPE } from "./shared";
|
|
2
|
+
export type Message = ActionRequestMessage | ActionStartMessage | ActionChunkMessage | ActionEndMessage | ActionErrorMessage | RscStartMessage | RscChunkMessage | RscEndMessage;
|
|
3
|
+
export type ActionRequestMessage = {
|
|
4
|
+
type: typeof MESSAGE_TYPE.ACTION_REQUEST;
|
|
5
|
+
id: string | null;
|
|
6
|
+
args: any;
|
|
7
|
+
requestId: string;
|
|
8
|
+
clientUrl: string;
|
|
9
|
+
};
|
|
10
|
+
export type ActionStartMessage = {
|
|
11
|
+
type: typeof MESSAGE_TYPE.ACTION_START;
|
|
12
|
+
id: string;
|
|
13
|
+
status: number;
|
|
14
|
+
};
|
|
15
|
+
export type ActionChunkMessage = {
|
|
16
|
+
type: typeof MESSAGE_TYPE.ACTION_CHUNK;
|
|
17
|
+
id: string;
|
|
18
|
+
payload: Uint8Array;
|
|
19
|
+
};
|
|
20
|
+
export type ActionEndMessage = {
|
|
21
|
+
type: typeof MESSAGE_TYPE.ACTION_END;
|
|
22
|
+
id: string;
|
|
23
|
+
};
|
|
24
|
+
export type ActionErrorMessage = {
|
|
25
|
+
type: typeof MESSAGE_TYPE.ACTION_ERROR;
|
|
26
|
+
id: string;
|
|
27
|
+
error: string;
|
|
28
|
+
};
|
|
29
|
+
export type RscStartMessage = {
|
|
30
|
+
type: typeof MESSAGE_TYPE.RSC_START;
|
|
31
|
+
id: string;
|
|
32
|
+
status: number;
|
|
33
|
+
};
|
|
34
|
+
export type RscChunkMessage = {
|
|
35
|
+
type: typeof MESSAGE_TYPE.RSC_CHUNK;
|
|
36
|
+
id: string;
|
|
37
|
+
payload: Uint8Array;
|
|
38
|
+
};
|
|
39
|
+
export type RscEndMessage = {
|
|
40
|
+
type: typeof MESSAGE_TYPE.RSC_END;
|
|
41
|
+
id: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Packs a message object into a Uint8Array for sending over WebSocket.
|
|
45
|
+
*/
|
|
46
|
+
export declare function packMessage(message: Message): Uint8Array;
|
|
47
|
+
/**
|
|
48
|
+
* Unpacks a Uint8Array from WebSocket into a message object.
|
|
49
|
+
*/
|
|
50
|
+
export declare function unpackMessage(data: Uint8Array): Message;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { MESSAGE_TYPE } from "./shared";
|
|
2
|
+
const TEXT_ENCODER = new TextEncoder();
|
|
3
|
+
const TEXT_DECODER = new TextDecoder();
|
|
4
|
+
const ID_LENGTH = 36; // Length of a UUID string
|
|
5
|
+
/**
|
|
6
|
+
* Packs a message object into a Uint8Array for sending over WebSocket.
|
|
7
|
+
*/
|
|
8
|
+
export function packMessage(message) {
|
|
9
|
+
switch (message.type) {
|
|
10
|
+
case MESSAGE_TYPE.ACTION_REQUEST: {
|
|
11
|
+
const msg = message;
|
|
12
|
+
const jsonPayload = JSON.stringify({
|
|
13
|
+
id: msg.id,
|
|
14
|
+
args: msg.args,
|
|
15
|
+
requestId: msg.requestId,
|
|
16
|
+
clientUrl: msg.clientUrl,
|
|
17
|
+
});
|
|
18
|
+
const payloadBytes = TEXT_ENCODER.encode(jsonPayload);
|
|
19
|
+
const packed = new Uint8Array(1 + payloadBytes.length);
|
|
20
|
+
packed[0] = msg.type;
|
|
21
|
+
packed.set(payloadBytes, 1);
|
|
22
|
+
return packed;
|
|
23
|
+
}
|
|
24
|
+
case MESSAGE_TYPE.ACTION_START:
|
|
25
|
+
case MESSAGE_TYPE.RSC_START: {
|
|
26
|
+
const msg = message;
|
|
27
|
+
const idBytes = TEXT_ENCODER.encode(msg.id);
|
|
28
|
+
if (idBytes.length !== ID_LENGTH) {
|
|
29
|
+
throw new Error("Invalid message ID length for START message");
|
|
30
|
+
}
|
|
31
|
+
const packed = new Uint8Array(1 + 2 + ID_LENGTH); // 1 for type, 2 for status
|
|
32
|
+
const view = new DataView(packed.buffer);
|
|
33
|
+
view.setUint8(0, msg.type);
|
|
34
|
+
view.setUint16(1, msg.status, false); // Big-endian
|
|
35
|
+
packed.set(idBytes, 3);
|
|
36
|
+
return packed;
|
|
37
|
+
}
|
|
38
|
+
case MESSAGE_TYPE.ACTION_CHUNK:
|
|
39
|
+
case MESSAGE_TYPE.RSC_CHUNK: {
|
|
40
|
+
const msg = message;
|
|
41
|
+
const idBytes = TEXT_ENCODER.encode(msg.id);
|
|
42
|
+
if (idBytes.length !== ID_LENGTH) {
|
|
43
|
+
throw new Error("Invalid message ID length for CHUNK message");
|
|
44
|
+
}
|
|
45
|
+
const packed = new Uint8Array(1 + ID_LENGTH + msg.payload.length);
|
|
46
|
+
packed[0] = msg.type;
|
|
47
|
+
packed.set(idBytes, 1);
|
|
48
|
+
packed.set(msg.payload, 1 + ID_LENGTH);
|
|
49
|
+
return packed;
|
|
50
|
+
}
|
|
51
|
+
case MESSAGE_TYPE.ACTION_END:
|
|
52
|
+
case MESSAGE_TYPE.RSC_END: {
|
|
53
|
+
const msg = message;
|
|
54
|
+
const idBytes = TEXT_ENCODER.encode(msg.id);
|
|
55
|
+
if (idBytes.length !== ID_LENGTH) {
|
|
56
|
+
throw new Error("Invalid message ID length for END message");
|
|
57
|
+
}
|
|
58
|
+
const packed = new Uint8Array(1 + ID_LENGTH);
|
|
59
|
+
packed[0] = msg.type;
|
|
60
|
+
packed.set(idBytes, 1);
|
|
61
|
+
return packed;
|
|
62
|
+
}
|
|
63
|
+
case MESSAGE_TYPE.ACTION_ERROR: {
|
|
64
|
+
const msg = message;
|
|
65
|
+
const idBytes = TEXT_ENCODER.encode(msg.id);
|
|
66
|
+
if (idBytes.length !== ID_LENGTH) {
|
|
67
|
+
throw new Error("Invalid message ID length for ERROR message");
|
|
68
|
+
}
|
|
69
|
+
const errorPayload = JSON.stringify({ error: msg.error });
|
|
70
|
+
const errorBytes = TEXT_ENCODER.encode(errorPayload);
|
|
71
|
+
const packed = new Uint8Array(1 + ID_LENGTH + errorBytes.length);
|
|
72
|
+
packed[0] = msg.type;
|
|
73
|
+
packed.set(idBytes, 1);
|
|
74
|
+
packed.set(errorBytes, 1 + ID_LENGTH);
|
|
75
|
+
return packed;
|
|
76
|
+
}
|
|
77
|
+
default:
|
|
78
|
+
// This should be unreachable if all message types are handled
|
|
79
|
+
throw new Error(`Unknown message type for packing`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Unpacks a Uint8Array from WebSocket into a message object.
|
|
84
|
+
*/
|
|
85
|
+
export function unpackMessage(data) {
|
|
86
|
+
if (data.length === 0) {
|
|
87
|
+
throw new Error("Cannot unpack empty message");
|
|
88
|
+
}
|
|
89
|
+
const messageType = data[0];
|
|
90
|
+
switch (messageType) {
|
|
91
|
+
case MESSAGE_TYPE.ACTION_REQUEST: {
|
|
92
|
+
const jsonPayload = TEXT_DECODER.decode(data.slice(1));
|
|
93
|
+
const parsed = JSON.parse(jsonPayload);
|
|
94
|
+
return { type: messageType, ...parsed };
|
|
95
|
+
}
|
|
96
|
+
case MESSAGE_TYPE.ACTION_START:
|
|
97
|
+
case MESSAGE_TYPE.RSC_START: {
|
|
98
|
+
if (data.length !== 1 + 2 + ID_LENGTH) {
|
|
99
|
+
throw new Error("Invalid START message length");
|
|
100
|
+
}
|
|
101
|
+
const view = new DataView(data.buffer);
|
|
102
|
+
const status = view.getUint16(1, false);
|
|
103
|
+
const id = TEXT_DECODER.decode(data.slice(3));
|
|
104
|
+
return { type: messageType, id, status };
|
|
105
|
+
}
|
|
106
|
+
case MESSAGE_TYPE.ACTION_CHUNK:
|
|
107
|
+
case MESSAGE_TYPE.RSC_CHUNK: {
|
|
108
|
+
if (data.length < 1 + ID_LENGTH) {
|
|
109
|
+
throw new Error("Invalid CHUNK message length");
|
|
110
|
+
}
|
|
111
|
+
const id = TEXT_DECODER.decode(data.slice(1, 1 + ID_LENGTH));
|
|
112
|
+
const payload = data.slice(1 + ID_LENGTH);
|
|
113
|
+
return { type: messageType, id, payload };
|
|
114
|
+
}
|
|
115
|
+
case MESSAGE_TYPE.ACTION_END:
|
|
116
|
+
case MESSAGE_TYPE.RSC_END: {
|
|
117
|
+
if (data.length !== 1 + ID_LENGTH) {
|
|
118
|
+
throw new Error("Invalid END message length");
|
|
119
|
+
}
|
|
120
|
+
const id = TEXT_DECODER.decode(data.slice(1, 1 + ID_LENGTH));
|
|
121
|
+
return { type: messageType, id };
|
|
122
|
+
}
|
|
123
|
+
case MESSAGE_TYPE.ACTION_ERROR: {
|
|
124
|
+
if (data.length < 1 + ID_LENGTH) {
|
|
125
|
+
throw new Error("Invalid ERROR message length");
|
|
126
|
+
}
|
|
127
|
+
const id = TEXT_DECODER.decode(data.slice(1, 1 + ID_LENGTH));
|
|
128
|
+
const errorPayload = TEXT_DECODER.decode(data.slice(1 + ID_LENGTH));
|
|
129
|
+
let error = "Unknown error";
|
|
130
|
+
try {
|
|
131
|
+
error = JSON.parse(errorPayload).error;
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
// ignore if it's not a json
|
|
135
|
+
}
|
|
136
|
+
return { type: messageType, id, error };
|
|
137
|
+
}
|
|
138
|
+
default:
|
|
139
|
+
throw new Error(`Unknown message type for unpacking: ${messageType}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export declare const MESSAGE_TYPE: {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
RSC_START: number;
|
|
3
|
+
RSC_CHUNK: number;
|
|
4
|
+
RSC_END: number;
|
|
5
|
+
ACTION_REQUEST: number;
|
|
6
|
+
ACTION_START: number;
|
|
7
|
+
ACTION_CHUNK: number;
|
|
8
|
+
ACTION_END: number;
|
|
9
|
+
ACTION_ERROR: number;
|
|
10
10
|
};
|
|
@@ -15,7 +15,8 @@ const getPackageManagerInfo = (targetDir) => {
|
|
|
15
15
|
if (existsSync(path.join(targetDir, "yarn.lock"))) {
|
|
16
16
|
return { name: "yarn", lockFile: "yarn.lock", command: "add" };
|
|
17
17
|
}
|
|
18
|
-
if (existsSync(path.join(targetDir, "pnpm-lock.yaml"))
|
|
18
|
+
if (existsSync(path.join(targetDir, "pnpm-lock.yaml")) ||
|
|
19
|
+
existsSync(path.join(targetDir, "node_modules", ".pnpm"))) {
|
|
19
20
|
return pnpmResult;
|
|
20
21
|
}
|
|
21
22
|
if (existsSync(path.join(targetDir, "package-lock.json"))) {
|
|
@@ -23,51 +24,75 @@ const getPackageManagerInfo = (targetDir) => {
|
|
|
23
24
|
}
|
|
24
25
|
return pnpmResult;
|
|
25
26
|
};
|
|
26
|
-
const performFullSync = async (sdkDir, targetDir) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
console.error("❌ Failed to get tarball name from npm pack.");
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const tarballPath = path.resolve(sdkDir, tarballName);
|
|
35
|
-
console.log(`💿 Installing ${tarballName} in ${targetDir}...`);
|
|
36
|
-
const pm = getPackageManagerInfo(targetDir);
|
|
37
|
-
const packageJsonPath = path.join(targetDir, "package.json");
|
|
38
|
-
const lockfilePath = path.join(targetDir, pm.lockFile);
|
|
39
|
-
const originalPackageJson = await fs
|
|
40
|
-
.readFile(packageJsonPath, "utf-8")
|
|
41
|
-
.catch(() => null);
|
|
42
|
-
const originalLockfile = await fs
|
|
43
|
-
.readFile(lockfilePath, "utf-8")
|
|
44
|
-
.catch(() => null);
|
|
27
|
+
const performFullSync = async (sdkDir, targetDir, cacheBust = false) => {
|
|
28
|
+
const sdkPackageJsonPath = path.join(sdkDir, "package.json");
|
|
29
|
+
let originalSdkPackageJson = null;
|
|
30
|
+
let tarballPath = "";
|
|
31
|
+
let tarballName = "";
|
|
45
32
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
33
|
+
if (cacheBust) {
|
|
34
|
+
console.log("💥 Cache-busting version for full sync...");
|
|
35
|
+
originalSdkPackageJson = await fs.readFile(sdkPackageJsonPath, "utf-8");
|
|
36
|
+
const packageJson = JSON.parse(originalSdkPackageJson);
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
// This is a temporary version used for cache busting
|
|
39
|
+
packageJson.version = `${packageJson.version}-dev.${now}`;
|
|
40
|
+
await fs.writeFile(sdkPackageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
41
|
+
}
|
|
42
|
+
console.log("📦 Packing SDK...");
|
|
43
|
+
const packResult = await $({ cwd: sdkDir }) `npm pack`;
|
|
44
|
+
tarballName = packResult.stdout?.trim() ?? "";
|
|
45
|
+
if (!tarballName) {
|
|
46
|
+
console.error("❌ Failed to get tarball name from npm pack.");
|
|
47
|
+
return;
|
|
50
48
|
}
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
tarballPath = path.resolve(sdkDir, tarballName);
|
|
50
|
+
console.log(`💿 Installing ${tarballName} in ${targetDir}...`);
|
|
51
|
+
const pm = getPackageManagerInfo(targetDir);
|
|
52
|
+
const packageJsonPath = path.join(targetDir, "package.json");
|
|
53
|
+
const lockfilePath = path.join(targetDir, pm.lockFile);
|
|
54
|
+
const originalPackageJson = await fs
|
|
55
|
+
.readFile(packageJsonPath, "utf-8")
|
|
56
|
+
.catch(() => null);
|
|
57
|
+
const originalLockfile = await fs
|
|
58
|
+
.readFile(lockfilePath, "utf-8")
|
|
59
|
+
.catch(() => null);
|
|
60
|
+
try {
|
|
61
|
+
const cmd = pm.name;
|
|
62
|
+
const args = [pm.command];
|
|
63
|
+
if (pm.name === "yarn") {
|
|
64
|
+
args.push(`file:${tarballPath}`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
args.push(tarballPath);
|
|
68
|
+
}
|
|
69
|
+
await $(cmd, args, {
|
|
70
|
+
cwd: targetDir,
|
|
71
|
+
stdio: "inherit",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
if (originalPackageJson) {
|
|
76
|
+
console.log("Restoring package.json...");
|
|
77
|
+
await fs.writeFile(packageJsonPath, originalPackageJson);
|
|
78
|
+
}
|
|
79
|
+
if (originalLockfile) {
|
|
80
|
+
console.log(`Restoring ${pm.lockFile}...`);
|
|
81
|
+
await fs.writeFile(lockfilePath, originalLockfile);
|
|
82
|
+
}
|
|
53
83
|
}
|
|
54
|
-
await $(cmd, args, {
|
|
55
|
-
cwd: targetDir,
|
|
56
|
-
stdio: "inherit",
|
|
57
|
-
});
|
|
58
84
|
}
|
|
59
85
|
finally {
|
|
60
|
-
if (
|
|
86
|
+
if (originalSdkPackageJson) {
|
|
61
87
|
console.log("Restoring package.json...");
|
|
62
|
-
await fs.writeFile(
|
|
88
|
+
await fs.writeFile(sdkPackageJsonPath, originalSdkPackageJson);
|
|
63
89
|
}
|
|
64
|
-
if (
|
|
65
|
-
console.log(
|
|
66
|
-
await fs.
|
|
90
|
+
if (tarballPath) {
|
|
91
|
+
console.log("Removing tarball...");
|
|
92
|
+
await fs.unlink(tarballPath).catch(() => {
|
|
93
|
+
// ignore if deletion fails
|
|
94
|
+
});
|
|
67
95
|
}
|
|
68
|
-
await fs.unlink(tarballPath).catch(() => {
|
|
69
|
-
// ignore if deletion fails
|
|
70
|
-
});
|
|
71
96
|
}
|
|
72
97
|
};
|
|
73
98
|
const performFastSync = async (sdkDir, targetDir) => {
|
|
@@ -87,6 +112,13 @@ const performFastSync = async (sdkDir, targetDir) => {
|
|
|
87
112
|
const performSync = async (sdkDir, targetDir) => {
|
|
88
113
|
console.log("🏗️ Rebuilding SDK...");
|
|
89
114
|
await $ `pnpm build`;
|
|
115
|
+
const forceFullSync = Boolean(process.env.RWSDK_FORCE_FULL_SYNC);
|
|
116
|
+
if (forceFullSync) {
|
|
117
|
+
console.log("🏃 Force full sync mode is enabled.");
|
|
118
|
+
await performFullSync(sdkDir, targetDir, true);
|
|
119
|
+
console.log("✅ Done syncing");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
90
122
|
const sdkPackageJsonPath = path.join(sdkDir, "package.json");
|
|
91
123
|
const installedSdkPackageJsonPath = path.join(targetDir, "node_modules/rwsdk/package.json");
|
|
92
124
|
let packageJsonChanged = true;
|
|
@@ -166,8 +198,21 @@ export const debugSync = async (opts) => {
|
|
|
166
198
|
ignoreInitial: true,
|
|
167
199
|
cwd: sdkDir,
|
|
168
200
|
});
|
|
169
|
-
|
|
170
|
-
|
|
201
|
+
let syncing = false;
|
|
202
|
+
// todo(justinvdm, 2025-07-22): Figure out wtf makes the full sync
|
|
203
|
+
// cause chokidar to find out about package.json after sync has resolved
|
|
204
|
+
let expectingFileChanges = Boolean(process.env.RWSDK_FORCE_FULL_SYNC);
|
|
205
|
+
watcher.on("all", async (_event, filePath) => {
|
|
206
|
+
if (syncing || filePath.endsWith(".tgz")) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (expectingFileChanges && process.env.RWSDK_FORCE_FULL_SYNC) {
|
|
210
|
+
expectingFileChanges = false;
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
syncing = true;
|
|
214
|
+
expectingFileChanges = true;
|
|
215
|
+
console.log(`\nDetected change, re-syncing... (file: ${filePath})`);
|
|
171
216
|
if (childProc && !childProc.killed) {
|
|
172
217
|
console.log("Stopping running process...");
|
|
173
218
|
childProc.kill();
|
|
@@ -176,6 +221,7 @@ export const debugSync = async (opts) => {
|
|
|
176
221
|
});
|
|
177
222
|
}
|
|
178
223
|
try {
|
|
224
|
+
watcher.unwatch(filesToWatch);
|
|
179
225
|
await performSync(sdkDir, targetDir);
|
|
180
226
|
runWatchedCommand();
|
|
181
227
|
}
|
|
@@ -183,6 +229,10 @@ export const debugSync = async (opts) => {
|
|
|
183
229
|
console.error("❌ Sync failed:", error);
|
|
184
230
|
console.log(" Still watching for changes...");
|
|
185
231
|
}
|
|
232
|
+
finally {
|
|
233
|
+
syncing = false;
|
|
234
|
+
watcher.add(filesToWatch);
|
|
235
|
+
}
|
|
186
236
|
});
|
|
187
237
|
const cleanup = async () => {
|
|
188
238
|
console.log("\nCleaning up...");
|