datastar-ssegen 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- package/.github/workflows/publish.yml +24 -0
- package/README.md +89 -0
- package/examples/commonHandlers.js +91 -0
- package/examples/express.example.js +38 -0
- package/examples/hyper-express.example.js +42 -0
- package/examples/node.example.js +71 -0
- package/index.js +273 -0
- package/package.json +31 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
# publish to npm
|
2
|
+
name: Publish Package
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
release:
|
8
|
+
types:
|
9
|
+
- created
|
10
|
+
jobs:
|
11
|
+
publish:
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
steps:
|
14
|
+
- name: Checkout code
|
15
|
+
uses: actions/checkout@v3
|
16
|
+
- name: Setup Node.js environment
|
17
|
+
uses: actions/setup-node@v3
|
18
|
+
with:
|
19
|
+
node-version: '18'
|
20
|
+
registry-url: 'https://registry.npmjs.org/'
|
21
|
+
- name: Publish to npm
|
22
|
+
run: npm publish
|
23
|
+
env:
|
24
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
![Version](https://img.shields.io/github/package-json/v/jmcudd/datastar-ssegen?filename=package.json)
|
2
|
+
![Stars](https://img.shields.io/github/stars/jmcudd/datastar-ssegen?style=flat)
|
3
|
+
|
4
|
+
# datastar-ssegen
|
5
|
+
|
6
|
+
## Overview
|
7
|
+
|
8
|
+
The `datastar-ssegen` is a backend JavaScript module designed to generate Server-Sent Events (SSE) for connected [Datastar](https://data-star.dev/) clients. It supports popular server frameworks such as Express.js, Node.js, and Hyper Express.js.
|
9
|
+
|
10
|
+
This package is engineered to integrate tightly with request and response objects of these backend frameworks, enabling efficient and reactive web application development.
|
11
|
+
|
12
|
+
### Key Features
|
13
|
+
|
14
|
+
- Real-time updates with Server-Sent Events tailored for Datastar clients
|
15
|
+
- Seamless integration with Express.js, Hyper Express.js, and Node HTTP
|
16
|
+
|
17
|
+
### Installation
|
18
|
+
|
19
|
+
Install the package via npm:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
npm install datastar-ssegen
|
23
|
+
```
|
24
|
+
|
25
|
+
### Quick Start Example with Express.js
|
26
|
+
|
27
|
+
Here's a straightforward example of setting up an Express.js server with the datastar-ssegen:
|
28
|
+
|
29
|
+
```javascript
|
30
|
+
import express from 'express';
|
31
|
+
import { ServerSentEventGenerator } from 'datastar-ssegen';
|
32
|
+
|
33
|
+
const app = express();
|
34
|
+
app.use(express.json());
|
35
|
+
|
36
|
+
// Define event handlers here
|
37
|
+
|
38
|
+
app.get('/messages', handleMessages);
|
39
|
+
app.get('/clock', handleClock);
|
40
|
+
|
41
|
+
const PORT = 3101;
|
42
|
+
app.listen(PORT, () => {
|
43
|
+
console.log(`Server running at http://localhost:${PORT}`);
|
44
|
+
});
|
45
|
+
```
|
46
|
+
|
47
|
+
### Client Interaction Example
|
48
|
+
|
49
|
+
Here's a simple HTML page to interact with the server:
|
50
|
+
|
51
|
+
```html
|
52
|
+
<!DOCTYPE html>
|
53
|
+
<html lang="en">
|
54
|
+
<head>
|
55
|
+
<meta charset="UTF-8">
|
56
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
57
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
58
|
+
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar/bundles/datastar.js"></script>
|
59
|
+
<title>SSE Example</title>
|
60
|
+
</head>
|
61
|
+
<body>
|
62
|
+
<h1>SSE Demo</h1>
|
63
|
+
<div id="greeting-area">Greeting: <button onclick="sse('/messages')">Get Greeting</button></div>
|
64
|
+
<div id="clock-area">Current Time: <button onclick="sse('/clock')">Start Clock</button></div>
|
65
|
+
</body>
|
66
|
+
</html>
|
67
|
+
```
|
68
|
+
|
69
|
+
### Available Functions
|
70
|
+
|
71
|
+
The `ServerSentEventGenerator` provides several functions to facilitate communication with connected Datastar clients using Server-Sent Events:
|
72
|
+
|
73
|
+
- **`init(request, response)`**: Initializes SSE communication with the specified request and response.
|
74
|
+
|
75
|
+
- **`_send(eventType, dataLines, sendOptions)`**: Sends a server-sent event (SSE) to the client. Options include setting an `eventId` and defining `retryDuration`.
|
76
|
+
|
77
|
+
- **`ReadSignals(signals)`**: Reads and merges signals based on HTTP methods with predefined signals, useful for parsing query or body data sent to the server.
|
78
|
+
|
79
|
+
- **`MergeFragments(fragments, options)`**: Sends a merge fragments event to update HTML content on the client. Options include `selector`, `mergeMode`, `settleDuration`, and `useViewTransition`.
|
80
|
+
|
81
|
+
- **`RemoveFragments(selector, options)`**: Dispatches events to remove HTML elements based on a CSS selector. Options can set a `settleDuration` or `useViewTransition`.
|
82
|
+
|
83
|
+
- **`MergeSignals(signals, options)`**: Sends a merge signals event to update or add client-side signals. Options may include `onlyIfMissing`.
|
84
|
+
|
85
|
+
- **`RemoveSignals(paths, options)`**: Sends an event to remove specific client-side signals identified by paths.
|
86
|
+
|
87
|
+
- **`ExecuteScript(script, options)`**: Directs the client to execute specified JavaScript code. Options can enable `autoRemove` of the script after execution.
|
88
|
+
|
89
|
+
This expanded set provides comprehensive functionality to build interactive web applications with real-time updates and dynamic HTML and signal management.
|
@@ -0,0 +1,91 @@
|
|
1
|
+
import { ServerSentEventGenerator } from "../index.js";
|
2
|
+
|
3
|
+
export let backendStore = {
|
4
|
+
someBackendValue: "This is something",
|
5
|
+
};
|
6
|
+
export const homepage = (name = "") => {
|
7
|
+
return `<html>
|
8
|
+
<head>
|
9
|
+
<title>${name} Datastar Test</title>
|
10
|
+
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@develop/bundles/datastar.js"></script>
|
11
|
+
</head>
|
12
|
+
<body>
|
13
|
+
<h3>${name} Datastar Test</h3>
|
14
|
+
<div data-signals="{theme: 'light', lastUpdate:'Never', xyz:'some signal'}">
|
15
|
+
<h3>Long Lived SSE:</h3>
|
16
|
+
<div id="clock" data-on-load="sse('/clock')">...Loading Clock</div>
|
17
|
+
|
18
|
+
<h3>MergeSignals:</h3>
|
19
|
+
<div>Last User Interaction:<span data-text="lastUpdate.value"></span></div>
|
20
|
+
<h3>Merge Fragments</h3>
|
21
|
+
<div id="quote">No Quote</div>
|
22
|
+
<button data-on-click="sse('/quote')">MergeFragments</button>
|
23
|
+
<h3>RemoveFragments</h3>
|
24
|
+
<div id="trash">
|
25
|
+
Remove me please!
|
26
|
+
<button data-on-click="sse('/removeTrash')">RemoveFragments</button>
|
27
|
+
</div>
|
28
|
+
<h3>ExecuteScript</h3>
|
29
|
+
<div>Print to Console</div>
|
30
|
+
<button data-on-click="sse('/printToConsole')">ExecuteScript</button>
|
31
|
+
|
32
|
+
<h3>ReadSignals</h3>
|
33
|
+
<button data-on-click="sse('/readSignals')">ReadSignals</button>
|
34
|
+
|
35
|
+
<h3>ReadSignals (post)</h3>
|
36
|
+
<button data-on-click="sse('/readSignals', {method: 'post'})">ReadSignals (post)</button>
|
37
|
+
|
38
|
+
|
39
|
+
<h3>RemoveSignals</h3>
|
40
|
+
<div>Signal xyz:<span data-text="xyz.value"></span></div>
|
41
|
+
<button data-on-click="sse('/removeSignal')">Test RemoveSignals: xyz</button>
|
42
|
+
</div>
|
43
|
+
</body>
|
44
|
+
</html>`;
|
45
|
+
};
|
46
|
+
|
47
|
+
export const handleQuote = async (req, res) => {
|
48
|
+
const sse = ServerSentEventGenerator.init(req, res);
|
49
|
+
const qoutes = [
|
50
|
+
"Any app that can be written in JavaScript, will eventually be written in JavaScript. - Jeff Atwood",
|
51
|
+
"JavaScript is the world's most misunderstood programming language. - Douglas Crockford",
|
52
|
+
"The strength of JavaScript is that you can do anything. The weakness is that you will. - Reg Braithwaite",
|
53
|
+
];
|
54
|
+
const randomQuote = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
55
|
+
await sse.MergeFragments(`<div id="quote">${randomQuote(qoutes)}</div>`);
|
56
|
+
await sse.MergeSignals({ lastUpdate: Date.now() });
|
57
|
+
res.end();
|
58
|
+
};
|
59
|
+
|
60
|
+
export const handleReadSignals = async (req, res) => {
|
61
|
+
const sse = ServerSentEventGenerator.init(req, res);
|
62
|
+
backendStore = await sse.ReadSignals(backendStore);
|
63
|
+
console.log("backendStore updated", backendStore);
|
64
|
+
res.end();
|
65
|
+
};
|
66
|
+
|
67
|
+
export const handleRemoveTrash = async (req, res) => {
|
68
|
+
const sse = ServerSentEventGenerator.init(req, res);
|
69
|
+
await sse.RemoveFragments("#trash");
|
70
|
+
await sse.MergeSignals({ lastUpdate: Date.now() });
|
71
|
+
res.end();
|
72
|
+
};
|
73
|
+
|
74
|
+
export const handleExecuteScript = async (req, res) => {
|
75
|
+
const sse = ServerSentEventGenerator.init(req, res);
|
76
|
+
await sse.ExecuteScript(`console.log("Hello from the backend!")`);
|
77
|
+
await sse.MergeSignals({ lastUpdate: Date.now() });
|
78
|
+
res.end();
|
79
|
+
};
|
80
|
+
|
81
|
+
export const handleClock = async function (req, res) {
|
82
|
+
const sse = ServerSentEventGenerator.init(req, res);
|
83
|
+
setInterval(async () => {
|
84
|
+
await sse.MergeFragments(`<div id="clock">${new Date()}</div>`);
|
85
|
+
}, 1000);
|
86
|
+
};
|
87
|
+
|
88
|
+
export const handleRemoveSignal = async (req, res) => {
|
89
|
+
const sse = ServerSentEventGenerator.init(req, res);
|
90
|
+
await sse.RemoveSignals(["xyz"]);
|
91
|
+
};
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import express from "express";
|
2
|
+
import { ServerSentEventGenerator } from "../index.js";
|
3
|
+
|
4
|
+
import {
|
5
|
+
homepage,
|
6
|
+
handleQuote,
|
7
|
+
handleReadSignals,
|
8
|
+
handleRemoveTrash,
|
9
|
+
handleExecuteScript,
|
10
|
+
handleClock,
|
11
|
+
handleRemoveSignal,
|
12
|
+
} from "./commonHandlers.js";
|
13
|
+
|
14
|
+
const app = express();
|
15
|
+
const PORT = 3101;
|
16
|
+
|
17
|
+
// Middleware to parse incoming JSON bodies if needed
|
18
|
+
app.use(express.json());
|
19
|
+
|
20
|
+
app.get("/", (req, res) => {
|
21
|
+
res.send(homepage("Express.js"));
|
22
|
+
});
|
23
|
+
|
24
|
+
app.get("/quote", handleQuote);
|
25
|
+
|
26
|
+
app.get("/readSignals", handleReadSignals);
|
27
|
+
|
28
|
+
app.get("/removeTrash", handleRemoveTrash);
|
29
|
+
|
30
|
+
app.get("/printToConsole", handleExecuteScript);
|
31
|
+
|
32
|
+
app.get("/clock", handleClock);
|
33
|
+
|
34
|
+
app.get("/removeSignal", handleRemoveSignal);
|
35
|
+
|
36
|
+
app.listen(PORT, () => {
|
37
|
+
console.log(`Express.js server http://localhost:${PORT}`);
|
38
|
+
});
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import HyperExpress from "hyper-express";
|
2
|
+
|
3
|
+
import {
|
4
|
+
homepage,
|
5
|
+
handleQuote,
|
6
|
+
handleReadSignals,
|
7
|
+
handleRemoveTrash,
|
8
|
+
handleExecuteScript,
|
9
|
+
handleClock,
|
10
|
+
handleRemoveSignal,
|
11
|
+
} from "./commonHandlers.js";
|
12
|
+
|
13
|
+
// Initialize HyperExpress server
|
14
|
+
const server = new HyperExpress.Server();
|
15
|
+
const PORT = 3102;
|
16
|
+
|
17
|
+
// Configure routes
|
18
|
+
server.get("/", (req, res) => {
|
19
|
+
res.html(homepage("hyper-express"));
|
20
|
+
});
|
21
|
+
|
22
|
+
server.get("/quote", handleQuote);
|
23
|
+
|
24
|
+
server.get("/readSignals", handleReadSignals);
|
25
|
+
|
26
|
+
server.get("/removeTrash", handleRemoveTrash);
|
27
|
+
|
28
|
+
server.get("/printToConsole", handleExecuteScript);
|
29
|
+
|
30
|
+
server.get("/clock", handleClock);
|
31
|
+
|
32
|
+
server.get("/removeSignal", handleRemoveSignal);
|
33
|
+
|
34
|
+
// Start the server
|
35
|
+
server
|
36
|
+
.listen(PORT)
|
37
|
+
.then(() => {
|
38
|
+
console.log(`HyperExpress server http://localhost:${PORT}`);
|
39
|
+
})
|
40
|
+
.catch((error) => {
|
41
|
+
console.error("Error starting server:", error);
|
42
|
+
});
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import http from "http";
|
2
|
+
import {
|
3
|
+
homepage,
|
4
|
+
handleQuote,
|
5
|
+
handleReadSignals,
|
6
|
+
handleRemoveTrash,
|
7
|
+
handleExecuteScript,
|
8
|
+
handleClock,
|
9
|
+
handleRemoveSignal,
|
10
|
+
} from "./commonHandlers.js";
|
11
|
+
|
12
|
+
import url from "url";
|
13
|
+
|
14
|
+
// Initialize an HTTP server
|
15
|
+
const PORT = 3100;
|
16
|
+
|
17
|
+
const server = http.createServer((req, res) => {
|
18
|
+
if (req.method === "GET") {
|
19
|
+
const parsedUrl = url.parse(req.url);
|
20
|
+
switch (parsedUrl.pathname) {
|
21
|
+
case "/":
|
22
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
23
|
+
res.end(homepage("node.js"));
|
24
|
+
break;
|
25
|
+
|
26
|
+
case "/quote":
|
27
|
+
handleQuote(req, res);
|
28
|
+
break;
|
29
|
+
|
30
|
+
case "/readSignals":
|
31
|
+
handleReadSignals(req, res);
|
32
|
+
break;
|
33
|
+
|
34
|
+
case "/removeTrash":
|
35
|
+
handleRemoveTrash(req, res);
|
36
|
+
break;
|
37
|
+
|
38
|
+
case "/printToConsole":
|
39
|
+
handleExecuteScript(req, res);
|
40
|
+
break;
|
41
|
+
|
42
|
+
case "/clock":
|
43
|
+
handleClock(req, res);
|
44
|
+
break;
|
45
|
+
|
46
|
+
case "/removeSignal":
|
47
|
+
handleRemoveSignal(req, res);
|
48
|
+
break;
|
49
|
+
|
50
|
+
default:
|
51
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
52
|
+
res.end("404 Not Found");
|
53
|
+
}
|
54
|
+
} else if (req.method === "POST") {
|
55
|
+
const parsedUrl = url.parse(req.url);
|
56
|
+
switch (parsedUrl.pathname) {
|
57
|
+
case "/readSignals":
|
58
|
+
console.log("post readsignals");
|
59
|
+
handleReadSignals(req, res);
|
60
|
+
break;
|
61
|
+
}
|
62
|
+
} else {
|
63
|
+
res.writeHead(405, { "Content-Type": "text/plain" });
|
64
|
+
res.end("Method Not Allowed");
|
65
|
+
}
|
66
|
+
});
|
67
|
+
|
68
|
+
// Start the server
|
69
|
+
server.listen(PORT, () => {
|
70
|
+
console.log(`Node.js server http://localhost:${PORT}`);
|
71
|
+
});
|
package/index.js
ADDED
@@ -0,0 +1,273 @@
|
|
1
|
+
import url from "url";
|
2
|
+
import querystring from "querystring";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* ServerSentEventGenerator is responsible for initializing and handling
|
6
|
+
* server-sent events (SSE) for different web frameworks.
|
7
|
+
*/
|
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
|
+
|
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;
|
52
|
+
|
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);
|
61
|
+
|
62
|
+
return eventString;
|
63
|
+
},
|
64
|
+
|
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;
|
77
|
+
|
78
|
+
const query = JSON.parse(datastarParam);
|
79
|
+
return {
|
80
|
+
...signals,
|
81
|
+
...query,
|
82
|
+
};
|
83
|
+
} else {
|
84
|
+
const body = await new Promise((resolve, reject) => {
|
85
|
+
let chunks = "";
|
86
|
+
this.req.on("data", (chunk) => {
|
87
|
+
chunks += chunk;
|
88
|
+
});
|
89
|
+
this.req.on("end", () => {
|
90
|
+
console.log("No more data in response.");
|
91
|
+
resolve(chunks);
|
92
|
+
});
|
93
|
+
});
|
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
|
+
}
|
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
|
+
|
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
|
+
*/
|
170
|
+
|
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}`);
|
182
|
+
} else {
|
183
|
+
throw Error("RemoveFragments missing selector.");
|
184
|
+
}
|
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
|
+
*/
|
200
|
+
|
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
|
+
*/
|
252
|
+
|
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
|
+
};
|
package/package.json
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
{
|
2
|
+
"name": "datastar-ssegen",
|
3
|
+
"version": "0.0.1",
|
4
|
+
"description": "Datastar Server-Sent Event generator",
|
5
|
+
"author": "John Cudd",
|
6
|
+
"type": "module",
|
7
|
+
"main": "index.js",
|
8
|
+
"scripts": {
|
9
|
+
"dev": "concurrently -c \"red,green,blue\" -n \"node,express,hyper-express\" \"npm run node\" \"npm run express\" \"npm run hyper-express\"",
|
10
|
+
"node": "nodemon examples/node.example.js",
|
11
|
+
"express": "nodemon examples/express.example.js",
|
12
|
+
"hyper-express": "nodemon examples/hyper-express.example.js"
|
13
|
+
},
|
14
|
+
"keywords": ["datastar", "hypermedia", "sse"],
|
15
|
+
"license": "mit",
|
16
|
+
"homepage": "https://github.com/jmcudd/datastar-ssegen#readme",
|
17
|
+
"repository": {
|
18
|
+
"type": "git",
|
19
|
+
"url": "git+https://github.com/jmcudd/datastar-ssegen.git"
|
20
|
+
},
|
21
|
+
"bugs": {
|
22
|
+
"url": "https://github.com/jmcudd/datastar-ssegen/issues"
|
23
|
+
},
|
24
|
+
"devdependencies": {
|
25
|
+
"concurrently": "^9.1.0",
|
26
|
+
"express": "^4.21.2",
|
27
|
+
"hyper-express": "^6.17.3",
|
28
|
+
"nodemon": "^3.1.9",
|
29
|
+
"npm-run-all": "^4.1.5"
|
30
|
+
}
|
31
|
+
}
|