Merge branch 'develop'
This commit is contained in:
commit
37fedbd3f1
|
@ -22,8 +22,8 @@ jobs:
|
|||
- name: Deploy to fly.io
|
||||
uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
- run: |
|
||||
docker container run --rm -t -v "${{ github.workspace }}":/app -v go-modules:/go/pkg/mod ghcr.io/starfederation/datastar-dev -c 'task tools'
|
||||
docker container run --rm -t -v "${{ github.workspace }}":/app -v go-modules:/go/pkg/mod ghcr.io/starfederation/datastar-dev -c 'task support'
|
||||
docker container run --rm -t -v "${{ github.workspace }}":/app -v go-modules:/go/pkg/mod ghcr.io/starfederation/datastar-dev -c 'go tool task tools'
|
||||
docker container run --rm -t -v "${{ github.workspace }}":/app -v go-modules:/go/pkg/mod ghcr.io/starfederation/datastar-dev -c 'go tool task support'
|
||||
flyctl deploy --local-only
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
|
|
|
@ -26,5 +26,5 @@ jobs:
|
|||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run CI tests
|
||||
run: |
|
||||
docker container run --rm -t -v "${{ github.workspace }}":/app -v go-modules:/go/pkg/mod ghcr.io/starfederation/datastar-dev -c 'task tools'
|
||||
docker container run --rm -t -v "${{ github.workspace }}":/app -v go-modules:/go/pkg/mod ghcr.io/starfederation/datastar-dev -c 'task test'
|
||||
docker container run --rm -t -v "${{ github.workspace }}":/app -v go-modules:/go/pkg/mod ghcr.io/starfederation/datastar-dev -c 'go tool task tools'
|
||||
docker container run --rm -t -v "${{ github.workspace }}":/app -v go-modules:/go/pkg/mod ghcr.io/starfederation/datastar-dev -c 'go tool task test'
|
||||
|
|
|
@ -53,7 +53,7 @@ Below are details and options available in each of the provided `make` commands:
|
|||
|
||||
You can pass in optional CLI arguments to override the default settings Datastar dev uses:
|
||||
|
||||
* `TAG=` (default: `1.23.1-alpine`) - allows you to specify the official [golang Docker image](https://hub.docker.com/_/golang) tag that should be used. Using this, you can change the version of Go the container runs, e.g.: `make image-build TAG="1.23-alpine"` will use the latest version of Go 1.23 for Alpine Linux.
|
||||
* `TAG=` (default is defined in `Dockerfile-dev`) - allows you to specify the official [golang Docker image](https://hub.docker.com/_/golang) tag that should be used. Using this, you can change the version of Go the container runs, e.g.: `make image-build TAG="1.24"` will use the latest patch version of Go 1.24 official Docker image.
|
||||
|
||||
### Terminating
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM docker.io/golang:1.23.3-alpine AS build
|
||||
FROM docker.io/golang:1.24.2-alpine AS build
|
||||
|
||||
RUN apk add --no-cache upx
|
||||
ENV PORT=8080
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
ARG TAG=1.23
|
||||
ARG TAG=1.24
|
||||
|
||||
FROM golang:$TAG
|
||||
|
||||
|
@ -37,13 +37,6 @@ RUN apt update && sudo apt upgrade \
|
|||
npm install -g npm@^10.0.0 \
|
||||
npm install -g pnpm \
|
||||
&& \
|
||||
# Install needed Go tooling \
|
||||
go install github.com/go-task/task/v3/cmd/task@latest \
|
||||
&& \
|
||||
go install github.com/a-h/templ/cmd/templ@latest \
|
||||
&& \
|
||||
go install github.com/valyala/quicktemplate/qtc@latest \
|
||||
&& \
|
||||
# Install flyctl cli \
|
||||
curl -L https://fly.io/install.sh | sh \
|
||||
&& \
|
||||
|
|
10
Makefile
10
Makefile
|
@ -1,4 +1,4 @@
|
|||
TAG?=1.23
|
||||
TAG?=1.24
|
||||
CONTAINER?=$(shell basename $(CURDIR))-dev
|
||||
DEV_PORT?=8080
|
||||
IMAGE_INFO=$(shell docker image inspect $(CONTAINER):$(TAG))
|
||||
|
@ -17,17 +17,17 @@ clean:
|
|||
docker volume rm go-modules
|
||||
# Run the development server
|
||||
dev: --image-check
|
||||
${DOCKER_RUN} --name ${CONTAINER}-$@ -e DEV_PORT="${DEV_PORT}" -p ${DEV_PORT}:${DEV_PORT} ${IMAGE_NAME} -c 'task -w'
|
||||
${DOCKER_RUN} --name ${CONTAINER}-$@ -e DEV_PORT="${DEV_PORT}" -p ${DEV_PORT}:${DEV_PORT} ${IMAGE_NAME} -c 'go tool task -w'
|
||||
# Build the Docker image
|
||||
image-build:
|
||||
docker build -f Dockerfile-dev . -t ${IMAGE_NAME} --build-arg TAG=${TAG} --no-cache
|
||||
${DOCKER_RUN} --name ${CONTAINER}-$@ ${IMAGE_NAME} -c 'task tools'
|
||||
${DOCKER_RUN} --name ${CONTAINER}-$@ ${IMAGE_NAME} -c 'go tool task tools'
|
||||
# Run the passed in task command
|
||||
task: --image-check
|
||||
${DOCKER_RUN} --name ${CONTAINER}-$@ -e DEV_PORT="${DEV_PORT}" -p ${DEV_PORT}:${DEV_PORT} ${IMAGE_NAME} -c 'task $(filter-out $@,$(MAKECMDGOALS)) $(MAKEFLAGS)'
|
||||
${DOCKER_RUN} --name ${CONTAINER}-$@ -e DEV_PORT="${DEV_PORT}" -p ${DEV_PORT}:${DEV_PORT} ${IMAGE_NAME} -c 'go tool task $(filter-out $@,$(MAKECMDGOALS)) $(MAKEFLAGS)'
|
||||
# Run the test suite
|
||||
test: --image-check
|
||||
${DOCKER_RUN} --name ${CONTAINER}-$@ -e DEV_PORT="${DEV_PORT}" -p ${DEV_PORT}:${DEV_PORT} ${IMAGE_NAME} -c 'task test'
|
||||
${DOCKER_RUN} --name ${CONTAINER}-$@ -e DEV_PORT="${DEV_PORT}" -p ${DEV_PORT}:${DEV_PORT} ${IMAGE_NAME} -c 'go tool task test'
|
||||
# Open a shell inside of the container
|
||||
ssh: --image-check
|
||||
${DOCKER_RUN} --name ${CONTAINER}-$@ --entrypoint=/bin/sh ${IMAGE_NAME}
|
||||
|
|
|
@ -28,7 +28,7 @@ Visit the [Datastar Website »](https://data-star.dev/)
|
|||
|
||||
Watch the [Videos »](https://www.youtube.com/@data-star)
|
||||
|
||||
Join the [Discord Server »](https://discord.com/channels/1296224603642925098/1296224603642925102)
|
||||
Join the [Discord Server »](https://discord.com/invite/bnRNgZjgPh)
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
@ -58,3 +58,5 @@ You can manually add your own plugins to the core:
|
|||
)
|
||||
</script>
|
||||
```
|
||||
|
||||
[](https://www.star-history.com/#starfederation/datastar&Date)
|
||||
|
|
20
Taskfile.yml
20
Taskfile.yml
|
@ -10,11 +10,18 @@ vars:
|
|||
|
||||
tasks:
|
||||
tools:
|
||||
platforms: [windows, linux, darwin/arm64, darwin/amd64, openbsd, dragonfly, freebsd, netbsd]
|
||||
platforms:
|
||||
[
|
||||
windows,
|
||||
linux,
|
||||
darwin/arm64,
|
||||
darwin/amd64,
|
||||
openbsd,
|
||||
dragonfly,
|
||||
freebsd,
|
||||
netbsd,
|
||||
]
|
||||
cmds:
|
||||
- go install github.com/go-task/task/v3/cmd/task@latest
|
||||
- go install github.com/a-h/templ/cmd/templ@latest
|
||||
|
||||
- platforms: [linux/amd64]
|
||||
cmd: test -f site/tailwindcli || wget -O site/tailwindcli https://github.com/dobicinaitis/tailwind-cli-extra/releases/download/v1.7.21/tailwindcss-extra-linux-x64
|
||||
|
||||
|
@ -37,7 +44,6 @@ tasks:
|
|||
cmd: test -f site/tailwindcli || (echo "#!/bin/sh" > site/tailwindcli && echo "tailwindcss $@" >> site/tailwindcli)
|
||||
|
||||
- chmod +x site/tailwindcli
|
||||
- go install github.com/valyala/quicktemplate/qtc@latest
|
||||
|
||||
version:
|
||||
cmds:
|
||||
|
@ -49,7 +55,7 @@ tasks:
|
|||
generates:
|
||||
- "**/*.qtpl.go"
|
||||
cmds:
|
||||
- qtc
|
||||
- go tool qtc
|
||||
|
||||
build:
|
||||
deps:
|
||||
|
@ -143,7 +149,7 @@ tasks:
|
|||
sources:
|
||||
- "**/*.templ"
|
||||
cmds:
|
||||
- templ generate .
|
||||
- go tool templ generate .
|
||||
|
||||
kill:
|
||||
method: none
|
||||
|
|
|
@ -63,4 +63,6 @@ You can manually add your own plugins to the core:
|
|||
</script>
|
||||
```
|
||||
|
||||
[](https://www.star-history.com/#starfederation/datastar&Date)
|
||||
|
||||
{%- endfunc -%}
|
||||
|
|
|
@ -152,6 +152,7 @@ func writeOutConsts(version string) error {
|
|||
"examples/ruby/hello-world/hello-world.html": helloWorldExample,
|
||||
"examples/rust/axum/hello-world/hello-world.html": helloWorldExample,
|
||||
"examples/rust/rocket/hello-world/hello-world.html": helloWorldExample,
|
||||
"examples/rust/rama/hello-world/hello-world.html": helloWorldExample,
|
||||
}
|
||||
|
||||
for path, tmplFn := range templates {
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
module github.com/starfederation/datastar/examples/go/hello-world
|
||||
|
||||
go 1.23.3
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.0
|
||||
github.com/starfederation/datastar v1.0.0-beta.2
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/starfederation/datastar v1.0.0-beta.11
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.3.819 // indirect
|
||||
github.com/CAFxX/httpcompression v0.0.9 // indirect
|
||||
github.com/a-h/templ v0.3.857 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/delaneyj/gostar v0.8.0 // indirect
|
||||
github.com/goccy/go-json v0.10.4 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/igrmk/treemap/v2 v2.0.1 // indirect
|
||||
github.com/samber/lo v1.47.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/samber/lo v1.49.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
)
|
||||
|
|
|
@ -1,22 +1,59 @@
|
|||
github.com/a-h/templ v0.3.819 h1:KDJ5jTFN15FyJnmSmo2gNirIqt7hfvBD2VXVDTySckM=
|
||||
github.com/a-h/templ v0.3.819/go.mod h1:iDJKJktpttVKdWoTkRNNLcllRI+BlpopJc+8au3gOUo=
|
||||
github.com/CAFxX/httpcompression v0.0.9 h1:0ue2X8dOLEpxTm8tt+OdHcgA+gbDge0OqFQWGKSqgrg=
|
||||
github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM=
|
||||
github.com/a-h/templ v0.3.857 h1:6EqcJuGZW4OL+2iZ3MD+NnIcG7nGkaQeF2Zq5kf9ZGg=
|
||||
github.com/a-h/templ v0.3.857/go.mod h1:qhrhAkRFubE7khxLZHsBFHfX+gWwVNKbzKeF9GlPV4M=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/delaneyj/gostar v0.8.0 h1:uT1JR+77P5ePL4BVTXsKNLtwUUtMAu/dNLryjEk95RA=
|
||||
github.com/delaneyj/gostar v0.8.0/go.mod h1:mlxRWAVbntRR2VWlpXAzt7y9HY+bQtEm/lsyFnGLx/w=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f h1:jopqB+UTSdJGEJT8tEqYyE29zN91fi2827oLET8tl7k=
|
||||
github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/igrmk/treemap/v2 v2.0.1 h1:Jhy4z3yhATvYZMWCmxsnHO5NnNZBdueSzvxh6353l+0=
|
||||
github.com/igrmk/treemap/v2 v2.0.1/go.mod h1:PkTPvx+8OHS8/41jnnyVY+oVsfkaOUZGcr+sfonosd4=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/starfederation/datastar v1.0.0-beta.2 h1:r1+PXLSe+wwZhIdGqbwdHEuDthIf5dL+BrP1pNRML3I=
|
||||
github.com/starfederation/datastar v1.0.0-beta.2/go.mod h1:iUb4gqem8vHiZ7NyAC09GN0hrAKd0LoY3w8ZuQ7b2gQ=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/starfederation/datastar v1.0.0-beta.11 h1:8P5Yt4B40cDWgHHAI7CkWbuxTxSSYqk48pLDfi3JZF0=
|
||||
github.com/starfederation/datastar v1.0.0-beta.11/go.mod h1:PCPV2lpiI0c86BrE2ndc+5yq8rO2IZWvLqILwk9tVV4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g=
|
||||
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -52,7 +52,7 @@ async def updates_asgi(request):
|
|||
async def time_updates():
|
||||
while True:
|
||||
yield DatastarStreamingHttpResponse.merge_fragments(
|
||||
[f"""<span id="currentTime">{datetime.now().isoformat()}"""]
|
||||
f"""<span id="currentTime">{datetime.now().isoformat()}"""
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
yield DatastarStreamingHttpResponse.merge_signals(
|
||||
|
@ -110,7 +110,7 @@ def updates_wsgi(request):
|
|||
def time_updates():
|
||||
while True:
|
||||
yield DatastarStreamingHttpResponse.merge_fragments(
|
||||
[f"""<span id="currentTime">{datetime.now().isoformat()}"""]
|
||||
f"""<span id="currentTime">{datetime.now().isoformat()}"""
|
||||
)
|
||||
time.sleep(0.5)
|
||||
yield DatastarStreamingHttpResponse.merge_signals(
|
||||
|
|
|
@ -54,7 +54,7 @@ async def read_root():
|
|||
async def time_updates():
|
||||
while True:
|
||||
yield DatastarStreamingResponse.merge_fragments(
|
||||
[f"""<span id="currentTime">{datetime.now().isoformat()}"""]
|
||||
f"""<span id="currentTime">{datetime.now().isoformat()}"""
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
yield DatastarStreamingResponse.merge_signals({"currentTime": f"{datetime.now().isoformat()}"})
|
||||
|
|
|
@ -93,7 +93,7 @@ def GreatTable(pattern=default_pattern):
|
|||
@app.post
|
||||
async def table(filter: str):
|
||||
async def _():
|
||||
yield DatastarStreamingResponse.merge_fragments([GreatTable(filter)])
|
||||
yield DatastarStreamingResponse.merge_fragments(GreatTable(filter))
|
||||
|
||||
return DatastarStreamingResponse(_())
|
||||
|
||||
|
@ -151,7 +151,7 @@ def index():
|
|||
async def clock():
|
||||
while True:
|
||||
now = datetime.isoformat(datetime.now())
|
||||
yield DatastarStreamingResponse.merge_fragments([Span(id="currentTime")(now)])
|
||||
yield DatastarStreamingResponse.merge_fragments(Span(id="currentTime")(now))
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
|
@ -165,7 +165,7 @@ async def hello():
|
|||
async def _():
|
||||
# Simulate load time
|
||||
await asyncio.sleep(1)
|
||||
yield DatastarStreamingResponse.merge_fragments([HELLO_BUTTON])
|
||||
yield DatastarStreamingResponse.merge_fragments(HELLO_BUTTON)
|
||||
|
||||
return DatastarStreamingResponse(_())
|
||||
|
||||
|
@ -185,7 +185,7 @@ async def reset():
|
|||
|
||||
async def _(sse):
|
||||
await asyncio.sleep(1)
|
||||
yield sse.merge_fragments([reset_and_hello])
|
||||
yield sse.merge_fragments(reset_and_hello)
|
||||
|
||||
return DatastarFastHTMLResponse(_)
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ async def index():
|
|||
async def clock():
|
||||
while True:
|
||||
now = datetime.isoformat(datetime.now())
|
||||
yield DatastarStreamingResponse.merge_fragments([Span(id="currentTime")(now)])
|
||||
yield DatastarStreamingResponse.merge_fragments(Span(id="currentTime")(now))
|
||||
await asyncio.sleep(1)
|
||||
yield DatastarStreamingResponse.merge_signals({"currentTime": f"{now}"})
|
||||
await asyncio.sleep(1)
|
||||
|
|
|
@ -47,7 +47,7 @@ async def updates():
|
|||
async def time_updates():
|
||||
while True:
|
||||
yield ServerSentEventGenerator.merge_fragments(
|
||||
[f"""<span id="currentTime">{datetime.now().isoformat()}"""]
|
||||
f"""<span id="currentTime">{datetime.now().isoformat()}"""
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
yield ServerSentEventGenerator.merge_signals({"currentTime": f"{datetime.now().isoformat()}"})
|
||||
|
|
|
@ -58,13 +58,11 @@ async def add_signal(request):
|
|||
|
||||
await response.send(
|
||||
SSE.merge_fragments(
|
||||
[
|
||||
"""
|
||||
"""
|
||||
<div class="time signal">
|
||||
Current time from signal: <span data-text="$currentTime">CURRENT_TIME</span>
|
||||
</div>
|
||||
"""
|
||||
],
|
||||
""",
|
||||
selector="#timers",
|
||||
merge_mode=FragmentMergeMode.FragmentMergeModeAppend,
|
||||
)
|
||||
|
@ -79,13 +77,11 @@ async def add_fragment(request):
|
|||
|
||||
await response.send(
|
||||
SSE.merge_fragments(
|
||||
[
|
||||
f"""\
|
||||
f"""\
|
||||
<div class="time fragment">
|
||||
Current time from fragment: {datetime.now().isoformat()}
|
||||
</div>
|
||||
"""
|
||||
],
|
||||
""",
|
||||
selector="#timers",
|
||||
merge_mode=FragmentMergeMode.FragmentMergeModeAppend,
|
||||
)
|
||||
|
@ -101,13 +97,11 @@ async def updates(request):
|
|||
while True:
|
||||
await response.send(
|
||||
SSE.merge_fragments(
|
||||
[
|
||||
f"""
|
||||
f"""
|
||||
<div class="time fragment" >
|
||||
Current time from fragment: {datetime.now().isoformat()}
|
||||
</div>
|
||||
"""
|
||||
],
|
||||
""",
|
||||
selector=".fragment",
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
name = "hello_world"
|
||||
version = "0.1.0"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[dependencies]
|
||||
async-stream = "0.3.6"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
Cargo.lock
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
edition = "2024"
|
||||
name = "hello_world"
|
||||
version = "0.1.0"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[dependencies]
|
||||
async-stream = "0.3.6"
|
||||
rama = { version = "0.2.0-alpha.11", features = ["http-full"] }
|
||||
datastar = { path = "../../../../sdk/rust", features = ["rama"] }
|
||||
futures = "0.3.31"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
tokio = { version = "1.43.0", features = ["full"] }
|
||||
tokio-stream = "0.1.17"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
|
@ -0,0 +1,35 @@
|
|||
<!-- This is auto-generated by Datastar. DO NOT EDIT. -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Datastar SDK Demo</title>
|
||||
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
|
||||
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.11/bundles/datastar.js"></script>
|
||||
</head>
|
||||
<body class="bg-white dark:bg-gray-900 text-lg max-w-xl mx-auto my-16">
|
||||
<div data-signals-delay="400" class="bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 rounded-lg px-6 py-8 ring shadow-xl ring-gray-900/5 space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<h1 class="text-gray-900 dark:text-white text-3xl font-semibold">
|
||||
Datastar SDK Demo
|
||||
</h1>
|
||||
<img src="https://data-star.dev/static/images/rocket.png" alt="Rocket" width="64" height="64"/>
|
||||
</div>
|
||||
<p class="mt-2">
|
||||
SSE events will be streamed from the backend to the frontend.
|
||||
</p>
|
||||
<div class="space-x-2">
|
||||
<label for="delay">
|
||||
Delay in milliseconds
|
||||
</label>
|
||||
<input data-bind-delay id="delay" type="number" step="100" min="0" class="w-36 rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-sky-500 focus:outline focus:outline-sky-500 dark:disabled:border-gray-700 dark:disabled:bg-gray-800/20" />
|
||||
</div>
|
||||
<button data-on-click="@get('/hello-world')" class="rounded-md bg-sky-500 px-5 py-2.5 leading-5 font-semibold text-white hover:bg-sky-700 hover:text-gray-100 cursor-pointer">
|
||||
Start
|
||||
</button>
|
||||
</div>
|
||||
<div class="my-16 text-8xl font-bold text-transparent" style="background: linear-gradient(to right in oklch, red, orange, yellow, green, blue, blue, violet); background-clip: text">
|
||||
<div id="message">Hello, world!</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,57 @@
|
|||
use {
|
||||
async_stream::stream,
|
||||
core::time::Duration,
|
||||
datastar::{
|
||||
Sse,
|
||||
prelude::{MergeFragments, ReadSignals},
|
||||
},
|
||||
rama::{
|
||||
error::BoxError,
|
||||
http::{IntoResponse, response::Html, server::HttpServer, service::web::Router},
|
||||
rt::Executor,
|
||||
},
|
||||
serde::Deserialize,
|
||||
tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt},
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), BoxError> {
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| format!("{}=debug", env!("CARGO_CRATE_NAME")).into()),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
let app = Router::new()
|
||||
.get("/", index)
|
||||
.get("/hello-world", hello_world);
|
||||
|
||||
tracing::debug!("listening on 127.0.0.1:3000");
|
||||
HttpServer::auto(Executor::default())
|
||||
.listen("127.0.0.1:3000", app)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn index() -> Html<&'static str> {
|
||||
Html(include_str!("../hello-world.html"))
|
||||
}
|
||||
|
||||
const MESSAGE: &str = "Hello, world!";
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Signals {
|
||||
pub delay: u64,
|
||||
}
|
||||
|
||||
async fn hello_world(ReadSignals(signals): ReadSignals<Signals>) -> impl IntoResponse {
|
||||
Sse(stream! {
|
||||
for i in 0..MESSAGE.len() {
|
||||
yield MergeFragments::new(format!("<div id='message'>{}</div>", &MESSAGE[0..i + 1])).into();
|
||||
tokio::time::sleep(Duration::from_millis(signals.delay)).await;
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
name = "hello_world"
|
||||
version = "0.1.0"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[dependencies]
|
||||
datastar = { path = "../../../../sdk/rust", features = ["rocket"] }
|
||||
|
|
82
go.mod
82
go.mod
|
@ -1,16 +1,15 @@
|
|||
module github.com/starfederation/datastar
|
||||
|
||||
go 1.23.3
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/CAFxX/httpcompression v0.0.9
|
||||
github.com/Jeffail/gabs/v2 v2.7.0
|
||||
github.com/TwiN/go-away v1.6.14
|
||||
github.com/a-h/templ v0.3.819
|
||||
github.com/TwiN/go-away v1.6.15
|
||||
github.com/a-h/templ v0.3.857
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/benbjohnson/hashfs v0.2.2
|
||||
github.com/blevesearch/bleve/v2 v2.4.4
|
||||
github.com/delaneyj/gostar v0.8.0
|
||||
github.com/delaneyj/toolbelt v0.3.16
|
||||
github.com/drhodes/golorem v0.0.0-20220328165741-da82e5b29246
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
|
@ -27,10 +26,10 @@ require (
|
|||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/huantt/plaintext-extractor v1.1.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/klauspost/compress v1.17.11
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/lithammer/fuzzysearch v1.1.8
|
||||
github.com/nats-io/nats-server/v2 v2.10.24
|
||||
github.com/nats-io/nats.go v1.38.0
|
||||
github.com/nats-io/nats-server/v2 v2.11.1
|
||||
github.com/nats-io/nats.go v1.41.1
|
||||
github.com/samber/lo v1.47.0
|
||||
github.com/segmentio/encoding v0.4.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
|
@ -42,7 +41,14 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Ladicle/tabwriter v1.0.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.5 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.9.3 // indirect
|
||||
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.15.0 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.12.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.1.12 // indirect
|
||||
|
@ -63,53 +69,91 @@ require (
|
|||
github.com/blevesearch/zapx/v15 v15.3.16 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b // indirect
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/chainguard-dev/git-urls v1.0.2 // indirect
|
||||
github.com/chewxy/math32 v1.11.1 // indirect
|
||||
github.com/cli/browser v1.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/denisbrodbeck/machineid v1.0.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/dominikbraun/graph v0.23.0 // indirect
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-git/v5 v5.14.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/go-task/task/v3 v3.42.1 // indirect
|
||||
github.com/go-task/template v0.1.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/protobuf v1.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/google/go-tpm v0.9.3 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/iancoleman/strcase v0.3.0 // indirect
|
||||
github.com/igrmk/treemap/v2 v2.0.1 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-zglob v0.0.6 // indirect
|
||||
github.com/minio/highwayhash v1.0.3 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/natefinch/atomic v1.0.1 // indirect
|
||||
github.com/nats-io/jwt/v2 v2.7.3 // indirect
|
||||
github.com/nats-io/nkeys v0.4.9 // indirect
|
||||
github.com/nats-io/nkeys v0.4.10 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/radovskyb/watcher v1.0.7 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/rzajac/clock v0.2.0 // indirect
|
||||
github.com/rzajac/zflake v0.8.0 // indirect
|
||||
github.com/sajari/fuzzy v1.0.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/ysmood/fetchup v0.2.4 // indirect
|
||||
github.com/ysmood/goob v0.4.0 // indirect
|
||||
github.com/ysmood/got v0.40.0 // indirect
|
||||
github.com/ysmood/leakless v0.9.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||
golang.org/x/image v0.23.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
google.golang.org/protobuf v1.36.2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.61.7 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.8.1 // indirect
|
||||
modernc.org/sqlite v1.34.4 // indirect
|
||||
mvdan.cc/sh/v3 v3.11.0 // indirect
|
||||
zombiezen.com/go/sqlite v1.4.0 // indirect
|
||||
)
|
||||
|
||||
tool (
|
||||
github.com/a-h/templ/cmd/templ
|
||||
github.com/go-task/task/v3/cmd/task
|
||||
github.com/valyala/quicktemplate/qtc
|
||||
)
|
||||
|
|
196
go.sum
196
go.sum
|
@ -1,18 +1,43 @@
|
|||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/CAFxX/httpcompression v0.0.9 h1:0ue2X8dOLEpxTm8tt+OdHcgA+gbDge0OqFQWGKSqgrg=
|
||||
github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM=
|
||||
github.com/Jeffail/gabs/v2 v2.7.0 h1:Y2edYaTcE8ZpRsR2AtmPu5xQdFDIthFG0jYhu5PY8kg=
|
||||
github.com/Jeffail/gabs/v2 v2.7.0/go.mod h1:dp5ocw1FvBBQYssgHsG7I1WYsiLRtkUaB1FEtSwvNUw=
|
||||
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
|
||||
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
|
||||
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
|
||||
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
|
||||
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM=
|
||||
github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
|
||||
github.com/TwiN/go-away v1.6.14 h1:gjFP+6/A36gmj0NpYX0Sz9hrdU0KtHwtNWYnsJgV4fo=
|
||||
github.com/TwiN/go-away v1.6.14/go.mod h1:d+Gv3XuqjIeFqXYuAIzlyNoDzr1vNsP5B/hRY3u/VLs=
|
||||
github.com/a-h/templ v0.3.819 h1:KDJ5jTFN15FyJnmSmo2gNirIqt7hfvBD2VXVDTySckM=
|
||||
github.com/a-h/templ v0.3.819/go.mod h1:iDJKJktpttVKdWoTkRNNLcllRI+BlpopJc+8au3gOUo=
|
||||
github.com/TwiN/go-away v1.6.15 h1:pm1UvsteYNnOWl4bcbhAXQnebR5UcqfcuCi3kl6LJhE=
|
||||
github.com/TwiN/go-away v1.6.15/go.mod h1:cgCIChHZZU7u9QjVuGAf3X95MPoMPuceCwnvj8+JDB0=
|
||||
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo=
|
||||
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ=
|
||||
github.com/a-h/templ v0.3.857 h1:6EqcJuGZW4OL+2iZ3MD+NnIcG7nGkaQeF2Zq5kf9ZGg=
|
||||
github.com/a-h/templ v0.3.857/go.mod h1:qhrhAkRFubE7khxLZHsBFHfX+gWwVNKbzKeF9GlPV4M=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
|
||||
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/benbjohnson/hashfs v0.2.2 h1:vFZtksphM5LcnMRFctj49jCUkCc7wp3NP6INyfjkse4=
|
||||
github.com/benbjohnson/hashfs v0.2.2/go.mod h1:7OMXaMVo1YkfiIPxKrl7OXkUTUgWjmsAKyR+E6xDIRM=
|
||||
github.com/bits-and-blooms/bitset v1.12.0 h1:U/q1fAF7xXRhFCrhROzIfffYnu+dlS38vCZtmFVPHmA=
|
||||
|
@ -55,15 +80,24 @@ github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b h1:ju9Az5Y
|
|||
github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b/go.mod h1:BlrYNpOu4BvVRslmIG+rLtKhmjIaRhIbG8sb9scGTwI=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ=
|
||||
github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
|
||||
github.com/chewxy/math32 v1.11.1 h1:b7PGHlp8KjylDoU8RrcEsRuGZhJuz8haxnKfuMMRqy8=
|
||||
github.com/chewxy/math32 v1.11.1/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
|
||||
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
|
||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/delaneyj/gostar v0.8.0 h1:uT1JR+77P5ePL4BVTXsKNLtwUUtMAu/dNLryjEk95RA=
|
||||
github.com/delaneyj/gostar v0.8.0/go.mod h1:mlxRWAVbntRR2VWlpXAzt7y9HY+bQtEm/lsyFnGLx/w=
|
||||
github.com/delaneyj/toolbelt v0.3.16 h1:SSBIGQ7a46pSuj91YU4MZYOIqpb/2KNTImaczXWUDUA=
|
||||
github.com/delaneyj/toolbelt v0.3.16/go.mod h1:2boweQCsRLt3IfxvP/rIJKnS+7PW7SIfMGZGipcvs3U=
|
||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||
|
@ -71,37 +105,71 @@ github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbj
|
|||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
|
||||
github.com/drhodes/golorem v0.0.0-20220328165741-da82e5b29246 h1:m0+1paUpmLlBpUxldAEvJZVCrNQpt2iyecCw4TdHdOc=
|
||||
github.com/drhodes/golorem v0.0.0-20220328165741-da82e5b29246/go.mod h1:NsKVpF4h4j13Vm6Cx7Kf0V03aJKjfaStvm5rvK4+FyQ=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/evanw/esbuild v0.24.2 h1:PQExybVBrjHjN6/JJiShRGIXh1hWVm6NepVnhZhrt0A=
|
||||
github.com/evanw/esbuild v0.24.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-faker/faker/v4 v4.5.0 h1:ARzAY2XoOL9tOUK+KSecUQzyXQsUaZHefjyF8x6YFHc=
|
||||
github.com/go-faker/faker/v4 v4.5.0/go.mod h1:p3oq1GRjG2PZ7yqeFFfQI20Xm61DoBDlCA8RiSyZ48M=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
|
||||
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
|
||||
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
|
||||
github.com/go-sanitize/sanitize v1.1.0 h1:wq9tl5+VfkyCacCZIVQf6ksegRpfWl3N2vAyyYD0F1I=
|
||||
github.com/go-sanitize/sanitize v1.1.0/go.mod h1:r+anm3xp/Y1+pTNvPSgHMznwb0VVZgszoMQs3naOf0A=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-task/task/v3 v3.42.1 h1:HOaFbZGLOrAy2V/dLsX2rGJZVG2Qx6268KUIAIXdNE4=
|
||||
github.com/go-task/task/v3 v3.42.1/go.mod h1:q9a3NGSYIQL6GdI920pypRSj0rcfKZ0gV/3sHtMxzds=
|
||||
github.com/go-task/template v0.1.0 h1:ym/r2G937RZA1bsgiWedNnY9e5kxDT+3YcoAnuIetTE=
|
||||
github.com/go-task/template v0.1.0/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
|
||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62 h1:pbAFUZisjG4s6sxvRJvf2N7vhpCvx2Oxb3PmS6pDO1g=
|
||||
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f h1:jopqB+UTSdJGEJT8tEqYyE29zN91fi2827oLET8tl7k=
|
||||
github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
|
||||
github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
|
@ -116,23 +184,27 @@ github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrR
|
|||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/huantt/plaintext-extractor v1.1.0 h1:dZkJN0fGZf1o8x9UdR6hHqkZnqIwX94YlGJ/lSXUZ5c=
|
||||
github.com/huantt/plaintext-extractor v1.1.0/go.mod h1:zIIbG/hZnsnLgzDbZ2T8fOrA4SLGWCoHWWYZo0Anx9c=
|
||||
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/igrmk/treemap/v2 v2.0.1 h1:Jhy4z3yhATvYZMWCmxsnHO5NnNZBdueSzvxh6353l+0=
|
||||
github.com/igrmk/treemap/v2 v2.0.1/go.mod h1:PkTPvx+8OHS8/41jnnyVY+oVsfkaOUZGcr+sfonosd4=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede h1:YrgBGwxMRK0Vq0WSCWFaZUnTsrA/PZE/xs1QZh+/edg=
|
||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
|
@ -141,48 +213,76 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-zglob v0.0.6 h1:mP8RnmCgho4oaUYDIDn6GNxYk+qJGUs8fJLn+twYj2A=
|
||||
github.com/mattn/go-zglob v0.0.6/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
|
||||
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
|
||||
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
|
||||
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
|
||||
github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE=
|
||||
github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4=
|
||||
github.com/nats-io/nats-server/v2 v2.10.24 h1:KcqqQAD0ZZcG4yLxtvSFJY7CYKVYlnlWoAiVZ6i/IY4=
|
||||
github.com/nats-io/nats-server/v2 v2.10.24/go.mod h1:olvKt8E5ZlnjyqBGbAXtxvSQKsPodISK5Eo/euIta4s=
|
||||
github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA=
|
||||
github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw=
|
||||
github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0=
|
||||
github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE=
|
||||
github.com/nats-io/nats-server/v2 v2.11.1 h1:LwdauqMqMNhTxTN3+WFTX6wGDOKntHljgZ+7gL5HCnk=
|
||||
github.com/nats-io/nats-server/v2 v2.11.1/go.mod h1:leXySghbdtXSUmWem8K9McnJ6xbJOb0t9+NQ5HTRZjI=
|
||||
github.com/nats-io/nats.go v1.41.1 h1:lCc/i5x7nqXbspxtmXaV4hRguMPHqE/kYltG9knrCdU=
|
||||
github.com/nats-io/nats.go v1.41.1/go.mod h1:mzHiutcAdZrg6WLfYVKXGseqqow2fWmwlTEUOHsI4jY=
|
||||
github.com/nats-io/nkeys v0.4.10 h1:glmRrpCmYLHByYcePvnTBEAwawwapjCPMjy2huw20wc=
|
||||
github.com/nats-io/nkeys v0.4.10/go.mod h1:OjRrnIKnWBFl+s4YK5ChQfvHP2fxqZexrKJoVVyWB3U=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
|
||||
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rzajac/clock v0.2.0 h1:mxiL5/iTu7+pciqYGMxqUNTR+T2nxVvIdEUn3wfF4rU=
|
||||
github.com/rzajac/clock v0.2.0/go.mod h1:7ybePrkaEnyNk5tBHJZYZbeBU+2werzUVXn+mKT6iyw=
|
||||
github.com/rzajac/zflake v0.8.0 h1:EYNCn2jh16JAGuKw+NJmTz0unAH81elaSrefd3KWriU=
|
||||
github.com/rzajac/zflake v0.8.0/go.mod h1:uSQN20u/2bvKMkRLrqnKRqUk6tb2Ixac09WMljsSFhc=
|
||||
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/segmentio/encoding v0.4.1 h1:KLGaLSW0jrmhB58Nn4+98spfvPvmo4Ci1P/WIQ9wn7w=
|
||||
github.com/segmentio/encoding v0.4.1/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.2-0.20201103103935-92707c0b2d50/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
|
@ -199,6 +299,8 @@ github.com/valyala/quicktemplate v1.8.0 h1:zU0tjbIqTRgKQzFY1L42zq0qR3eh4WoQQdIdq
|
|||
github.com/valyala/quicktemplate v1.8.0/go.mod h1:qIqW8/igXt8fdrUln5kOSb+KWMaJ4Y8QUsfd1k6L2jM=
|
||||
github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E=
|
||||
github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/ysmood/fetchup v0.2.4 h1:2kfWr/UrdiHg4KYRrxL2Jcrqx4DZYD+OtWu7WPBZl5o=
|
||||
|
@ -224,11 +326,12 @@ go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
|||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
|
@ -243,28 +346,33 @@ golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
|||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -272,8 +380,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -282,8 +390,11 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
|||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
|
@ -291,10 +402,10 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
@ -304,13 +415,16 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb
|
|||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
|
||||
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -338,5 +452,7 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
|||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
|
||||
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
|
||||
zombiezen.com/go/sqlite v1.4.0 h1:N1s3RIljwtp4541Y8rM880qgGIgq3fTD2yks1xftnKU=
|
||||
zombiezen.com/go/sqlite v1.4.0/go.mod h1:0w9F1DN9IZj9AcLS9YDKMboubCACkwYCGkzoy3eG5ik=
|
||||
|
|
|
@ -58,3 +58,5 @@ You can manually add your own plugins to the core:
|
|||
)
|
||||
</script>
|
||||
```
|
||||
|
||||
[](https://www.star-history.com/#starfederation/datastar&Date)
|
|
@ -1,6 +1,5 @@
|
|||
{:lint-as {fr.jeremyschoffen.datastar.utils/defroutes clojure.core/def
|
||||
starfederation.datastar.clojure.utils/transient-> clojure.core/->
|
||||
starfederation.datastar.clojure.utils/def-clone clojure.core/def}
|
||||
starfederation.datastar.clojure.utils/transient-> clojure.core/->}
|
||||
:hooks
|
||||
{:analyze-call
|
||||
{test.utils/with-server hooks.test-hooks/with-server}}
|
||||
|
|
|
@ -1,5 +1,32 @@
|
|||
# Release notes for the Clojure SDK
|
||||
|
||||
## 2025-04-07
|
||||
|
||||
### Added
|
||||
|
||||
- The vars holding keywords (like `on-open`) in the adapter namespaces were not
|
||||
properly recognized by Clj-kondo. This generated `unresolved var` warnings. A
|
||||
specific Clj-kondo config has been added to fix these warnings.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The HTTP version detection that determines whether to add the
|
||||
`Connection keep-alive` HTTP header has been changed. The header is now
|
||||
properly added for versions older than `HTTP/1.1`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Bumped the ring version from `1.13.0` to `1.14.1`. This encourages users
|
||||
to use Jetty 12 when using ring jetty adapter.
|
||||
|
||||
## 2025-03-31
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed the use of the deprecated `:on-open` and `:on-close` keywords. The
|
||||
`->sse-response` functions of both adapters will not use them anymore. The
|
||||
corresponding docstrings are updated.
|
||||
|
||||
## 2025-03-11
|
||||
|
||||
### Deprecated
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
(def-clone gzip-buffered-writer-profile ac/gzip-buffered-writer-profile)
|
||||
|
||||
|
||||
;; TODO: remove deprecated get-on-open/close next release
|
||||
(defn ->sse-response
|
||||
"Make a Ring like response that will start a SSE stream.
|
||||
|
||||
|
@ -30,9 +29,6 @@
|
|||
Note that the SSE connection stays opened util you close it.
|
||||
|
||||
General options:
|
||||
- `:on-open`: deprecated in favor of [[on-open]]
|
||||
- `:on-close`: deprecated in favor of [[on-close]]
|
||||
|
||||
- `:status`: status for the HTTP response, defaults to 200.
|
||||
- `:headers`: ring headers map to add to the response.
|
||||
- [[on-open]]: mandatory callback called when the generator is ready to send.
|
||||
|
@ -53,9 +49,9 @@
|
|||
namespace if you want to write your own profiles.
|
||||
"
|
||||
[ring-request opts]
|
||||
{:pre [(ac/get-on-open opts)]}
|
||||
(let [on-open-cb (ac/get-on-open opts)
|
||||
on-close-cb (ac/get-on-close opts)
|
||||
{:pre [(ac/on-open opts)]}
|
||||
(let [on-open-cb (ac/on-open opts)
|
||||
on-close-cb (ac/on-close opts)
|
||||
future-send! (promise)
|
||||
future-gen (promise)]
|
||||
(hk-server/as-channel ring-request
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
{:paths ["src/main"]
|
||||
:deps {org.ring-clojure/ring-core-protocols {:mvn/version "1.13.0"}}}
|
||||
:deps {org.ring-clojure/ring-core-protocols {:mvn/version "1.14.1"}}}
|
||||
|
|
|
@ -41,9 +41,6 @@
|
|||
- [[write-profile]]: write profile for the connection
|
||||
defaults to [[basic-profile]]
|
||||
|
||||
- `:on-open`: deprecated in favor of [[on-open]]
|
||||
- `:on-close`: deprecated in favor of [[on-close]]
|
||||
|
||||
When it comes to write profiles, the SDK provides:
|
||||
- [[basic-profile]]
|
||||
- [[buffered-writer-profile]]
|
||||
|
@ -54,7 +51,7 @@
|
|||
namespace if you want to write your own profiles.
|
||||
"
|
||||
[ring-request {:keys [status] :as opts}]
|
||||
{:pre [(ac/get-on-open opts)]}
|
||||
{:pre [(ac/on-open opts)]}
|
||||
(let [sse-gen (impl/->sse-gen)]
|
||||
{:status (or status 200)
|
||||
:headers (ac/headers ring-request opts)
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
(write! writer event-type data-lines event-opts)
|
||||
(ac/flush writer)))))
|
||||
|
||||
;; TODO: Remove the get-on-open/close next release
|
||||
|
||||
;; Note that the send! field has 2 usages:
|
||||
;; - it stores the sending function
|
||||
|
@ -49,7 +48,7 @@
|
|||
(set! send! (->send output-stream opts))
|
||||
(set! on-exception (or (ac/on-exception opts)
|
||||
ac/default-on-exception))
|
||||
(when-let [cb (ac/get-on-close opts)]
|
||||
(when-let [cb (ac/on-close opts)]
|
||||
(set! on-close cb)))
|
||||
|
||||
;; flush the HTTP headers
|
||||
|
@ -68,7 +67,7 @@
|
|||
(if-let [e @!error]
|
||||
(throw e) ;; if error throw, the lock is already released
|
||||
;; if all is ok call on-open, it can safely throw...
|
||||
(when-let [on-open (-> response ::opts (ac/get-on-open))]
|
||||
(when-let [on-open (-> response ::opts ac/on-open)]
|
||||
(on-open this)))))))
|
||||
|
||||
p/SSEGenerator
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{:paths ["sdk/src/main"]
|
||||
{:paths []
|
||||
|
||||
:deps {io.github.paintparty/fireworks {:mvn/version "0.10.4"}
|
||||
:deps {sdk/sdk {:local/root "./sdk"}
|
||||
io.github.paintparty/fireworks {:mvn/version "0.10.4"}
|
||||
com.taoensso/telemere {:mvn/version "1.0.0-RC3"}
|
||||
com.aayushatharva.brotli4j/brotli4j {:mvn/version "1.18.0"}}
|
||||
|
||||
|
@ -42,7 +43,7 @@
|
|||
:http-kit {:extra-deps {sdk/adapter-http-kit {:local/root "./adapter-http-kit"}}}
|
||||
|
||||
:ring-jetty {:extra-deps {sdk/adapter-ring {:local/root "./adapter-ring"}
|
||||
ring/ring-jetty-adapter {:mvn/version "1.13.0"}}}
|
||||
ring/ring-jetty-adapter {:mvn/version "1.14.1"}}}
|
||||
|
||||
:ring-rj9a {:extra-deps {sdk/adapter-ring {:local/root "./adapter-ring"}
|
||||
info.sunng/ring-jetty9-adapter {:mvn/version "0.36.1"}}}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
{:paths ["src/main"]}
|
||||
{:paths ["src/main" "resources"]}
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{:lint-as
|
||||
{starfederation.datastar.clojure.utils/def-clone clojure.core/def}}
|
||||
|
|
@ -134,15 +134,17 @@
|
|||
(-> (transient {})
|
||||
(u/merge-transient! sse/base-SSE-headers)
|
||||
(cond->
|
||||
(sse/http1? ring-request) (assoc! "Connection" "keep-alive",)
|
||||
encoding (assoc! "Content-Encoding" encoding))
|
||||
(sse/add-keep-alive? ring-request) (assoc! "Connection" "keep-alive",)
|
||||
encoding (assoc! "Content-Encoding" encoding))
|
||||
(u/merge-transient! (:headers opts))
|
||||
persistent!)))
|
||||
|
||||
|
||||
(comment
|
||||
(headers {:protocol "HTTP/2"} {})
|
||||
(headers {:protocol "HTTP/2"} {write-profile {content-encoding "br"}}))
|
||||
(headers {:protocol "HTTP/1.0"} {})
|
||||
(headers {:protocol "HTTP/1.1"} {})
|
||||
(headers {:protocol "HTTP/2"} {})
|
||||
(headers {:protocol "HTTP/2"} {write-profile {content-encoding "br"}}))
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
;; Utilities for wrapping an OutputStream
|
||||
|
@ -411,12 +413,3 @@
|
|||
(throw (ex-info "Error sending SSE event." ctx e))))
|
||||
|
||||
|
||||
;; TODO: remove next version
|
||||
(defn get-on-open [opts]
|
||||
(or (on-open opts) (:on-open opts)))
|
||||
|
||||
(defn get-on-close [opts]
|
||||
(or (on-close opts) (:on-close opts)))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -15,11 +15,16 @@
|
|||
"Content-Type" "text/event-stream"})
|
||||
|
||||
|
||||
(defn http1? [ring-request]
|
||||
(defn add-keep-alive? [ring-request]
|
||||
(let [protocol (:protocol ring-request)]
|
||||
(or
|
||||
(nil? protocol)
|
||||
(= "HTTP/1.1" protocol))))
|
||||
(or (nil? protocol)
|
||||
(neg? (compare protocol "HTTP/1.1")))))
|
||||
|
||||
(comment
|
||||
(add-keep-alive? {:protocol "HTTP/0.9"})
|
||||
(add-keep-alive? {:protocol "HTTP/1.0"})
|
||||
(add-keep-alive? {:protocol "HTTP/1.1"})
|
||||
(add-keep-alive? {:protocol "HTTP/2"}))
|
||||
|
||||
|
||||
(defn headers
|
||||
|
@ -41,7 +46,7 @@
|
|||
(-> (transient {})
|
||||
(u/merge-transient! base-SSE-headers)
|
||||
(cond->
|
||||
(http1? ring-request) (assoc! "Connection" "keep-alive",))
|
||||
(add-keep-alive? ring-request) (assoc! "Connection" "keep-alive",))
|
||||
(u/merge-transient! (:headers opts))
|
||||
persistent!))
|
||||
|
||||
|
|
|
@ -55,6 +55,3 @@
|
|||
(u/clear-terminal!)
|
||||
(u/reboot-hk-server! #'handler))
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -11,9 +11,7 @@
|
|||
[starfederation.datastar.clojure.adapter.common :as ac]
|
||||
[starfederation.datastar.clojure.api :as d*]
|
||||
[starfederation.datastar.clojure.api.sse :as sse]
|
||||
[test.utils :as u])
|
||||
(:import
|
||||
java.util.concurrent.CountDownLatch))
|
||||
[test.utils :as u]))
|
||||
|
||||
|
||||
(def ^:dynamic *ctx* nil)
|
||||
|
@ -93,7 +91,8 @@
|
|||
(println "Killing agents")
|
||||
(shutdown-agents)))))
|
||||
|
||||
(install-shutdown-hooks!)
|
||||
(defonce _ (install-shutdown-hooks!))
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
;; Generic counters tests
|
||||
;; -----------------------------------------------------------------------------
|
||||
|
@ -143,13 +142,12 @@
|
|||
(defn- ->persistent-sse-handler
|
||||
"Make a ring handler that puts a sse gen into an atom for use later.
|
||||
Counts down an latch to allow the tests to continue."
|
||||
[->sse-response !conn ^CountDownLatch latch]
|
||||
[->sse-response !conn]
|
||||
(fn handler
|
||||
([req]
|
||||
(->sse-response req
|
||||
{ac/on-open (fn [sse-gen]
|
||||
(reset! !conn sse-gen)
|
||||
(.countDown latch))}))
|
||||
(deliver !conn sse-gen))}))
|
||||
([req respond _raise]
|
||||
(respond (handler req)))))
|
||||
|
||||
|
@ -159,11 +157,9 @@
|
|||
We put together an atom to store a sse generator, a countdown latch and a
|
||||
ring handler hooked to them."
|
||||
[->sse-response]
|
||||
(let [!conn (atom nil)
|
||||
latch (CountDownLatch. 1)
|
||||
handler (->persistent-sse-handler ->sse-response !conn latch)]
|
||||
(let [!conn (promise)
|
||||
handler (->persistent-sse-handler ->sse-response !conn)]
|
||||
{:!conn !conn
|
||||
:latch latch
|
||||
:handler handler}))
|
||||
|
||||
|
||||
|
@ -177,8 +173,7 @@
|
|||
{:keys [!conn latch handler]} (setup-persistent-see-state ->sse-response)]
|
||||
(u/with-server server handler (dissoc server-opts :get-port)
|
||||
(binding [*ctx* {:port (get-port server)
|
||||
:!conn !conn
|
||||
:latch latch}]
|
||||
:!conn !conn}]
|
||||
(f))))))
|
||||
|
||||
|
||||
|
@ -188,13 +183,14 @@
|
|||
|
||||
|
||||
(defn run-persistent-sse-test! []
|
||||
(let [{:keys [port latch !conn]} *ctx*
|
||||
response (http/request {:url (u/url port "")})]
|
||||
(.await ^CountDownLatch latch)
|
||||
(let [sse-gen @!conn]
|
||||
(persistent-see-send-events! sse-gen)
|
||||
(d*/close-sse! sse-gen)
|
||||
(deref response 10 :error))))
|
||||
(let [{:keys [port !conn]} *ctx*
|
||||
response (http/request {:url (u/url port "")})
|
||||
sse-gen (deref !conn 100 nil)]
|
||||
(when-not sse-gen
|
||||
(throw (ex-info "The handler did not deliver the persistent sse-gen." {})))
|
||||
(persistent-see-send-events! sse-gen)
|
||||
(d*/close-sse! sse-gen)
|
||||
(deref response 10 :error)))
|
||||
|
||||
|
||||
|
||||
|
@ -202,12 +198,18 @@
|
|||
(lt/expect (= (:status response) 200)))
|
||||
|
||||
|
||||
(def SSE-headers-1 (update-keys (sse/headers {}) (comp keyword string/lower-case)))
|
||||
#_{:clj-kondo/ignore true}
|
||||
(def SSE-headers-2+ (update-keys (sse/headers {:protocol "2"}) (comp keyword string/lower-case)))
|
||||
(defn ->headers [req]
|
||||
(-> req
|
||||
sse/headers
|
||||
(update-keys (comp keyword string/lower-case))))
|
||||
|
||||
(def SSE-headers-1-dot-0 (->headers {}))
|
||||
(def SSE-headers-1-dot-1 (->headers {:protocol "HTTP/1.1"}))
|
||||
(def SSE-headers-2+ (->headers {:protocol "HTTP/2"}))
|
||||
|
||||
|
||||
(defn p-sse-http1-headers-ok? [response]
|
||||
(lt/expect (mc/match? SSE-headers-1 (:headers response))))
|
||||
(lt/expect (mc/match? SSE-headers-1-dot-1 (:headers response))))
|
||||
|
||||
|
||||
(def expected-p-sse-res-body
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
(:import
|
||||
[java.io Closeable ByteArrayOutputStream OutputStreamWriter]))
|
||||
|
||||
|
||||
;; Mock Http-kit channel
|
||||
(defrecord Channel [^ByteArrayOutputStream baos
|
||||
!ch-open?
|
||||
!on-close]
|
||||
|
@ -64,7 +64,7 @@
|
|||
(hk-server/on-close c
|
||||
(fn [status]
|
||||
(send!)
|
||||
(when-let [callback (:on-close opts)]
|
||||
(when-let [callback (ac/on-close opts)]
|
||||
(callback c status))))
|
||||
(impl/->sse-gen c send!)))
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<AssemblyName>StarFederation.Datastar</AssemblyName>
|
||||
<Version>1.0.0-beta.4</Version>
|
||||
<Version>1.0.0-beta.5</Version>
|
||||
<RootNamespace>StarFederation.Datastar</RootNamespace>
|
||||
<Nullable>disabled</Nullable>
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ module ServerSentEvent =
|
|||
then $"id: {sse.Id |> ValueOption.get}"
|
||||
|
||||
if (sse.Retry <> Consts.DefaultSseRetryDuration)
|
||||
then $"retry: {sse.Retry.Milliseconds}"
|
||||
then $"retry: {sse.Retry.TotalMilliseconds}"
|
||||
|
||||
yield! sse.DataLines |> Array.map (fun dataLine -> $"data: {dataLine}")
|
||||
|
||||
|
|
|
@ -1,89 +1,18 @@
|
|||
# Go SDK for Datastar
|
||||
|
||||
[](https://pkg.go.dev/github.com/starfederation/datastar)
|
||||
[](https://pkg.go.dev/github.com/starfederation/datastar)
|
||||
|
||||
Implements the [SDK spec](../README.md) and exposes an abstract
|
||||
ServerSentEventGenerator struct that can be used to implement runtime specific
|
||||
classes.
|
||||
ServerSentEventGenerator struct that can be used to implement runtime specific classes.
|
||||
|
||||
Usage is straightforward:
|
||||
## Installation
|
||||
|
||||
```go
|
||||
package main
|
||||
```sh
|
||||
go get -u github.com/starfederation/datastar/sdk/go
|
||||
```
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
## Examples
|
||||
|
||||
datastar "github.com/starfederation/datastar/sdk/go"
|
||||
)
|
||||
|
||||
const port = 9001
|
||||
|
||||
func main() {
|
||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||
mux := http.NewServeMux()
|
||||
|
||||
cdn := "https://cdn.jsdelivr.net/gh/starfederation/datastar@develop/bundles/datastar.js"
|
||||
style := "display:flex;flex-direction:column;background-color:oklch(25.3267% 0.015896
|
||||
252.417568);height:100vh;justify-content:center;align-items:center;font-family:ui-sans-serif, system-ui, sans-serif,
|
||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';"
|
||||
|
||||
page := []byte(fmt.Sprintf(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
||||
<script type="module" defer src="%s"></script>
|
||||
</head>
|
||||
|
||||
<body style="%s">
|
||||
<span id="feed" data-on-load="%s"></span>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
`, cdn, style, datastar.GetSSE("/stream")))
|
||||
|
||||
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(page)
|
||||
})
|
||||
|
||||
mux.HandleFunc("GET /stream", func(w http.ResponseWriter, r *http.Request) {
|
||||
ticker := time.NewTicker(100 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
sse := datastar.NewSSE(w, r)
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
case <-r.Context().Done(): logger.Debug("Client connection closed") return case <-ticker.C: bytes :=make([]byte, 3) _,
|
||||
err :=rand.Read(bytes) if err !=nil { logger.Error("Error generating random bytes: ", slog.String(" error",
|
||||
err.Error())) return } hexString :=hex.EncodeToString(bytes) frag :=fmt.Sprintf(`<span id="feed"
|
||||
style="color:#%s;border:1px solid #%s;border-radius:0.25rem;padding:1rem;">%s</span>`, hexString, hexString,
|
||||
hexString)
|
||||
|
||||
sse.MergeFragments(frag)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.Info(fmt.Sprintf("Server starting at 0.0.0.0:%d", port))
|
||||
if err := http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), mux); err != nil {
|
||||
logger.Error("Error starting server:", slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
The [Datastar website](https://data-star.dev) acts as a [set of
|
||||
examples](https://github.com/starfederation/datastar/tree/develop/site) for how to use the SDK.
|
||||
- [Basic Usage](https://github.com/starfederation/datastar/tree/develop/sdk/go/examples/basic)
|
||||
- [Hot Reload](https://github.com/starfederation/datastar/tree/develop/sdk/go/examples/hotreload)
|
||||
- The [Datastar website](https://data-star.dev) also acts as a [set of examples](https://github.com/starfederation/datastar/tree/develop/site) for how to use the SDK.
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
datastar "github.com/starfederation/datastar/sdk/go"
|
||||
)
|
||||
|
||||
const (
|
||||
cdn = "https://cdn.jsdelivr.net/gh/starfederation/datastar@develop/bundles/datastar.js"
|
||||
port = 9001
|
||||
)
|
||||
|
||||
func main() {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
style := "display:flex;flex-direction:column;background-color:oklch(25.3267% 0.015896 252.417568);height:100vh;justify-content:center;align-items:center;font-family:ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';"
|
||||
|
||||
page := []byte(fmt.Sprintf(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
||||
<script type="module" defer src="%s"></script>
|
||||
</head>
|
||||
|
||||
<body style="%s">
|
||||
<span id="feed" data-on-load="%s"></span>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
`, cdn, style, datastar.GetSSE("/stream")))
|
||||
|
||||
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(page)
|
||||
})
|
||||
|
||||
mux.HandleFunc("GET /stream", func(w http.ResponseWriter, r *http.Request) {
|
||||
ticker := time.NewTicker(100 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
sse := datastar.NewSSE(w, r)
|
||||
for {
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
slog.Debug("Client connection closed")
|
||||
return
|
||||
case <-ticker.C:
|
||||
bytes := make([]byte, 3)
|
||||
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
slog.Error("Error generating random bytes: ", slog.String("error", err.Error()))
|
||||
return
|
||||
}
|
||||
hexString := hex.EncodeToString(bytes)
|
||||
frag := fmt.Sprintf(`<span id="feed" style="color:#%s;border:1px solid #%s;border-radius:0.25rem;padding:1rem;">%s</span>`, hexString, hexString, hexString)
|
||||
|
||||
sse.MergeFragments(frag)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
slog.Info(fmt.Sprintf("Server starting at 0.0.0.0:%d", port))
|
||||
if err := http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), mux); err != nil {
|
||||
slog.Error("Error starting server:", slog.String("error", err.Error()))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
datastar "github.com/starfederation/datastar/sdk/go"
|
||||
)
|
||||
|
||||
const (
|
||||
serverAddress = "localhost:9001"
|
||||
cdn = "https://cdn.jsdelivr.net/gh/starfederation/datastar@develop/bundles/datastar.js"
|
||||
)
|
||||
|
||||
var hotReloadOnlyOnce sync.Once
|
||||
|
||||
func HotReloadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
sse := datastar.NewSSE(w, r)
|
||||
hotReloadOnlyOnce.Do(func() {
|
||||
// Refresh the client page as soon as connection
|
||||
// is established. This will occur only once
|
||||
// after the server starts.
|
||||
sse.ExecuteScript(
|
||||
"window.location.reload()",
|
||||
datastar.WithExecuteScriptRetryDuration(time.Second),
|
||||
)
|
||||
})
|
||||
|
||||
// Freeze the event stream until the connection
|
||||
// is lost for any reason. This will force the client
|
||||
// to attempt to reconnect after the server reboots.
|
||||
<-r.Context().Done()
|
||||
}
|
||||
|
||||
func PageWithHotReload(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(fmt.Sprintf(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
||||
<script type="module" defer src="%s"></script>
|
||||
</head>
|
||||
|
||||
<!-- next line mounts the refresh script -->
|
||||
<body data-on-load="@get('/hotreload', {retryInterval: 100})">
|
||||
<main>
|
||||
<p>
|
||||
This page will automatically reload on any filesystem change. Update this paragraph, save changes, and
|
||||
switch back to the open browser tab to observe
|
||||
the update.
|
||||
</p>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
`, cdn)))
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Hot reload requires a file system watcher and
|
||||
// a refresh script. Reflex is one of the best tools
|
||||
// for running a command on each file change.
|
||||
//
|
||||
// $ go install github.com/cespare/reflex@latest
|
||||
// $ reflex --start-service -- sh -c 'go run .'
|
||||
//
|
||||
// The refresh script is a Datastar handler
|
||||
// that emits a page refresh event only once
|
||||
// for each server start.
|
||||
//
|
||||
// When the the file watcher forces the server to restart,
|
||||
// Datastar client will lose the network connection to the
|
||||
// server and attempt to reconnect. Once the connection is
|
||||
// established, the client will receive the refresh event.
|
||||
http.HandleFunc("/hotreload", HotReloadHandler)
|
||||
http.HandleFunc("/", PageWithHotReload)
|
||||
slog.Info(fmt.Sprintf(
|
||||
"Open your browser to: http://%s/",
|
||||
serverAddress,
|
||||
))
|
||||
http.ListenAndServe(serverAddress, nil)
|
||||
|
||||
// Tip: read the reflex documentation to see advanced usage
|
||||
// options like responding to specific file changes by filter.
|
||||
//
|
||||
// $ reflex --help
|
||||
}
|
|
@ -9,31 +9,44 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// ConsoleLog is a convenience method for [see.ExecuteScript].
|
||||
// It is equivalent to calling [see.ExecuteScript] with [see.WithScript] option set to `console.log(msg)`.
|
||||
func (sse *ServerSentEventGenerator) ConsoleLog(msg string, opts ...ExecuteScriptOption) error {
|
||||
call := fmt.Sprintf("console.log(%q)", msg)
|
||||
return sse.ExecuteScript(call, opts...)
|
||||
}
|
||||
|
||||
// ConsoleLogf is a convenience method for [see.ExecuteScript].
|
||||
// It is equivalent to calling [see.ExecuteScript] with [see.WithScript] option set to `console.log(fmt.Sprintf(format, args...))`.
|
||||
func (sse *ServerSentEventGenerator) ConsoleLogf(format string, args ...any) error {
|
||||
return sse.ConsoleLog(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// ConsoleError is a convenience method for [see.ExecuteScript].
|
||||
// It is equivalent to calling [see.ExecuteScript] with [see.WithScript] option set to `console.error(msg)`.
|
||||
func (sse *ServerSentEventGenerator) ConsoleError(err error, opts ...ExecuteScriptOption) error {
|
||||
call := fmt.Sprintf("console.error(%q)", err.Error())
|
||||
return sse.ExecuteScript(call, opts...)
|
||||
}
|
||||
|
||||
// Redirectf is a convenience method for [see.ExecuteScript].
|
||||
// It sends a redirect event to the client formatted using [fmt.Sprintf].
|
||||
func (sse *ServerSentEventGenerator) Redirectf(format string, args ...any) error {
|
||||
url := fmt.Sprintf(format, args...)
|
||||
return sse.Redirect(url)
|
||||
}
|
||||
|
||||
// Redirect is a convenience method for [see.ExecuteScript].
|
||||
// It sends a redirect event to the client .
|
||||
func (sse *ServerSentEventGenerator) Redirect(url string, opts ...ExecuteScriptOption) error {
|
||||
js := fmt.Sprintf("setTimeout(() => window.location.href = %q)", url)
|
||||
return sse.ExecuteScript(js, opts...)
|
||||
}
|
||||
|
||||
type DispatchCustomEventOptions struct {
|
||||
// dispatchCustomEventOptions holds the configuration data
|
||||
// modified by [DispatchCustomEventOption]s
|
||||
// for dispatching custom events to the client.
|
||||
type dispatchCustomEventOptions struct {
|
||||
EventID string
|
||||
RetryDuration time.Duration
|
||||
Selector string
|
||||
|
@ -42,44 +55,78 @@ type DispatchCustomEventOptions struct {
|
|||
Composed bool
|
||||
}
|
||||
|
||||
type DispatchCustomEventOption func(*DispatchCustomEventOptions)
|
||||
// DispatchCustomEventOption configures one custom
|
||||
// server-sent event.
|
||||
type DispatchCustomEventOption func(*dispatchCustomEventOptions)
|
||||
|
||||
// WithDispatchCustomEventEventID configures an optional event ID for the custom event.
|
||||
// The client message field [lastEventId] will be set to this value.
|
||||
// If the next event does not have an event ID, the last used event ID will remain.
|
||||
//
|
||||
// [lastEventId]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/lastEventId
|
||||
func WithDispatchCustomEventEventID(id string) DispatchCustomEventOption {
|
||||
return func(o *DispatchCustomEventOptions) {
|
||||
return func(o *dispatchCustomEventOptions) {
|
||||
o.EventID = id
|
||||
}
|
||||
}
|
||||
|
||||
// WithDispatchCustomEventRetryDuration overrides the [DefaultSseRetryDuration] for one custom event.
|
||||
func WithDispatchCustomEventRetryDuration(retryDuration time.Duration) DispatchCustomEventOption {
|
||||
return func(o *DispatchCustomEventOptions) {
|
||||
return func(o *dispatchCustomEventOptions) {
|
||||
o.RetryDuration = retryDuration
|
||||
}
|
||||
}
|
||||
|
||||
// WithDispatchCustomEventSelector replaces the default custom event target `document` with a
|
||||
// [CSS selector]. If the selector matches multiple HTML elements, the event will be dispatched
|
||||
// from each one. For example, if the selector is `#my-element`, the event will be dispatched
|
||||
// from the element with the ID `my-element`. If the selector is `main > section`, the event will be dispatched
|
||||
// from each `<section>` element which is a direct child of the `<main>` element.
|
||||
//
|
||||
// [CSS selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors
|
||||
func WithDispatchCustomEventSelector(selector string) DispatchCustomEventOption {
|
||||
return func(o *DispatchCustomEventOptions) {
|
||||
return func(o *dispatchCustomEventOptions) {
|
||||
o.Selector = selector
|
||||
}
|
||||
}
|
||||
|
||||
// WithDispatchCustomEventBubbles overrides the default custom [event bubbling] `true` value.
|
||||
// Setting bubbling to `false` is equivalent to calling `event.stopPropagation()` Javascript
|
||||
// command on the client side for the dispatched event. This prevents the event from triggering
|
||||
// event handlers of its parent elements.
|
||||
//
|
||||
// [event bubbling]: https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/Event_bubbling
|
||||
func WithDispatchCustomEventBubbles(bubbles bool) DispatchCustomEventOption {
|
||||
return func(o *DispatchCustomEventOptions) {
|
||||
return func(o *dispatchCustomEventOptions) {
|
||||
o.Bubbles = bubbles
|
||||
}
|
||||
}
|
||||
|
||||
// WithDispatchCustomEventCancelable overrides the default custom [event cancelability] `true` value.
|
||||
// Setting cancelability to `false` is blocks `event.preventDefault()` Javascript
|
||||
// command on the client side for the dispatched event.
|
||||
//
|
||||
// [event cancelability]: https://developer.mozilla.org/en-US/docs/Web/API/Event/cancelable
|
||||
func WithDispatchCustomEventCancelable(cancelable bool) DispatchCustomEventOption {
|
||||
return func(o *DispatchCustomEventOptions) {
|
||||
return func(o *dispatchCustomEventOptions) {
|
||||
o.Cancelable = cancelable
|
||||
}
|
||||
}
|
||||
|
||||
// WithDispatchCustomEventComposed overrides the default custom [event composed] `true` value.
|
||||
// It indicates whether or not the event will propagate across the shadow HTML DOM boundary into
|
||||
// the document DOM tree. When `false`, the shadow root will be the last node to be offered the event.
|
||||
//
|
||||
// [event composed]: https://developer.mozilla.org/en-US/docs/Web/API/Event/composed
|
||||
func WithDispatchCustomEventComposed(composed bool) DispatchCustomEventOption {
|
||||
return func(o *DispatchCustomEventOptions) {
|
||||
return func(o *dispatchCustomEventOptions) {
|
||||
o.Composed = composed
|
||||
}
|
||||
}
|
||||
|
||||
// DispatchCustomEvent is a convenience method for dispatching a custom event by executing
|
||||
// a client side script via [sse.ExecuteScript] call. The detail struct is marshaled to JSON and
|
||||
// passed as a parameter to the event.
|
||||
func (sse *ServerSentEventGenerator) DispatchCustomEvent(eventName string, detail any, opts ...DispatchCustomEventOption) error {
|
||||
if eventName == "" {
|
||||
return fmt.Errorf("eventName is required")
|
||||
|
@ -91,7 +138,7 @@ func (sse *ServerSentEventGenerator) DispatchCustomEvent(eventName string, detai
|
|||
}
|
||||
|
||||
const defaultSelector = "document"
|
||||
options := DispatchCustomEventOptions{
|
||||
options := dispatchCustomEventOptions{
|
||||
EventID: "",
|
||||
RetryDuration: DefaultSseRetryDuration,
|
||||
Selector: defaultSelector,
|
||||
|
@ -143,17 +190,25 @@ elements.forEach((element) => {
|
|||
|
||||
}
|
||||
|
||||
// ReplaceURL replaces the current URL in the browser's history.
|
||||
func (sse *ServerSentEventGenerator) ReplaceURL(u url.URL, opts ...ExecuteScriptOption) error {
|
||||
js := fmt.Sprintf(`window.history.replaceState({}, "", %q)`, u.String())
|
||||
return sse.ExecuteScript(js, opts...)
|
||||
}
|
||||
|
||||
// ReplaceURLQuerystring is a convenience wrapper for [sse.ReplaceURL] that replaces the query
|
||||
// string of the current URL request with new a new query built from the provided values.
|
||||
func (sse *ServerSentEventGenerator) ReplaceURLQuerystring(r *http.Request, values url.Values, opts ...ExecuteScriptOption) error {
|
||||
// TODO: rename this function to ReplaceURLQuery
|
||||
u := *r.URL
|
||||
u.RawQuery = values.Encode()
|
||||
return sse.ReplaceURL(u, opts...)
|
||||
}
|
||||
|
||||
// Prefetch is a convenience wrapper for [sse.ExecuteScript] that prefetches the provided links.
|
||||
// It follows the Javascript [speculation rules API] prefetch specification.
|
||||
//
|
||||
// [speculation rules API]: https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API
|
||||
func (sse *ServerSentEventGenerator) Prefetch(urls ...string) error {
|
||||
wrappedURLs := make([]string, len(urls))
|
||||
for i, url := range urls {
|
||||
|
|
|
@ -7,33 +7,48 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
type ExecuteScriptOptions struct {
|
||||
// executeScriptOptions hold script options that will be translated to [SSEEventOptions].
|
||||
type executeScriptOptions struct {
|
||||
EventID string
|
||||
RetryDuration time.Duration
|
||||
Attributes []string
|
||||
AutoRemove *bool
|
||||
}
|
||||
|
||||
type ExecuteScriptOption func(*ExecuteScriptOptions)
|
||||
// ExecuteScriptOption configures script execution event that will be sent to the client.
|
||||
type ExecuteScriptOption func(*executeScriptOptions)
|
||||
|
||||
// WithExecuteScriptEventID configures an optional event ID for the script execution event.
|
||||
// The client message field [lastEventId] will be set to this value.
|
||||
// If the next event does not have an event ID, the last used event ID will remain.
|
||||
//
|
||||
// [lastEventId]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/lastEventId
|
||||
func WithExecuteScriptEventID(id string) ExecuteScriptOption {
|
||||
return func(o *ExecuteScriptOptions) {
|
||||
return func(o *executeScriptOptions) {
|
||||
o.EventID = id
|
||||
}
|
||||
}
|
||||
|
||||
// WithExecuteScriptRetryDuration overrides the [DefaultSseRetryDuration] for this script
|
||||
// execution only.
|
||||
func WithExecuteScriptRetryDuration(retryDuration time.Duration) ExecuteScriptOption {
|
||||
return func(o *ExecuteScriptOptions) {
|
||||
return func(o *executeScriptOptions) {
|
||||
o.RetryDuration = retryDuration
|
||||
}
|
||||
}
|
||||
|
||||
// WithExecuteScriptAttributes overrides the default script attribute
|
||||
// value `type module`, which renders as `<script type="module">` in client's browser.
|
||||
// Each attribute should include a pair of plain words representing the attribute name and value
|
||||
// without any formatting.
|
||||
func WithExecuteScriptAttributes(attributes ...string) ExecuteScriptOption {
|
||||
return func(o *ExecuteScriptOptions) {
|
||||
return func(o *executeScriptOptions) {
|
||||
o.Attributes = attributes
|
||||
}
|
||||
}
|
||||
|
||||
// WithExecuteScriptAttributeKVs is an alternative option for [WithExecuteScriptAttributes].
|
||||
// Even parameters are keys, odd parameters are their values.
|
||||
func WithExecuteScriptAttributeKVs(kvs ...string) ExecuteScriptOption {
|
||||
if len(kvs)%2 != 0 {
|
||||
panic("WithExecuteScriptAttributeKVs requires an even number of arguments")
|
||||
|
@ -46,14 +61,16 @@ func WithExecuteScriptAttributeKVs(kvs ...string) ExecuteScriptOption {
|
|||
return WithExecuteScriptAttributes(attributes...)
|
||||
}
|
||||
|
||||
// WithExecuteScriptAutoRemove requires the client to eliminate the script element after its execution.
|
||||
func WithExecuteScriptAutoRemove(autoremove bool) ExecuteScriptOption {
|
||||
return func(o *ExecuteScriptOptions) {
|
||||
return func(o *executeScriptOptions) {
|
||||
o.AutoRemove = &autoremove
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteScript runs a script in the client browser. Seperate commands with semicolons.
|
||||
func (sse *ServerSentEventGenerator) ExecuteScript(scriptContents string, opts ...ExecuteScriptOption) error {
|
||||
options := &ExecuteScriptOptions{
|
||||
options := &executeScriptOptions{
|
||||
RetryDuration: DefaultSseRetryDuration,
|
||||
Attributes: []string{"type module"},
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package datastar
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/delaneyj/gostar/elements"
|
||||
"github.com/valyala/bytebufferpool"
|
||||
)
|
||||
|
||||
// ValidFragmentMergeTypes is a list of valid fragment merge modes.
|
||||
var ValidFragmentMergeTypes = []FragmentMergeMode{
|
||||
FragmentMergeModeMorph,
|
||||
FragmentMergeModeInner,
|
||||
|
@ -19,68 +20,107 @@ var ValidFragmentMergeTypes = []FragmentMergeMode{
|
|||
FragmentMergeModeUpsertAttributes,
|
||||
}
|
||||
|
||||
// FragmentMergeTypeFromString converts a string to a [FragmentMergeMode].
|
||||
func FragmentMergeTypeFromString(s string) (FragmentMergeMode, error) {
|
||||
for _, t := range ValidFragmentMergeTypes {
|
||||
if string(t) == s {
|
||||
return t, nil
|
||||
}
|
||||
switch s {
|
||||
case "morph":
|
||||
return FragmentMergeModeMorph, nil
|
||||
case "inner":
|
||||
return FragmentMergeModeInner, nil
|
||||
case "outer":
|
||||
return FragmentMergeModeOuter, nil
|
||||
case "prepend":
|
||||
return FragmentMergeModePrepend, nil
|
||||
case "append":
|
||||
return FragmentMergeModeAppend, nil
|
||||
case "before":
|
||||
return FragmentMergeModeBefore, nil
|
||||
case "after":
|
||||
return FragmentMergeModeAfter, nil
|
||||
case "upsertAttributes":
|
||||
return FragmentMergeModeUpsertAttributes, nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid fragment merge type: %s", s)
|
||||
}
|
||||
return "", fmt.Errorf("invalid fragment merge type: %s", s)
|
||||
}
|
||||
|
||||
// WithMergeMorph creates a MergeFragmentOption that merges fragments using the morph mode.
|
||||
func WithMergeMorph() MergeFragmentOption {
|
||||
return WithMergeMode(FragmentMergeModeMorph)
|
||||
}
|
||||
|
||||
// WithMergeInner creates a MergeFragmentOption that merges fragments using the inner mode.
|
||||
func WithMergeInner() MergeFragmentOption {
|
||||
return WithMergeMode(FragmentMergeModeInner)
|
||||
}
|
||||
|
||||
// WithMergeOuter creates a MergeFragmentOption that merges fragments using the outer mode.
|
||||
func WithMergeOuter() MergeFragmentOption {
|
||||
return WithMergeMode(FragmentMergeModeOuter)
|
||||
}
|
||||
|
||||
// WithMergePrepend creates a MergeFragmentOption that merges fragments using the prepend mode.
|
||||
func WithMergePrepend() MergeFragmentOption {
|
||||
return WithMergeMode(FragmentMergeModePrepend)
|
||||
}
|
||||
|
||||
// WithMergeAppend creates a MergeFragmentOption that merges fragments using the append mode.
|
||||
func WithMergeAppend() MergeFragmentOption {
|
||||
return WithMergeMode(FragmentMergeModeAppend)
|
||||
}
|
||||
|
||||
// WithMergeBefore creates a MergeFragmentOption that merges fragments using the before mode.
|
||||
func WithMergeBefore() MergeFragmentOption {
|
||||
return WithMergeMode(FragmentMergeModeBefore)
|
||||
}
|
||||
|
||||
// WithMergeAfter creates a MergeFragmentOption that merges fragments using the after mode.
|
||||
func WithMergeAfter() MergeFragmentOption {
|
||||
return WithMergeMode(FragmentMergeModeAfter)
|
||||
}
|
||||
|
||||
// WithMergeUpsertAttributes creates a MergeFragmentOption that merges fragments using the upsert attributes mode.
|
||||
func WithMergeUpsertAttributes() MergeFragmentOption {
|
||||
return WithMergeMode(FragmentMergeModeUpsertAttributes)
|
||||
}
|
||||
|
||||
// WithSelectorID is a convenience wrapper for [WithSelector] option
|
||||
// equivalent to calling `WithSelector("#"+id)`.
|
||||
func WithSelectorID(id string) MergeFragmentOption {
|
||||
return WithSelector("#" + id)
|
||||
}
|
||||
|
||||
// WithViewTransitions enables the use of view transitions when merging fragments.
|
||||
func WithViewTransitions() MergeFragmentOption {
|
||||
return func(o *MergeFragmentOptions) {
|
||||
return func(o *mergeFragmentOptions) {
|
||||
o.UseViewTransitions = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithoutViewTransitions disables the use of view transitions when merging fragments.
|
||||
func WithoutViewTransitions() MergeFragmentOption {
|
||||
return func(o *MergeFragmentOptions) {
|
||||
return func(o *mergeFragmentOptions) {
|
||||
o.UseViewTransitions = false
|
||||
}
|
||||
}
|
||||
|
||||
// MergeFragmentf is a convenience wrapper for [MergeFragments] option
|
||||
// equivalent to calling `MergeFragments(fmt.Sprintf(format, args...))`.
|
||||
func (sse *ServerSentEventGenerator) MergeFragmentf(format string, args ...any) error {
|
||||
return sse.MergeFragments(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (sse *ServerSentEventGenerator) MergeFragmentTempl(c templ.Component, opts ...MergeFragmentOption) error {
|
||||
// TemplComponent satisfies the component rendering interface for HTML template engine [Templ].
|
||||
// This separate type ensures compatibility with [Templ] without imposing a dependency requirement
|
||||
// on those who prefer to use a different template engine.
|
||||
//
|
||||
// [Templ]: https://templ.guide/
|
||||
type TemplComponent interface {
|
||||
Render(ctx context.Context, w io.Writer) error
|
||||
}
|
||||
|
||||
// MergeFragmentTempl is a convenience adaptor of [sse.MergeFragments] for [TemplComponent].
|
||||
func (sse *ServerSentEventGenerator) MergeFragmentTempl(c TemplComponent, opts ...MergeFragmentOption) error {
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
if err := c.Render(sse.Context(), buf); err != nil {
|
||||
|
@ -92,7 +132,17 @@ func (sse *ServerSentEventGenerator) MergeFragmentTempl(c templ.Component, opts
|
|||
return nil
|
||||
}
|
||||
|
||||
func (sse *ServerSentEventGenerator) MergeFragmentGostar(child elements.ElementRenderer, opts ...MergeFragmentOption) error {
|
||||
// GoStarElementRenderer satisfies the component rendering interface for HTML template engine [GoStar].
|
||||
// This separate type ensures compatibility with [GoStar] without imposing a dependency requirement
|
||||
// on those who prefer to use a different template engine.
|
||||
//
|
||||
// [GoStar]: https://github.com/delaneyj/gostar
|
||||
type GoStarElementRenderer interface {
|
||||
Render(w io.Writer) error
|
||||
}
|
||||
|
||||
// MergeFragmentGostar is a convenience adaptor of [sse.MergeFragments] for [GoStarElementRenderer].
|
||||
func (sse *ServerSentEventGenerator) MergeFragmentGostar(child GoStarElementRenderer, opts ...MergeFragmentOption) error {
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
if err := child.Render(buf); err != nil {
|
||||
|
@ -104,22 +154,37 @@ func (sse *ServerSentEventGenerator) MergeFragmentGostar(child elements.ElementR
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetSSE is a convenience method for generating Datastar backend [get] action attribute.
|
||||
//
|
||||
// [get]: https://data-star.dev/reference/action_plugins#get
|
||||
func GetSSE(urlFormat string, args ...any) string {
|
||||
return fmt.Sprintf(`@get('%s')`, fmt.Sprintf(urlFormat, args...))
|
||||
}
|
||||
|
||||
// PostSSE is a convenience method for generating Datastar backend [post] action attribute.
|
||||
//
|
||||
// [post]: https://data-star.dev/reference/action_plugins#post
|
||||
func PostSSE(urlFormat string, args ...any) string {
|
||||
return fmt.Sprintf(`@post('%s')`, fmt.Sprintf(urlFormat, args...))
|
||||
}
|
||||
|
||||
// PutSSE is a convenience method for generating Datastar backend [put] action attribute.
|
||||
//
|
||||
// [put]: https://data-star.dev/reference/action_plugins#put
|
||||
func PutSSE(urlFormat string, args ...any) string {
|
||||
return fmt.Sprintf(`@put('%s')`, fmt.Sprintf(urlFormat, args...))
|
||||
}
|
||||
|
||||
// PatchSSE is a convenience method for generating Datastar backend [patch] action attribute.
|
||||
//
|
||||
// [patch]: https://data-star.dev/reference/action_plugins#patch
|
||||
func PatchSSE(urlFormat string, args ...any) string {
|
||||
return fmt.Sprintf(`@patch('%s')`, fmt.Sprintf(urlFormat, args...))
|
||||
}
|
||||
|
||||
// DeleteSSE is a convenience method for generating Datastar backend [delete] action attribute.
|
||||
//
|
||||
// [delete]: https://data-star.dev/reference/action_plugins#delete
|
||||
func DeleteSSE(urlFormat string, args ...any) string {
|
||||
return fmt.Sprintf(`@delete('%s')`, fmt.Sprintf(urlFormat, args...))
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
type MergeFragmentOptions struct {
|
||||
// mergeFragmentOptions holds the configuration data for [MergeFragmentOption]s used
|
||||
// for initialization of [sse.MergeFragments] event.
|
||||
type mergeFragmentOptions struct {
|
||||
EventID string
|
||||
RetryDuration time.Duration
|
||||
Selector string
|
||||
|
@ -15,37 +17,61 @@ type MergeFragmentOptions struct {
|
|||
UseViewTransitions bool
|
||||
}
|
||||
|
||||
type MergeFragmentOption func(*MergeFragmentOptions)
|
||||
// MergeFragmentOption configures the [sse.MergeFragments] event initialization.
|
||||
type MergeFragmentOption func(*mergeFragmentOptions)
|
||||
|
||||
// WithMergeFragmentsEventID configures an optional event ID for the fragments merge event.
|
||||
// The client message field [lastEventId] will be set to this value.
|
||||
// If the next event does not have an event ID, the last used event ID will remain.
|
||||
//
|
||||
// [lastEventId]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/lastEventId
|
||||
func WithMergeFragmentsEventID(id string) MergeFragmentOption {
|
||||
return func(o *mergeFragmentOptions) {
|
||||
o.EventID = id
|
||||
}
|
||||
}
|
||||
|
||||
// WithSelectorf is a convenience wrapper for [WithSelector] option that formats the selector string
|
||||
// using the provided format and arguments similar to [fmt.Sprintf].
|
||||
func WithSelectorf(selectorFormat string, args ...any) MergeFragmentOption {
|
||||
selector := fmt.Sprintf(selectorFormat, args...)
|
||||
return WithSelector(selector)
|
||||
}
|
||||
|
||||
// WithSelector specifies the [CSS selector] for HTML elements that a fragment will be merged over or
|
||||
// merged next to, depending on the merge mode.
|
||||
//
|
||||
// [CSS selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors
|
||||
func WithSelector(selector string) MergeFragmentOption {
|
||||
return func(o *MergeFragmentOptions) {
|
||||
return func(o *mergeFragmentOptions) {
|
||||
o.Selector = selector
|
||||
}
|
||||
}
|
||||
|
||||
// WithMergeMode overrides the [DefaultFragmentMergeMode] for the fragment.
|
||||
// Choose a valid [FragmentMergeMode].
|
||||
func WithMergeMode(merge FragmentMergeMode) MergeFragmentOption {
|
||||
return func(o *MergeFragmentOptions) {
|
||||
return func(o *mergeFragmentOptions) {
|
||||
o.MergeMode = merge
|
||||
}
|
||||
}
|
||||
|
||||
// WithUseViewTransitions specifies whether to use [view transitions] when merging fragments.
|
||||
//
|
||||
// [view transitions]: https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API
|
||||
func WithUseViewTransitions(useViewTransition bool) MergeFragmentOption {
|
||||
return func(o *MergeFragmentOptions) {
|
||||
return func(o *mergeFragmentOptions) {
|
||||
o.UseViewTransitions = useViewTransition
|
||||
}
|
||||
}
|
||||
|
||||
// MergeFragments sends an HTML fragment to the client to update the DOM tree with.
|
||||
func (sse *ServerSentEventGenerator) MergeFragments(fragment string, opts ...MergeFragmentOption) error {
|
||||
options := &MergeFragmentOptions{
|
||||
EventID: "",
|
||||
RetryDuration: DefaultSseRetryDuration,
|
||||
Selector: "",
|
||||
MergeMode: FragmentMergeModeMorph,
|
||||
options := &mergeFragmentOptions{
|
||||
EventID: "",
|
||||
RetryDuration: DefaultSseRetryDuration,
|
||||
Selector: "",
|
||||
MergeMode: FragmentMergeModeMorph,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
|
@ -88,38 +114,54 @@ func (sse *ServerSentEventGenerator) MergeFragments(fragment string, opts ...Mer
|
|||
return nil
|
||||
}
|
||||
|
||||
type RemoveFragmentsOptions struct {
|
||||
// mergeFragmentOptions holds the configuration data for [RemoveFragmentsOption]s used
|
||||
// for initialization of [sse.RemoveFragments] event.
|
||||
type removeFragmentsOptions struct {
|
||||
EventID string
|
||||
RetryDuration time.Duration
|
||||
UseViewTransitions *bool
|
||||
}
|
||||
|
||||
type RemoveFragmentsOption func(*RemoveFragmentsOptions)
|
||||
// RemoveFragmentsOption configures the [sse.RemoveFragments] event.
|
||||
type RemoveFragmentsOption func(*removeFragmentsOptions)
|
||||
|
||||
// WithRemoveEventID configures an optional event ID for the fragment removal event.
|
||||
// The client message field [lastEventId] will be set to this value.
|
||||
// If the next event does not have an event ID, the last used event ID will remain.
|
||||
//
|
||||
// [lastEventId]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/lastEventId
|
||||
func WithRemoveEventID(id string) RemoveFragmentsOption {
|
||||
return func(o *RemoveFragmentsOptions) {
|
||||
return func(o *removeFragmentsOptions) {
|
||||
o.EventID = id
|
||||
}
|
||||
}
|
||||
|
||||
// WithExecuteScriptRetryDuration overrides the [DefaultSseRetryDuration] for this script
|
||||
// execution only.
|
||||
func WithRemoveRetryDuration(d time.Duration) RemoveFragmentsOption {
|
||||
return func(o *RemoveFragmentsOptions) {
|
||||
return func(o *removeFragmentsOptions) {
|
||||
o.RetryDuration = d
|
||||
}
|
||||
}
|
||||
|
||||
// WithRemoveUseViewTransitions specifies whether to use [view transitions] when merging fragments.
|
||||
//
|
||||
// [view transitions]: https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API
|
||||
func WithRemoveUseViewTransitions(useViewTransition bool) RemoveFragmentsOption {
|
||||
return func(o *RemoveFragmentsOptions) {
|
||||
return func(o *removeFragmentsOptions) {
|
||||
o.UseViewTransitions = &useViewTransition
|
||||
}
|
||||
}
|
||||
|
||||
// MergeFragments sends a [CSS selector] to the client to update the DOM tree by removing matching elements.
|
||||
//
|
||||
// [CSS selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors
|
||||
func (sse *ServerSentEventGenerator) RemoveFragments(selector string, opts ...RemoveFragmentsOption) error {
|
||||
if selector == "" {
|
||||
panic("missing " + SelectorDatalineLiteral)
|
||||
}
|
||||
|
||||
options := &RemoveFragmentsOptions{
|
||||
options := &removeFragmentsOptions{
|
||||
EventID: "",
|
||||
RetryDuration: DefaultSseRetryDuration,
|
||||
UseViewTransitions: nil,
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package datastar
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAllValidFragmentMergeTypes(t *testing.T) {
|
||||
var err error
|
||||
for _, validType := range ValidFragmentMergeTypes {
|
||||
if _, err = FragmentMergeTypeFromString(string(validType)); err != nil {
|
||||
t.Errorf("Expected %v to be a valid fragment merge type, but it was rejected: %v", validType, err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = FragmentMergeTypeFromString(""); err == nil {
|
||||
t.Errorf("Expected an empty string to be an invalid fragment merge type, but it was accepted")
|
||||
}
|
||||
|
||||
if _, err = FragmentMergeTypeFromString("fakeType"); err == nil {
|
||||
t.Errorf("Expected a fake type to be an invalid fragment merge type, but it was accepted")
|
||||
}
|
||||
}
|
|
@ -5,6 +5,9 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// MarshalAndMergeSignals is a convenience method for [see.MergeSignals].
|
||||
// It marshals a given signals struct into JSON and
|
||||
// emits a [EventTypeMergeSignals] event.
|
||||
func (sse *ServerSentEventGenerator) MarshalAndMergeSignals(signals any, opts ...MergeSignalsOption) error {
|
||||
b, err := json.Marshal(signals)
|
||||
if err != nil {
|
||||
|
@ -17,6 +20,8 @@ func (sse *ServerSentEventGenerator) MarshalAndMergeSignals(signals any, opts ..
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalAndMergeSignalsIfMissing is a convenience method for [see.MarshalAndMergeSignals].
|
||||
// It is equivalent to calling [see.MarshalAndMergeSignals] with [see.WithOnlyIfMissing(true)] option.
|
||||
func (sse *ServerSentEventGenerator) MarshalAndMergeSignalsIfMissing(signals any, opts ...MergeSignalsOption) error {
|
||||
if err := sse.MarshalAndMergeSignals(
|
||||
signals,
|
||||
|
@ -27,6 +32,8 @@ func (sse *ServerSentEventGenerator) MarshalAndMergeSignalsIfMissing(signals any
|
|||
return nil
|
||||
}
|
||||
|
||||
// MergeSignalsIfMissingRaw is a convenience method for [see.MergeSignals].
|
||||
// It is equivalent to calling [see.MergeSignals] with [see.WithOnlyIfMissing(true)] option.
|
||||
func (sse *ServerSentEventGenerator) MergeSignalsIfMissingRaw(signalsJSON string) error {
|
||||
if err := sse.MergeSignals([]byte(signalsJSON), WithOnlyIfMissing(true)); err != nil {
|
||||
return fmt.Errorf("failed to merge signals if missing: %w", err)
|
||||
|
|
|
@ -13,37 +13,49 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
// ErrNoPathsProvided is returned when no paths were provided for // for [sse.RemoveSignals] call.
|
||||
ErrNoPathsProvided = errors.New("no paths provided")
|
||||
)
|
||||
|
||||
type MergeSignalsOptions struct {
|
||||
// mergeSignalsOptions holds configuration options for merging signals.
|
||||
type mergeSignalsOptions struct {
|
||||
EventID string
|
||||
RetryDuration time.Duration
|
||||
OnlyIfMissing bool
|
||||
}
|
||||
|
||||
type MergeSignalsOption func(*MergeSignalsOptions)
|
||||
// MergeSignalsOption configures one [EventTypeMergeSignals] event.
|
||||
type MergeSignalsOption func(*mergeSignalsOptions)
|
||||
|
||||
// WithMergeSignalsEventID configures an optional event ID for the signals merge event.
|
||||
// The client message field [lastEventId] will be set to this value.
|
||||
// If the next event does not have an event ID, the last used event ID will remain.
|
||||
//
|
||||
// [lastEventId]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/lastEventId
|
||||
func WithMergeSignalsEventID(id string) MergeSignalsOption {
|
||||
return func(o *MergeSignalsOptions) {
|
||||
return func(o *mergeSignalsOptions) {
|
||||
o.EventID = id
|
||||
}
|
||||
}
|
||||
|
||||
// WithMergeSignalsRetryDuration overrides the [DefaultSseRetryDuration] for signal merging.
|
||||
func WithMergeSignalsRetryDuration(retryDuration time.Duration) MergeSignalsOption {
|
||||
return func(o *MergeSignalsOptions) {
|
||||
return func(o *mergeSignalsOptions) {
|
||||
o.RetryDuration = retryDuration
|
||||
}
|
||||
}
|
||||
|
||||
// WithOnlyIfMissing instructs the client to only merge signals if they are missing.
|
||||
func WithOnlyIfMissing(onlyIfMissing bool) MergeSignalsOption {
|
||||
return func(o *MergeSignalsOptions) {
|
||||
return func(o *mergeSignalsOptions) {
|
||||
o.OnlyIfMissing = onlyIfMissing
|
||||
}
|
||||
}
|
||||
|
||||
// MergeSignals sends a [EventTypeMergeSignals] to the client.
|
||||
// Requires a JSON-encoded payload.
|
||||
func (sse *ServerSentEventGenerator) MergeSignals(signalsContents []byte, opts ...MergeSignalsOption) error {
|
||||
options := &MergeSignalsOptions{
|
||||
options := &mergeSignalsOptions{
|
||||
EventID: "",
|
||||
RetryDuration: DefaultSseRetryDuration,
|
||||
OnlyIfMissing: false,
|
||||
|
@ -79,6 +91,8 @@ func (sse *ServerSentEventGenerator) MergeSignals(signalsContents []byte, opts .
|
|||
return nil
|
||||
}
|
||||
|
||||
// RemoveSignals sends a [EventTypeRemoveSignals] event to the client.
|
||||
// Requires a non-empty list of paths.
|
||||
func (sse *ServerSentEventGenerator) RemoveSignals(paths ...string) error {
|
||||
if len(paths) == 0 {
|
||||
return ErrNoPathsProvided
|
||||
|
@ -98,6 +112,12 @@ func (sse *ServerSentEventGenerator) RemoveSignals(paths ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ReadSignals extracts Datastar signals from
|
||||
// an HTTP request and unmarshals them into the signals target,
|
||||
// which should be a pointer to a struct.
|
||||
//
|
||||
// Expects signals in [URL.Query] for [http.MethodGet] requests.
|
||||
// Expects JSON-encoded signals in [Request.Body] for other request methods.
|
||||
func ReadSignals(r *http.Request, signals any) error {
|
||||
var dsInput []byte
|
||||
|
||||
|
|
|
@ -12,37 +12,65 @@ import (
|
|||
"github.com/CAFxX/httpcompression"
|
||||
)
|
||||
|
||||
// CompressionStrategy indicates the strategy for selecting the compression algorithm.
|
||||
type CompressionStrategy string
|
||||
|
||||
const (
|
||||
ClientPriority = "client_priority"
|
||||
ServerPriority = "server_priority"
|
||||
Forced = "forced"
|
||||
// ClientPriority indicates that the client's preferred compression algorithm
|
||||
// should be used if possible.
|
||||
ClientPriority CompressionStrategy = "client_priority"
|
||||
|
||||
// ServerPriority indicates that the server's preferred compression algorithm
|
||||
// should be used.
|
||||
ServerPriority CompressionStrategy = "server_priority"
|
||||
|
||||
// Forced indicates that the first provided compression
|
||||
// algorithm must be used regardless of client or server preferences.
|
||||
Forced CompressionStrategy = "forced"
|
||||
)
|
||||
|
||||
// Compressor pairs a [httpcompression.CompressorProvider]
|
||||
// with an encoding HTTP content type.
|
||||
type Compressor struct {
|
||||
Encoding string
|
||||
Compressor httpcompression.CompressorProvider
|
||||
}
|
||||
|
||||
type CompressionConfig struct {
|
||||
// compressionOptions holds all the data for server-sent events
|
||||
// message compression configuration initiated by [CompressionOption]s.
|
||||
type compressionOptions struct {
|
||||
CompressionStrategy CompressionStrategy
|
||||
ClientEncodings []string
|
||||
Compressors []Compressor
|
||||
}
|
||||
|
||||
type CompressionOption func(*CompressionConfig)
|
||||
// CompressionOption configures server-sent events
|
||||
// message compression.
|
||||
type CompressionOption func(*compressionOptions)
|
||||
|
||||
// GzipOption configures the Gzip compression algorithm.
|
||||
type GzipOption func(*gzip.Options)
|
||||
|
||||
// WithGzipLevel determines the algorithm's compression level.
|
||||
// Higher values result in smaller output at the cost of higher CPU usage.
|
||||
//
|
||||
// Choose one of the following levels:
|
||||
// - [gzip.NoCompression]
|
||||
// - [gzip.BestSpeed]
|
||||
// - [gzip.BestCompression]
|
||||
// - [gzip.DefaultCompression]
|
||||
// - [gzip.HuffmanOnly]
|
||||
func WithGzipLevel(level int) GzipOption {
|
||||
return func(opts *gzip.Options) {
|
||||
opts.Level = level
|
||||
}
|
||||
}
|
||||
|
||||
// WithGzip appends a [Gzip] compressor to the list of compressors.
|
||||
//
|
||||
// [Gzip]: https://en.wikipedia.org/wiki/Gzip
|
||||
func WithGzip(opts ...GzipOption) CompressionOption {
|
||||
return func(cfg *CompressionConfig) {
|
||||
return func(cfg *compressionOptions) {
|
||||
// set default options
|
||||
options := gzip.Options{
|
||||
Level: gzip.DefaultCompression,
|
||||
|
@ -60,26 +88,40 @@ func WithGzip(opts ...GzipOption) CompressionOption {
|
|||
}
|
||||
|
||||
cfg.Compressors = append(cfg.Compressors, compressor)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// DeflateOption configures the Deflate compression algorithm.
|
||||
type DeflateOption func(*zlib.Options)
|
||||
|
||||
// WithDeflateLevel determines the algorithm's compression level.
|
||||
// Higher values result in smaller output at the cost of higher CPU usage.
|
||||
//
|
||||
// Choose one of the following levels:
|
||||
// - [zlib.NoCompression]
|
||||
// - [zlib.BestSpeed]
|
||||
// - [zlib.BestCompression]
|
||||
// - [zlib.DefaultCompression]
|
||||
// - [zlib.HuffmanOnly]
|
||||
func WithDeflateLevel(level int) DeflateOption {
|
||||
return func(opts *zlib.Options) {
|
||||
opts.Level = level
|
||||
}
|
||||
}
|
||||
|
||||
// WithDeflateDictionary sets the dictionary used by the algorithm.
|
||||
// This can improve compression ratio for repeated data.
|
||||
func WithDeflateDictionary(dict []byte) DeflateOption {
|
||||
return func(opts *zlib.Options) {
|
||||
opts.Dictionary = dict
|
||||
}
|
||||
}
|
||||
|
||||
// WithDeflate appends a [Deflate] compressor to the list of compressors.
|
||||
//
|
||||
// [Deflate]: https://en.wikipedia.org/wiki/Deflate
|
||||
func WithDeflate(opts ...DeflateOption) CompressionOption {
|
||||
return func(cfg *CompressionConfig) {
|
||||
return func(cfg *compressionOptions) {
|
||||
options := zlib.Options{
|
||||
Level: zlib.DefaultCompression,
|
||||
}
|
||||
|
@ -99,22 +141,33 @@ func WithDeflate(opts ...DeflateOption) CompressionOption {
|
|||
}
|
||||
}
|
||||
|
||||
type brotliOption func(*brotli.Options)
|
||||
// BrotliOption configures the Brotli compression algorithm.
|
||||
type BrotliOption func(*brotli.Options)
|
||||
|
||||
func WithBrotliLevel(level int) brotliOption {
|
||||
// WithBrotliLevel determines the algorithm's compression level.
|
||||
// Higher values result in smaller output at the cost of higher CPU usage.
|
||||
// Fastest compression level is 0. Best compression level is 11.
|
||||
// Defaults to 6.
|
||||
func WithBrotliLevel(level int) BrotliOption {
|
||||
return func(opts *brotli.Options) {
|
||||
opts.Quality = level
|
||||
}
|
||||
}
|
||||
|
||||
func WithBrotliLGWin(lgwin int) brotliOption {
|
||||
// WithBrotliLGWin the sliding window size for Brotli compression
|
||||
// algorithm. Select a value between 10 and 24.
|
||||
// Defaults to 0, indicating automatic window size selection based on compression quality.
|
||||
func WithBrotliLGWin(lgwin int) BrotliOption {
|
||||
return func(opts *brotli.Options) {
|
||||
opts.LGWin = lgwin
|
||||
}
|
||||
}
|
||||
|
||||
func WithBrotli(opts ...brotliOption) CompressionOption {
|
||||
return func(cfg *CompressionConfig) {
|
||||
// WithBrotli appends a [Brotli] compressor to the list of compressors.
|
||||
//
|
||||
// [Brotli]: https://en.wikipedia.org/wiki/Brotli
|
||||
func WithBrotli(opts ...BrotliOption) CompressionOption {
|
||||
return func(cfg *compressionOptions) {
|
||||
options := brotli.Options{
|
||||
Quality: brotli.DefaultCompression,
|
||||
}
|
||||
|
@ -134,9 +187,11 @@ func WithBrotli(opts ...brotliOption) CompressionOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithZstd appends a [Zstd] compressor to the list of compressors.
|
||||
//
|
||||
// [Zstd]: https://en.wikipedia.org/wiki/Zstd
|
||||
func WithZstd(opts ...zstd_opts.EOption) CompressionOption {
|
||||
return func(cfg *CompressionConfig) {
|
||||
|
||||
return func(cfg *compressionOptions) {
|
||||
zstdCompressor, _ := zstd.New(opts...)
|
||||
|
||||
compressor := Compressor{
|
||||
|
@ -148,28 +203,37 @@ func WithZstd(opts ...zstd_opts.EOption) CompressionOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithClientPriority sets the compression strategy to [ClientPriority].
|
||||
// The compression algorithm will be selected based on the
|
||||
// client's preference from the list of included compressors.
|
||||
func WithClientPriority() CompressionOption {
|
||||
return func(cfg *CompressionConfig) {
|
||||
return func(cfg *compressionOptions) {
|
||||
cfg.CompressionStrategy = ClientPriority
|
||||
}
|
||||
}
|
||||
|
||||
// WithServerPriority sets the compression strategy to [ServerPriority].
|
||||
// The compression algorithm will be selected based on the
|
||||
// server's preference from the list of included compressors.
|
||||
func WithServerPriority() CompressionOption {
|
||||
return func(cfg *CompressionConfig) {
|
||||
return func(cfg *compressionOptions) {
|
||||
cfg.CompressionStrategy = ServerPriority
|
||||
}
|
||||
}
|
||||
|
||||
// WithForced sets the compression strategy to [Forced].
|
||||
// The first compression algorithm will be selected
|
||||
// from the list of included compressors.
|
||||
func WithForced() CompressionOption {
|
||||
return func(cfg *CompressionConfig) {
|
||||
return func(cfg *compressionOptions) {
|
||||
cfg.CompressionStrategy = Forced
|
||||
}
|
||||
}
|
||||
|
||||
// WithCompression adds compression to server-sent event stream.
|
||||
func WithCompression(opts ...CompressionOption) SSEOption {
|
||||
|
||||
return func(sse *ServerSentEventGenerator) {
|
||||
cfg := &CompressionConfig{
|
||||
cfg := &compressionOptions{
|
||||
CompressionStrategy: ClientPriority,
|
||||
ClientEncodings: parseEncodings(sse.acceptEncoding),
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import (
|
|||
"github.com/valyala/bytebufferpool"
|
||||
)
|
||||
|
||||
// ServerSentEventGenerator streams events into
|
||||
// an [http.ResponseWriter]. Each event is flushed immediately.
|
||||
type ServerSentEventGenerator struct {
|
||||
ctx context.Context
|
||||
mu *sync.Mutex
|
||||
|
@ -23,8 +25,13 @@ type ServerSentEventGenerator struct {
|
|||
acceptEncoding string
|
||||
}
|
||||
|
||||
// SSEOption configures the initialization of an
|
||||
// HTTP Server-Sent Event stream.
|
||||
type SSEOption func(*ServerSentEventGenerator)
|
||||
|
||||
// NewSSE upgrades an [http.ResponseWriter] to an HTTP Server-Sent Event stream.
|
||||
// The connection is kept alive until the context is canceled or the response is closed by returning from the handler.
|
||||
// Run an event loop for persistent streaming.
|
||||
func NewSSE(w http.ResponseWriter, r *http.Request, opts ...SSEOption) *ServerSentEventGenerator {
|
||||
rc := http.NewResponseController(w)
|
||||
|
||||
|
@ -43,7 +50,7 @@ func NewSSE(w http.ResponseWriter, r *http.Request, opts ...SSEOption) *ServerSe
|
|||
acceptEncoding: r.Header.Get("Accept-Encoding"),
|
||||
}
|
||||
|
||||
// Apply options
|
||||
// apply options
|
||||
for _, opt := range opts {
|
||||
opt(sseHandler)
|
||||
}
|
||||
|
@ -65,27 +72,39 @@ func NewSSE(w http.ResponseWriter, r *http.Request, opts ...SSEOption) *ServerSe
|
|||
return sseHandler
|
||||
}
|
||||
|
||||
// Context returns the context associated with the upgraded connection.
|
||||
// It is equivalent to calling [request.Context].
|
||||
func (sse *ServerSentEventGenerator) Context() context.Context {
|
||||
return sse.ctx
|
||||
}
|
||||
|
||||
type ServerSentEventData struct {
|
||||
// serverSentEventData holds event configuration data for
|
||||
// [SSEEventOption]s.
|
||||
type serverSentEventData struct {
|
||||
Type EventType
|
||||
EventID string
|
||||
Data []string
|
||||
RetryDuration time.Duration
|
||||
}
|
||||
|
||||
type SSEEventOption func(*ServerSentEventData)
|
||||
// SSEEventOption modifies one server-sent event.
|
||||
type SSEEventOption func(*serverSentEventData)
|
||||
|
||||
// WithSSEEventId configures an optional event ID for one server-sent event.
|
||||
// The client message field [lastEventId] will be set to this value.
|
||||
// If the next event does not have an event ID, the last used event ID will remain.
|
||||
//
|
||||
// [lastEventId]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/lastEventId
|
||||
func WithSSEEventId(id string) SSEEventOption {
|
||||
return func(e *ServerSentEventData) {
|
||||
return func(e *serverSentEventData) {
|
||||
e.EventID = id
|
||||
}
|
||||
}
|
||||
|
||||
// WithSSERetryDuration overrides the [DefaultSseRetryDuration] for
|
||||
// one server-sent event.
|
||||
func WithSSERetryDuration(retryDuration time.Duration) SSEEventOption {
|
||||
return func(e *ServerSentEventData) {
|
||||
return func(e *serverSentEventData) {
|
||||
e.RetryDuration = retryDuration
|
||||
}
|
||||
}
|
||||
|
@ -102,12 +121,14 @@ func writeJustError(w io.Writer, b []byte) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// Send emits a server-sent event to the client. Method is safe for
|
||||
// concurrent use.
|
||||
func (sse *ServerSentEventGenerator) Send(eventType EventType, dataLines []string, opts ...SSEEventOption) error {
|
||||
sse.mu.Lock()
|
||||
defer sse.mu.Unlock()
|
||||
|
||||
// create the event
|
||||
evt := ServerSentEventData{
|
||||
evt := serverSentEventData{
|
||||
Type: eventType,
|
||||
Data: dataLines,
|
||||
RetryDuration: DefaultSseRetryDuration,
|
||||
|
@ -187,6 +208,5 @@ func (sse *ServerSentEventGenerator) Send(eventType EventType, dataLines []strin
|
|||
}
|
||||
|
||||
// log.Print(NewLine + buf.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
package datastar
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
NewLine = "\n"
|
||||
DoubleNewLine = "\n\n"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEventTypeError = errors.New("event type is required")
|
||||
|
||||
newLineBuf = []byte(NewLine)
|
||||
doubleNewLineBuf = []byte(DoubleNewLine)
|
||||
)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# Datastar-py
|
||||
# datastar-py
|
||||
|
||||
The `datastar_py` package provides backend helpers for the [Datastar](https://data-star.dev) JS library.
|
||||
The `datastar-py` package provides backend helpers for the [Datastar](https://data-star.dev) JS library.
|
||||
|
||||
Datastar requires all backend responses to use SSE. This allows the backend to
|
||||
send any number of responses, from zero to inifinity.
|
||||
|
||||
`Datastar-py` helps with the formatting of these responses, while also
|
||||
`datastar-py` helps with the formatting of these responses, while also
|
||||
providing helper functions for the different supported responses.
|
||||
|
||||
To use `datastar-py`, import the SSE generator in your app and then use
|
||||
|
@ -32,7 +32,7 @@ async def updates():
|
|||
return response
|
||||
```
|
||||
|
||||
There are also a number of custom responses/helpers for various frameworks. Current ly the following frameworks are supported:
|
||||
There are also a number of custom responses/helpers for various frameworks. Currently the following frameworks are supported:
|
||||
|
||||
* [Sanic](https://sanic.dev/en/)
|
||||
* [Django](https://www.djangoproject.com/)
|
||||
|
|
|
@ -37,7 +37,7 @@ build-backend = "hatchling.build"
|
|||
dev = [
|
||||
"django>=4.2.17",
|
||||
"fastapi[standard]>=0.115.4",
|
||||
# "fasthtml>=0.12.0",
|
||||
"python-fasthtml>=0.12.0; python_version > '3.10'",
|
||||
"flask[async]>=3.0.3",
|
||||
"quart>=0.19.9",
|
||||
"sanic>=24.6.0",
|
||||
|
|
|
@ -1,15 +1,2 @@
|
|||
from typing import override
|
||||
|
||||
from fastcore.xml import to_xml
|
||||
|
||||
from .sse import SSE_HEADERS, ServerSentEventGenerator
|
||||
from .starlette import DatastarStreamingResponse as _DatastarStreamingResponse
|
||||
|
||||
|
||||
class DatastarStreamingResponse(_DatastarStreamingResponse):
|
||||
@classmethod
|
||||
@override
|
||||
def merge_fragments(cls, fragments, *args, **kwargs):
|
||||
xml_fragments = [f if isinstance(f, str) else to_xml(f) for f in fragments]
|
||||
# From here, business as usual
|
||||
return super().merge_fragments(xml_fragments, *args, **kwargs)
|
||||
from .sse import ServerSentEventGenerator
|
||||
from .starlette import DatastarStreamingResponse
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
from itertools import chain
|
||||
from typing import Optional
|
||||
from typing import Optional, Protocol, Union, runtime_checkable
|
||||
|
||||
import datastar_py.consts as consts
|
||||
|
||||
|
@ -11,6 +11,17 @@ SSE_HEADERS = {
|
|||
}
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class _HtmlProvider(Protocol):
|
||||
"""A type that produces text ready to be placed in an HTML document.
|
||||
|
||||
This is a convention used by html producing/consuming libraries. This lets
|
||||
e.g. fasthtml fasttags, or htpy elements, be passed straight in to
|
||||
merge_fragments."""
|
||||
|
||||
def __html__(self) -> str: ...
|
||||
|
||||
|
||||
class ServerSentEventGenerator:
|
||||
__slots__ = ()
|
||||
|
||||
|
@ -38,13 +49,15 @@ class ServerSentEventGenerator:
|
|||
@classmethod
|
||||
def merge_fragments(
|
||||
cls,
|
||||
fragments: list[str],
|
||||
fragments: Union[str, _HtmlProvider],
|
||||
selector: Optional[str] = None,
|
||||
merge_mode: Optional[consts.FragmentMergeMode] = None,
|
||||
use_view_transition: bool = consts.DEFAULT_FRAGMENTS_USE_VIEW_TRANSITIONS,
|
||||
event_id: Optional[int] = None,
|
||||
retry_duration: int = consts.DEFAULT_SSE_RETRY_DURATION,
|
||||
):
|
||||
if isinstance(fragments, _HtmlProvider):
|
||||
fragments = fragments.__html__()
|
||||
data_lines = []
|
||||
if merge_mode:
|
||||
data_lines.append(f"data: {consts.MERGE_MODE_DATALINE_LITERAL} {merge_mode}")
|
||||
|
@ -57,8 +70,7 @@ class ServerSentEventGenerator:
|
|||
|
||||
data_lines.extend(
|
||||
f"data: {consts.FRAGMENTS_DATALINE_LITERAL} {x}"
|
||||
for fragment in fragments
|
||||
for x in fragment.splitlines()
|
||||
for x in fragments.splitlines()
|
||||
)
|
||||
|
||||
return ServerSentEventGenerator._send(
|
||||
|
@ -162,3 +174,7 @@ class ServerSentEventGenerator:
|
|||
event_id,
|
||||
retry_duration,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def redirect(cls, location: str):
|
||||
return cls.execute_script(f"setTimeout(() => window.location = '{location}')")
|
||||
|
|
|
@ -1,43 +1,51 @@
|
|||
[package]
|
||||
authors = ["Johnathan Stevers <jmstevers@gmail.com>"]
|
||||
authors = [
|
||||
"Johnathan Stevers <jmstevers@gmail.com>",
|
||||
"Glen Henri J. De Cauwsemaecker <glen@plabayo.tech>",
|
||||
]
|
||||
categories = ["web-programming"]
|
||||
description = "Datastar is the Rust implementation of the [Datastar](https://data-star.dev) SDK."
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
homepage = "https://data-star.dev"
|
||||
keywords = ["datastar", "web", "backend"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "datastar"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/starfederation/datastar-rs"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[dev-dependencies]
|
||||
async-stream = { version = "0.3.6", default-features = false }
|
||||
serde = { version = "1.0.217", default-features = false, features = ["derive"] }
|
||||
serde_json = { version = "1.0.138", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||
serde_json = { version = "1", default-features = false, features = ["std"] }
|
||||
tokio = { version = "1.43.0", features = ["full"] }
|
||||
axum = { version = "0.8.1" }
|
||||
rocket = { version = "0.5.1", features = ["json"] }
|
||||
rama = { version = "0.2.0-alpha.10", features = ["http-full"] }
|
||||
|
||||
|
||||
[dependencies]
|
||||
matchit = "0.8.4"
|
||||
axum = { version = "0.8.1", default-features = false, optional = true, features = [
|
||||
"query",
|
||||
"tokio",
|
||||
] }
|
||||
futures-util = { version = "0.3.31", default-features = false }
|
||||
http-body = { version = "1.0.1", default-features = false, optional = true }
|
||||
pin-project-lite = { version = "0.2.16", default-features = false, optional = true }
|
||||
futures-util = { version = "0.3", default-features = false }
|
||||
http-body = { version = "1.0", default-features = false, optional = true }
|
||||
pin-project-lite = { version = "0.2", default-features = false, optional = true }
|
||||
rocket = { version = "0.5.1", default-features = false, optional = true }
|
||||
serde = { version = "1.0.217", default-features = false, optional = true, features = [
|
||||
rama = { version = "0.2.0-alpha.11", default-features = false, optional = true, features = [
|
||||
"http",
|
||||
] }
|
||||
serde = { version = "1", default-features = false, optional = true, features = [
|
||||
"derive",
|
||||
] }
|
||||
serde_json = { version = "1.0.138", default-features = false, optional = true, features = [
|
||||
serde_json = { version = "1", default-features = false, optional = true, features = [
|
||||
"std",
|
||||
] }
|
||||
sync_wrapper = { version = "1.0.2", default-features = false, optional = true }
|
||||
sync_wrapper = { version = "1", default-features = false, optional = true }
|
||||
bytes = { version = "1", default-features = false, optional = true }
|
||||
|
||||
|
||||
[features]
|
||||
|
@ -51,6 +59,14 @@ axum = [
|
|||
]
|
||||
http2 = []
|
||||
rocket = ["dep:rocket"]
|
||||
rama = [
|
||||
"dep:rama",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:pin-project-lite",
|
||||
"dep:bytes",
|
||||
"dep:sync_wrapper",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Datastar Rust SDK
|
||||
|
||||
An implementation of the Datastar SDK in Rust with framework integration for Axum and Rocket.
|
||||
An implementation of the Datastar SDK in Rust with framework integration for Axum, Rocket and Rama.
|
||||
|
||||
# Usage
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#[cfg(feature = "axum")]
|
||||
pub mod axum;
|
||||
#[cfg(feature = "rama")]
|
||||
pub mod rama;
|
||||
#[cfg(feature = "rocket")]
|
||||
pub mod rocket;
|
||||
|
||||
|
@ -23,6 +25,8 @@ mod consts;
|
|||
pub mod prelude {
|
||||
#[cfg(feature = "axum")]
|
||||
pub use crate::axum::ReadSignals;
|
||||
#[cfg(all(feature = "rama", not(feature = "axum")))]
|
||||
pub use crate::rama::ReadSignals;
|
||||
pub use crate::{
|
||||
consts::FragmentMergeMode, execute_script::ExecuteScript, merge_fragments::MergeFragments,
|
||||
merge_signals::MergeSignals, remove_fragments::RemoveFragments,
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
//! Rama integration for Datastar.
|
||||
//!
|
||||
//! Learn more about rama at
|
||||
//! <https://github.com/plabayo/rama>.
|
||||
|
||||
use {
|
||||
crate::{Sse, TrySse, prelude::DatastarEvent},
|
||||
bytes::Bytes,
|
||||
futures_util::{Stream, StreamExt},
|
||||
pin_project_lite::pin_project,
|
||||
rama::http::{
|
||||
Body, BodyExtractExt, IntoResponse, Method, Request, Response, StatusCode,
|
||||
dep::http_body::{Body as HttpBody, Frame},
|
||||
header,
|
||||
service::web::extract::{FromRequest, Query},
|
||||
},
|
||||
serde::{Deserialize, de::DeserializeOwned},
|
||||
std::{
|
||||
convert::Infallible,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
},
|
||||
sync_wrapper::SyncWrapper,
|
||||
};
|
||||
|
||||
pin_project! {
|
||||
struct SseBody<S> {
|
||||
#[pin]
|
||||
stream: SyncWrapper<S>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> IntoResponse for Sse<S>
|
||||
where
|
||||
S: Stream<Item = DatastarEvent> + Send + 'static,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
(
|
||||
[
|
||||
(header::CONTENT_TYPE, "text/event-stream"),
|
||||
(header::CACHE_CONTROL, "no-cache"),
|
||||
(header::CONNECTION, "keep-alive"),
|
||||
],
|
||||
Body::new(SseBody {
|
||||
stream: SyncWrapper::new(self.0.map(Ok::<_, Infallible>)),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, E> IntoResponse for TrySse<S>
|
||||
where
|
||||
S: Stream<Item = Result<DatastarEvent, E>> + Send + 'static,
|
||||
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
(
|
||||
[
|
||||
(header::CONTENT_TYPE, "text/event-stream"),
|
||||
(header::CACHE_CONTROL, "no-cache"),
|
||||
(header::CONNECTION, "keep-alive"),
|
||||
],
|
||||
Body::new(SseBody {
|
||||
stream: SyncWrapper::new(self.0),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, E> HttpBody for SseBody<S>
|
||||
where
|
||||
S: Stream<Item = Result<DatastarEvent, E>>,
|
||||
{
|
||||
type Data = Bytes;
|
||||
type Error = E;
|
||||
|
||||
fn poll_frame(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
|
||||
let this = self.project();
|
||||
|
||||
match this.stream.get_pin_mut().poll_next(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Some(Err(error))) => Poll::Ready(Some(Err(error))),
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Ready(Some(Ok(event))) => {
|
||||
Poll::Ready(Some(Ok(Frame::data(event.to_string().into()))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DatastarParam {
|
||||
datastar: serde_json::Value,
|
||||
}
|
||||
|
||||
/// [`ReadSignals`] is a request extractor that reads datastar signals from the request.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use datastar::rama::ReadSignals;
|
||||
/// use serde::Deserialize;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Signals {
|
||||
/// foo: String,
|
||||
/// bar: i32,
|
||||
/// }
|
||||
///
|
||||
/// async fn handler(ReadSignals(signals): ReadSignals<Signals>) {
|
||||
/// println!("foo: {}", signals.foo);
|
||||
/// println!("bar: {}", signals.bar);
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct ReadSignals<T: DeserializeOwned>(pub T);
|
||||
|
||||
impl<T> FromRequest for ReadSignals<T>
|
||||
where
|
||||
T: DeserializeOwned + Send + Sync + 'static,
|
||||
{
|
||||
type Rejection = Response;
|
||||
|
||||
async fn from_request(req: Request) -> Result<Self, Self::Rejection> {
|
||||
let json = match *req.method() {
|
||||
Method::GET => {
|
||||
let query =
|
||||
Query::<DatastarParam>::parse_query_str(req.uri().query().unwrap_or(""))
|
||||
.map_err(IntoResponse::into_response)?;
|
||||
|
||||
let signals = query.0.datastar.as_str().ok_or_else(|| {
|
||||
(StatusCode::BAD_REQUEST, "Failed to parse JSON").into_response()
|
||||
})?;
|
||||
|
||||
serde_json::from_str(signals)
|
||||
.map_err(|err| (StatusCode::BAD_REQUEST, err.to_string()).into_response())?
|
||||
}
|
||||
_ => req
|
||||
.into_body()
|
||||
.try_into_json()
|
||||
.await
|
||||
.map_err(|err| (StatusCode::BAD_REQUEST, err.to_string()).into_response())?,
|
||||
};
|
||||
|
||||
Ok(Self(json))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {
|
||||
super::Sse,
|
||||
crate::{
|
||||
prelude::ReadSignals,
|
||||
testing::{self, Signals},
|
||||
},
|
||||
rama::{
|
||||
error::BoxError,
|
||||
http::{IntoResponse, server::HttpServer, service::web::Router},
|
||||
rt::Executor,
|
||||
},
|
||||
};
|
||||
|
||||
async fn test(ReadSignals(signals): ReadSignals<Signals>) -> impl IntoResponse {
|
||||
Sse(testing::test(signals.events))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sdk_test() -> Result<(), BoxError> {
|
||||
HttpServer::auto(Executor::default())
|
||||
.listen(
|
||||
"127.0.0.1:3000",
|
||||
Router::new().get("/test", test).post("/test", test),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue