@flink-app/streaming-plugin 0.12.1-alpha.45

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.
@@ -0,0 +1,276 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.StreamingPlugin = void 0;
40
+ exports.streamingPlugin = streamingPlugin;
41
+ var flink_1 = require("@flink-app/flink");
42
+ /**
43
+ * Creates a stream writer for the given format
44
+ */
45
+ function createStreamWriter(res, format, debug) {
46
+ var closed = false;
47
+ res.on("close", function () {
48
+ closed = true;
49
+ if (debug) {
50
+ flink_1.log.debug("[StreamingPlugin] Client closed connection");
51
+ }
52
+ });
53
+ return {
54
+ write: function (data) {
55
+ if (closed) {
56
+ if (debug) {
57
+ flink_1.log.warn("[StreamingPlugin] Attempted write to closed stream");
58
+ }
59
+ return;
60
+ }
61
+ try {
62
+ if (format === "sse") {
63
+ // SSE format: data: {json}\n\n
64
+ res.write("data: ".concat(JSON.stringify(data), "\n\n"));
65
+ }
66
+ else if (format === "ndjson") {
67
+ // NDJSON format: {json}\n
68
+ res.write("".concat(JSON.stringify(data), "\n"));
69
+ }
70
+ }
71
+ catch (err) {
72
+ flink_1.log.error("[StreamingPlugin] Error writing to stream:", err);
73
+ closed = true;
74
+ }
75
+ },
76
+ error: function (error) {
77
+ if (closed)
78
+ return;
79
+ var errorMessage = typeof error === "string" ? error : error.message;
80
+ try {
81
+ if (format === "sse") {
82
+ // SSE error event
83
+ res.write("event: error\ndata: ".concat(JSON.stringify({ message: errorMessage }), "\n\n"));
84
+ }
85
+ else if (format === "ndjson") {
86
+ // NDJSON error object
87
+ res.write("".concat(JSON.stringify({ error: errorMessage }), "\n"));
88
+ }
89
+ }
90
+ catch (err) {
91
+ flink_1.log.error("[StreamingPlugin] Error writing error to stream:", err);
92
+ closed = true;
93
+ }
94
+ },
95
+ end: function () {
96
+ if (closed)
97
+ return;
98
+ closed = true;
99
+ res.end();
100
+ },
101
+ isOpen: function () {
102
+ return !closed;
103
+ },
104
+ };
105
+ }
106
+ /**
107
+ * Streaming plugin for Flink Framework
108
+ * Provides SSE and NDJSON streaming support
109
+ */
110
+ var StreamingPlugin = /** @class */ (function () {
111
+ function StreamingPlugin(options) {
112
+ if (options === void 0) { options = {}; }
113
+ this.id = "streaming";
114
+ this.options = {
115
+ defaultFormat: options.defaultFormat || "sse",
116
+ debug: options.debug || false,
117
+ };
118
+ }
119
+ StreamingPlugin.prototype.init = function (app) {
120
+ return __awaiter(this, void 0, void 0, function () {
121
+ return __generator(this, function (_a) {
122
+ this.app = app;
123
+ if (this.options.debug) {
124
+ flink_1.log.info("[StreamingPlugin] Initialized with default format: ".concat(this.options.defaultFormat));
125
+ }
126
+ return [2 /*return*/];
127
+ });
128
+ });
129
+ };
130
+ // Implementation
131
+ StreamingPlugin.prototype.registerStreamHandler = function (handlerOrModule, routeProps) {
132
+ var _this = this;
133
+ if (!this.app) {
134
+ throw new Error("[StreamingPlugin] Plugin not initialized - call app.start() first");
135
+ }
136
+ if (!this.app.expressApp) {
137
+ throw new Error("[StreamingPlugin] Express app not available");
138
+ }
139
+ // Determine if we received a module or explicit handler+props
140
+ var handler;
141
+ var props;
142
+ if (typeof handlerOrModule === "function") {
143
+ // Explicit handler function + route props
144
+ if (!routeProps) {
145
+ throw new Error("[StreamingPlugin] Route props are required when registering with explicit handler function");
146
+ }
147
+ handler = handlerOrModule;
148
+ props = routeProps;
149
+ }
150
+ else {
151
+ // Handler module with default export and Route
152
+ var module_1 = handlerOrModule;
153
+ if (!module_1.default) {
154
+ throw new Error("[StreamingPlugin] Handler module must have a default export");
155
+ }
156
+ if (!module_1.Route) {
157
+ throw new Error("[StreamingPlugin] Handler module must export Route configuration");
158
+ }
159
+ handler = module_1.default;
160
+ props = module_1.Route;
161
+ }
162
+ var method = props.method || flink_1.HttpMethod.get;
163
+ var format = props.format || this.options.defaultFormat;
164
+ var methodAndRoute = "".concat(method.toUpperCase(), " ").concat(props.path);
165
+ // Register Express route
166
+ this.app.expressApp[method](props.path, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
167
+ var authenticated, err_1, streamWriter, err_2;
168
+ return __generator(this, function (_a) {
169
+ switch (_a.label) {
170
+ case 0:
171
+ if (!props.permissions) return [3 /*break*/, 4];
172
+ if (!this.app.auth) {
173
+ flink_1.log.error("[StreamingPlugin] ".concat(methodAndRoute, " requires authentication but no auth plugin is configured"));
174
+ res.status(500).json({
175
+ status: 500,
176
+ error: {
177
+ title: "Internal Server Error",
178
+ detail: "Authentication not configured",
179
+ },
180
+ });
181
+ return [2 /*return*/];
182
+ }
183
+ _a.label = 1;
184
+ case 1:
185
+ _a.trys.push([1, 3, , 4]);
186
+ return [4 /*yield*/, this.app.auth.authenticateRequest(req, props.permissions)];
187
+ case 2:
188
+ authenticated = _a.sent();
189
+ if (!authenticated) {
190
+ res.status(401).json({
191
+ status: 401,
192
+ error: {
193
+ title: "Unauthorized",
194
+ detail: "Authentication required",
195
+ },
196
+ });
197
+ return [2 /*return*/];
198
+ }
199
+ return [3 /*break*/, 4];
200
+ case 3:
201
+ err_1 = _a.sent();
202
+ flink_1.log.error("[StreamingPlugin] ".concat(methodAndRoute, " authentication error:"), err_1);
203
+ res.status(401).json({
204
+ status: 401,
205
+ error: {
206
+ title: "Unauthorized",
207
+ detail: "Authentication failed",
208
+ },
209
+ });
210
+ return [2 /*return*/];
211
+ case 4:
212
+ // Set headers based on format
213
+ if (format === "sse") {
214
+ res.setHeader("Content-Type", "text/event-stream");
215
+ res.setHeader("Cache-Control", "no-cache");
216
+ res.setHeader("Connection", "keep-alive");
217
+ res.setHeader("X-Accel-Buffering", "no"); // Disable nginx buffering
218
+ }
219
+ else if (format === "ndjson") {
220
+ res.setHeader("Content-Type", "application/x-ndjson");
221
+ res.setHeader("Cache-Control", "no-cache");
222
+ res.setHeader("X-Accel-Buffering", "no");
223
+ }
224
+ // Flush headers immediately
225
+ res.flushHeaders();
226
+ streamWriter = createStreamWriter(res, format, this.options.debug);
227
+ _a.label = 5;
228
+ case 5:
229
+ _a.trys.push([5, 7, 8, 9]);
230
+ if (this.options.debug) {
231
+ flink_1.log.debug("[StreamingPlugin] ".concat(methodAndRoute, " - Stream started"));
232
+ }
233
+ // Call the handler
234
+ return [4 /*yield*/, handler({
235
+ req: req,
236
+ ctx: this.app.ctx,
237
+ stream: streamWriter,
238
+ origin: props.origin,
239
+ })];
240
+ case 6:
241
+ // Call the handler
242
+ _a.sent();
243
+ if (this.options.debug) {
244
+ flink_1.log.debug("[StreamingPlugin] ".concat(methodAndRoute, " - Handler completed"));
245
+ }
246
+ return [3 /*break*/, 9];
247
+ case 7:
248
+ err_2 = _a.sent();
249
+ flink_1.log.error("[StreamingPlugin] ".concat(methodAndRoute, " - Handler error:"), err_2);
250
+ // Send error to client if stream still open
251
+ if (streamWriter.isOpen()) {
252
+ streamWriter.error(err_2);
253
+ }
254
+ return [3 /*break*/, 9];
255
+ case 8:
256
+ // Ensure stream is closed
257
+ if (streamWriter.isOpen()) {
258
+ streamWriter.end();
259
+ }
260
+ return [7 /*endfinally*/];
261
+ case 9: return [2 /*return*/];
262
+ }
263
+ });
264
+ }); });
265
+ flink_1.log.info("[StreamingPlugin] Registered streaming route ".concat(methodAndRoute, " (").concat(format, ")"));
266
+ };
267
+ return StreamingPlugin;
268
+ }());
269
+ exports.StreamingPlugin = StreamingPlugin;
270
+ /**
271
+ * Factory function to create streaming plugin
272
+ */
273
+ function streamingPlugin(options) {
274
+ return new StreamingPlugin(options);
275
+ }
276
+ //# sourceMappingURL=StreamingPlugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamingPlugin.js","sourceRoot":"","sources":["../src/StreamingPlugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { StreamingPlugin, streamingPlugin } from "./StreamingPlugin";
2
+ export type { StreamFormat, StreamWriter, StreamHandlerProps, StreamHandler, StreamHandlerModule, StreamingRouteProps, StreamingPluginOptions, } from "./types";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGrE,YAAY,EACR,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,GACzB,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.streamingPlugin = exports.StreamingPlugin = void 0;
4
+ // Main plugin export
5
+ var StreamingPlugin_1 = require("./StreamingPlugin");
6
+ Object.defineProperty(exports, "StreamingPlugin", { enumerable: true, get: function () { return StreamingPlugin_1.StreamingPlugin; } });
7
+ Object.defineProperty(exports, "streamingPlugin", { enumerable: true, get: function () { return StreamingPlugin_1.streamingPlugin; } });
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qBAAqB;AACrB,qDAAqE;AAA5D,kHAAA,eAAe,OAAA;AAAE,kHAAA,eAAe,OAAA"}
@@ -0,0 +1,86 @@
1
+ import { FlinkContext, FlinkRequest, RouteProps, HttpMethod } from "@flink-app/flink";
2
+ /**
3
+ * Streaming format types
4
+ */
5
+ export type StreamFormat = "sse" | "ndjson";
6
+ /**
7
+ * Stream writer interface for sending data to clients
8
+ */
9
+ export interface StreamWriter<T = any> {
10
+ /**
11
+ * Write data to the stream
12
+ */
13
+ write(data: T): void;
14
+ /**
15
+ * Send an error event to the stream
16
+ */
17
+ error(error: Error | string): void;
18
+ /**
19
+ * End the stream
20
+ */
21
+ end(): void;
22
+ /**
23
+ * Check if the connection is still open
24
+ */
25
+ isOpen(): boolean;
26
+ }
27
+ /**
28
+ * Props passed to streaming handlers
29
+ */
30
+ export interface StreamHandlerProps<Ctx extends FlinkContext, T = any> {
31
+ req: FlinkRequest;
32
+ ctx: Ctx;
33
+ stream: StreamWriter<T>;
34
+ origin?: string;
35
+ }
36
+ /**
37
+ * Streaming handler function signature
38
+ */
39
+ export type StreamHandler<Ctx extends FlinkContext, T = any> = (props: StreamHandlerProps<Ctx, T>) => Promise<void> | void;
40
+ /**
41
+ * Route configuration for streaming endpoints
42
+ */
43
+ export interface StreamingRouteProps extends Omit<RouteProps, "skipAutoRegister"> {
44
+ /**
45
+ * HTTP path for the streaming endpoint
46
+ */
47
+ path: string;
48
+ /**
49
+ * HTTP method (defaults to GET)
50
+ */
51
+ method?: HttpMethod;
52
+ /**
53
+ * Streaming format to use
54
+ * - 'sse': Server-Sent Events (text/event-stream)
55
+ * - 'ndjson': Newline-Delimited JSON (application/x-ndjson)
56
+ * @default 'sse'
57
+ */
58
+ format?: StreamFormat;
59
+ /**
60
+ * Must be true for streaming handlers (prevents auto-registration)
61
+ */
62
+ skipAutoRegister: true;
63
+ }
64
+ /**
65
+ * Options for creating the streaming plugin
66
+ */
67
+ export interface StreamingPluginOptions {
68
+ /**
69
+ * Default format if not specified in route props
70
+ * @default 'sse'
71
+ */
72
+ defaultFormat?: StreamFormat;
73
+ /**
74
+ * Enable debug logging
75
+ * @default false
76
+ */
77
+ debug?: boolean;
78
+ }
79
+ /**
80
+ * Handler module type (namespace import with default export and Route)
81
+ */
82
+ export interface StreamHandlerModule<Ctx extends FlinkContext = any, T = any> {
83
+ default: StreamHandler<Ctx, T>;
84
+ Route: StreamingRouteProps;
85
+ }
86
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,QAAQ,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,GAAG;IACjC;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IAErB;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC;IAEnC;;OAEG;IACH,GAAG,IAAI,IAAI,CAAC;IAEZ;;OAEG;IACH,MAAM,IAAI,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,GAAG,SAAS,YAAY,EAAE,CAAC,GAAG,GAAG;IACjE,GAAG,EAAE,YAAY,CAAC;IAClB,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,CAAC,GAAG,SAAS,YAAY,EAAE,CAAC,GAAG,GAAG,IAAI,CAC3D,KAAK,EAAE,kBAAkB,CAAC,GAAG,EAAE,CAAC,CAAC,KAChC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE1B;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC;IAC7E;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IAEpB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,gBAAgB,EAAE,IAAI,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACnC;;;OAGG;IACH,aAAa,CAAC,EAAE,YAAY,CAAC;IAE7B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,GAAG,SAAS,YAAY,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG;IACxE,OAAO,EAAE,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC/B,KAAK,EAAE,mBAAmB,CAAC;CAC9B"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Example: Authenticated Streaming Handler
3
+ *
4
+ * This example shows how to use authentication with streaming handlers.
5
+ * Works with any Flink auth plugin (JWT, BankID, OAuth, etc.)
6
+ */
7
+
8
+ import { StreamHandler, StreamingRouteProps } from "@flink-app/streaming-plugin";
9
+ import { FlinkContext } from "@flink-app/flink";
10
+
11
+ // Route configuration with permissions
12
+ export const Route: StreamingRouteProps = {
13
+ path: "/admin/live-metrics",
14
+ format: "sse",
15
+ skipAutoRegister: true,
16
+ permissions: ["admin"], // Only users with 'admin' role can access
17
+ };
18
+
19
+ interface MetricEvent {
20
+ metric: string;
21
+ value: number;
22
+ timestamp: number;
23
+ userId: string;
24
+ }
25
+
26
+ /**
27
+ * Authenticated streaming handler
28
+ * Only accessible to users with 'admin' permission
29
+ */
30
+ const GetLiveMetrics: StreamHandler<FlinkContext, MetricEvent> = async ({ req, stream }) => {
31
+ // req.user is populated by the auth plugin after successful authentication
32
+ const userId = (req as any).user?.userId || "unknown";
33
+
34
+ console.log(`Admin user ${userId} connected to live metrics stream`);
35
+
36
+ let count = 0;
37
+ const maxMetrics = 20;
38
+
39
+ // Stream metrics every second
40
+ const interval = setInterval(() => {
41
+ if (!stream.isOpen() || count >= maxMetrics) {
42
+ clearInterval(interval);
43
+ stream.end();
44
+ console.log(`User ${userId} disconnected from metrics stream`);
45
+ return;
46
+ }
47
+
48
+ // Send metric with user context
49
+ stream.write({
50
+ metric: "cpu_usage",
51
+ value: Math.random() * 100,
52
+ timestamp: Date.now(),
53
+ userId,
54
+ });
55
+
56
+ count++;
57
+ }, 1000);
58
+ };
59
+
60
+ export default GetLiveMetrics;
61
+
62
+ /**
63
+ * CLIENT USAGE:
64
+ *
65
+ * ```typescript
66
+ * // Must include authentication header (depends on your auth plugin)
67
+ * const eventSource = new EventSource('/admin/live-metrics', {
68
+ * headers: {
69
+ * 'Authorization': 'Bearer your-jwt-token'
70
+ * }
71
+ * });
72
+ *
73
+ * eventSource.onmessage = (event) => {
74
+ * const metric = JSON.parse(event.data);
75
+ * console.log(`Metric from user ${metric.userId}:`, metric);
76
+ * };
77
+ *
78
+ * eventSource.onerror = (error) => {
79
+ * console.error('Authentication failed or connection lost');
80
+ * };
81
+ * ```
82
+ *
83
+ * Note: EventSource API doesn't support custom headers in browsers.
84
+ * For authenticated SSE, you may need to:
85
+ * 1. Pass token as query parameter: `/admin/live-metrics?token=...`
86
+ * 2. Use cookie-based authentication
87
+ * 3. Use Fetch API with ReadableStream instead of EventSource
88
+ */
@@ -0,0 +1,157 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NDJSON Streaming Example</title>
7
+ <style>
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
+ max-width: 800px;
11
+ margin: 50px auto;
12
+ padding: 20px;
13
+ }
14
+ #output {
15
+ background: #f5f5f5;
16
+ padding: 20px;
17
+ border-radius: 8px;
18
+ min-height: 200px;
19
+ white-space: pre-wrap;
20
+ font-family: 'Monaco', monospace;
21
+ font-size: 14px;
22
+ }
23
+ button {
24
+ background: #007bff;
25
+ color: white;
26
+ border: none;
27
+ padding: 10px 20px;
28
+ border-radius: 4px;
29
+ cursor: pointer;
30
+ font-size: 16px;
31
+ }
32
+ button:hover {
33
+ background: #0056b3;
34
+ }
35
+ button:disabled {
36
+ background: #ccc;
37
+ cursor: not-allowed;
38
+ }
39
+ input {
40
+ padding: 10px;
41
+ font-size: 16px;
42
+ border: 1px solid #ddd;
43
+ border-radius: 4px;
44
+ width: 300px;
45
+ }
46
+ .controls {
47
+ margin-bottom: 20px;
48
+ }
49
+ </style>
50
+ </head>
51
+ <body>
52
+ <h1>NDJSON Streaming Example</h1>
53
+ <p>This example demonstrates consuming NDJSON streams from Flink (LLM-style chat streaming).</p>
54
+
55
+ <div class="controls">
56
+ <input type="text" id="prompt" placeholder="Enter your prompt..." value="Tell me a story">
57
+ <button id="startBtn" onclick="startStream()">Start Stream</button>
58
+ <button id="stopBtn" onclick="stopStream()" disabled>Stop Stream</button>
59
+ </div>
60
+
61
+ <h3>Response:</h3>
62
+ <div id="output"></div>
63
+
64
+ <script>
65
+ let reader = null;
66
+ let controller = null;
67
+
68
+ async function startStream() {
69
+ const prompt = document.getElementById('prompt').value;
70
+ const output = document.getElementById('output');
71
+ const startBtn = document.getElementById('startBtn');
72
+ const stopBtn = document.getElementById('stopBtn');
73
+
74
+ output.textContent = '';
75
+ startBtn.disabled = true;
76
+ stopBtn.disabled = false;
77
+
78
+ try {
79
+ const response = await fetch(`http://localhost:3333/chat/stream?prompt=${encodeURIComponent(prompt)}`);
80
+
81
+ if (!response.ok) {
82
+ throw new Error(`HTTP error! status: ${response.status}`);
83
+ }
84
+
85
+ reader = response.body.getReader();
86
+ const decoder = new TextDecoder();
87
+ let buffer = '';
88
+
89
+ while (true) {
90
+ const { done, value } = await reader.read();
91
+
92
+ if (done) {
93
+ console.log('Stream complete');
94
+ break;
95
+ }
96
+
97
+ // Decode chunk
98
+ buffer += decoder.decode(value, { stream: true });
99
+
100
+ // Split by newlines
101
+ const lines = buffer.split('\n');
102
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
103
+
104
+ // Process each complete line
105
+ for (const line of lines) {
106
+ if (line.trim()) {
107
+ try {
108
+ const event = JSON.parse(line);
109
+
110
+ // Append delta to output
111
+ if (event.delta) {
112
+ output.textContent += event.delta;
113
+ }
114
+
115
+ // Check for completion
116
+ if (event.done) {
117
+ console.log('Stream marked as done');
118
+ output.textContent += '\n\n[Stream complete]';
119
+ reader = null;
120
+ startBtn.disabled = false;
121
+ stopBtn.disabled = true;
122
+ return;
123
+ }
124
+
125
+ // Handle errors
126
+ if (event.error) {
127
+ output.textContent += `\n\nError: ${event.error}`;
128
+ throw new Error(event.error);
129
+ }
130
+ } catch (parseError) {
131
+ console.error('Failed to parse JSON:', parseError, 'Line:', line);
132
+ }
133
+ }
134
+ }
135
+ }
136
+ } catch (error) {
137
+ console.error('Stream error:', error);
138
+ output.textContent += `\n\nError: ${error.message}`;
139
+ } finally {
140
+ reader = null;
141
+ startBtn.disabled = false;
142
+ stopBtn.disabled = true;
143
+ }
144
+ }
145
+
146
+ function stopStream() {
147
+ if (reader) {
148
+ reader.cancel();
149
+ reader = null;
150
+ document.getElementById('output').textContent += '\n\n[Stream stopped by user]';
151
+ document.getElementById('startBtn').disabled = false;
152
+ document.getElementById('stopBtn').disabled = true;
153
+ }
154
+ }
155
+ </script>
156
+ </body>
157
+ </html>