@sylphx/lens-server 4.1.3 → 4.1.4

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/index.js CHANGED
@@ -1045,6 +1045,88 @@ class LensServerImpl {
1045
1045
  headers: baseHeaders
1046
1046
  });
1047
1047
  }
1048
+ const isSseRequest = url.searchParams.get("_sse") === "1" || request.headers.get("accept") === "text/event-stream";
1049
+ if (request.method === "GET" && isSseRequest) {
1050
+ const path = pathname.replace(/^\//, "");
1051
+ const inputParam = url.searchParams.get("input");
1052
+ let input;
1053
+ if (inputParam) {
1054
+ try {
1055
+ input = JSON.parse(inputParam);
1056
+ } catch {
1057
+ const encoder = new TextEncoder;
1058
+ const errorStream = new ReadableStream({
1059
+ start(controller) {
1060
+ const data = `event: error
1061
+ data: ${JSON.stringify({ error: "Invalid input JSON" })}
1062
+
1063
+ `;
1064
+ controller.enqueue(encoder.encode(data));
1065
+ controller.close();
1066
+ }
1067
+ });
1068
+ return new Response(errorStream, {
1069
+ headers: {
1070
+ "Content-Type": "text/event-stream",
1071
+ "Cache-Control": "no-cache",
1072
+ Connection: "keep-alive"
1073
+ }
1074
+ });
1075
+ }
1076
+ }
1077
+ const self = this;
1078
+ const stream = new ReadableStream({
1079
+ start(controller) {
1080
+ const encoder = new TextEncoder;
1081
+ try {
1082
+ const result = self.execute({ path, input });
1083
+ if (result && typeof result === "object" && "subscribe" in result) {
1084
+ const observable = result;
1085
+ const subscription = observable.subscribe({
1086
+ next: (value) => {
1087
+ const data = `data: ${JSON.stringify(value)}
1088
+
1089
+ `;
1090
+ controller.enqueue(encoder.encode(data));
1091
+ },
1092
+ error: (err) => {
1093
+ const data = `event: error
1094
+ data: ${JSON.stringify({ error: err.message })}
1095
+
1096
+ `;
1097
+ controller.enqueue(encoder.encode(data));
1098
+ controller.close();
1099
+ },
1100
+ complete: () => {
1101
+ controller.close();
1102
+ }
1103
+ });
1104
+ if (request.signal) {
1105
+ request.signal.addEventListener("abort", () => {
1106
+ subscription.unsubscribe();
1107
+ controller.close();
1108
+ });
1109
+ }
1110
+ }
1111
+ } catch (execError) {
1112
+ const errMsg = execError instanceof Error ? execError.message : "Internal error";
1113
+ const data = `event: error
1114
+ data: ${JSON.stringify({ error: errMsg })}
1115
+
1116
+ `;
1117
+ controller.enqueue(encoder.encode(data));
1118
+ controller.close();
1119
+ }
1120
+ }
1121
+ });
1122
+ return new Response(stream, {
1123
+ headers: {
1124
+ "Content-Type": "text/event-stream",
1125
+ "Cache-Control": "no-cache",
1126
+ Connection: "keep-alive"
1127
+ }
1128
+ });
1129
+ }
1048
1130
  if (request.method === "POST") {
1049
1131
  let body;
1050
1132
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/lens-server",
3
- "version": "4.1.3",
3
+ "version": "4.1.4",
4
4
  "description": "Server runtime for Lens API framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1276,6 +1276,101 @@ class LensServerImpl<
1276
1276
  });
1277
1277
  }
1278
1278
 
1279
+ // SSE: GET /{path}?_sse=1&input={...}
1280
+ // Client uses EventSource which sends GET requests with path in URL
1281
+ const isSseRequest =
1282
+ url.searchParams.get("_sse") === "1" || request.headers.get("accept") === "text/event-stream";
1283
+
1284
+ if (request.method === "GET" && isSseRequest) {
1285
+ // Extract path from URL (strip leading slash)
1286
+ const path = pathname.replace(/^\//, "");
1287
+
1288
+ // Parse input from query params
1289
+ const inputParam = url.searchParams.get("input");
1290
+ let input: unknown;
1291
+ if (inputParam) {
1292
+ try {
1293
+ input = JSON.parse(inputParam);
1294
+ } catch {
1295
+ // Return SSE error for malformed JSON
1296
+ const encoder = new TextEncoder();
1297
+ const errorStream = new ReadableStream({
1298
+ start(controller) {
1299
+ const data = `event: error\ndata: ${JSON.stringify({ error: "Invalid input JSON" })}\n\n`;
1300
+ controller.enqueue(encoder.encode(data));
1301
+ controller.close();
1302
+ },
1303
+ });
1304
+ return new Response(errorStream, {
1305
+ headers: {
1306
+ "Content-Type": "text/event-stream",
1307
+ "Cache-Control": "no-cache",
1308
+ Connection: "keep-alive",
1309
+ },
1310
+ });
1311
+ }
1312
+ }
1313
+
1314
+ // Create SSE stream
1315
+ const self = this;
1316
+ const stream = new ReadableStream({
1317
+ start(controller) {
1318
+ const encoder = new TextEncoder();
1319
+
1320
+ try {
1321
+ const result = self.execute({ path, input });
1322
+
1323
+ if (result && typeof result === "object" && "subscribe" in result) {
1324
+ const observable = result as {
1325
+ subscribe: (handlers: {
1326
+ next: (value: unknown) => void;
1327
+ error: (err: Error) => void;
1328
+ complete: () => void;
1329
+ }) => { unsubscribe: () => void };
1330
+ };
1331
+
1332
+ const subscription = observable.subscribe({
1333
+ next: (value) => {
1334
+ // Send full LensResult envelope
1335
+ const data = `data: ${JSON.stringify(value)}\n\n`;
1336
+ controller.enqueue(encoder.encode(data));
1337
+ },
1338
+ error: (err) => {
1339
+ const data = `event: error\ndata: ${JSON.stringify({ error: err.message })}\n\n`;
1340
+ controller.enqueue(encoder.encode(data));
1341
+ controller.close();
1342
+ },
1343
+ complete: () => {
1344
+ controller.close();
1345
+ },
1346
+ });
1347
+
1348
+ // Clean up on abort
1349
+ if (request.signal) {
1350
+ request.signal.addEventListener("abort", () => {
1351
+ subscription.unsubscribe();
1352
+ controller.close();
1353
+ });
1354
+ }
1355
+ }
1356
+ } catch (execError) {
1357
+ const errMsg = execError instanceof Error ? execError.message : "Internal error";
1358
+ const data = `event: error\ndata: ${JSON.stringify({ error: errMsg })}\n\n`;
1359
+ controller.enqueue(encoder.encode(data));
1360
+ controller.close();
1361
+ }
1362
+ },
1363
+ });
1364
+
1365
+ return new Response(stream, {
1366
+ headers: {
1367
+ "Content-Type": "text/event-stream",
1368
+ "Cache-Control": "no-cache",
1369
+ Connection: "keep-alive",
1370
+ },
1371
+ });
1372
+ }
1373
+
1279
1374
  // Operations: POST to any path (client sends path in body)
1280
1375
  if (request.method === "POST") {
1281
1376
  let body: { path?: string; input?: unknown };