diff --git a/sdk/typescript/examples/deno.ts b/sdk/typescript/examples/deno.ts
index 82f0ddd2d..4eb91e45d 100644
--- a/sdk/typescript/examples/deno.ts
+++ b/sdk/typescript/examples/deno.ts
@@ -34,7 +34,6 @@ serve(async (req: Request) => {
stream.mergeFragments(
`
Hello ${reader.signals.foo}
`,
);
- stream.close();
});
}
diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json
index c4506b88f..f2942de36 100644
--- a/sdk/typescript/package.json
+++ b/sdk/typescript/package.json
@@ -3,7 +3,9 @@
"version": "0.0.1",
"description": "TypeScript SDK for Datastar",
"scripts": {
- "tsc": "tsc"
+ "check": "deno lint && deno check src/node/node.ts && deno check src/web/deno.ts",
+ "serve-deno": "deno run -A src/web/deno.ts",
+ "serve-node": "deno run -A build.ts && node npm/esm/node/node.js"
},
"type": "module",
"repository": {
@@ -17,11 +19,11 @@
},
"homepage": "https://github.com/starfederation/datastar#readme",
"dependencies": {
- "@types/node": "^22.10.2",
- "deepmerge-ts": "^7.1.4",
- "type-fest": "^4.32.0"
+ "deepmerge-ts": "^7.1.4"
},
"devDependencies": {
- "typescript": "~5.6.2"
+ "typescript": "~5.6.2",
+ "@types/node": "^22.10.2",
+ "type-fest": "^4.32.0"
}
}
diff --git a/sdk/typescript/src/abstractServerSentEventGenerator.ts b/sdk/typescript/src/abstractServerSentEventGenerator.ts
index 21f3e5ec0..c464e0e5a 100644
--- a/sdk/typescript/src/abstractServerSentEventGenerator.ts
+++ b/sdk/typescript/src/abstractServerSentEventGenerator.ts
@@ -8,9 +8,10 @@ import {
MergeSignalsOptions,
} from "./types.ts";
-import { DefaultExecuteScriptAttributes } from "./consts.ts";
-
-import { DefaultSseRetryDurationMs } from "./consts.ts";
+import {
+ DefaultExecuteScriptAttributes,
+ DefaultSseRetryDurationMs,
+} from "./consts.ts";
import type { Jsonifiable } from "npm:type-fest";
@@ -78,10 +79,6 @@ export abstract class ServerSentEventGenerator {
}
private hasDefaultValue(key: string, val: unknown): boolean {
- if (key === DefaultExecuteScriptAttributes.split(" ")[0]) {
- return val === DefaultExecuteScriptAttributes.split(" ")[1];
- }
-
if (key in DefaultMapping) {
return val === DefaultMapping[key as keyof typeof DefaultMapping];
}
@@ -132,17 +129,19 @@ export abstract class ServerSentEventGenerator {
/**
* Sends a merge signals event.
*
- * @param data - Data object that will be merged into the client's signals.
- * @param options - Additional options for merging.
+ * @param data - Data object or json string that will be merged into the client's signals.
+ * @param [options] - Additional options for merging.
*/
public mergeSignals(
- data: Record,
+ data: Record | string,
options?: MergeSignalsOptions,
): ReturnType {
const { eventId, retryDuration, ...eventOptions } = options ||
{} as Partial;
+
+ const signals = typeof data === "string" ? data : JSON.stringify(data);
const dataLines = this.eachOptionIsADataLine(eventOptions)
- .concat(this.eachNewlineIsADataLine("signals", JSON.stringify(data)));
+ .concat(this.eachNewlineIsADataLine("signals", signals));
return this.send("datastar-merge-signals", dataLines, {
eventId,
@@ -153,17 +152,19 @@ export abstract class ServerSentEventGenerator {
/**
* Sends a remove signals event.
*
- * @param paths - Array of paths to remove from the client's signals
- * @param options - Additional options for removing signals.
+ * @param paths - An array of paths or a string containing space separated paths.
+ * @param [options] - Additional options for removing signals.
*/
public removeSignals(
- paths: string[],
+ paths: string[] | string,
options?: DatastarEventOptions,
): ReturnType {
const eventOptions = options || {} as DatastarEventOptions;
- const dataLines = paths.flatMap((path) => path.split(" ")).map((path) =>
- `paths ${path}`
- );
+ const pathsArray = typeof paths === "string"
+ ? paths.split(" ")
+ : paths.flatMap((path) => path.split(" "));
+
+ const dataLines = pathsArray.map((path) => `paths ${path}`);
return this.send("datastar-remove-signals", dataLines, eventOptions);
}
@@ -172,7 +173,7 @@ export abstract class ServerSentEventGenerator {
* Executes a script on the client-side.
*
* @param script - Script code to execute.
- * @param options - Additional options for execution.
+ * @param [options] - Additional options for execution.
*/
public executeScript(
script: string,
@@ -184,9 +185,18 @@ export abstract class ServerSentEventGenerator {
attributes,
...eventOptions
} = options || {} as Partial;
-
- const attributesDataLines = this.eachOptionIsADataLine(attributes ?? {})
- .map((line) => `attributes ${line}`);
+ const attributesArray = attributes instanceof Array
+ ? attributes
+ : this.eachOptionIsADataLine(attributes ?? {});
+
+ const attributesDataLines = attributesArray.filter((line) => {
+ const parts = line.split(" ");
+ const defaultParts = DefaultExecuteScriptAttributes.split(" ");
+ if (parts[0] === defaultParts[0] && parts[1]) {
+ return parts[1] !== defaultParts[1];
+ }
+ return true;
+ }).map((line) => `attributes ${line}`);
const dataLines = attributesDataLines.concat(
this.eachOptionIsADataLine(eventOptions),
diff --git a/sdk/typescript/src/node/node.ts b/sdk/typescript/src/node/node.ts
index 2be165850..b06a5c016 100644
--- a/sdk/typescript/src/node/node.ts
+++ b/sdk/typescript/src/node/node.ts
@@ -1,3 +1,4 @@
+import { VERSION } from "../consts.ts";
import { createServer } from "node:http";
import { ServerSentEventGenerator } from "./serverSentEventGenerator.ts";
import type { Jsonifiable } from "npm:type-fest";
@@ -5,12 +6,13 @@ import type { Jsonifiable } from "npm:type-fest";
const hostname = "127.0.0.1";
const port = 3000;
+// This server is used for testing the node sdk
const server = createServer(async (req, res) => {
if (req.url === "/") {
const headers = new Headers({ "Content-Type": "text/html" });
res.setHeaders(headers);
res.end(
- `Hello
`,
+ `Hello
`,
);
} else if (req.url?.includes("/test")) {
const reader = await ServerSentEventGenerator.readSignals(req);
diff --git a/sdk/typescript/src/node/serverSentEventGenerator.ts b/sdk/typescript/src/node/serverSentEventGenerator.ts
index e9e170b38..ba1f638c1 100644
--- a/sdk/typescript/src/node/serverSentEventGenerator.ts
+++ b/sdk/typescript/src/node/serverSentEventGenerator.ts
@@ -28,24 +28,20 @@ export class ServerSentEventGenerator extends AbstractSSEGenerator {
}
/**
- * Closes the stream
- */
- public close() {
- this.res.end();
- }
-
- /**
- * Initializes the server-sent event generator and executes the streamFunc function.
+ * Initializes the server-sent event generator and executes the onStart callback.
*
* @param req - The NodeJS request object.
* @param res - The NodeJS response object.
* @param onStart - A function that will be passed the initialized ServerSentEventGenerator class as it's first parameter.
* @param options? - An object that can contain onError and onCancel callbacks as well as a keepalive boolean.
* The onAbort callback will be called whenever the request is aborted
+ *
* The onError callback will be called whenever an error is met. If provided, the onAbort callback will also be executed.
* If an onError callback is not provided, then the stream will be ended and the error will be thrown up.
- * When keepalive is true (default is false), the stream will be kept open indefinitely,
- * otherwise it will be closed when the onStart callback finishes.
+ *
+ * The stream is always closed after the onStart callback ends.
+ * If onStart is non blocking, but you still need the stream to stay open after it is called,
+ * then the keepalive option will maintain it open until the request is aborted by the client.
*/
static async stream(
req: IncomingMessage,
diff --git a/sdk/typescript/src/types.ts b/sdk/typescript/src/types.ts
index 2c1377ce0..7d96b3e44 100644
--- a/sdk/typescript/src/types.ts
+++ b/sdk/typescript/src/types.ts
@@ -95,7 +95,7 @@ type ScriptAttributes = {
export interface ExecuteScriptOptions extends DatastarEventOptions {
[DatastarDatalineAutoRemove]?: boolean;
- [DatastarDatalineAttributes]?: ScriptAttributes;
+ [DatastarDatalineAttributes]?: ScriptAttributes | string[];
}
export interface ExecuteScriptEvent {
diff --git a/sdk/typescript/src/web/deno.ts b/sdk/typescript/src/web/deno.ts
index 3c8d1618a..b9a3cce9e 100644
--- a/sdk/typescript/src/web/deno.ts
+++ b/sdk/typescript/src/web/deno.ts
@@ -1,13 +1,15 @@
+import { VERSION } from "../consts.ts";
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";
import { ServerSentEventGenerator } from "./serverSentEventGenerator.ts";
import type { Jsonifiable } from "npm:type-fest";
+// This server is used for testing the web standard based sdk
serve(async (req: Request) => {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response(
- `Hello
`,
+ `Hello
`,
{
headers: { "Content-Type": "text/html" },
},
@@ -19,7 +21,6 @@ serve(async (req: Request) => {
if (isEventArray(events)) {
return ServerSentEventGenerator.stream((stream) => {
testEvents(stream, events);
- stream.close();
});
}
}
@@ -28,7 +29,6 @@ serve(async (req: Request) => {
stream.mergeFragments('Merged
');
await delay(5000);
stream.mergeFragments('After 5 seconds
');
- stream.close();
});
}
diff --git a/sdk/typescript/src/web/serverSentEventGenerator.ts b/sdk/typescript/src/web/serverSentEventGenerator.ts
index b118b394f..79f47c309 100644
--- a/sdk/typescript/src/web/serverSentEventGenerator.ts
+++ b/sdk/typescript/src/web/serverSentEventGenerator.ts
@@ -22,23 +22,20 @@ export class ServerSentEventGenerator extends AbstractSSEGenerator {
}
/**
- * Closes the ReadableStream
- */
- public close() {
- this.controller.close();
- }
-
- /**
- * Initializes the server-sent event generator and executes the streamFunc function.
+ * Initializes the server-sent event generator and executes the onStart callback.
*
* @param onStart - A function that will be passed the initialized ServerSentEventGenerator class as it's first parameter.
* @param options? - An object that can contain options for the Response constructor onError and onCancel callbacks and a keepalive boolean.
* The onAbort callback will be called whenever the request is aborted or the stream is cancelled
+ *
* The onError callback will be called whenever an error is met. If provided, the onAbort callback will also be executed.
* If an onError callback is not provided, then the stream will be ended and the error will be thrown up.
+ *
* If responseInit is provided, then it will be passed to the Response constructor along with the default headers.
- * When keepalive is true (default is false), the stream will be kept open indefinitely,
- * otherwise it will be closed when the onStart callback finishes.
+ *
+ * The stream is always closed after the onStart callback ends.
+ * If onStart is non blocking, but you still need the stream to stay open after it is called,
+ * then the keepalive option will maintain it open until the request is aborted by the client.
*
* @returns an HTTP Response
*/
@@ -58,7 +55,7 @@ export class ServerSentEventGenerator extends AbstractSSEGenerator {
try {
const stream = onStart(generator);
if (stream instanceof Promise) await stream;
- if (options?.keepalive) {
+ if (!options?.keepalive) {
controller.close();
}
} catch (error) {