datastar-ssegen 0.0.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.
- 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
|
+

|
2
|
+

|
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
|
+
}
|