@sylphx/lens-server 4.1.3 → 4.1.5

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,107 @@ class LensServerImpl {
1045
1045
  headers: baseHeaders
1046
1046
  });
1047
1047
  }
1048
+ if (request.method === "GET" && pathname.endsWith("/__lens/sse")) {
1049
+ const path = url.searchParams.get("path");
1050
+ if (!path) {
1051
+ const encoder = new TextEncoder;
1052
+ const errorStream = new ReadableStream({
1053
+ start(controller) {
1054
+ const data = `event: error
1055
+ data: ${JSON.stringify({ error: "Missing path parameter" })}
1056
+
1057
+ `;
1058
+ controller.enqueue(encoder.encode(data));
1059
+ controller.close();
1060
+ }
1061
+ });
1062
+ return new Response(errorStream, {
1063
+ headers: {
1064
+ "Content-Type": "text/event-stream",
1065
+ "Cache-Control": "no-cache",
1066
+ Connection: "keep-alive"
1067
+ }
1068
+ });
1069
+ }
1070
+ const inputParam = url.searchParams.get("input");
1071
+ let input;
1072
+ if (inputParam) {
1073
+ try {
1074
+ input = JSON.parse(inputParam);
1075
+ } catch {
1076
+ const encoder = new TextEncoder;
1077
+ const errorStream = new ReadableStream({
1078
+ start(controller) {
1079
+ const data = `event: error
1080
+ data: ${JSON.stringify({ error: "Invalid input JSON" })}
1081
+
1082
+ `;
1083
+ controller.enqueue(encoder.encode(data));
1084
+ controller.close();
1085
+ }
1086
+ });
1087
+ return new Response(errorStream, {
1088
+ headers: {
1089
+ "Content-Type": "text/event-stream",
1090
+ "Cache-Control": "no-cache",
1091
+ Connection: "keep-alive"
1092
+ }
1093
+ });
1094
+ }
1095
+ }
1096
+ const self = this;
1097
+ const stream = new ReadableStream({
1098
+ start(controller) {
1099
+ const encoder = new TextEncoder;
1100
+ try {
1101
+ const result = self.execute({ path, input });
1102
+ if (result && typeof result === "object" && "subscribe" in result) {
1103
+ const observable = result;
1104
+ const subscription = observable.subscribe({
1105
+ next: (value) => {
1106
+ const data = `data: ${JSON.stringify(value)}
1107
+
1108
+ `;
1109
+ controller.enqueue(encoder.encode(data));
1110
+ },
1111
+ error: (err) => {
1112
+ const data = `event: error
1113
+ data: ${JSON.stringify({ error: err.message })}
1114
+
1115
+ `;
1116
+ controller.enqueue(encoder.encode(data));
1117
+ controller.close();
1118
+ },
1119
+ complete: () => {
1120
+ controller.close();
1121
+ }
1122
+ });
1123
+ if (request.signal) {
1124
+ request.signal.addEventListener("abort", () => {
1125
+ subscription.unsubscribe();
1126
+ controller.close();
1127
+ });
1128
+ }
1129
+ }
1130
+ } catch (execError) {
1131
+ const errMsg = execError instanceof Error ? execError.message : "Internal error";
1132
+ const data = `event: error
1133
+ data: ${JSON.stringify({ error: errMsg })}
1134
+
1135
+ `;
1136
+ controller.enqueue(encoder.encode(data));
1137
+ controller.close();
1138
+ }
1139
+ }
1140
+ });
1141
+ return new Response(stream, {
1142
+ headers: {
1143
+ "Content-Type": "text/event-stream",
1144
+ "Cache-Control": "no-cache",
1145
+ Connection: "keep-alive"
1146
+ }
1147
+ });
1148
+ }
1048
1149
  if (request.method === "POST") {
1049
1150
  let body;
1050
1151
  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.5",
4
4
  "description": "Server runtime for Lens API framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1276,6 +1276,117 @@ class LensServerImpl<
1276
1276
  });
1277
1277
  }
1278
1278
 
1279
+ // SSE: GET /__lens/sse?path={path}&input={...}
1280
+ // Dedicated endpoint with path in query param - consistent with POST body format
1281
+ // This avoids base path parsing issues when server is mounted at /api etc.
1282
+ if (request.method === "GET" && pathname.endsWith("/__lens/sse")) {
1283
+ const path = url.searchParams.get("path");
1284
+
1285
+ // Validate path parameter
1286
+ if (!path) {
1287
+ const encoder = new TextEncoder();
1288
+ const errorStream = new ReadableStream({
1289
+ start(controller) {
1290
+ const data = `event: error\ndata: ${JSON.stringify({ error: "Missing path parameter" })}\n\n`;
1291
+ controller.enqueue(encoder.encode(data));
1292
+ controller.close();
1293
+ },
1294
+ });
1295
+ return new Response(errorStream, {
1296
+ headers: {
1297
+ "Content-Type": "text/event-stream",
1298
+ "Cache-Control": "no-cache",
1299
+ Connection: "keep-alive",
1300
+ },
1301
+ });
1302
+ }
1303
+
1304
+ // Parse input from query params
1305
+ const inputParam = url.searchParams.get("input");
1306
+ let input: unknown;
1307
+ if (inputParam) {
1308
+ try {
1309
+ input = JSON.parse(inputParam);
1310
+ } catch {
1311
+ // Return SSE error for malformed JSON
1312
+ const encoder = new TextEncoder();
1313
+ const errorStream = new ReadableStream({
1314
+ start(controller) {
1315
+ const data = `event: error\ndata: ${JSON.stringify({ error: "Invalid input JSON" })}\n\n`;
1316
+ controller.enqueue(encoder.encode(data));
1317
+ controller.close();
1318
+ },
1319
+ });
1320
+ return new Response(errorStream, {
1321
+ headers: {
1322
+ "Content-Type": "text/event-stream",
1323
+ "Cache-Control": "no-cache",
1324
+ Connection: "keep-alive",
1325
+ },
1326
+ });
1327
+ }
1328
+ }
1329
+
1330
+ // Create SSE stream
1331
+ const self = this;
1332
+ const stream = new ReadableStream({
1333
+ start(controller) {
1334
+ const encoder = new TextEncoder();
1335
+
1336
+ try {
1337
+ const result = self.execute({ path, input });
1338
+
1339
+ if (result && typeof result === "object" && "subscribe" in result) {
1340
+ const observable = result as {
1341
+ subscribe: (handlers: {
1342
+ next: (value: unknown) => void;
1343
+ error: (err: Error) => void;
1344
+ complete: () => void;
1345
+ }) => { unsubscribe: () => void };
1346
+ };
1347
+
1348
+ const subscription = observable.subscribe({
1349
+ next: (value) => {
1350
+ // Send full LensResult envelope
1351
+ const data = `data: ${JSON.stringify(value)}\n\n`;
1352
+ controller.enqueue(encoder.encode(data));
1353
+ },
1354
+ error: (err) => {
1355
+ const data = `event: error\ndata: ${JSON.stringify({ error: err.message })}\n\n`;
1356
+ controller.enqueue(encoder.encode(data));
1357
+ controller.close();
1358
+ },
1359
+ complete: () => {
1360
+ controller.close();
1361
+ },
1362
+ });
1363
+
1364
+ // Clean up on abort
1365
+ if (request.signal) {
1366
+ request.signal.addEventListener("abort", () => {
1367
+ subscription.unsubscribe();
1368
+ controller.close();
1369
+ });
1370
+ }
1371
+ }
1372
+ } catch (execError) {
1373
+ const errMsg = execError instanceof Error ? execError.message : "Internal error";
1374
+ const data = `event: error\ndata: ${JSON.stringify({ error: errMsg })}\n\n`;
1375
+ controller.enqueue(encoder.encode(data));
1376
+ controller.close();
1377
+ }
1378
+ },
1379
+ });
1380
+
1381
+ return new Response(stream, {
1382
+ headers: {
1383
+ "Content-Type": "text/event-stream",
1384
+ "Cache-Control": "no-cache",
1385
+ Connection: "keep-alive",
1386
+ },
1387
+ });
1388
+ }
1389
+
1279
1390
  // Operations: POST to any path (client sends path in body)
1280
1391
  if (request.method === "POST") {
1281
1392
  let body: { path?: string; input?: unknown };