datastar-ssegen 0.0.2 → 0.1.1

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.
@@ -1,4 +1,3 @@
1
- # publish to npm
2
1
  name: Publish Package
3
2
  on:
4
3
  push:
@@ -8,12 +7,12 @@ on:
8
7
  types:
9
8
  - created
10
9
  jobs:
11
- publish:
10
+ publish-npm:
12
11
  runs-on: ubuntu-latest
13
12
  steps:
14
13
  - name: Checkout code
15
14
  uses: actions/checkout@v3
16
- - name: Setup Node.js environment
15
+ - name: Setup Node.js environment for npm
17
16
  uses: actions/setup-node@v3
18
17
  with:
19
18
  node-version: '18'
package/README.md CHANGED
@@ -34,7 +34,7 @@ const app = express();
34
34
  app.use(express.json());
35
35
 
36
36
  app.get('/qoute', (req,res)=> {
37
- const sse = ServerSentEventGenerator.init(req, res);
37
+ const sse = ServerSentEventGenerator(req, res);
38
38
  const qoutes = [
39
39
  "Any app that can be written in JavaScript, will eventually be written in JavaScript. - Jeff Atwood",
40
40
  "JavaScript is the world's most misunderstood programming language. - Douglas Crockford",
@@ -46,7 +46,7 @@ const sse = ServerSentEventGenerator.init(req, res);
46
46
  res.end();
47
47
  });
48
48
  app.get('/clock', (req, res)=> {
49
- const sse = ServerSentEventGenerator.init(req, res);
49
+ const sse = ServerSentEventGenerator(req, res);
50
50
  setInterval(async () => {
51
51
  await sse.MergeFragments(`<div id="clock">Current Time: ${new Date()}</div>`);
52
52
  }, 1000);
@@ -84,9 +84,7 @@ Here's a simple HTML page to interact with the server:
84
84
 
85
85
  The `ServerSentEventGenerator` provides several functions to facilitate communication with connected Datastar clients using Server-Sent Events:
86
86
 
87
- - **`init(request, response)`**: Initializes SSE communication with the specified request and response.
88
-
89
- - **`_send(eventType, dataLines, sendOptions)`**: Sends a server-sent event (SSE) to the client. Options include setting an `eventId` and defining `retryDuration`.
87
+ - **`ServerSentEventGenerator(request, response)`**: Initializes SSE communication with the specified request and response.
90
88
 
91
89
  - **`ReadSignals(signals)`**: Reads and merges signals based on HTTP methods with predefined signals, useful for parsing query or body data sent to the server.
92
90
 
@@ -45,7 +45,7 @@ export const homepage = (name = "") => {
45
45
  };
46
46
 
47
47
  export const handleQuote = async (req, res) => {
48
- const sse = ServerSentEventGenerator.init(req, res);
48
+ const sse = ServerSentEventGenerator(req, res);
49
49
  const qoutes = [
50
50
  "Any app that can be written in JavaScript, will eventually be written in JavaScript. - Jeff Atwood",
51
51
  "JavaScript is the world's most misunderstood programming language. - Douglas Crockford",
@@ -58,34 +58,37 @@ export const handleQuote = async (req, res) => {
58
58
  };
59
59
 
60
60
  export const handleReadSignals = async (req, res) => {
61
- const sse = ServerSentEventGenerator.init(req, res);
61
+ console.log(req.method, "readSignals?");
62
+ const sse = ServerSentEventGenerator(req, res);
62
63
  backendStore = await sse.ReadSignals(backendStore);
63
64
  console.log("backendStore updated", backendStore);
64
65
  res.end();
65
66
  };
66
67
 
67
68
  export const handleRemoveTrash = async (req, res) => {
68
- const sse = ServerSentEventGenerator.init(req, res);
69
+ const sse = ServerSentEventGenerator(req, res);
69
70
  await sse.RemoveFragments("#trash");
70
71
  await sse.MergeSignals({ lastUpdate: Date.now() });
71
72
  res.end();
72
73
  };
73
74
 
74
75
  export const handleExecuteScript = async (req, res) => {
75
- const sse = ServerSentEventGenerator.init(req, res);
76
- await sse.ExecuteScript(`console.log("Hello from the backend!")`);
76
+ const sse = ServerSentEventGenerator(req, res);
77
+ await sse.ExecuteScript(`console.log("Hello from the backend!"); //My comment
78
+ //What avbout this?
79
+ console.log('second consolelog on new line');`);
77
80
  await sse.MergeSignals({ lastUpdate: Date.now() });
78
81
  res.end();
79
82
  };
80
83
 
81
84
  export const handleClock = async function (req, res) {
82
- const sse = ServerSentEventGenerator.init(req, res);
85
+ const sse = ServerSentEventGenerator(req, res);
83
86
  setInterval(async () => {
84
87
  await sse.MergeFragments(`<div id="clock">${new Date()}</div>`);
85
88
  }, 1000);
86
89
  };
87
90
 
88
91
  export const handleRemoveSignal = async (req, res) => {
89
- const sse = ServerSentEventGenerator.init(req, res);
92
+ const sse = ServerSentEventGenerator(req, res);
90
93
  await sse.RemoveSignals(["xyz"]);
91
94
  };
@@ -24,6 +24,7 @@ app.get("/", (req, res) => {
24
24
  app.get("/quote", handleQuote);
25
25
 
26
26
  app.get("/readSignals", handleReadSignals);
27
+ app.post("/readSignals", handleReadSignals);
27
28
 
28
29
  app.get("/removeTrash", handleRemoveTrash);
29
30
 
@@ -22,6 +22,7 @@ server.get("/", (req, res) => {
22
22
  server.get("/quote", handleQuote);
23
23
 
24
24
  server.get("/readSignals", handleReadSignals);
25
+ server.post("/readSignals", handleReadSignals);
25
26
 
26
27
  server.get("/removeTrash", handleRemoveTrash);
27
28
 
package/index.js CHANGED
@@ -1,273 +1,366 @@
1
+ // @ts-check
1
2
  import url from "url";
2
3
  import querystring from "querystring";
3
4
 
4
5
  /**
5
- * ServerSentEventGenerator is responsible for initializing and handling
6
- * server-sent events (SSE) for different web frameworks.
6
+ * @typedef {object} ServerSentEventMethods
7
+ * @property {Function} _send - Sends a server-sent event.
8
+ * @property {Function} ReadSignals - Reads signals based on HTTP methods and merges them with predefined signals.
9
+ * @property {Function} MergeFragments - Sends a merge fragments event with specified options.
10
+ * @property {Function} RemoveFragments - Sends a remove fragments event.
11
+ * @property {Function} MergeSignals - Sends a merge signals event, with merging options.
12
+ * @property {Function} RemoveSignals - Sends a remove signals event, requires signal paths.
13
+ * @property {Function} ExecuteScript - Executes a defined script on the client-side.
7
14
  */
8
- export const ServerSentEventGenerator = {
9
- /**
10
- * Initializes the server-sent event generator.
11
- *
12
- * @param {object} res - The response object from the framework.
13
- * @returns {Object} Methods for manipulating server-sent events.
14
- */
15
- init: function (request, response) {
16
- return {
17
- headersSent: false,
18
- req: request,
19
- res: response,
20
- /**
21
- * @typedef {Object} SendOptions
22
- * @property {?string} [eventId=null] - Event ID to attach.
23
- * @property {number} [retryDuration=1000] - Retry duration in milliseconds.
24
- */
25
15
 
26
- /**
27
- * Sends a server-sent event (SSE) to the client.
28
- *
29
- * @param {string} eventType - The type of the event.
30
- * @param {string[]} dataLines - Lines of data to send.
31
- * @param {SendOptions} [sendOptions] - Additional options for sending events.
32
- */
33
- _send: function (
34
- eventType,
35
- dataLines,
36
- sendOptions = {
37
- eventId: null,
38
- retryDuration: 1000,
39
- }
40
- ) {
41
- //Prepare the message for sending.
42
- let data = dataLines.map((line) => `data: ${line}\n`).join("") + "\n";
43
- let eventString = "";
44
- if (sendOptions.eventId != null) {
45
- eventString += `id: ${sendOptions.eventId}\n`;
46
- }
47
- if (eventType) {
48
- eventString += `event: ${eventType}\n`;
49
- }
50
- eventString += `retry: ${sendOptions.retryDuration}\n`;
51
- eventString += data;
16
+ /**
17
+ * @typedef {object} SendOptions
18
+ * @property {number|null} [eventId] - The ID of the event.
19
+ * @property {number|null} [retryDuration] - Duration in milliseconds to wait before attempting a retry.
20
+ */
52
21
 
53
- //Send Event
54
- if (!this.headersSent) {
55
- this.res.setHeader("Cache-Control", "nocache");
56
- this.res.setHeader("Connection", "keep-alive");
57
- this.res.setHeader("Content-Type", "text/event-stream");
58
- this.headersSent = true;
59
- }
60
- this.res.write(eventString);
22
+ /**
23
+ * @typedef {object} MergeFragmentsOptions
24
+ * @property {string|null} [selector] - CSS selector to scope the merge action.
25
+ * @property {string} [mergeMode=morph] - Mode to use for merging fragments.
26
+ * @property {number} [settleDuration=300] - Duration for settling the merge.
27
+ * @property {boolean|null} [useViewTransition] - Use CSS view transitions if supported.
28
+ * @property {number|null} [eventId] - Event ID for the merge fragments event.
29
+ * @property {number|null} [retryDuration] - Retry duration for the event.
30
+ */
61
31
 
62
- return eventString;
63
- },
32
+ /**
33
+ * @typedef {object} RemoveFragmentsOptions
34
+ * @property {number} [settleDuration] - Duration for settling the removal.
35
+ * @property {boolean|null} [useViewTransition] - Use CSS view transitions if supported.
36
+ * @property {number|null} [eventId] - Event ID for the remove fragments event.
37
+ * @property {number|null} [retryDuration] - Retry duration for the event.
38
+ */
64
39
 
65
- /**
66
- * Reads signals based on HTTP methods and merges them with provided signals.
67
- *
68
- * @param {object} signals - Predefined signals to merge with.
69
- * @returns {Promise<object>} Merged signals object.
70
- */
71
- ReadSignals: async function (signals) {
72
- if (this.req.method === "GET") {
73
- // Parse the URL
74
- const parsedUrl = url.parse(this.req.url);
75
- const parsedQuery = querystring.parse(parsedUrl.query);
76
- const datastarParam = parsedQuery.datastar;
40
+ /**
41
+ * @typedef {object} MergeSignalsOptions
42
+ * @property {boolean} [onlyIfMissing=false] - Merge only if the signal is missing.
43
+ * @property {number|null} [eventId] - Event ID for the merge signals event.
44
+ * @property {number|null} [retryDuration] - Retry duration for the event.
45
+ */
77
46
 
78
- const query = JSON.parse(datastarParam);
79
- return {
80
- ...signals,
81
- ...query,
82
- };
83
- } else {
84
- const body = await new Promise((resolve, reject) => {
47
+ /**
48
+ * @typedef {object} ExecuteScriptOptions
49
+ * @property {boolean|null} [autoRemove] - Automatically remove the script after execution.
50
+ * @property {number|null} [eventId] - Event ID for the execute script event.
51
+ * @property {number|null} [retryDuration] - Retry duration for the event.
52
+ */
53
+
54
+ /**
55
+ * @typedef {Object} HttpRequest
56
+ * @property {string} method - The HTTP method, e.g., 'GET', 'POST', etc.
57
+ * @property {string} url - The URL of the request.
58
+ * @property {Object.<string, string>} headers - The HTTP headers.
59
+ * @property {Object} [body] - The payload of the request.
60
+ * @property {Function} [json] - Parses the request body
61
+ * @property {Function} [on] - Adds event handlers to the request
62
+ */
63
+
64
+ /**
65
+ * @typedef {Object} HttpResponse
66
+ * @property {number} statusCode - The HTTP status code, e.g., 200, 404, etc.
67
+ * @property {Object.<string, string>} headers - The HTTP headers.
68
+ * @property {Object|string} [body] - The response body.
69
+ * @property {Function} setHeader - Sets a header on the response
70
+ * @property {Function} write - Writes to the response body
71
+ */
72
+
73
+ /**
74
+ * Initializes the server-sent event generator.
75
+ *
76
+ * @param {HttpRequest} request - The request object.
77
+ * @param {HttpResponse} response - The response object.
78
+ * @returns {ServerSentEventMethods} Methods for manipulating server-sent events.
79
+ */
80
+ export function ServerSentEventGenerator(request, response) {
81
+ const generatorMethods = {
82
+ headersSent: false,
83
+ req: request,
84
+ res: response,
85
+ /**
86
+ * Sends a server-sent event (SSE) to the client.
87
+ *
88
+ * @param {string} eventType - The type of the event.
89
+ * @param {string[]} dataLines - Lines of data to send.
90
+ * @param {SendOptions} [sendOptions] - Additional options for sending events.
91
+ */
92
+ _send: function (
93
+ eventType,
94
+ dataLines,
95
+ sendOptions = {
96
+ eventId: null,
97
+ retryDuration: 1000,
98
+ }
99
+ ) {
100
+ //Prepare the message for sending.
101
+ let data = dataLines.map((line) => `data: ${line}\n`).join("") + "\n";
102
+ let eventString = "";
103
+ eventString += `comment: "dev"\n`;
104
+ if (sendOptions.eventId != null) {
105
+ eventString += `id: ${sendOptions.eventId}\n`;
106
+ }
107
+ if (eventType) {
108
+ eventString += `event: ${eventType}\n`;
109
+ }
110
+ eventString += `retry: ${sendOptions.retryDuration}\n`;
111
+ eventString += data;
112
+
113
+ //Send Event
114
+ if (!this.headersSent) {
115
+ this.res?.setHeader("Cache-Control", "nocache");
116
+ this.res?.setHeader("Connection", "keep-alive");
117
+ this.res?.setHeader("Content-Type", "text/event-stream");
118
+ this.headersSent = true;
119
+ }
120
+ this.res.write(eventString);
121
+
122
+ return eventString;
123
+ },
124
+
125
+ /**
126
+ * Reads signals based on HTTP methods and merges them with provided signals.
127
+ *
128
+ * @param {object} signals - Predefined signals to merge with.
129
+ * @returns {Promise<object>} Merged signals object.
130
+ */
131
+ ReadSignals: async function (signals) {
132
+ if (this.req.method === "GET") {
133
+ // Parse the URL
134
+ const parsedUrl = url.parse(this.req.url);
135
+ const parsedQuery = querystring.parse(parsedUrl.query);
136
+ const datastarParam = parsedQuery.datastar;
137
+
138
+ const query = JSON.parse(datastarParam);
139
+ return {
140
+ ...signals,
141
+ ...query,
142
+ };
143
+ } else {
144
+ let body = this.req?.body;
145
+ if (this.req?.json) {
146
+ body = await this.req.json();
147
+ }
148
+
149
+ if (!body) {
150
+ body = await new Promise((resolve, reject) => {
85
151
  let chunks = "";
86
152
  this.req.on("data", (chunk) => {
87
153
  chunks += chunk;
88
154
  });
89
155
  this.req.on("end", () => {
90
- console.log("No more data in response.");
91
- resolve(chunks);
156
+ resolve(JSON.parse(chunks));
92
157
  });
93
158
  });
94
- let parsedBody = {};
95
- try {
96
- parsedBody = JSON.parse(body);
97
- } catch (err) {
98
- console.error(
99
- "Problem reading signals, could not parse body as JSON."
100
- );
101
- }
102
- console.log("parsed Body", parsedBody);
103
- return { ...signals, ...parsedBody };
104
159
  }
105
- },
106
- /**
107
- * @typedef {Object} MergeFragmentsOptions
108
- * @property {string} [selector] - CSS selector affected.
109
- * @property {string} [mergeMode="morph"] - Mode for merging.
110
- * @property {number} [settleDuration=300] - Duration to settle.
111
- * @property {?boolean} [useViewTransition=null] - Use view transition.
112
- * @property {?string} [eventId=null] - Event ID to attach.
113
- * @property {?number} [retryDuration=null] - Retry duration in milliseconds.
114
- */
115
160
 
116
- /**
117
- * Sends a merge fragments event.
118
- *
119
- * @param {string[]} fragments - Array of fragment identifiers.
120
- * @param {MergeFragmentsOptions} options - Additional options for merging.
121
- * @throws Will throw an error if fragments are missing.
122
- */
123
- MergeFragments: function (
124
- fragments,
125
- options = {
126
- selector: null,
127
- mergeMode: "morph",
128
- settleDuration: 300,
129
- useViewTransition: null,
130
- eventId: null,
131
- retryDuration: null,
132
- }
133
- ) {
134
- let dataLines = [];
135
- if (options?.selector != null)
136
- dataLines.push(`selector ${options.selector}`);
137
- if (options?.settleDuration != null)
138
- dataLines.push(`settleDuration ${options.settleDuration}`);
139
- if (options?.useViewTransition != null)
140
- dataLines.push(`useViewTransition ${options.useViewTransition}`);
141
- if (fragments) {
142
- if (typeof fragments === "string") {
143
- // Handle case where 'fragments' is a string
144
- dataLines.push(`fragments ${fragments.replace(/[\r\n]+/g, "")}`);
145
- } else if (Array.isArray(fragments)) {
146
- // Handle case where 'fragments' is an array
147
- fragments.forEach((frag) => {
148
- dataLines.push(`fragments ${frag.replace(/[\r\n]+/g, "")}`);
149
- });
150
- } else {
151
- throw Error(
152
- "Invalid type for fragments. Expected string or array."
153
- );
154
- }
155
- } else {
156
- throw Error("MergeFragments missing fragment(s).");
157
- }
158
- return this._send("datastar-merge-fragments", dataLines, {
159
- eventId: options?.eventId,
160
- retryDuration: options?.retryDuration,
161
- });
162
- },
163
- /**
164
- * @typedef {Object} RemoveFragmentsOptions
165
- * @property {number} [settleDuration] - Duration to settle.
166
- * @property {?boolean} [useViewTransition=null] - Use view transition.
167
- * @property {?string} [eventId=null] - Event ID to attach.
168
- * @property {?number} [retryDuration=null] - Retry duration in milliseconds.
169
- */
161
+ return { ...signals, ...body };
162
+ }
163
+ },
170
164
 
171
- /**
172
- * Sends a remove fragments event.
173
- *
174
- * @param {string} selector - CSS selector of fragments to remove.
175
- * @param {RemoveFragmentsOptions} options - Additional options for removing.
176
- * @throws Will throw an error if selector is missing.
177
- */
178
- RemoveFragments: function (selector, options) {
179
- let dataLines = [];
180
- if (selector) {
181
- dataLines.push(`selector ${selector}`);
165
+ /**
166
+ * Sends a merge fragments event.
167
+ *
168
+ * @param {string[]|string} fragments - Array of fragment identifiers.
169
+ * @param {MergeFragmentsOptions} options - Additional options for merging.
170
+ * @throws Will throw an error if fragments are missing.
171
+ */
172
+ MergeFragments: function (
173
+ fragments,
174
+ options = {
175
+ selector: null,
176
+ mergeMode: "morph",
177
+ settleDuration: 300,
178
+ useViewTransition: null,
179
+ eventId: null,
180
+ retryDuration: null,
181
+ }
182
+ ) {
183
+ let dataLines = [];
184
+ if (options?.selector != null)
185
+ dataLines.push(`selector ${options.selector}`);
186
+ if (options?.settleDuration != null)
187
+ dataLines.push(`settleDuration ${options.settleDuration}`);
188
+ if (options?.useViewTransition != null)
189
+ dataLines.push(`useViewTransition ${options.useViewTransition}`);
190
+ if (fragments) {
191
+ if (typeof fragments === "string") {
192
+ // Handle case where 'fragments' is a string
193
+ dataLines.push(`fragments ${fragments.replace(/[\r\n]+/g, "")}`);
194
+ } else if (Array.isArray(fragments)) {
195
+ // Handle case where 'fragments' is an array
196
+ fragments.forEach((frag) => {
197
+ dataLines.push(`fragments ${frag.replace(/[\r\n]+/g, "")}`);
198
+ });
182
199
  } else {
183
- throw Error("RemoveFragments missing selector.");
200
+ throw Error("Invalid type for fragments. Expected string or array.");
184
201
  }
185
- if (options?.settleDuration != null)
186
- dataLines.push(`settleDuration ${options.settleDuration}`);
187
- if (options?.useViewTransition != null)
188
- dataLines.push(`useViewTransition ${options.useViewTransition}`);
189
- return this._send(`datastar-remove-fragments`, dataLines, {
190
- eventId: options?.eventId,
191
- retryDuration: options?.retryDuration,
192
- });
193
- },
194
- /**
195
- * @typedef {Object} MergeSignalsOptions
196
- * @property {boolean} [onlyIfMissing] - Merge only if signals are missing.
197
- * @property {?string} [eventId=null] - Event ID to attach.
198
- * @property {?number} [retryDuration=null] - Retry duration in milliseconds.
199
- */
202
+ } else {
203
+ throw Error("MergeFragments missing fragment(s).");
204
+ }
205
+ return this._send("datastar-merge-fragments", dataLines, {
206
+ eventId: options?.eventId,
207
+ retryDuration: options?.retryDuration,
208
+ });
209
+ },
210
+ /**
211
+ * Sends a remove fragments event.
212
+ *
213
+ * @param {string} selector - CSS selector of fragments to remove.
214
+ * @param {RemoveFragmentsOptions} options - Additional options for removing.
215
+ * @throws Will throw an error if selector is missing.
216
+ */
217
+ RemoveFragments: function (selector, options) {
218
+ let dataLines = [];
219
+ if (selector) {
220
+ dataLines.push(`selector ${selector}`);
221
+ } else {
222
+ throw Error("RemoveFragments missing selector.");
223
+ }
224
+ if (options?.settleDuration != null)
225
+ dataLines.push(`settleDuration ${options.settleDuration}`);
226
+ if (options?.useViewTransition != null)
227
+ dataLines.push(`useViewTransition ${options.useViewTransition}`);
228
+ return this._send(`datastar-remove-fragments`, dataLines, {
229
+ eventId: options?.eventId,
230
+ retryDuration: options?.retryDuration,
231
+ });
232
+ },
200
233
 
201
- /**
202
- * Sends a merge signals event.
203
- *
204
- * @param {object} signals - Signals to merge.
205
- * @param {MergeSignalsOptions} options - Additional options for merging.
206
- * @throws Will throw an error if signals are missing.
207
- */
208
- MergeSignals: function (signals, options) {
209
- let dataLines = [];
210
- if (options?.onlyIfMissing === true) {
211
- dataLines.push(`onlyIfMissing true`);
212
- }
213
- if (signals) {
214
- dataLines.push(`signals ${JSON.stringify(signals)}`);
215
- } else {
216
- throw Error("MergeSignals missing signals.");
217
- }
218
- return this._send(`datastar-merge-signals`, dataLines, {
219
- eventId: options?.eventId,
220
- retryDuration: options?.retryDuration,
221
- });
222
- },
223
- /**
224
- * Sends a remove signals event.
225
- *
226
- * @param {string[]} paths - Paths of signals to remove.
227
- * @param {SendOptions} options - Additional options for removing signals.
228
- * @throws Will throw an error if paths are missing.
229
- */
230
- RemoveSignals: function (paths, options) {
231
- let dataLines = [];
232
- if (paths) {
233
- paths
234
- .map((path) => {
235
- dataLines.push(`paths ${path}`);
236
- })
237
- .join("");
238
- } else {
239
- throw Error("RemoveSignals missing paths");
240
- }
241
- return this._send(`datastar-remove-signals`, dataLines, {
242
- eventId: options?.eventId,
243
- retryDuration: options?.retryDuration,
244
- });
245
- },
246
- /**
247
- * @typedef {Object} ExecuteScriptOptions
248
- * @property {boolean} [autoRemove] - Automatically remove the script after execution.
249
- * @property {?string} [eventId=null] - Event ID to attach.
250
- * @property {?number} [retryDuration=null] - Retry duration in milliseconds.
251
- */
234
+ /**
235
+ * Sends a merge signals event.
236
+ *
237
+ * @param {object} signals - Signals to merge.
238
+ * @param {MergeSignalsOptions} options - Additional options for merging.
239
+ * @throws Will throw an error if signals are missing.
240
+ */
241
+ MergeSignals: function (signals, options) {
242
+ let dataLines = [];
243
+ if (options?.onlyIfMissing === true) {
244
+ dataLines.push(`onlyIfMissing true`);
245
+ }
246
+ if (signals) {
247
+ dataLines.push(`signals ${JSON.stringify(signals)}`);
248
+ } else {
249
+ throw Error("MergeSignals missing signals.");
250
+ }
251
+ return this._send(`datastar-merge-signals`, dataLines, {
252
+ eventId: options?.eventId,
253
+ retryDuration: options?.retryDuration,
254
+ });
255
+ },
256
+ /**
257
+ * Sends a remove signals event.
258
+ *
259
+ * @param {string[]} paths - Paths of signals to remove.
260
+ * @param {SendOptions} options - Additional options for removing signals.
261
+ * @throws Will throw an error if paths are missing.
262
+ */
263
+ RemoveSignals: function (paths, options) {
264
+ /** @type {Array<string>} */
265
+ let dataLines = [];
266
+ if (paths) {
267
+ paths
268
+ .map((path) => {
269
+ dataLines.push(`paths ${path}`);
270
+ })
271
+ .join("");
272
+ } else {
273
+ throw Error("RemoveSignals missing paths");
274
+ }
275
+ return this._send(`datastar-remove-signals`, dataLines, {
276
+ eventId: options?.eventId,
277
+ retryDuration: options?.retryDuration,
278
+ });
279
+ },
252
280
 
253
- /**
254
- * Executes a script on the client-side.
255
- *
256
- * @param {string} script - Script code to execute.
257
- * @param {ExecuteScriptOptions} options - Additional options for execution.
258
- */
259
- ExecuteScript: function (script, options) {
260
- let dataLines = [];
261
- if (options?.autoRemove != null)
262
- dataLines.push(`autoRemove ${options.autoRemove}`);
263
- if (script) {
264
- dataLines.push(`script ${script}`);
265
- }
266
- return this._send(`datastar-execute-script`, dataLines, {
267
- eventId: options?.eventId,
268
- retryDuration: options?.retryDuration,
269
- });
270
- },
271
- };
272
- },
273
- };
281
+ /**
282
+ * Executes a script on the client-side.
283
+ *
284
+ * @param {string} script - Script code to execute.
285
+ * @param {ExecuteScriptOptions} options - Additional options for execution.
286
+ */
287
+ ExecuteScript: function (script, options) {
288
+ let dataLines = [];
289
+ if (options?.autoRemove != null) {
290
+ dataLines.push(`autoRemove ${options.autoRemove}`);
291
+ }
292
+
293
+ if (script) {
294
+ const lines = script.split("\n");
295
+ let insideBlockComment = false;
296
+ const processedLines = lines
297
+ .map((line) => {
298
+ line = line.trim();
299
+ let result = [];
300
+ let isInString = false;
301
+ let isEscape = false;
302
+
303
+ for (let i = 0; i < line.length; i++) {
304
+ const char = line[i];
305
+
306
+ if (insideBlockComment) {
307
+ // Check for end of block comment
308
+ if (char === "*" && line[i + 1] === "/") {
309
+ insideBlockComment = false;
310
+ i++; // Skip the '/'
311
+ }
312
+ continue;
313
+ }
314
+
315
+ if (isInString) {
316
+ result.push(char);
317
+ if (char === "\\" && !isEscape) {
318
+ isEscape = true; // Detect escaping
319
+ } else if ((char === '"' || char === "'") && !isEscape) {
320
+ isInString = false; // End of the string literal
321
+ } else {
322
+ isEscape = false;
323
+ }
324
+ } else {
325
+ // Not inside any string
326
+ if (char === '"' || char === "'") {
327
+ isInString = true;
328
+ result.push(char);
329
+ } else if (char === "/" && line[i + 1] === "/") {
330
+ break; // Start of single-line comment, ignore rest
331
+ } else if (char === "/" && line[i + 1] === "*") {
332
+ insideBlockComment = true; // Begin block comment
333
+ i++; // Skip the '*'
334
+ } else {
335
+ result.push(char);
336
+ }
337
+ }
338
+ }
339
+
340
+ // Join the result and ensure it ends with a semicolon if not empty
341
+ const codePart = result.join("").trim();
342
+ return codePart && !codePart.endsWith(";")
343
+ ? `${codePart};`
344
+ : codePart;
345
+ })
346
+ .filter((line) => line.length > 0);
347
+
348
+ const singleLineScript = processedLines.join(" ");
349
+ dataLines.push(`script ${singleLineScript}`);
350
+ }
351
+
352
+ return this._send(`datastar-execute-script`, dataLines, {
353
+ eventId: options?.eventId,
354
+ retryDuration: options?.retryDuration,
355
+ });
356
+ },
357
+ };
358
+ return Object.assign(
359
+ {
360
+ headersSent: false,
361
+ req: request,
362
+ res: response,
363
+ },
364
+ generatorMethods
365
+ );
366
+ }
package/jsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "noImplicitAny": true,
5
+ "strictFunctionTypes": true,
6
+ "strictPropertyInitialization": true,
7
+ "strictBindCallApply": true,
8
+ "noImplicitThis": true,
9
+ "noImplicitReturns": true,
10
+ "alwaysStrict": true,
11
+ "esModuleInterop": true,
12
+ "checkJs": true,
13
+ "allowJs": true,
14
+ "declaration": true,
15
+ "target": "ES2016",
16
+ "module": "ESNext",
17
+ "moduleResolution": "node",
18
+ "outDir": "dist"
19
+ },
20
+ "include": ["*.js"],
21
+ "verbose": true
22
+ }
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "datastar-ssegen",
3
- "version": "0.0.2",
3
+ "version": "0.1.1",
4
4
  "description": "Datastar Server-Sent Event generator",
5
5
  "author": "John Cudd",
6
6
  "type": "module",
7
7
  "main": "index.js",
8
+ "types": "index.d.ts",
8
9
  "scripts": {
9
10
  "dev": "concurrently -c \"red,green,blue\" -n \"node,express,hyper-express\" \"npm run node\" \"npm run express\" \"npm run hyper-express\"",
10
11
  "node": "nodemon examples/node.example.js",
11
12
  "express": "nodemon examples/express.example.js",
12
- "hyper-express": "nodemon examples/hyper-express.example.js"
13
+ "hyper-express": "nodemon examples/hyper-express.example.js",
14
+ "check-types": "tsc --project ./jsconfig.json"
13
15
  },
14
16
  "keywords": [
15
17
  "datastar",
@@ -31,5 +33,13 @@
31
33
  "hyper-express": "^6.17.3",
32
34
  "nodemon": "^3.1.9",
33
35
  "npm-run-all": "^4.1.5"
36
+ },
37
+ "devDependencies": {
38
+ "concurrently": "^9.1.0",
39
+ "typescript": "^5.7.2"
40
+ },
41
+ "dependencies": {
42
+ "express": "^4.21.2",
43
+ "hyper-express": "^6.17.3"
34
44
  }
35
45
  }