datastar-ssegen 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,7 +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.
87
+ - **`ServerSentEventGenerator(request, response)`**: Initializes SSE communication with the specified request and response.
88
88
 
89
89
  - **`_send(eventType, dataLines, sendOptions)`**: Sends a server-sent event (SSE) to the client. Options include setting an `eventId` and defining `retryDuration`.
90
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.0",
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
  }