From 6de198b1a4f482b391abd2e8fa2167c8d4843e97 Mon Sep 17 00:00:00 2001 From: Chase Sterling Date: Wed, 16 Jul 2025 14:49:01 -0400 Subject: [PATCH] Resurrect Delaney's json constants and SDK guide --- sdk/ADR.md | 427 ++++++++++++++++++++++++ sdk/README.md | 437 ++----------------------- sdk/datastar-sdk-config-v1.json | 76 +++++ sdk/datastar-sdk-config-v1.schema.json | 121 +++++++ 4 files changed, 647 insertions(+), 414 deletions(-) create mode 100644 sdk/ADR.md create mode 100644 sdk/datastar-sdk-config-v1.json create mode 100644 sdk/datastar-sdk-config-v1.schema.json diff --git a/sdk/ADR.md b/sdk/ADR.md new file mode 100644 index 000000000..83835da89 --- /dev/null +++ b/sdk/ADR.md @@ -0,0 +1,427 @@ +# Architecture Decision Record: Datastar SDK + +## Summary + +Datastar SDK provides unified tooling for building Hypermedia On Whatever you Like (HOWL) based UIs across multiple languages. While Datastar supports various plugins, the default bundle focuses on a robust Server-Sent Event (SSE) approach, addressing the lack of good SSE tooling in most languages and backends. + +## Decision + +Provide a language-agnostic SDK with these principles: + +1. **Minimal Core**: Keep the SDK as minimal as possible +2. **Sugar Extensions**: Allow per-language/framework extended features in SDK "sugar" versions + +### Naming Rationale + +**Why "Patch" instead of "Merge":** +The prefix "Patch" was chosen to better reflect the non-idempotent nature of these operations. Unlike PUT requests that replace entire resources, PATCH requests apply partial modifications. This aligns with our SDKs behavior where operations modify specific parts of the DOM or signal state rather than replacing them entirely. + +**Why "Elements" instead of "Fragments":** +We use "Elements" because it accurately describes what the SDK handles - complete HTML elements, not arbitrary DOM nodes like text nodes or document fragments. This naming matches the actual intent and constraints of the system, making the API clearer and more predictable for developers. + +## Details + +### Core Mechanics + +The core mechanics of Datastar’s SSE support is + +1. **Server → Browser**: Data is sent as SSE events +2. **Browser → Server**: Data arrives as JSON under the `datastar` namespace + +# SDK Specification + +> [!WARNING] +> All naming conventions use Go as the reference implementation. Adapt to language-specific conventions while maintaining consistency. + +## ServerSentEventGenerator + +**Required**: A `ServerSentEventGenerator` namespace/class/struct (implementation may vary by language). + +--- + +### Construction / Initialization + +**Requirements:** + +| Requirement | Description | +|-------------|-------------| +| **Constructor** | ***Must*** accept HTTP Request and Response objects | +| **Response Headers** | ***Must*** set:
• `Cache-Control: no-cache`
• `Content-Type: text/event-stream`
• `Connection: keep-alive` (HTTP/1.1 only - [see spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection)) | +| **Immediate Flush** | ***Should*** flush response immediately to prevent timeouts | +| **Thread Safety** | ***Should*** ensure ordered delivery (e.g., mutex in Go) | + +--- + +### `ServerSentEventGenerator.send` + +``` +ServerSentEventGenerator.send( + eventType: EventType, + dataLines: string[], + options?: { + eventId?: string, + retryDuration?: durationInMilliseconds + } +) +``` + +A unified sending function ***should*** be used internally (private/protected). + +#### Parameters + +##### EventType + +String enum of supported events: + +| Event | Description | +|-------|-------------| +| `datastar-patch-elements` | Patches HTML elements into the DOM | +| `datastar-patch-signals` | Patches signals into the signal store | + +##### Options + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `eventId` | string | - | Unique event identifier for replay functionality ([SSE spec](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#id)) | +| `retryDuration` | ms | `1000` | Reconnection delay after connection loss ([SSE spec](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#retry)) | + +#### Implementation Requirements + +***Must*** write to response buffer in this exact order: + +1. `event: EVENT_TYPE\n` +2. `id: EVENT_ID\n` (if `eventId` provided) +3. `retry: RETRY_DURATION\n` (***unless*** default of `1000`) +4. `data: DATA\n` (for each of the `dataLines`) +5. `\n` (end of event) +6. ***Should*** flush immediately (note: compression middleware may interfere) + +**Error Handling**: ***Must*** return/throw errors per language conventions. + +--- + +### `ServerSentEventGenerator.PatchElements` + +```go +ServerSentEventGenerator.PatchElements( + elements: string, + options?: { + selector?: string, + mode?: ElementPatchMode, + useViewTransition?: boolean, + eventId?: string, + retryDuration?: durationInMilliseconds + } +) +``` + +#### Example Output + +
+ Minimal Example + + ``` + event: datastar-patch-elements + data: elements
1
+ + ``` +
+ +
+ Full Example (all options) + + ``` + event: datastar-patch-elements + id: 123 + retry: 2000 + data: mode inner + data: selector #feed + data: useViewTransition true + data: elements
+ data: elements 1 + data: elements
+ + ``` +
+ +
+ Patch elements based on their ID + + ``` + event: datastar-patch-elements + data: elements
New content.
+ data: elements
Other new content.
+ ``` +
+ +
+ Insert a new element based on a selector + + ``` + event: datastar-patch-elements + data: mode append + data: selector #mycontainer + data: elements
New content
+ ``` +
+ +
+ Remove elements based on a selector + + ``` + event: datastar-patch-elements + data: mode remove + data: selector #feed, #otherid + ``` +
+ +
+ Remove elements without a selector + + ``` + event: datastar-patch-elements + data: mode remove + data: elements
+ ``` +
+ +`PatchElements` sends HTML elements to the browser for DOM manipulation. + +> [!TIP] +> - To remove elements, use the `remove` patch mode +> - To execute JavaScript, send a ` + ``` + + +
+ Full Example (all options) + + ``` + event: datastar-patch-elements + id: 123 + retry: 2000 + data: mode append + data: selector body + data: elements + ``` +
+ +### Parameters + +- **script**: One or more lines of JavaScript. + +### Options + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `autoRemove` | boolean | `true` | Removes the script tag after executing | +| `attributes` | []string | - | Attributes to add to the script tag | + +### Implementation + +***Must*** call `ServerSentEventGenerator.send` with event type `datastar-patch-elements`, sending a `script` tag containing the JavaScript to execute. If `autoRemove` is `true`, `data-effect="el.remove()"` must be added to the `script` tag. If `attributes` exist, they must be added to the `script` tag. + +**Data format** (only include non-defaults): +- `selector body\n` +- `mode append\n` +- `elements SCRIPT_TAG\n` + +--- + +## `ReadSignals` + +`ReadSignals` parses incoming signal data from the browser into backend objects. + +```go +ReadSignals(request *http.Request, signals any) error +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `request` | HTTP Request | Language-specific request object | +| `signals` | any | Target object/struct for unmarshaling | + +### Implementation + +The function ***must*** parse the incoming HTTP request based on the method: + +| Method | Data Location | Format | Description | +|--------|---------------|--------|-------------| +| `GET` | Query parameter `datastar` | URL-encoded JSON | Extract from query string | +| Others | Request body | JSON | Parse request body directly | + +**Error Handling**: ***Must*** return error for invalid JSON. diff --git a/sdk/README.md b/sdk/README.md index 83835da89..fc4235bb5 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -1,427 +1,36 @@ -# Architecture Decision Record: Datastar SDK +# Datastar SDK Directory -## Summary +This directory contains the SDK test suite and related tools for Datastar SDK development. -Datastar SDK provides unified tooling for building Hypermedia On Whatever you Like (HOWL) based UIs across multiple languages. While Datastar supports various plugins, the default bundle focuses on a robust Server-Sent Event (SSE) approach, addressing the lack of good SSE tooling in most languages and backends. +## Structure -## Decision +- `tests/` - Comprehensive test suite for validating SDK implementations +- `datastar-sdk-config.json` - SDK configuration with constants and defaults +- `datastar-sdk-config.schema.json-v1.json` - JSON schema for configuration validation +- `ADR.md` - Architecture Decision Record for SDK specifications -Provide a language-agnostic SDK with these principles: +## SDK Development -1. **Minimal Core**: Keep the SDK as minimal as possible -2. **Sugar Extensions**: Allow per-language/framework extended features in SDK "sugar" versions +### Configuration -### Naming Rationale +The SDK configuration system provides: -**Why "Patch" instead of "Merge":** -The prefix "Patch" was chosen to better reflect the non-idempotent nature of these operations. Unlike PUT requests that replace entire resources, PATCH requests apply partial modifications. This aligns with our SDKs behavior where operations modify specific parts of the DOM or signal state rather than replacing them entirely. +- **Constants**: Event types, element patch modes, and other enums +- **Defaults**: Default values for booleans and durations +- **Literals**: List of known dataline literals +- **JSON Schema**: Validation for the configuration format -**Why "Elements" instead of "Fragments":** -We use "Elements" because it accurately describes what the SDK handles - complete HTML elements, not arbitrary DOM nodes like text nodes or document fragments. This naming matches the actual intent and constraints of the system, making the API clearer and more predictable for developers. +### Creating a New SDK -## Details +1. Read the [Architecture Decision Record](./ADR.md) for SDK specifications +2. Use [`datastar-sdk-config.json`](./datastar-sdk-config.json) as your source of constants +3. Implement the required `ServerSentEventGenerator` interface +4. Validate your implementation using the [test suite](./tests/README.mdl) -### Core Mechanics +## Official SDKs -The core mechanics of Datastar’s SSE support is +Official SDKs can be found at the [Datastar website](https://data-star.dev/reference/sdks). -1. **Server → Browser**: Data is sent as SSE events -2. **Browser → Server**: Data arrives as JSON under the `datastar` namespace +## Support -# SDK Specification - -> [!WARNING] -> All naming conventions use Go as the reference implementation. Adapt to language-specific conventions while maintaining consistency. - -## ServerSentEventGenerator - -**Required**: A `ServerSentEventGenerator` namespace/class/struct (implementation may vary by language). - ---- - -### Construction / Initialization - -**Requirements:** - -| Requirement | Description | -|-------------|-------------| -| **Constructor** | ***Must*** accept HTTP Request and Response objects | -| **Response Headers** | ***Must*** set:
• `Cache-Control: no-cache`
• `Content-Type: text/event-stream`
• `Connection: keep-alive` (HTTP/1.1 only - [see spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection)) | -| **Immediate Flush** | ***Should*** flush response immediately to prevent timeouts | -| **Thread Safety** | ***Should*** ensure ordered delivery (e.g., mutex in Go) | - ---- - -### `ServerSentEventGenerator.send` - -``` -ServerSentEventGenerator.send( - eventType: EventType, - dataLines: string[], - options?: { - eventId?: string, - retryDuration?: durationInMilliseconds - } -) -``` - -A unified sending function ***should*** be used internally (private/protected). - -#### Parameters - -##### EventType - -String enum of supported events: - -| Event | Description | -|-------|-------------| -| `datastar-patch-elements` | Patches HTML elements into the DOM | -| `datastar-patch-signals` | Patches signals into the signal store | - -##### Options - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `eventId` | string | - | Unique event identifier for replay functionality ([SSE spec](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#id)) | -| `retryDuration` | ms | `1000` | Reconnection delay after connection loss ([SSE spec](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#retry)) | - -#### Implementation Requirements - -***Must*** write to response buffer in this exact order: - -1. `event: EVENT_TYPE\n` -2. `id: EVENT_ID\n` (if `eventId` provided) -3. `retry: RETRY_DURATION\n` (***unless*** default of `1000`) -4. `data: DATA\n` (for each of the `dataLines`) -5. `\n` (end of event) -6. ***Should*** flush immediately (note: compression middleware may interfere) - -**Error Handling**: ***Must*** return/throw errors per language conventions. - ---- - -### `ServerSentEventGenerator.PatchElements` - -```go -ServerSentEventGenerator.PatchElements( - elements: string, - options?: { - selector?: string, - mode?: ElementPatchMode, - useViewTransition?: boolean, - eventId?: string, - retryDuration?: durationInMilliseconds - } -) -``` - -#### Example Output - -
- Minimal Example - - ``` - event: datastar-patch-elements - data: elements
1
- - ``` -
- -
- Full Example (all options) - - ``` - event: datastar-patch-elements - id: 123 - retry: 2000 - data: mode inner - data: selector #feed - data: useViewTransition true - data: elements
- data: elements 1 - data: elements
- - ``` -
- -
- Patch elements based on their ID - - ``` - event: datastar-patch-elements - data: elements
New content.
- data: elements
Other new content.
- ``` -
- -
- Insert a new element based on a selector - - ``` - event: datastar-patch-elements - data: mode append - data: selector #mycontainer - data: elements
New content
- ``` -
- -
- Remove elements based on a selector - - ``` - event: datastar-patch-elements - data: mode remove - data: selector #feed, #otherid - ``` -
- -
- Remove elements without a selector - - ``` - event: datastar-patch-elements - data: mode remove - data: elements
- ``` -
- -`PatchElements` sends HTML elements to the browser for DOM manipulation. - -> [!TIP] -> - To remove elements, use the `remove` patch mode -> - To execute JavaScript, send a ` - ``` - - -
- Full Example (all options) - - ``` - event: datastar-patch-elements - id: 123 - retry: 2000 - data: mode append - data: selector body - data: elements - ``` -
- -### Parameters - -- **script**: One or more lines of JavaScript. - -### Options - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `autoRemove` | boolean | `true` | Removes the script tag after executing | -| `attributes` | []string | - | Attributes to add to the script tag | - -### Implementation - -***Must*** call `ServerSentEventGenerator.send` with event type `datastar-patch-elements`, sending a `script` tag containing the JavaScript to execute. If `autoRemove` is `true`, `data-effect="el.remove()"` must be added to the `script` tag. If `attributes` exist, they must be added to the `script` tag. - -**Data format** (only include non-defaults): -- `selector body\n` -- `mode append\n` -- `elements SCRIPT_TAG\n` - ---- - -## `ReadSignals` - -`ReadSignals` parses incoming signal data from the browser into backend objects. - -```go -ReadSignals(request *http.Request, signals any) error -``` - -### Parameters - -| Parameter | Type | Description | -|-----------|------|-------------| -| `request` | HTTP Request | Language-specific request object | -| `signals` | any | Target object/struct for unmarshaling | - -### Implementation - -The function ***must*** parse the incoming HTTP request based on the method: - -| Method | Data Location | Format | Description | -|--------|---------------|--------|-------------| -| `GET` | Query parameter `datastar` | URL-encoded JSON | Extract from query string | -| Others | Request body | JSON | Parse request body directly | - -**Error Handling**: ***Must*** return error for invalid JSON. +For SDK-specific issues, please open an issue in the respective SDK repository. For general Datastar questions or cross-SDK concerns, use the main [Datastar repository](https://github.com/starfederation/datastar). \ No newline at end of file diff --git a/sdk/datastar-sdk-config-v1.json b/sdk/datastar-sdk-config-v1.json new file mode 100644 index 000000000..409225b54 --- /dev/null +++ b/sdk/datastar-sdk-config-v1.json @@ -0,0 +1,76 @@ +{ + "$schema": "./datastar-sdk-config.schema.json-v1.json", + "datastarKey": "datastar", + "defaults": { + "booleans": { + "elementsUseViewTransitions": false, + "patchSignalsOnlyIfMissing": false + }, + "durations": { + "sseRetryDuration": 1000 + } + }, + "datalineLiterals": [ + "selector", + "mode", + "elements", + "useViewTransition", + "signals", + "onlyIfMissing" + ], + "enums": { + "ElementPatchMode": { + "description": "The mode in which an element is patched into the DOM.", + "default": "outer", + "values": [ + { + "value": "outer", + "description": "Morphs the element into the existing element." + }, + { + "value": "inner", + "description": "Replaces the inner HTML of the existing element." + }, + { + "value": "remove", + "description": "Removes the existing element." + }, + { + "value": "replace", + "description": "Replaces the existing element with the new element." + }, + { + "value": "prepend", + "description": "Prepends the element inside to the existing element." + }, + { + "value": "append", + "description": "Appends the element inside the existing element." + }, + { + "value": "before", + "description": "Inserts the element before the existing element." + }, + { + "value": "after", + "description": "Inserts the element after the existing element." + } + ] + }, + "EventType": { + "description": "The type protocol on top of SSE which allows for core pushed based communication between the server and the client.", + "values": [ + { + "name": "PatchElements", + "value": "datastar-patch-elements", + "description": "An event for patching HTML elements into the DOM." + }, + { + "name": "PatchSignals", + "value": "datastar-patch-signals", + "description": "An event for patching signals." + } + ] + } + } +} diff --git a/sdk/datastar-sdk-config-v1.schema.json b/sdk/datastar-sdk-config-v1.schema.json new file mode 100644 index 000000000..5425af55b --- /dev/null +++ b/sdk/datastar-sdk-config-v1.schema.json @@ -0,0 +1,121 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://datastar.dev/schemas/sdk-config.schema.json", + "title": "Datastar SDK Configuration", + "description": "Configuration schema for Datastar SDK authors", + "type": "object", + "required": ["version", "datastarKey", "defaults", "enums", "datalineLiterals"], + "properties": { + "version": { + "type": "string", + "description": "The version of Datastar", + "pattern": "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9]+)?$" + }, + "datastarKey": { + "type": "string", + "description": "The key used for datastar attributes", + "default": "datastar" + }, + "defaults": { + "type": "object", + "description": "Default values for various SDK settings", + "properties": { + "booleans": { + "type": "object", + "description": "Boolean default values", + "properties": { + "elementsUseViewTransitions": { + "type": "boolean", + "description": "Should elements be patched using the ViewTransition API?", + "default": false + }, + "patchSignalsOnlyIfMissing": { + "type": "boolean", + "description": "Should a given set of signals patch if they are missing?", + "default": false + } + } + }, + "durations": { + "type": "object", + "description": "Duration default values (in milliseconds)", + "properties": { + "sseRetryDuration": { + "type": "integer", + "description": "The default duration for retrying SSE on connection reset (in milliseconds)", + "default": 1000 + } + } + } + } + }, + "datalineLiterals": { + "type": "array", + "description": "Literal strings used in dataline attributes", + "items": { + "type": "string" + }, + "default": ["selector", "mode", "elements", "useViewTransition", "signals", "onlyIfMissing"] + }, + "enums": { + "type": "object", + "description": "Enum definitions used in the SDK", + "properties": { + "ElementPatchMode": { + "type": "object", + "description": "The mode in which an element is patched into the DOM", + "properties": { + "description": { + "type": "string" + }, + "default": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "object", + "required": ["value", "description"], + "properties": { + "value": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + } + } + }, + "EventType": { + "type": "object", + "description": "The type protocol on top of SSE which allows for core pushed based communication between the server and the client", + "properties": { + "description": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "object", + "required": ["value", "description"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + } + } + } + } + } + } +} \ No newline at end of file