diff --git a/plugins/cmd/hostgen/README.md b/plugins/cmd/hostgen/README.md
deleted file mode 100644
index 3b1f798af..000000000
--- a/plugins/cmd/hostgen/README.md
+++ /dev/null
@@ -1,433 +0,0 @@
-# hostgen
-
-A code generator for Navidrome's plugin host functions. It reads Go interface definitions with special annotations and generates Extism host function wrappers.
-
-## Usage
-
-```bash
-hostgen -input
-output -package [-v] [-dry-run] [-host-only] [-plugin-only]
-```
-
-### Flags
-
-| Flag | Description | Default |
-|----------------|----------------------------------------------------------------|----------|
-| `-input` | Directory containing Go source files with annotated interfaces | Required |
-| `-output` | Directory where generated files will be written | Required |
-| `-package` | Package name for generated files | Required |
-| `-v` | Verbose output | `false` |
-| `-dry-run` | Parse and validate without writing files | `false` |
-| `-host-only` | Generate only host-side wrapper code | `false` |
-| `-plugin-only` | Generate only plugin/client-side wrapper code | `false` |
-| `-go` | Generate Go client wrappers | `true`* |
-| `-python` | Generate Python client wrappers | `false` |
-| `-rust` | Generate Rust client wrappers | `false` |
-
-\* `-go` is enabled by default when neither `-python` nor `-rust` is specified. Use combinations like `-go -python -rust` to generate multiple languages.
-
-By default, both host and Go plugin code are generated. Use `-host-only` or `-plugin-only` to generate only one type. Use `-python` to generate Python wrappers and `-rust` to generate Rust wrappers.
-
-### Example
-
-```bash
-go run ./plugins/cmd/hostgen \
- -input ./plugins/host \
- -output ./plugins/host \
- -package host
-```
-
-Or via `go generate` (recommended):
-
-```go
-//go:generate go run ../cmd/hostgen -input . -output . -package host
-package host
-```
-
-## Annotations
-
-### `//nd:hostservice`
-
-Marks an interface as a host service that will have wrappers generated.
-
-```go
-//nd:hostservice name= permission=
-type MyService interface { ... }
-```
-
-| Parameter | Description | Required |
-|--------------|-----------------------------------------------------------------|----------|
-| `name` | Service name used in generated type names and function prefixes | Yes |
-| `permission` | Permission required by plugins to use this service | Yes |
-
-### `//nd:hostfunc`
-
-Marks a method within a host service interface for export to plugins.
-
-```go
-//nd:hostfunc [name=]
-MethodName(ctx context.Context, ...) (result Type, err error)
-```
-
-| Parameter | Description | Required |
-|-----------|-------------------------------------------------------------------------|----------|
-| `name` | Custom export name (default: `_` in lowercase) | No |
-
-## Input Format
-
-Host service interfaces must follow these conventions:
-
-1. **First parameter must be `context.Context`** - Required for all methods
-2. **Last return value should be `error`** - For proper error handling
-3. **Annotations must be on consecutive lines** - No blank comment lines between doc and annotation
-
-### Example Interface
-
-```go
-package host
-
-import "context"
-
-// SubsonicAPIService provides access to Navidrome's Subsonic API.
-// This documentation becomes part of the generated code.
-//nd:hostservice name=SubsonicAPI permission=subsonicapi
-type SubsonicAPIService interface {
- // Call executes a Subsonic API request and returns the response.
- //nd:hostfunc
- Call(ctx context.Context, uri string) (response string, err error)
-}
-```
-
-## Generated Output
-
-For each annotated interface, hostgen generates:
-
-### Request/Response Types
-
-```go
-// SubsonicAPICallRequest is the request type for SubsonicAPI.Call.
-type SubsonicAPICallRequest struct {
- Uri string `json:"uri"`
-}
-
-// SubsonicAPICallResponse is the response type for SubsonicAPI.Call.
-type SubsonicAPICallResponse struct {
- Response string `json:"response,omitempty"`
- Error string `json:"error,omitempty"`
-}
-```
-
-### Registration Function
-
-```go
-// RegisterSubsonicAPIHostFunctions registers SubsonicAPI service host functions.
-func RegisterSubsonicAPIHostFunctions(service SubsonicAPIService) []extism.HostFunction {
- return []extism.HostFunction{
- newSubsonicAPICallHostFunction(service),
- }
-}
-```
-
-### Host Function Wrappers
-
-Each method gets a wrapper that:
-1. Reads JSON request from plugin memory
-2. Unmarshals to the request type
-3. Calls the service method
-4. Marshals the response
-5. Writes JSON response to plugin memory
-
-## Supported Types
-
-hostgen supports these Go types in method signatures:
-
-| Type | JSON Representation |
-|-------------------------------|------------------------------------------|
-| `string`, `int`, `bool`, etc. | Native JSON types |
-| `[]T` (slices) | JSON arrays |
-| `map[K]V` (maps) | JSON objects |
-| `*T` (pointers) | Nullable fields |
-| `interface{}` / `any` | Converts to `any` |
-| Custom structs | JSON objects (must be JSON-serializable) |
-
-### Multiple Return Values
-
-Methods can return multiple values (plus error):
-
-```go
-//nd:hostfunc
-Search(ctx context.Context, query string) (results []string, total int, hasMore bool, err error)
-```
-
-Generates:
-
-```go
-type ServiceSearchResponse struct {
- Results []string `json:"results,omitempty"`
- Total int `json:"total,omitempty"`
- HasMore bool `json:"hasMore,omitempty"`
- Error string `json:"error,omitempty"`
-}
-```
-
-## Output Files
-
-### Host Code (Navidrome-side)
-
-Generated files are named `_gen.go` (lowercase) and placed in the output directory. Each file includes:
-
-- `// Code generated by hostgen. DO NOT EDIT.` header
-- Required imports (`context`, `encoding/json`, `extism`)
-- Request/response struct types
-- Registration function
-- Host function wrappers
-- Helper functions (`writeResponse`, `writeErrorResponse`)
-
-### Go Client Library (Go/TinyGo WASM)
-
-Generated files are named `nd_host_.go` (lowercase) and placed in the `go/` subdirectory of the output directory. The `go/` directory is a complete Go module (`github.com/navidrome/navidrome/plugins/host/go`) with package name `ndhost`, intended for import by Navidrome plugins built with TinyGo.
-
-The generator also creates:
-- `doc.go` - Package documentation listing all available services
-- `go.mod` - Go module file with required dependencies
-
-Each service file includes:
-
-- `// Code generated by hostgen. DO NOT EDIT.` header
-- Required imports (`encoding/json`, `errors`, `github.com/extism/go-pdk`)
-- `//go:wasmimport` declarations for each host function
-- Response struct types and any struct definitions from the service
-- Wrapper functions that handle memory allocation and JSON parsing
-
-#### Using the Go SDK
-
-Import the SDK in your plugin:
-
-```go
-import ndhost "github.com/navidrome/navidrome/plugins/host/go"
-
-// Use host services with the ndhost prefix
-result, err := ndhost.CacheGetString("my-key")
-scheduleID, err := ndhost.SchedulerScheduleOneTime(60, "payload", "")
-```
-
-Add to your `go.mod`:
-
-```
-require github.com/navidrome/navidrome/plugins/host/go v0.0.0
-replace github.com/navidrome/navidrome/plugins/host/go => ../../host/go
-```
-
-See [plugins/host/go/README.md](../../host/go/README.md) for detailed documentation.
-
-### Example Output Structure
-
-```
-output/
-├── subsonicapi_gen.go # Host-side code (for Navidrome)
-├── go/
-│ ├── doc.go # Package documentation
-│ ├── go.mod # Go module file
-│ └── nd_host_subsonicapi.go # Plugin-side code (for TinyGo plugins)
-├── python/
-│ └── nd_host_subsonicapi.py # Plugin-side code (for Python plugins)
-└── rust/
- └── nd_host_subsonicapi.rs # Plugin-side code (for Rust plugins)
-```
-
-### Python Client Code (extism-py WASM)
-
-Generated files are named `nd_host_.py` (lowercase) and placed in the `python/` subdirectory of the output directory. These files are intended for use in Navidrome plugins built with extism-py. Each file includes:
-
-- `# Code generated by hostgen. DO NOT EDIT.` header
-- Required imports (`dataclasses`, `typing`, `extism`, `json`)
-- `HostFunctionError` exception class for error handling
-- `@extism.import_fn` declarations for raw host functions
-- `@dataclass` types for methods with multiple return values
-- Wrapper functions with type hints, docstrings, and snake_case names
-
-#### Python Type Mapping
-
-| Go Type | Python Type |
-|-------------------------|-------------|
-| `string` | `str` |
-| `int`, `int32`, `int64` | `int` |
-| `float32`, `float64` | `float` |
-| `bool` | `bool` |
-| `[]byte` | `bytes` |
-| Unknown | `Any` |
-
-#### Python Function Naming
-
-Functions follow PEP 8 snake_case convention:
-
-| Go Method | Python Function |
-|-------------------------------|----------------------------------|
-| `SubsonicAPI.Call` | `subsonicapi_call()` |
-| `Scheduler.ScheduleRecurring` | `scheduler_schedule_recurring()` |
-| `Cache.GetString` | `cache_get_string()` |
-
-#### Multi-Value Returns
-
-Methods with multiple return values use dataclasses:
-
-```python
-@dataclass
-class CacheGetStringResult:
- value: str
- exists: bool
-
-def cache_get_string(key: str) -> CacheGetStringResult:
- ...
-```
-
-#### Python Plugin Usage
-
-> **Important:** Due to a limitation in extism-py, you cannot directly import the generated Python wrappers.
-> The `@extism.import_fn` decorators are only detected when defined in the plugin's main `__init__.py` file.
-> Generated Python files serve as **reference/template code** - copy the needed functions into your plugin.
-
-Example of copying the generated wrapper into your plugin's `__init__.py`:
-
-```python
-import extism
-import json
-
-# Copy host function declarations from generated files into your __init__.py
-@extism.import_fn("extism:host/user", "subsonicapi_call")
-def _host_subsonicapi_call(input_ptr: extism.JsonI64) -> extism.JsonI64:
- pass
-
-def subsonicapi_call(endpoint: str) -> str:
- """Call the SubsonicAPI with the given endpoint."""
- result = _host_subsonicapi_call(endpoint)
- return result
-
-# Now use it in your plugin
-@extism.plugin_fn
-def my_plugin_function():
- try:
- response = subsonicapi_call("getAlbumList2?type=random&size=10")
- data = json.loads(response)
- except Exception as e:
- extism.log(extism.LogLevel.Error, f"API error: {e}")
-```
-
-### Rust Client Code (extism-pdk WASM)
-
-Generated files are named `nd_host_.rs` (lowercase) and placed in the `rust/` subdirectory of the output directory. These files are intended for use in Navidrome plugins built with `extism-pdk`. Each file includes:
-
-- `// Code generated by hostgen. DO NOT EDIT.` header
-- Required imports (`extism_pdk::*`, `serde`)
-- Request/response struct types with `#[derive(Serialize, Deserialize)]`
-- `#[host_fn]` extern blocks for raw host function imports
-- Public wrapper functions with `Result` returns and snake_case names
-
-#### Rust Type Mapping
-
-| Go Type | Rust Type | Notes |
-|-------------------------------|-------------------------------|------------------------------|
-| `string` | `String` / `&str` | `&str` for params |
-| `int`, `int32` | `i32` | |
-| `int64` | `i64` | |
-| `float32` | `f32` | |
-| `float64` | `f64` | |
-| `bool` | `bool` | |
-| `[]byte` | `Vec` | |
-| `[]T` | `Vec` | |
-| `map[K]V` | `HashMap` | From `std::collections` |
-| `*T` | `Option` | |
-| `interface{}` / `any` | `serde_json::Value` | |
-
-#### Rust Function Naming
-
-Functions follow Rust snake_case convention:
-
-| Go Method | Rust Function |
-|-------------------------------|----------------------------------|
-| `SubsonicAPI.Call` | `subsonicapi_call()` |
-| `Scheduler.ScheduleRecurring` | `scheduler_schedule_recurring()` |
-| `Cache.GetString` | `cache_get_string()` |
-
-#### Rust Plugin Usage
-
-The generated Rust wrappers form a library crate (`nd-host`) that plugins can depend on. Add the dependency to your plugin's `Cargo.toml`:
-
-```toml
-[dependencies]
-extism-pdk = "1.2"
-nd-host = { path = "../../host/rust" }
-```
-
-Then import and use the services:
-
-```rust
-use nd_host::{cache, scheduler, artwork};
-
-#[plugin_fn]
-pub fn my_callback(input: String) -> FnResult {
- // Use cache service
- cache::cache_set_string("key", "value", 3600)?;
- let value = cache::cache_get_string("key")?;
-
- // Schedule a task
- scheduler::scheduler_schedule_one_time(60, "payload", "task-id")?;
-
- // Get artwork URL
- let url = artwork::artwork_get_track_url("track-id", 300)?;
-
- Ok("done")
-}
-```
-
-See [discord-rich-presence-rs](../examples/discord-rich-presence-rs/) for a complete example using all Rust host wrappers.
-
-## Troubleshooting
-
-### Annotations Not Detected
-
-Ensure annotations are on consecutive lines with no blank `//` lines:
-
-```go
-// ✅ Correct
-// Documentation for the service.
-//nd:hostservice name=Test permission=test
-
-// ❌ Wrong - blank comment line breaks detection
-// Documentation for the service.
-//
-//nd:hostservice name=Test permission=test
-```
-
-### Methods Not Exported
-
-Methods without `//nd:hostfunc` annotation are skipped. Ensure the annotation is directly above the method:
-
-```go
-// ✅ Correct
-// Method documentation.
-//nd:hostfunc
-MyMethod(ctx context.Context) error
-
-// ❌ Wrong - annotation not directly above method
-//nd:hostfunc
-
-MyMethod(ctx context.Context) error
-```
-
-### Generated Files Skipped
-
-Files ending in `_gen.go` are automatically skipped during parsing to avoid processing previously generated code.
-
-## Development
-
-Run tests:
-
-```bash
-go test -v ./plugins/cmd/hostgen/...
-```
-
-The test suite includes:
-- CLI integration tests
-- Complex type handling (structs, slices, maps, pointers)
-- Multiple return value scenarios
-- Error cases and edge conditions
diff --git a/plugins/cmd/hostgen/integration_test.go b/plugins/cmd/hostgen/integration_test.go
deleted file mode 100644
index a82e7162b..000000000
--- a/plugins/cmd/hostgen/integration_test.go
+++ /dev/null
@@ -1,597 +0,0 @@
-package main
-
-import (
- "fmt"
- "go/format"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
-
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
-)
-
-var _ = Describe("hostgen CLI", Ordered, func() {
- var (
- testDir string
- outputDir string
- hostgenBin string
- )
-
- BeforeAll(func() {
- // Set testdata directory
- testdataDir = filepath.Join(mustGetWd(GinkgoT()), "plugins", "cmd", "hostgen", "testdata")
-
- // Build the hostgen binary
- hostgenBin = filepath.Join(os.TempDir(), "hostgen-test")
- cmd := exec.Command("go", "build", "-o", hostgenBin, ".")
- cmd.Dir = filepath.Join(mustGetWd(GinkgoT()), "plugins", "cmd", "hostgen")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Failed to build hostgen: %s", output)
- DeferCleanup(func() {
- os.Remove(hostgenBin)
- })
- })
-
- BeforeEach(func() {
- var err error
- testDir, err = os.MkdirTemp("", "hostgen-test-input-*")
- Expect(err).ToNot(HaveOccurred())
- outputDir, err = os.MkdirTemp("", "hostgen-test-output-*")
- Expect(err).ToNot(HaveOccurred())
- })
-
- AfterEach(func() {
- os.RemoveAll(testDir)
- os.RemoveAll(outputDir)
- })
-
- Describe("CLI flags and behavior", func() {
- BeforeEach(func() {
- serviceCode := `package testpkg
-
-import "context"
-
-//nd:hostservice name=Test permission=test
-type TestService interface {
- //nd:hostfunc
- DoAction(ctx context.Context, input string) (output string, err error)
-}
-`
- Expect(os.WriteFile(filepath.Join(testDir, "service.go"), []byte(serviceCode), 0600)).To(Succeed())
- })
-
- It("supports verbose mode", func() {
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "testpkg", "-v")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
-
- outputStr := string(output)
- Expect(outputStr).To(ContainSubstring("Input directory:"))
- Expect(outputStr).To(ContainSubstring("Output directory:"))
- Expect(outputStr).To(ContainSubstring("Found 1 host service(s)"))
- Expect(outputStr).To(ContainSubstring("Generated"))
- })
-
- It("supports dry-run mode", func() {
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "testpkg", "-dry-run")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
-
- Expect(string(output)).To(ContainSubstring("RegisterTestHostFunctions"))
- Expect(filepath.Join(outputDir, "test_gen.go")).ToNot(BeAnExistingFile())
- })
-
- It("infers package name from output directory", func() {
- customOutput, err := os.MkdirTemp("", "mypkg")
- Expect(err).ToNot(HaveOccurred())
- defer os.RemoveAll(customOutput)
-
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", customOutput)
- _, err = cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred())
-
- content, err := os.ReadFile(filepath.Join(customOutput, "test_gen.go"))
- Expect(err).ToNot(HaveOccurred())
- Expect(string(content)).To(ContainSubstring("package mypkg"))
- })
-
- It("returns error for invalid input directory", func() {
- cmd := exec.Command(hostgenBin, "-input", "/nonexistent/path")
- output, err := cmd.CombinedOutput()
- Expect(err).To(HaveOccurred())
- Expect(string(output)).To(ContainSubstring("parsing source files"))
- })
-
- It("handles no annotated services gracefully", func() {
- Expect(os.WriteFile(filepath.Join(testDir, "service.go"), []byte("package testpkg\n"), 0600)).To(Succeed())
-
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-v")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
- Expect(string(output)).To(ContainSubstring("No host services found"))
- })
-
- It("generates separate files for multiple services", func() {
- // Remove service.go created by BeforeEach
- Expect(os.Remove(filepath.Join(testDir, "service.go"))).To(Succeed())
-
- service1 := `package testpkg
-import "context"
-//nd:hostservice name=ServiceA permission=a
-type ServiceA interface {
- //nd:hostfunc
- MethodA(ctx context.Context) error
-}
-`
- service2 := `package testpkg
-import "context"
-//nd:hostservice name=ServiceB permission=b
-type ServiceB interface {
- //nd:hostfunc
- MethodB(ctx context.Context) error
-}
-`
- Expect(os.WriteFile(filepath.Join(testDir, "a.go"), []byte(service1), 0600)).To(Succeed())
- Expect(os.WriteFile(filepath.Join(testDir, "b.go"), []byte(service2), 0600)).To(Succeed())
-
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "testpkg", "-v")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
- Expect(string(output)).To(ContainSubstring("Found 2 host service(s)"))
-
- Expect(filepath.Join(outputDir, "servicea_gen.go")).To(BeAnExistingFile())
- Expect(filepath.Join(outputDir, "serviceb_gen.go")).To(BeAnExistingFile())
- })
-
- It("generates only host code with -host-only flag", func() {
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "testpkg", "-host-only")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
-
- Expect(filepath.Join(outputDir, "test_gen.go")).To(BeAnExistingFile())
- Expect(filepath.Join(outputDir, "go")).ToNot(BeADirectory())
- })
-
- It("generates only client code with -plugin-only flag", func() {
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "main", "-plugin-only")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
-
- // Host code should not exist in output root
- entries, err := os.ReadDir(outputDir)
- Expect(err).ToNot(HaveOccurred())
-
- var genFiles []string
- for _, e := range entries {
- if e.Name() != "go" {
- genFiles = append(genFiles, e.Name())
- }
- }
- Expect(genFiles).To(BeEmpty(), "Expected no host code files, found: %v", genFiles)
-
- // Client code should exist in go/ subdirectory
- Expect(filepath.Join(outputDir, "go")).To(BeADirectory())
- Expect(filepath.Join(outputDir, "go", "nd_host_test.go")).To(BeAnExistingFile())
- })
-
- It("generates both host and client code by default", func() {
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "testpkg")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
-
- // Host code in output root
- Expect(filepath.Join(outputDir, "test_gen.go")).To(BeAnExistingFile())
-
- // Client code in go/ subdirectory
- Expect(filepath.Join(outputDir, "go")).To(BeADirectory())
- Expect(filepath.Join(outputDir, "go", "nd_host_test.go")).To(BeAnExistingFile())
- })
-
- It("rejects using both -host-only and -plugin-only together", func() {
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "testpkg", "-host-only", "-plugin-only")
- output, err := cmd.CombinedOutput()
- Expect(err).To(HaveOccurred())
- Expect(string(output)).To(ContainSubstring("-host-only and -plugin-only cannot be used together"))
- })
- })
-
- Describe("code generation", func() {
- DescribeTable("generates correct host and client output",
- func(serviceFile, hostExpectedFile, goClientExpectedFile, pyClientExpectedFile, rsClientExpectedFile string) {
- serviceCode := readTestdata(serviceFile)
- hostExpected := readTestdata(hostExpectedFile)
- goClientExpected := readTestdata(goClientExpectedFile)
- pyClientExpected := readTestdata(pyClientExpectedFile)
- rsClientExpected := readTestdata(rsClientExpectedFile)
-
- Expect(os.WriteFile(filepath.Join(testDir, "service.go"), []byte(serviceCode), 0600)).To(Succeed())
-
- // Generate host and all client code (Go, Python, Rust)
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "testpkg", "-go", "-python", "-rust")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
-
- // Verify host code
- entries, err := os.ReadDir(outputDir)
- Expect(err).ToNot(HaveOccurred())
-
- var hostFiles []string
- for _, e := range entries {
- if e.Name() != "go" && e.Name() != "python" && e.Name() != "rust" && !e.IsDir() {
- hostFiles = append(hostFiles, e.Name())
- }
- }
- Expect(hostFiles).To(HaveLen(1), "Expected exactly one host file, got: %v", hostFiles)
-
- hostActual, err := os.ReadFile(filepath.Join(outputDir, hostFiles[0]))
- Expect(err).ToNot(HaveOccurred())
-
- formattedHostActual, err := format.Source(hostActual)
- Expect(err).ToNot(HaveOccurred(), "Generated host code is not valid Go:\n%s", hostActual)
-
- formattedHostExpected, err := format.Source([]byte(hostExpected))
- Expect(err).ToNot(HaveOccurred(), "Expected host code is not valid Go")
-
- Expect(string(formattedHostActual)).To(Equal(string(formattedHostExpected)), "Host code mismatch")
-
- // Verify Go client code
- goDir := filepath.Join(outputDir, "go")
- goClientEntries, err := os.ReadDir(goDir)
- Expect(err).ToNot(HaveOccurred())
- Expect(goClientEntries).To(HaveLen(4), "Expected Go client file, stub file, doc.go, and go.mod")
-
- // Find the client file (not doc.go, go.mod, or stub)
- var goClientName string
- for _, entry := range goClientEntries {
- name := entry.Name()
- if name != "doc.go" && name != "go.mod" && !strings.HasSuffix(name, "_stub.go") {
- goClientName = name
- break
- }
- }
- Expect(goClientName).ToNot(BeEmpty(), "Expected to find Go client file")
-
- goClientActual, err := os.ReadFile(filepath.Join(goDir, goClientName))
- Expect(err).ToNot(HaveOccurred())
-
- formattedGoClientActual, err := format.Source(goClientActual)
- Expect(err).ToNot(HaveOccurred(), "Generated Go client code is not valid Go:\n%s", goClientActual)
-
- formattedGoClientExpected, err := format.Source([]byte(goClientExpected))
- Expect(err).ToNot(HaveOccurred(), "Expected Go client code is not valid Go")
-
- Expect(string(formattedGoClientActual)).To(Equal(string(formattedGoClientExpected)), "Go client code mismatch")
-
- // Verify Python client code
- pythonDir := filepath.Join(outputDir, "python")
- pyClientEntries, err := os.ReadDir(pythonDir)
- Expect(err).ToNot(HaveOccurred())
- Expect(pyClientEntries).To(HaveLen(1), "Expected exactly one Python client file")
-
- pyClientActual, err := os.ReadFile(filepath.Join(pythonDir, pyClientEntries[0].Name()))
- Expect(err).ToNot(HaveOccurred())
-
- Expect(string(pyClientActual)).To(Equal(pyClientExpected), "Python client code mismatch")
-
- // Verify Rust client code
- rustDir := filepath.Join(outputDir, "rust")
- rsClientEntries, err := os.ReadDir(rustDir)
- Expect(err).ToNot(HaveOccurred())
- Expect(rsClientEntries).To(HaveLen(2), "Expected Rust client file and lib.rs")
-
- // Find the client file (not lib.rs)
- var rsClientName string
- for _, entry := range rsClientEntries {
- if entry.Name() != "lib.rs" {
- rsClientName = entry.Name()
- break
- }
- }
- Expect(rsClientName).ToNot(BeEmpty(), "Expected to find Rust client file")
-
- rsClientActual, err := os.ReadFile(filepath.Join(rustDir, rsClientName))
- Expect(err).ToNot(HaveOccurred())
-
- Expect(string(rsClientActual)).To(Equal(rsClientExpected), "Rust client code mismatch")
- },
-
- Entry("simple string params",
- "echo_service.go", "echo_expected.go", "echo_client_expected.go", "echo_client_expected.py", "echo_client_expected.rs"),
-
- Entry("multiple simple params (int32)",
- "math_service.go", "math_expected.go", "math_client_expected.go", "math_client_expected.py", "math_client_expected.rs"),
-
- Entry("struct param with request type",
- "store_service.go", "store_expected.go", "store_client_expected.go", "store_client_expected.py", "store_client_expected.rs"),
-
- Entry("mixed simple and complex params",
- "list_service.go", "list_expected.go", "list_client_expected.go", "list_client_expected.py", "list_client_expected.rs"),
-
- Entry("method without error",
- "counter_service.go", "counter_expected.go", "counter_client_expected.go", "counter_client_expected.py", "counter_client_expected.rs"),
-
- Entry("no params, error only",
- "ping_service.go", "ping_expected.go", "ping_client_expected.go", "ping_client_expected.py", "ping_client_expected.rs"),
-
- Entry("map and interface types",
- "meta_service.go", "meta_expected.go", "meta_client_expected.go", "meta_client_expected.py", "meta_client_expected.rs"),
-
- Entry("pointer types",
- "users_service.go", "users_expected.go", "users_client_expected.go", "users_client_expected.py", "users_client_expected.rs"),
-
- Entry("multiple returns",
- "search_service.go", "search_expected.go", "search_client_expected.go", "search_client_expected.py", "search_client_expected.rs"),
-
- Entry("bytes",
- "codec_service.go", "codec_expected.go", "codec_client_expected.go", "codec_client_expected.py", "codec_client_expected.rs"),
- )
-
- It("generates compilable host code for comprehensive service", func() {
- serviceCode := readTestdata("comprehensive_service.go")
-
- Expect(os.WriteFile(filepath.Join(testDir, "service.go"), []byte(serviceCode), 0600)).To(Succeed())
-
- // Create go.mod
- goMod := "module testpkg\n\ngo 1.23\n\nrequire github.com/extism/go-sdk v1.7.1\n"
- Expect(os.WriteFile(filepath.Join(testDir, "go.mod"), []byte(goMod), 0600)).To(Succeed())
-
- // Generate host code only
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", testDir, "-package", "testpkg", "-host-only")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Generation failed: %s", output)
-
- // Tidy dependencies
- goGetCmd := exec.Command("go", "mod", "tidy")
- goGetCmd.Dir = testDir
- goGetOutput, err := goGetCmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "go mod tidy failed: %s", goGetOutput)
-
- // Build
- buildCmd := exec.Command("go", "build", ".")
- buildCmd.Dir = testDir
- buildOutput, err := buildCmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Build failed: %s", buildOutput)
- })
-
- It("generates compilable client code for comprehensive service", func() {
- serviceCode := readTestdata("comprehensive_service.go")
-
- Expect(os.WriteFile(filepath.Join(testDir, "service.go"), []byte(serviceCode), 0600)).To(Succeed())
-
- // Generate client code only to a separate client directory
- clientDir := filepath.Join(outputDir, "client")
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", clientDir, "-package", "main", "-plugin-only")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Generation failed: %s", output)
-
- // Read generated client code
- goDir := filepath.Join(clientDir, "go")
- entries, err := os.ReadDir(goDir)
- Expect(err).ToNot(HaveOccurred())
- Expect(entries).To(HaveLen(4), "Expected Go client file, stub file, doc.go, and go.mod")
-
- // Find the client file (not doc.go, go.mod, or stub)
- var clientFileName string
- for _, entry := range entries {
- name := entry.Name()
- if name != "doc.go" && name != "go.mod" && !strings.HasSuffix(name, "_stub.go") {
- clientFileName = name
- break
- }
- }
- Expect(clientFileName).ToNot(BeEmpty(), "Expected to find Go client file")
-
- content, err := os.ReadFile(filepath.Join(goDir, clientFileName))
- Expect(err).ToNot(HaveOccurred())
-
- // Verify key expected content first
- contentStr := string(content)
- // Should have wasmimport declarations for all methods
- Expect(contentStr).To(ContainSubstring("//go:wasmimport extism:host/user comprehensive_simpleparams"))
- Expect(contentStr).To(ContainSubstring("//go:wasmimport extism:host/user comprehensive_structparam"))
- Expect(contentStr).To(ContainSubstring("//go:wasmimport extism:host/user comprehensive_noerror"))
- Expect(contentStr).To(ContainSubstring("//go:wasmimport extism:host/user comprehensive_noparams"))
- Expect(contentStr).To(ContainSubstring("//go:wasmimport extism:host/user comprehensive_noparamsnoreturns"))
-
- // Should have response types for methods with complex returns
- Expect(contentStr).To(ContainSubstring("type ComprehensiveSimpleParamsResponse struct"))
- Expect(contentStr).To(ContainSubstring("type ComprehensiveMultipleReturnsResponse struct"))
-
- // Should have wrapper functions
- Expect(contentStr).To(ContainSubstring("func ComprehensiveSimpleParams("))
- Expect(contentStr).To(ContainSubstring("func ComprehensiveNoParams()"))
- Expect(contentStr).To(ContainSubstring("func ComprehensiveNoParamsNoReturns()"))
-
- // The generated code is now package ndhost, so we need to import it
- // Create a plugin directory with proper import structure
- pluginDir := filepath.Join(clientDir, "plugin")
- Expect(os.MkdirAll(pluginDir, 0750)).To(Succeed())
-
- // Create go.mod for the plugin that imports the generated library
- goMod := fmt.Sprintf(`module testplugin
-
-go 1.24
-
-require github.com/navidrome/navidrome/plugins/host/go v0.0.0
-
-replace github.com/navidrome/navidrome/plugins/host/go => %s
-`, goDir)
- Expect(os.WriteFile(filepath.Join(pluginDir, "go.mod"), []byte(goMod), 0600)).To(Succeed())
-
- // Add a simple main function that imports and uses the ndhost package
- mainGo := `package main
-
-import ndhost "github.com/navidrome/navidrome/plugins/host/go"
-
-func main() {}
-
-// Use some functions to ensure import is not unused
-var _ = ndhost.ComprehensiveNoParams
-`
- Expect(os.WriteFile(filepath.Join(pluginDir, "main.go"), []byte(mainGo), 0600)).To(Succeed())
-
- // Tidy dependencies for the generated go library
- goTidyLibCmd := exec.Command("go", "mod", "tidy")
- goTidyLibCmd.Dir = goDir
- goTidyLibOutput, err := goTidyLibCmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "go mod tidy (library) failed: %s", goTidyLibOutput)
-
- // Tidy dependencies for the plugin
- goTidyCmd := exec.Command("go", "mod", "tidy")
- goTidyCmd.Dir = pluginDir
- goTidyOutput, err := goTidyCmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "go mod tidy (plugin) failed: %s", goTidyOutput)
-
- // Build as WASM plugin - this validates the client code compiles correctly
- buildCmd := exec.Command("go", "build", "-buildmode=c-shared", "-o", "plugin.wasm", ".")
- buildCmd.Dir = pluginDir
- buildCmd.Env = append(os.Environ(), "GOOS=wasip1", "GOARCH=wasm")
- buildOutput, err := buildCmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "WASM build failed: %s", buildOutput)
-
- // Verify .wasm file was created
- Expect(filepath.Join(pluginDir, "plugin.wasm")).To(BeAnExistingFile())
- })
-
- It("generates Python client code with -python flag", func() {
- serviceCode := `package testpkg
-
-import "context"
-
-//nd:hostservice name=Test permission=test
-type TestService interface {
- //nd:hostfunc
- DoAction(ctx context.Context, input string) (output string, err error)
-}
-`
- Expect(os.WriteFile(filepath.Join(testDir, "service.go"), []byte(serviceCode), 0600)).To(Succeed())
-
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "testpkg", "-python", "-plugin-only")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
-
- // Verify Python client code exists
- pythonDir := filepath.Join(outputDir, "python")
- Expect(pythonDir).To(BeADirectory())
-
- pythonFile := filepath.Join(pythonDir, "nd_host_test.py")
- Expect(pythonFile).To(BeAnExistingFile())
-
- content, err := os.ReadFile(pythonFile)
- Expect(err).ToNot(HaveOccurred())
-
- contentStr := string(content)
- Expect(contentStr).To(ContainSubstring("Code generated by hostgen. DO NOT EDIT."))
- Expect(contentStr).To(ContainSubstring("class HostFunctionError(Exception):"))
- Expect(contentStr).To(ContainSubstring(`@extism.import_fn("extism:host/user", "test_doaction")`))
- Expect(contentStr).To(ContainSubstring("def test_do_action(input: str) -> str:"))
- })
-
- It("generates both Go and Python client code with -go -python flags", func() {
- serviceCode := `package testpkg
-
-import "context"
-
-//nd:hostservice name=Test permission=test
-type TestService interface {
- //nd:hostfunc
- DoAction(ctx context.Context, input string) (output string, err error)
-}
-`
- Expect(os.WriteFile(filepath.Join(testDir, "service.go"), []byte(serviceCode), 0600)).To(Succeed())
-
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "testpkg", "-go", "-python", "-plugin-only")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
-
- // Verify both Go and Python client code exist
- goDir := filepath.Join(outputDir, "go")
- Expect(goDir).To(BeADirectory())
- Expect(filepath.Join(goDir, "nd_host_test.go")).To(BeAnExistingFile())
-
- pythonDir := filepath.Join(outputDir, "python")
- Expect(pythonDir).To(BeADirectory())
- Expect(filepath.Join(pythonDir, "nd_host_test.py")).To(BeAnExistingFile())
- })
-
- It("generates Python code with dataclass for multi-value returns", func() {
- serviceCode := `package testpkg
-
-import "context"
-
-//nd:hostservice name=Cache permission=cache
-type CacheService interface {
- //nd:hostfunc
- GetString(ctx context.Context, key string) (value string, exists bool, err error)
-}
-`
- Expect(os.WriteFile(filepath.Join(testDir, "service.go"), []byte(serviceCode), 0600)).To(Succeed())
-
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "testpkg", "-python", "-plugin-only")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
-
- content, err := os.ReadFile(filepath.Join(outputDir, "python", "nd_host_cache.py"))
- Expect(err).ToNot(HaveOccurred())
-
- contentStr := string(content)
- Expect(contentStr).To(ContainSubstring("@dataclass"))
- Expect(contentStr).To(ContainSubstring("class CacheGetStringResult:"))
- Expect(contentStr).To(ContainSubstring("value: str"))
- Expect(contentStr).To(ContainSubstring("exists: bool"))
- Expect(contentStr).To(ContainSubstring("def cache_get_string(key: str) -> CacheGetStringResult:"))
- })
-
- It("generates Python code for methods with no parameters", func() {
- serviceCode := `package testpkg
-
-import "context"
-
-//nd:hostservice name=Test permission=test
-type TestService interface {
- //nd:hostfunc
- Ping(ctx context.Context) (status string, err error)
-}
-`
- Expect(os.WriteFile(filepath.Join(testDir, "service.go"), []byte(serviceCode), 0600)).To(Succeed())
-
- cmd := exec.Command(hostgenBin, "-input", testDir, "-output", outputDir, "-package", "testpkg", "-python", "-plugin-only")
- output, err := cmd.CombinedOutput()
- Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output)
-
- content, err := os.ReadFile(filepath.Join(outputDir, "python", "nd_host_test.py"))
- Expect(err).ToNot(HaveOccurred())
-
- contentStr := string(content)
- Expect(contentStr).To(ContainSubstring("def test_ping() -> str:"))
- Expect(contentStr).To(ContainSubstring(`request_bytes = b"{}"`))
- })
- })
-})
-
-var testdataDir string
-
-func readTestdata(filename string) string {
- content, err := os.ReadFile(filepath.Join(testdataDir, filename))
- Expect(err).ToNot(HaveOccurred(), "Failed to read testdata file: %s", filename)
- return string(content)
-}
-
-func mustGetWd(t FullGinkgoTInterface) string {
- dir, err := os.Getwd()
- if err != nil {
- t.Fatal(err)
- }
- for {
- if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
- return dir
- }
- parent := filepath.Dir(dir)
- if parent == dir {
- t.Fatal("could not find project root")
- }
- dir = parent
- }
-}
diff --git a/plugins/cmd/hostgen/internal/generator.go b/plugins/cmd/hostgen/internal/generator.go
deleted file mode 100644
index a7ad31ab3..000000000
--- a/plugins/cmd/hostgen/internal/generator.go
+++ /dev/null
@@ -1,301 +0,0 @@
-package internal
-
-import (
- "bytes"
- "embed"
- "fmt"
- "strings"
- "text/template"
-)
-
-//go:embed templates/*.tmpl
-var templatesFS embed.FS
-
-// hostFuncMap returns the template functions for host code generation.
-func hostFuncMap(svc Service) template.FuncMap {
- return template.FuncMap{
- "lower": strings.ToLower,
- "title": strings.Title,
- "exportName": func(m Method) string { return m.FunctionName(svc.ExportPrefix()) },
- "requestType": func(m Method) string { return m.RequestTypeName(svc.Name) },
- "responseType": func(m Method) string { return m.ResponseTypeName(svc.Name) },
- }
-}
-
-// clientFuncMap returns the template functions for client code generation.
-func clientFuncMap(svc Service) template.FuncMap {
- return template.FuncMap{
- "lower": strings.ToLower,
- "title": strings.Title,
- "exportName": func(m Method) string { return m.FunctionName(svc.ExportPrefix()) },
- "requestType": func(m Method) string { return m.RequestTypeName(svc.Name) },
- "responseType": func(m Method) string { return m.ResponseTypeName(svc.Name) },
- "formatDoc": formatDoc,
- }
-}
-
-// pythonFuncMap returns the template functions for Python client code generation.
-func pythonFuncMap(svc Service) template.FuncMap {
- return template.FuncMap{
- "lower": strings.ToLower,
- "exportName": func(m Method) string { return m.FunctionName(svc.ExportPrefix()) },
- "pythonFunc": func(m Method) string { return m.PythonFunctionName(svc.ExportPrefix()) },
- "pythonResultType": func(m Method) string { return m.PythonResultTypeName(svc.Name) },
- "pythonDefault": pythonDefaultValue,
- }
-}
-
-// GenerateHost generates the host function wrapper code for a service.
-func GenerateHost(svc Service, pkgName string) ([]byte, error) {
- tmplContent, err := templatesFS.ReadFile("templates/host.go.tmpl")
- if err != nil {
- return nil, fmt.Errorf("reading host template: %w", err)
- }
-
- tmpl, err := template.New("host").Funcs(hostFuncMap(svc)).Parse(string(tmplContent))
- if err != nil {
- return nil, fmt.Errorf("parsing template: %w", err)
- }
-
- data := templateData{
- Package: pkgName,
- Service: svc,
- }
-
- var buf bytes.Buffer
- if err := tmpl.Execute(&buf, data); err != nil {
- return nil, fmt.Errorf("executing template: %w", err)
- }
-
- return buf.Bytes(), nil
-}
-
-// GenerateClientGo generates client wrapper code for plugins to call host functions.
-func GenerateClientGo(svc Service) ([]byte, error) {
- tmplContent, err := templatesFS.ReadFile("templates/client.go.tmpl")
- if err != nil {
- return nil, fmt.Errorf("reading client template: %w", err)
- }
-
- tmpl, err := template.New("client").Funcs(clientFuncMap(svc)).Parse(string(tmplContent))
- if err != nil {
- return nil, fmt.Errorf("parsing template: %w", err)
- }
-
- data := templateData{
- Service: svc,
- }
-
- var buf bytes.Buffer
- if err := tmpl.Execute(&buf, data); err != nil {
- return nil, fmt.Errorf("executing template: %w", err)
- }
-
- return buf.Bytes(), nil
-}
-
-// GenerateClientGoStub generates stub code for non-WASM platforms.
-// These stubs provide type definitions and function signatures for IDE support,
-// but panic at runtime since host functions are only available in WASM plugins.
-func GenerateClientGoStub(svc Service) ([]byte, error) {
- tmplContent, err := templatesFS.ReadFile("templates/client_stub.go.tmpl")
- if err != nil {
- return nil, fmt.Errorf("reading client stub template: %w", err)
- }
-
- tmpl, err := template.New("client_stub").Funcs(clientFuncMap(svc)).Parse(string(tmplContent))
- if err != nil {
- return nil, fmt.Errorf("parsing template: %w", err)
- }
-
- data := templateData{
- Service: svc,
- }
-
- var buf bytes.Buffer
- if err := tmpl.Execute(&buf, data); err != nil {
- return nil, fmt.Errorf("executing template: %w", err)
- }
-
- return buf.Bytes(), nil
-}
-
-type templateData struct {
- Package string
- Service Service
-}
-
-// formatDoc formats a documentation string for Go comments.
-// It prefixes each line with "// " and trims trailing whitespace.
-func formatDoc(doc string) string {
- if doc == "" {
- return ""
- }
- lines := strings.Split(strings.TrimSpace(doc), "\n")
- var result []string
- for _, line := range lines {
- result = append(result, "// "+strings.TrimRight(line, " \t"))
- }
- return strings.Join(result, "\n")
-}
-
-// GenerateClientPython generates Python client wrapper code for plugins.
-func GenerateClientPython(svc Service) ([]byte, error) {
- tmplContent, err := templatesFS.ReadFile("templates/client.py.tmpl")
- if err != nil {
- return nil, fmt.Errorf("reading Python client template: %w", err)
- }
-
- tmpl, err := template.New("client_py").Funcs(pythonFuncMap(svc)).Parse(string(tmplContent))
- if err != nil {
- return nil, fmt.Errorf("parsing template: %w", err)
- }
-
- data := templateData{
- Service: svc,
- }
-
- var buf bytes.Buffer
- if err := tmpl.Execute(&buf, data); err != nil {
- return nil, fmt.Errorf("executing template: %w", err)
- }
-
- return buf.Bytes(), nil
-}
-
-// pythonDefaultValue returns a Python default value for response.get() calls.
-func pythonDefaultValue(p Param) string {
- switch p.Type {
- case "string":
- return `, ""`
- case "int", "int32", "int64":
- return ", 0"
- case "float32", "float64":
- return ", 0.0"
- case "bool":
- return ", False"
- case "[]byte":
- return ", b\"\""
- default:
- return ", None"
- }
-}
-
-// rustFuncMap returns the template functions for Rust client code generation.
-func rustFuncMap(svc Service) template.FuncMap {
- knownStructs := svc.KnownStructs()
- return template.FuncMap{
- "lower": strings.ToLower,
- "exportName": func(m Method) string { return m.FunctionName(svc.ExportPrefix()) },
- "requestType": func(m Method) string { return m.RequestTypeName(svc.Name) },
- "responseType": func(m Method) string { return m.ResponseTypeName(svc.Name) },
- "rustFunc": func(m Method) string { return m.RustFunctionName(svc.ExportPrefix()) },
- "rustDocComment": RustDocComment,
- "rustType": func(p Param) string { return p.RustTypeWithStructs(knownStructs) },
- "rustParamType": func(p Param) string { return p.RustParamTypeWithStructs(knownStructs) },
- "fieldRustType": func(f FieldDef) string { return f.RustType(knownStructs) },
- }
-}
-
-// GenerateClientRust generates Rust client wrapper code for plugins.
-func GenerateClientRust(svc Service) ([]byte, error) {
- tmplContent, err := templatesFS.ReadFile("templates/client.rs.tmpl")
- if err != nil {
- return nil, fmt.Errorf("reading Rust client template: %w", err)
- }
-
- tmpl, err := template.New("client_rs").Funcs(rustFuncMap(svc)).Parse(string(tmplContent))
- if err != nil {
- return nil, fmt.Errorf("parsing template: %w", err)
- }
-
- data := templateData{
- Service: svc,
- }
-
- var buf bytes.Buffer
- if err := tmpl.Execute(&buf, data); err != nil {
- return nil, fmt.Errorf("executing template: %w", err)
- }
-
- return buf.Bytes(), nil
-}
-
-// firstLine returns the first line of a multi-line string, with the first word removed.
-func firstLine(s string) string {
- line := s
- if idx := strings.Index(s, "\n"); idx >= 0 {
- line = s[:idx]
- }
- // Remove the first word (service name like "ArtworkService")
- if idx := strings.Index(line, " "); idx >= 0 {
- line = line[idx+1:]
- }
- return line
-}
-
-// GenerateRustLib generates the lib.rs file that exposes all service modules.
-func GenerateRustLib(services []Service) ([]byte, error) {
- tmplContent, err := templatesFS.ReadFile("templates/lib.rs.tmpl")
- if err != nil {
- return nil, fmt.Errorf("reading Rust lib template: %w", err)
- }
-
- tmpl, err := template.New("lib_rs").Funcs(template.FuncMap{
- "lower": strings.ToLower,
- "firstLine": firstLine,
- }).Parse(string(tmplContent))
- if err != nil {
- return nil, fmt.Errorf("parsing template: %w", err)
- }
-
- data := struct {
- Services []Service
- }{
- Services: services,
- }
-
- var buf bytes.Buffer
- if err := tmpl.Execute(&buf, data); err != nil {
- return nil, fmt.Errorf("executing template: %w", err)
- }
-
- return buf.Bytes(), nil
-}
-
-// GenerateGoDoc generates the doc.go file that provides package documentation.
-func GenerateGoDoc(services []Service) ([]byte, error) {
- tmplContent, err := templatesFS.ReadFile("templates/doc.go.tmpl")
- if err != nil {
- return nil, fmt.Errorf("reading Go doc template: %w", err)
- }
-
- tmpl, err := template.New("doc_go").Funcs(template.FuncMap{
- "firstLine": firstLine,
- }).Parse(string(tmplContent))
- if err != nil {
- return nil, fmt.Errorf("parsing template: %w", err)
- }
-
- data := struct {
- Services []Service
- }{
- Services: services,
- }
-
- var buf bytes.Buffer
- if err := tmpl.Execute(&buf, data); err != nil {
- return nil, fmt.Errorf("executing template: %w", err)
- }
-
- return buf.Bytes(), nil
-}
-
-// GenerateGoMod generates the go.mod file for the Go client library.
-func GenerateGoMod() ([]byte, error) {
- tmplContent, err := templatesFS.ReadFile("templates/go.mod.tmpl")
- if err != nil {
- return nil, fmt.Errorf("reading go.mod template: %w", err)
- }
- return tmplContent, nil
-}
diff --git a/plugins/cmd/hostgen/internal/generator_test.go b/plugins/cmd/hostgen/internal/generator_test.go
deleted file mode 100644
index c2aebe49c..000000000
--- a/plugins/cmd/hostgen/internal/generator_test.go
+++ /dev/null
@@ -1,679 +0,0 @@
-package internal
-
-import (
- "go/format"
- "os"
-
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
-)
-
-var _ = Describe("Generator", func() {
- Describe("GenerateHost", func() {
- It("should generate valid Go code for a simple service with strings", func() {
- // All methods use JSON request/response types
- svc := Service{
- Name: "SubsonicAPI",
- Permission: "subsonicapi",
- Interface: "SubsonicAPIService",
- Methods: []Method{
- {
- Name: "Call",
- HasError: true,
- Params: []Param{NewParam("uri", "string")},
- Returns: []Param{NewParam("response", "string")},
- },
- },
- }
-
- code, err := GenerateHost(svc, "host")
- Expect(err).NotTo(HaveOccurred())
-
- // Verify the code is valid Go
- _, err = format.Source(code)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
-
- // Check for generated header
- Expect(codeStr).To(ContainSubstring("Code generated by hostgen. DO NOT EDIT."))
-
- // Check for package declaration
- Expect(codeStr).To(ContainSubstring("package host"))
-
- // All methods now use request type for JSON protocol
- Expect(codeStr).To(ContainSubstring("type SubsonicAPICallRequest struct"))
- Expect(codeStr).To(ContainSubstring(`Uri string `))
-
- // Response type with error handling
- Expect(codeStr).To(ContainSubstring("type SubsonicAPICallResponse struct"))
- Expect(codeStr).To(ContainSubstring(`Response string `))
- Expect(codeStr).To(ContainSubstring(`Error string `))
-
- // Check for registration function
- Expect(codeStr).To(ContainSubstring("func RegisterSubsonicAPIHostFunctions(service SubsonicAPIService)"))
-
- // Check for host function name
- Expect(codeStr).To(ContainSubstring(`"subsonicapi_call"`))
-
- // Check for JSON unmarshal (all methods use JSON now)
- Expect(codeStr).To(ContainSubstring("json.Unmarshal"))
- })
-
- It("should generate code for methods without parameters", func() {
- svc := Service{
- Name: "Test",
- Permission: "test",
- Interface: "TestService",
- Methods: []Method{
- {
- Name: "NoParams",
- HasError: true,
- Returns: []Param{NewParam("result", "string")},
- },
- },
- }
-
- code, err := GenerateHost(svc, "host")
- Expect(err).NotTo(HaveOccurred())
-
- _, err = format.Source(code)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
- // Methods without params don't need a request type - no params to serialize
- Expect(codeStr).NotTo(ContainSubstring("type TestNoParamsRequest struct"))
- // But still uses PTR input/output for consistency
- Expect(codeStr).To(MatchRegexp(`\[\]extism\.ValueType\{extism\.ValueTypePTR\},\s*\[\]extism\.ValueType\{extism\.ValueTypePTR\}`))
- })
-
- It("should generate code for methods without return values", func() {
- svc := Service{
- Name: "Test",
- Permission: "test",
- Interface: "TestService",
- Methods: []Method{
- {
- Name: "NoReturn",
- HasError: true,
- Params: []Param{NewParam("input", "string")},
- },
- },
- }
-
- code, err := GenerateHost(svc, "host")
- Expect(err).NotTo(HaveOccurred())
-
- _, err = format.Source(code)
- Expect(err).NotTo(HaveOccurred())
- })
-
- It("should generate code for multiple methods", func() {
- svc := Service{
- Name: "Scheduler",
- Permission: "scheduler",
- Interface: "SchedulerService",
- Methods: []Method{
- {
- Name: "ScheduleRecurring",
- HasError: true,
- Params: []Param{NewParam("cronExpression", "string")},
- Returns: []Param{NewParam("scheduleID", "string")},
- },
- {
- Name: "ScheduleOneTime",
- HasError: true,
- Params: []Param{NewParam("delaySeconds", "int32")},
- Returns: []Param{NewParam("scheduleID", "string")},
- },
- {
- Name: "CancelSchedule",
- HasError: true,
- Params: []Param{NewParam("scheduleID", "string")},
- Returns: []Param{NewParam("canceled", "bool")},
- },
- },
- }
-
- code, err := GenerateHost(svc, "host")
- Expect(err).NotTo(HaveOccurred())
-
- _, err = format.Source(code)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
- Expect(codeStr).To(ContainSubstring("scheduler_schedulerecurring"))
- Expect(codeStr).To(ContainSubstring("scheduler_scheduleonetime"))
- Expect(codeStr).To(ContainSubstring("scheduler_cancelschedule"))
- })
-
- It("should handle multiple simple parameters with JSON", func() {
- // All params use JSON - single PTR input
- svc := Service{
- Name: "Test",
- Permission: "test",
- Interface: "TestService",
- Methods: []Method{
- {
- Name: "MultiParam",
- HasError: true,
- Params: []Param{
- NewParam("name", "string"),
- NewParam("count", "int32"),
- NewParam("enabled", "bool"),
- },
- Returns: []Param{NewParam("result", "string")},
- },
- },
- }
-
- code, err := GenerateHost(svc, "host")
- Expect(err).NotTo(HaveOccurred())
-
- _, err = format.Source(code)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
- // All methods use request type with JSON protocol
- Expect(codeStr).To(ContainSubstring("type TestMultiParamRequest struct"))
- // Check for JSON unmarshal (all methods use JSON now)
- Expect(codeStr).To(ContainSubstring("json.Unmarshal"))
- // Check that input/output ValueType both use PTR (JSON)
- Expect(codeStr).To(MatchRegexp(`\[\]extism\.ValueType\{extism\.ValueTypePTR\},\s*\[\]extism\.ValueType\{extism\.ValueTypePTR\}`))
- })
-
- It("should use single PTR for mixed simple and complex params", func() {
- // When any param needs JSON, all are bundled into one request struct
- svc := Service{
- Name: "Test",
- Permission: "test",
- Interface: "TestService",
- Methods: []Method{
- {
- Name: "MixedParam",
- HasError: true,
- Params: []Param{
- NewParam("id", "string"), // simple (PTR for string)
- NewParam("tags", "[]string"), // complex - needs JSON
- },
- Returns: []Param{NewParam("count", "int32")}, // simple
- },
- },
- }
-
- code, err := GenerateHost(svc, "host")
- Expect(err).NotTo(HaveOccurred())
-
- _, err = format.Source(code)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
- // Request type IS needed because of complex param
- Expect(codeStr).To(ContainSubstring("type TestMixedParamRequest struct"))
- // When using request type, only ONE PTR for input (the JSON request)
- Expect(codeStr).To(MatchRegexp(`\[\]extism\.ValueType\{extism\.ValueTypePTR\},\s*\[\]extism\.ValueType\{extism\.ValueTypePTR\}`))
- })
-
- It("should generate proper JSON tags for complex types", func() {
- // Complex types (structs, slices, maps) need JSON serialization
- svc := Service{
- Name: "Test",
- Permission: "test",
- Interface: "TestService",
- Methods: []Method{
- {
- Name: "Method",
- HasError: true,
- Params: []Param{NewParam("inputValue", "[]string")}, // slice needs JSON
- Returns: []Param{NewParam("outputValue", "map[string]string")}, // map needs JSON
- },
- },
- }
-
- code, err := GenerateHost(svc, "host")
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
- // Complex params need request type with JSON tags
- Expect(codeStr).To(ContainSubstring(`json:"inputValue"`))
- // Complex returns need response type with JSON tags
- Expect(codeStr).To(ContainSubstring(`json:"outputValue,omitempty"`))
- })
-
- It("should include required imports", func() {
- // Service with complex types needs JSON import
- svc := Service{
- Name: "Test",
- Permission: "test",
- Interface: "TestService",
- Methods: []Method{
- {
- Name: "Method",
- HasError: true,
- Params: []Param{NewParam("data", "MyStruct")}, // struct needs JSON
- },
- },
- }
-
- code, err := GenerateHost(svc, "host")
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
- Expect(codeStr).To(ContainSubstring(`"context"`))
- Expect(codeStr).To(ContainSubstring(`"encoding/json"`))
- Expect(codeStr).To(ContainSubstring(`extism "github.com/extism/go-sdk"`))
- })
-
- It("should always include json import for JSON protocol", func() {
- // All services use JSON protocol, so json import is always needed
- svc := Service{
- Name: "Test",
- Permission: "test",
- Interface: "TestService",
- Methods: []Method{
- {
- Name: "Method",
- Params: []Param{NewParam("count", "int32")},
- Returns: []Param{NewParam("result", "int64")},
- },
- },
- }
-
- code, err := GenerateHost(svc, "host")
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
- Expect(codeStr).To(ContainSubstring(`"context"`))
- Expect(codeStr).To(ContainSubstring(`"encoding/json"`))
- Expect(codeStr).To(ContainSubstring(`extism "github.com/extism/go-sdk"`))
- })
- })
-
- Describe("toJSONName", func() {
- It("should convert to camelCase matching Rust serde behavior", func() {
- Expect(toJSONName("InputValue")).To(Equal("inputValue"))
- Expect(toJSONName("URI")).To(Equal("uri"))
- Expect(toJSONName("id")).To(Equal("id"))
- Expect(toJSONName("ID")).To(Equal("id"))
- Expect(toJSONName("ConnectionID")).To(Equal("connectionId"))
- Expect(toJSONName("NewConnectionID")).To(Equal("newConnectionId"))
- Expect(toJSONName("XMLHTTPRequest")).To(Equal("xmlhttpRequest"))
- Expect(toJSONName("APIKey")).To(Equal("apiKey"))
- })
-
- It("should handle empty string", func() {
- Expect(toJSONName("")).To(Equal(""))
- })
- })
-
- Describe("NewParam", func() {
- It("should create param with auto-generated JSON name", func() {
- p := NewParam("MyParam", "string")
- Expect(p.Name).To(Equal("MyParam"))
- Expect(p.Type).To(Equal("string"))
- Expect(p.JSONName).To(Equal("myParam"))
- })
- })
-
- Describe("Python type and name helpers", func() {
- Describe("ToPythonType", func() {
- It("should map Go types to Python types", func() {
- Expect(ToPythonType("string")).To(Equal("str"))
- Expect(ToPythonType("int")).To(Equal("int"))
- Expect(ToPythonType("int32")).To(Equal("int"))
- Expect(ToPythonType("int64")).To(Equal("int"))
- Expect(ToPythonType("float32")).To(Equal("float"))
- Expect(ToPythonType("float64")).To(Equal("float"))
- Expect(ToPythonType("bool")).To(Equal("bool"))
- Expect(ToPythonType("[]byte")).To(Equal("bytes"))
- Expect(ToPythonType("unknown")).To(Equal("Any"))
- })
- })
-
- Describe("ToSnakeCase", func() {
- It("should convert PascalCase to snake_case", func() {
- Expect(ToSnakeCase("ScheduleRecurring")).To(Equal("schedule_recurring"))
- Expect(ToSnakeCase("GetString")).To(Equal("get_string"))
- Expect(ToSnakeCase("simple")).To(Equal("simple"))
- })
-
- It("should handle acronyms correctly", func() {
- Expect(ToSnakeCase("ID")).To(Equal("id"))
- Expect(ToSnakeCase("ScheduleID")).To(Equal("schedule_id"))
- Expect(ToSnakeCase("NewScheduleID")).To(Equal("new_schedule_id"))
- Expect(ToSnakeCase("XMLParser")).To(Equal("xml_parser"))
- Expect(ToSnakeCase("GetHTTPResponse")).To(Equal("get_http_response"))
- })
- })
-
- Describe("Method.PythonFunctionName", func() {
- It("should generate snake_case function name with service prefix", func() {
- m := Method{Name: "GetString"}
- Expect(m.PythonFunctionName("cache")).To(Equal("cache_get_string"))
- })
- })
-
- Describe("Param.PythonType", func() {
- It("should return Python type for parameter", func() {
- p := NewParam("value", "string")
- Expect(p.PythonType()).To(Equal("str"))
- })
- })
-
- Describe("Param.PythonName", func() {
- It("should return snake_case name for parameter", func() {
- p := NewParam("ttlSeconds", "int64")
- Expect(p.PythonName()).To(Equal("ttl_seconds"))
- })
- })
- })
-
- Describe("GenerateClientPython", func() {
- It("should generate valid Python code for a simple service", func() {
- svc := Service{
- Name: "SubsonicAPI",
- Permission: "subsonicapi",
- Interface: "SubsonicAPIService",
- Methods: []Method{
- {
- Name: "Call",
- HasError: true,
- Params: []Param{NewParam("uri", "string")},
- Returns: []Param{NewParam("responseJSON", "string")},
- },
- },
- }
-
- code, err := GenerateClientPython(svc)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
-
- // Check for generated header
- Expect(codeStr).To(ContainSubstring("Code generated by hostgen. DO NOT EDIT."))
-
- // Check for imports
- Expect(codeStr).To(ContainSubstring("from dataclasses import dataclass"))
- Expect(codeStr).To(ContainSubstring("import extism"))
- Expect(codeStr).To(ContainSubstring("import json"))
-
- // Check for exception class
- Expect(codeStr).To(ContainSubstring("class HostFunctionError(Exception):"))
-
- // Check for raw import function
- Expect(codeStr).To(ContainSubstring(`@extism.import_fn("extism:host/user", "subsonicapi_call")`))
- Expect(codeStr).To(ContainSubstring("def _subsonicapi_call(offset: int) -> int:"))
-
- // Check for wrapper function with type hints
- Expect(codeStr).To(ContainSubstring("def subsonicapi_call(uri: str) -> str:"))
-
- // Check for error handling
- Expect(codeStr).To(ContainSubstring("raise HostFunctionError(response["))
- })
-
- It("should generate dataclass for multi-value returns", func() {
- svc := Service{
- Name: "Cache",
- Permission: "cache",
- Interface: "CacheService",
- Methods: []Method{
- {
- Name: "GetString",
- HasError: true,
- Params: []Param{NewParam("key", "string")},
- Returns: []Param{
- NewParam("value", "string"),
- NewParam("exists", "bool"),
- },
- },
- },
- }
-
- code, err := GenerateClientPython(svc)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
-
- // Check for dataclass
- Expect(codeStr).To(ContainSubstring("@dataclass"))
- Expect(codeStr).To(ContainSubstring("class CacheGetStringResult:"))
- Expect(codeStr).To(ContainSubstring("value: str"))
- Expect(codeStr).To(ContainSubstring("exists: bool"))
-
- // Check that function returns dataclass
- Expect(codeStr).To(ContainSubstring("def cache_get_string(key: str) -> CacheGetStringResult:"))
- Expect(codeStr).To(ContainSubstring("return CacheGetStringResult("))
- })
-
- It("should handle methods with no parameters", func() {
- svc := Service{
- Name: "Test",
- Permission: "test",
- Interface: "TestService",
- Methods: []Method{
- {
- Name: "NoParams",
- HasError: true,
- Returns: []Param{NewParam("result", "string")},
- },
- },
- }
-
- code, err := GenerateClientPython(svc)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
-
- // Function with no params
- Expect(codeStr).To(ContainSubstring("def test_no_params() -> str:"))
- // Empty request
- Expect(codeStr).To(ContainSubstring(`request_bytes = b"{}"`))
- })
-
- It("should handle methods with no return values", func() {
- svc := Service{
- Name: "Test",
- Permission: "test",
- Interface: "TestService",
- Methods: []Method{
- {
- Name: "NoReturn",
- HasError: true,
- Params: []Param{NewParam("input", "string")},
- },
- },
- }
-
- code, err := GenerateClientPython(svc)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
-
- // Function returns None
- Expect(codeStr).To(ContainSubstring("def test_no_return(input: str) -> None:"))
- })
-
- It("should generate correct Python defaults for different types", func() {
- svc := Service{
- Name: "Test",
- Permission: "test",
- Interface: "TestService",
- Methods: []Method{
- {
- Name: "AllTypes",
- HasError: true,
- Returns: []Param{
- NewParam("strVal", "string"),
- NewParam("intVal", "int64"),
- NewParam("floatVal", "float64"),
- NewParam("boolVal", "bool"),
- },
- },
- },
- }
-
- code, err := GenerateClientPython(svc)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
-
- // Check defaults in response.get() calls
- Expect(codeStr).To(ContainSubstring(`response.get("strVal", "")`))
- Expect(codeStr).To(ContainSubstring(`response.get("intVal", 0)`))
- Expect(codeStr).To(ContainSubstring(`response.get("floatVal", 0.0)`))
- Expect(codeStr).To(ContainSubstring(`response.get("boolVal", False)`))
- })
- })
-
- Describe("GenerateGoDoc", func() {
- It("should generate valid doc.go content for multiple services", func() {
- services := []Service{
- {
- Name: "Cache",
- Permission: "cache",
- Interface: "CacheService",
- Doc: "CacheService provides temporary key-value storage with TTL.",
- },
- {
- Name: "Scheduler",
- Permission: "scheduler",
- Interface: "SchedulerService",
- Doc: "SchedulerService manages scheduled tasks.",
- },
- }
-
- code, err := GenerateGoDoc(services)
- Expect(err).NotTo(HaveOccurred())
-
- // Verify it's valid Go code
- _, err = format.Source(code)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
-
- // Check for generated header
- Expect(codeStr).To(ContainSubstring("Code generated by hostgen. DO NOT EDIT."))
-
- // Check for package declaration
- Expect(codeStr).To(ContainSubstring("package ndhost"))
-
- // Check for package documentation
- Expect(codeStr).To(ContainSubstring("Package ndhost provides Navidrome host function wrappers"))
-
- // Check that services are listed
- Expect(codeStr).To(ContainSubstring("Cache:"))
- Expect(codeStr).To(ContainSubstring("Scheduler:"))
- })
- })
-
- Describe("GenerateGoMod", func() {
- It("should generate valid go.mod content", func() {
- code, err := GenerateGoMod()
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
-
- // Check for module declaration
- Expect(codeStr).To(ContainSubstring("module github.com/navidrome/navidrome/plugins/host/go"))
-
- // Check for Go version
- Expect(codeStr).To(ContainSubstring("go 1.24"))
-
- // Check for extism-go-pdk dependency
- Expect(codeStr).To(ContainSubstring("github.com/extism/go-pdk"))
- })
- })
-
- Describe("GenerateClientGoStub", func() {
- It("should generate valid stub code with panic functions", func() {
- svc := Service{
- Name: "Cache",
- Permission: "cache",
- Interface: "CacheService",
- Doc: "CacheService provides caching capabilities.",
- Methods: []Method{
- {
- Name: "Get",
- Doc: "Get retrieves a value from the cache.",
- Params: []Param{
- {Name: "key", Type: "string"},
- },
- Returns: []Param{
- {Name: "value", Type: "string"},
- {Name: "exists", Type: "bool"},
- },
- },
- },
- }
-
- code, err := GenerateClientGoStub(svc)
- Expect(err).NotTo(HaveOccurred())
-
- // Verify it's valid Go code
- _, err = format.Source(code)
- Expect(err).NotTo(HaveOccurred())
-
- codeStr := string(code)
-
- // Check for build tag (non-WASM)
- Expect(codeStr).To(ContainSubstring("//go:build !wasip1"))
-
- // Check for package declaration
- Expect(codeStr).To(ContainSubstring("package ndhost"))
-
- // Check for stub comment
- Expect(codeStr).To(ContainSubstring("stub implementations for non-WASM builds"))
-
- // Check for panic in function body
- Expect(codeStr).To(ContainSubstring(`panic("ndhost: CacheGet is only available in WASM plugins")`))
-
- // Check that types are defined (needed for IDE support)
- Expect(codeStr).To(ContainSubstring("type CacheGetResponse struct"))
- })
- })
-
- Describe("Integration", func() {
- It("should generate compilable code from parsed source", func() {
- // This is an integration test that verifies the full pipeline
- src := `package host
-
-import "context"
-
-// TestService is a test service.
-//nd:hostservice name=Test permission=test
-type TestService interface {
- // DoSomething does something.
- //nd:hostfunc
- DoSomething(ctx context.Context, input string) (output string, err error)
-}
-`
- // Create temporary directory
- tmpDir := GinkgoT().TempDir()
- path := tmpDir + "/test.go"
- err := writeFile(path, src)
- Expect(err).NotTo(HaveOccurred())
-
- // Parse
- services, err := ParseDirectory(tmpDir)
- Expect(err).NotTo(HaveOccurred())
- Expect(services).To(HaveLen(1))
-
- // Generate
- code, err := GenerateHost(services[0], "host")
- Expect(err).NotTo(HaveOccurred())
-
- // Format (validates syntax)
- formatted, err := format.Source(code)
- Expect(err).NotTo(HaveOccurred())
-
- // Verify key elements
- codeStr := string(formatted)
- Expect(codeStr).To(ContainSubstring("RegisterTestHostFunctions"))
- Expect(codeStr).To(ContainSubstring(`"test_dosomething"`))
- })
- })
-})
-
-func writeFile(path, content string) error {
- return os.WriteFile(path, []byte(content), 0600)
-}
diff --git a/plugins/cmd/hostgen/internal/internal_suite_test.go b/plugins/cmd/hostgen/internal/internal_suite_test.go
deleted file mode 100644
index 2e25efaef..000000000
--- a/plugins/cmd/hostgen/internal/internal_suite_test.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package internal
-
-import (
- "testing"
-
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
-)
-
-func TestInternal(t *testing.T) {
- RegisterFailHandler(Fail)
- RunSpecs(t, "Hostgen Internal Suite")
-}
diff --git a/plugins/cmd/hostgen/internal/parser.go b/plugins/cmd/hostgen/internal/parser.go
deleted file mode 100644
index 48ffdeb0e..000000000
--- a/plugins/cmd/hostgen/internal/parser.go
+++ /dev/null
@@ -1,474 +0,0 @@
-package internal
-
-import (
- "fmt"
- "go/ast"
- "go/parser"
- "go/token"
- "os"
- "path/filepath"
- "regexp"
- "strings"
-)
-
-// Annotation patterns
-var (
- // //nd:hostservice name=ServiceName permission=key
- hostServicePattern = regexp.MustCompile(`//nd:hostservice\s+(.*)`)
- // //nd:hostfunc [name=CustomName]
- hostFuncPattern = regexp.MustCompile(`//nd:hostfunc(?:\s+(.*))?`)
- // key=value pairs
- keyValuePattern = regexp.MustCompile(`(\w+)=(\S+)`)
-)
-
-// ParseDirectory parses all Go source files in a directory and extracts host services.
-func ParseDirectory(dir string) ([]Service, error) {
- entries, err := os.ReadDir(dir)
- if err != nil {
- return nil, fmt.Errorf("reading directory: %w", err)
- }
-
- var services []Service
- fset := token.NewFileSet()
-
- for _, entry := range entries {
- if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
- continue
- }
- // Skip generated files and test files
- if strings.HasSuffix(entry.Name(), "_gen.go") || strings.HasSuffix(entry.Name(), "_test.go") {
- continue
- }
-
- path := filepath.Join(dir, entry.Name())
- parsed, err := parseFile(fset, path)
- if err != nil {
- return nil, fmt.Errorf("parsing %s: %w", entry.Name(), err)
- }
- services = append(services, parsed...)
- }
-
- return services, nil
-}
-
-// parseFile parses a single Go source file and extracts host services.
-func parseFile(fset *token.FileSet, path string) ([]Service, error) {
- f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
- if err != nil {
- return nil, err
- }
-
- // First pass: collect all struct definitions in the file
- allStructs := parseStructs(f)
- structMap := make(map[string]StructDef)
- for _, s := range allStructs {
- structMap[s.Name] = s
- }
-
- var services []Service
-
- for _, decl := range f.Decls {
- genDecl, ok := decl.(*ast.GenDecl)
- if !ok || genDecl.Tok != token.TYPE {
- continue
- }
-
- for _, spec := range genDecl.Specs {
- typeSpec, ok := spec.(*ast.TypeSpec)
- if !ok {
- continue
- }
-
- interfaceType, ok := typeSpec.Type.(*ast.InterfaceType)
- if !ok {
- continue
- }
-
- // Check for //nd:hostservice annotation in doc comment
- docText, rawDoc := getDocComment(genDecl, typeSpec)
- svcAnnotation := parseHostServiceAnnotation(rawDoc)
- if svcAnnotation == nil {
- continue
- }
-
- service := Service{
- Name: svcAnnotation["name"],
- Permission: svcAnnotation["permission"],
- Interface: typeSpec.Name.Name,
- Doc: cleanDoc(docText),
- }
-
- // Parse methods and collect referenced types
- referencedTypes := make(map[string]bool)
- for _, method := range interfaceType.Methods.List {
- if len(method.Names) == 0 {
- continue // Embedded interface
- }
-
- funcType, ok := method.Type.(*ast.FuncType)
- if !ok {
- continue
- }
-
- // Check for //nd:hostfunc annotation
- methodDocText, methodRawDoc := getMethodDocComment(method)
- methodAnnotation := parseHostFuncAnnotation(methodRawDoc)
- if methodAnnotation == nil {
- continue
- }
-
- m, err := parseMethod(method.Names[0].Name, funcType, methodAnnotation, cleanDoc(methodDocText))
- if err != nil {
- return nil, fmt.Errorf("parsing method %s.%s: %w", typeSpec.Name.Name, method.Names[0].Name, err)
- }
- service.Methods = append(service.Methods, m)
-
- // Collect referenced types from params and returns
- for _, p := range m.Params {
- collectReferencedTypes(p.Type, referencedTypes)
- }
- for _, r := range m.Returns {
- collectReferencedTypes(r.Type, referencedTypes)
- }
- }
-
- // Attach referenced structs to the service
- for typeName := range referencedTypes {
- if s, exists := structMap[typeName]; exists {
- service.Structs = append(service.Structs, s)
- }
- }
-
- if len(service.Methods) > 0 {
- services = append(services, service)
- }
- }
- }
-
- return services, nil
-}
-
-// parseStructs extracts all struct type definitions from a parsed Go file.
-func parseStructs(f *ast.File) []StructDef {
- var structs []StructDef
-
- for _, decl := range f.Decls {
- genDecl, ok := decl.(*ast.GenDecl)
- if !ok || genDecl.Tok != token.TYPE {
- continue
- }
-
- for _, spec := range genDecl.Specs {
- typeSpec, ok := spec.(*ast.TypeSpec)
- if !ok {
- continue
- }
-
- structType, ok := typeSpec.Type.(*ast.StructType)
- if !ok {
- continue
- }
-
- docText, _ := getDocComment(genDecl, typeSpec)
- s := StructDef{
- Name: typeSpec.Name.Name,
- Doc: cleanDoc(docText),
- }
-
- // Parse struct fields
- for _, field := range structType.Fields.List {
- if len(field.Names) == 0 {
- continue // Embedded field
- }
-
- fieldDef := parseStructField(field)
- s.Fields = append(s.Fields, fieldDef...)
- }
-
- structs = append(structs, s)
- }
- }
-
- return structs
-}
-
-// parseStructField parses a struct field and returns FieldDef for each name.
-func parseStructField(field *ast.Field) []FieldDef {
- var fields []FieldDef
- typeName := typeToString(field.Type)
-
- // Parse struct tag for JSON field name and omitempty
- jsonTag := ""
- omitEmpty := false
- if field.Tag != nil {
- tag := field.Tag.Value
- // Remove backticks
- tag = strings.Trim(tag, "`")
- // Parse json tag
- jsonTag, omitEmpty = parseJSONTag(tag)
- }
-
- // Get doc comment
- var doc string
- if field.Doc != nil {
- doc = cleanDoc(field.Doc.Text())
- }
-
- for _, name := range field.Names {
- fieldJSONTag := jsonTag
- if fieldJSONTag == "" {
- // Default to field name with camelCase
- fieldJSONTag = toJSONName(name.Name)
- }
- fields = append(fields, FieldDef{
- Name: name.Name,
- Type: typeName,
- JSONTag: fieldJSONTag,
- OmitEmpty: omitEmpty,
- Doc: doc,
- })
- }
-
- return fields
-}
-
-// parseJSONTag extracts the json field name and omitempty flag from a struct tag.
-func parseJSONTag(tag string) (name string, omitEmpty bool) {
- // Find json:"..." in the tag
- for _, part := range strings.Split(tag, " ") {
- if strings.HasPrefix(part, `json:"`) {
- value := strings.TrimPrefix(part, `json:"`)
- value = strings.TrimSuffix(value, `"`)
- parts := strings.Split(value, ",")
- if len(parts) > 0 && parts[0] != "-" {
- name = parts[0]
- }
- for _, opt := range parts[1:] {
- if opt == "omitempty" {
- omitEmpty = true
- }
- }
- return
- }
- }
- return "", false
-}
-
-// collectReferencedTypes extracts custom type names from a Go type string.
-// It handles pointers, slices, and maps, collecting base type names.
-func collectReferencedTypes(goType string, refs map[string]bool) {
- // Strip pointer
- if strings.HasPrefix(goType, "*") {
- collectReferencedTypes(goType[1:], refs)
- return
- }
- // Strip slice
- if strings.HasPrefix(goType, "[]") {
- if goType != "[]byte" {
- collectReferencedTypes(goType[2:], refs)
- }
- return
- }
- // Handle map
- if strings.HasPrefix(goType, "map[") {
- rest := goType[4:] // Remove "map["
- depth := 1
- keyEnd := 0
- for i, r := range rest {
- if r == '[' {
- depth++
- } else if r == ']' {
- depth--
- if depth == 0 {
- keyEnd = i
- break
- }
- }
- }
- keyType := rest[:keyEnd]
- valueType := rest[keyEnd+1:]
- collectReferencedTypes(keyType, refs)
- collectReferencedTypes(valueType, refs)
- return
- }
-
- // Check if it's a custom type (starts with uppercase, not a builtin)
- if len(goType) > 0 && goType[0] >= 'A' && goType[0] <= 'Z' {
- switch goType {
- case "String", "Bool", "Int", "Int32", "Int64", "Float32", "Float64":
- // Not custom types (just capitalized for some reason)
- default:
- refs[goType] = true
- }
- }
-}
-
-// toJSONName is imported from types.go via the same package
-
-// getDocComment extracts the doc comment for a type spec.
-// Returns both the readable doc text and the raw comment text (which includes pragma-style comments).
-func getDocComment(genDecl *ast.GenDecl, typeSpec *ast.TypeSpec) (docText, rawText string) {
- var docGroup *ast.CommentGroup
- // First check the TypeSpec's own doc (when multiple types in one block)
- if typeSpec.Doc != nil {
- docGroup = typeSpec.Doc
- } else if genDecl.Doc != nil {
- // Fall back to GenDecl doc (single type declaration)
- docGroup = genDecl.Doc
- }
- if docGroup == nil {
- return "", ""
- }
- return docGroup.Text(), commentGroupRaw(docGroup)
-}
-
-// commentGroupRaw returns all comment text including pragma-style comments (//nd:...).
-// Go's ast.CommentGroup.Text() strips comments without a space after //, so we need this.
-func commentGroupRaw(cg *ast.CommentGroup) string {
- if cg == nil {
- return ""
- }
- var lines []string
- for _, c := range cg.List {
- lines = append(lines, c.Text)
- }
- return strings.Join(lines, "\n")
-}
-
-// getMethodDocComment extracts the doc comment for a method.
-func getMethodDocComment(field *ast.Field) (docText, rawText string) {
- if field.Doc == nil {
- return "", ""
- }
- return field.Doc.Text(), commentGroupRaw(field.Doc)
-}
-
-// parseHostServiceAnnotation extracts //nd:hostservice annotation parameters.
-func parseHostServiceAnnotation(doc string) map[string]string {
- for _, line := range strings.Split(doc, "\n") {
- line = strings.TrimSpace(line)
- match := hostServicePattern.FindStringSubmatch(line)
- if match != nil {
- return parseKeyValuePairs(match[1])
- }
- }
- return nil
-}
-
-// parseHostFuncAnnotation extracts //nd:hostfunc annotation parameters.
-func parseHostFuncAnnotation(doc string) map[string]string {
- for _, line := range strings.Split(doc, "\n") {
- line = strings.TrimSpace(line)
- match := hostFuncPattern.FindStringSubmatch(line)
- if match != nil {
- params := parseKeyValuePairs(match[1])
- if params == nil {
- params = make(map[string]string)
- }
- return params
- }
- }
- return nil
-}
-
-// parseKeyValuePairs extracts key=value pairs from annotation text.
-func parseKeyValuePairs(text string) map[string]string {
- matches := keyValuePattern.FindAllStringSubmatch(text, -1)
- if len(matches) == 0 {
- return nil
- }
- result := make(map[string]string)
- for _, m := range matches {
- result[m[1]] = m[2]
- }
- return result
-}
-
-// parseMethod parses a method signature into a Method struct.
-func parseMethod(name string, funcType *ast.FuncType, annotation map[string]string, doc string) (Method, error) {
- m := Method{
- Name: name,
- ExportName: annotation["name"],
- Doc: doc,
- }
-
- // Parse parameters (skip context.Context)
- if funcType.Params != nil {
- for _, field := range funcType.Params.List {
- typeName := typeToString(field.Type)
- if typeName == "context.Context" {
- continue // Skip context parameter
- }
-
- for _, name := range field.Names {
- m.Params = append(m.Params, NewParam(name.Name, typeName))
- }
- }
- }
-
- // Parse return values
- if funcType.Results != nil {
- for _, field := range funcType.Results.List {
- typeName := typeToString(field.Type)
- if typeName == "error" {
- m.HasError = true
- continue // Track error but don't include in Returns
- }
-
- // Handle anonymous returns
- if len(field.Names) == 0 {
- // Generate a name based on position
- m.Returns = append(m.Returns, NewParam("result", typeName))
- } else {
- for _, name := range field.Names {
- m.Returns = append(m.Returns, NewParam(name.Name, typeName))
- }
- }
- }
- }
-
- return m, nil
-}
-
-// typeToString converts an AST type expression to a string.
-func typeToString(expr ast.Expr) string {
- switch t := expr.(type) {
- case *ast.Ident:
- return t.Name
- case *ast.SelectorExpr:
- return typeToString(t.X) + "." + t.Sel.Name
- case *ast.StarExpr:
- return "*" + typeToString(t.X)
- case *ast.ArrayType:
- if t.Len == nil {
- return "[]" + typeToString(t.Elt)
- }
- return fmt.Sprintf("[%s]%s", typeToString(t.Len), typeToString(t.Elt))
- case *ast.MapType:
- return fmt.Sprintf("map[%s]%s", typeToString(t.Key), typeToString(t.Value))
- case *ast.BasicLit:
- return t.Value
- case *ast.InterfaceType:
- // Empty interface (interface{} or any)
- if t.Methods == nil || len(t.Methods.List) == 0 {
- return "any"
- }
- // Non-empty interfaces can't be easily represented
- return "any"
- default:
- return fmt.Sprintf("%T", expr)
- }
-}
-
-// cleanDoc removes annotation lines from documentation.
-func cleanDoc(doc string) string {
- var lines []string
- for _, line := range strings.Split(doc, "\n") {
- trimmed := strings.TrimSpace(line)
- if strings.HasPrefix(trimmed, "//nd:") {
- continue
- }
- lines = append(lines, line)
- }
- return strings.TrimSpace(strings.Join(lines, "\n"))
-}
diff --git a/plugins/cmd/hostgen/internal/parser_test.go b/plugins/cmd/hostgen/internal/parser_test.go
deleted file mode 100644
index 98c2cfb58..000000000
--- a/plugins/cmd/hostgen/internal/parser_test.go
+++ /dev/null
@@ -1,292 +0,0 @@
-package internal
-
-import (
- "os"
- "path/filepath"
-
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
-)
-
-var _ = Describe("Parser", func() {
- var tmpDir string
-
- BeforeEach(func() {
- var err error
- tmpDir, err = os.MkdirTemp("", "hostgen-test-*")
- Expect(err).NotTo(HaveOccurred())
- })
-
- AfterEach(func() {
- os.RemoveAll(tmpDir)
- })
-
- Describe("ParseDirectory", func() {
- It("should parse a simple host service interface", func() {
- src := `package host
-
-import "context"
-
-// SubsonicAPIService provides access to Navidrome's Subsonic API.
-//nd:hostservice name=SubsonicAPI permission=subsonicapi
-type SubsonicAPIService interface {
- // Call executes a Subsonic API request.
- //nd:hostfunc
- Call(ctx context.Context, uri string) (response string, err error)
-}
-`
- err := os.WriteFile(filepath.Join(tmpDir, "service.go"), []byte(src), 0600)
- Expect(err).NotTo(HaveOccurred())
-
- services, err := ParseDirectory(tmpDir)
- Expect(err).NotTo(HaveOccurred())
- Expect(services).To(HaveLen(1))
-
- svc := services[0]
- Expect(svc.Name).To(Equal("SubsonicAPI"))
- Expect(svc.Permission).To(Equal("subsonicapi"))
- Expect(svc.Interface).To(Equal("SubsonicAPIService"))
- Expect(svc.Methods).To(HaveLen(1))
-
- m := svc.Methods[0]
- Expect(m.Name).To(Equal("Call"))
- Expect(m.HasError).To(BeTrue())
- Expect(m.Params).To(HaveLen(1))
- Expect(m.Params[0].Name).To(Equal("uri"))
- Expect(m.Params[0].Type).To(Equal("string"))
- Expect(m.Returns).To(HaveLen(1))
- Expect(m.Returns[0].Name).To(Equal("response"))
- Expect(m.Returns[0].Type).To(Equal("string"))
- })
-
- It("should parse multiple methods", func() {
- src := `package host
-
-import "context"
-
-// SchedulerService provides scheduling capabilities.
-//nd:hostservice name=Scheduler permission=scheduler
-type SchedulerService interface {
- //nd:hostfunc
- ScheduleRecurring(ctx context.Context, cronExpression string) (scheduleID string, err error)
-
- //nd:hostfunc
- ScheduleOneTime(ctx context.Context, delaySeconds int32) (scheduleID string, err error)
-
- //nd:hostfunc
- CancelSchedule(ctx context.Context, scheduleID string) (canceled bool, err error)
-}
-`
- err := os.WriteFile(filepath.Join(tmpDir, "scheduler.go"), []byte(src), 0600)
- Expect(err).NotTo(HaveOccurred())
-
- services, err := ParseDirectory(tmpDir)
- Expect(err).NotTo(HaveOccurred())
- Expect(services).To(HaveLen(1))
-
- svc := services[0]
- Expect(svc.Name).To(Equal("Scheduler"))
- Expect(svc.Methods).To(HaveLen(3))
-
- Expect(svc.Methods[0].Name).To(Equal("ScheduleRecurring"))
- Expect(svc.Methods[0].Params[0].Type).To(Equal("string"))
-
- Expect(svc.Methods[1].Name).To(Equal("ScheduleOneTime"))
- Expect(svc.Methods[1].Params[0].Type).To(Equal("int32"))
-
- Expect(svc.Methods[2].Name).To(Equal("CancelSchedule"))
- Expect(svc.Methods[2].Returns[0].Type).To(Equal("bool"))
- })
-
- It("should skip methods without hostfunc annotation", func() {
- src := `package host
-
-import "context"
-
-//nd:hostservice name=Test permission=test
-type TestService interface {
- //nd:hostfunc
- Exported(ctx context.Context) error
-
- // This method is not exported
- NotExported(ctx context.Context) error
-}
-`
- err := os.WriteFile(filepath.Join(tmpDir, "test.go"), []byte(src), 0600)
- Expect(err).NotTo(HaveOccurred())
-
- services, err := ParseDirectory(tmpDir)
- Expect(err).NotTo(HaveOccurred())
- Expect(services).To(HaveLen(1))
- Expect(services[0].Methods).To(HaveLen(1))
- Expect(services[0].Methods[0].Name).To(Equal("Exported"))
- })
-
- It("should handle custom export name", func() {
- src := `package host
-
-import "context"
-
-//nd:hostservice name=Test permission=test
-type TestService interface {
- //nd:hostfunc name=custom_export_name
- MyMethod(ctx context.Context) error
-}
-`
- err := os.WriteFile(filepath.Join(tmpDir, "test.go"), []byte(src), 0600)
- Expect(err).NotTo(HaveOccurred())
-
- services, err := ParseDirectory(tmpDir)
- Expect(err).NotTo(HaveOccurred())
- Expect(services[0].Methods[0].ExportName).To(Equal("custom_export_name"))
- Expect(services[0].Methods[0].FunctionName("test")).To(Equal("custom_export_name"))
- })
-
- It("should skip generated files", func() {
- regularSrc := `package host
-
-import "context"
-
-//nd:hostservice name=Test permission=test
-type TestService interface {
- //nd:hostfunc
- Method(ctx context.Context) error
-}
-`
- genSrc := `// Code generated. DO NOT EDIT.
-package host
-
-//nd:hostservice name=Generated permission=gen
-type GeneratedService interface {
- //nd:hostfunc
- Method() error
-}
-`
- err := os.WriteFile(filepath.Join(tmpDir, "test.go"), []byte(regularSrc), 0600)
- Expect(err).NotTo(HaveOccurred())
- err = os.WriteFile(filepath.Join(tmpDir, "test_gen.go"), []byte(genSrc), 0600)
- Expect(err).NotTo(HaveOccurred())
-
- services, err := ParseDirectory(tmpDir)
- Expect(err).NotTo(HaveOccurred())
- Expect(services).To(HaveLen(1))
- Expect(services[0].Name).To(Equal("Test"))
- })
-
- It("should skip interfaces without hostservice annotation", func() {
- src := `package host
-
-import "context"
-
-// Regular interface without annotation
-type RegularInterface interface {
- Method(ctx context.Context) error
-}
-
-//nd:hostservice name=Annotated permission=annotated
-type AnnotatedService interface {
- //nd:hostfunc
- Method(ctx context.Context) error
-}
-`
- err := os.WriteFile(filepath.Join(tmpDir, "test.go"), []byte(src), 0600)
- Expect(err).NotTo(HaveOccurred())
-
- services, err := ParseDirectory(tmpDir)
- Expect(err).NotTo(HaveOccurred())
- Expect(services).To(HaveLen(1))
- Expect(services[0].Name).To(Equal("Annotated"))
- })
-
- It("should return empty slice for directory with no host services", func() {
- src := `package host
-
-type RegularInterface interface {
- Method() error
-}
-`
- err := os.WriteFile(filepath.Join(tmpDir, "test.go"), []byte(src), 0600)
- Expect(err).NotTo(HaveOccurred())
-
- services, err := ParseDirectory(tmpDir)
- Expect(err).NotTo(HaveOccurred())
- Expect(services).To(BeEmpty())
- })
- })
-
- Describe("parseKeyValuePairs", func() {
- It("should parse key=value pairs", func() {
- result := parseKeyValuePairs("name=Test permission=test")
- Expect(result).To(HaveKeyWithValue("name", "Test"))
- Expect(result).To(HaveKeyWithValue("permission", "test"))
- })
-
- It("should return nil for empty input", func() {
- result := parseKeyValuePairs("")
- Expect(result).To(BeNil())
- })
- })
-
- Describe("typeToString", func() {
- It("should handle basic types", func() {
- src := `package test
-type T interface {
- Method(s string, i int, b bool) ([]byte, error)
-}
-`
- err := os.WriteFile(filepath.Join(tmpDir, "types.go"), []byte(src), 0600)
- Expect(err).NotTo(HaveOccurred())
-
- // Parse and verify type conversion works
- // This is implicitly tested through ParseDirectory
- })
-
- It("should convert interface{} to any", func() {
- src := `package test
-
-import "context"
-
-//nd:hostservice name=Test permission=test
-type TestService interface {
- //nd:hostfunc
- GetMetadata(ctx context.Context) (data map[string]interface{}, err error)
-}
-`
- err := os.WriteFile(filepath.Join(tmpDir, "test.go"), []byte(src), 0600)
- Expect(err).NotTo(HaveOccurred())
-
- services, err := ParseDirectory(tmpDir)
- Expect(err).NotTo(HaveOccurred())
- Expect(services).To(HaveLen(1))
- Expect(services[0].Methods[0].Returns[0].Type).To(Equal("map[string]any"))
- })
- })
-
- Describe("Method helpers", func() {
- It("should generate correct function names", func() {
- m := Method{Name: "Call"}
- Expect(m.FunctionName("subsonicapi")).To(Equal("subsonicapi_call"))
-
- m.ExportName = "custom_name"
- Expect(m.FunctionName("subsonicapi")).To(Equal("custom_name"))
- })
-
- It("should generate correct type names", func() {
- m := Method{Name: "Call"}
- Expect(m.RequestTypeName("SubsonicAPI")).To(Equal("SubsonicAPICallRequest"))
- Expect(m.ResponseTypeName("SubsonicAPI")).To(Equal("SubsonicAPICallResponse"))
- })
- })
-
- Describe("Service helpers", func() {
- It("should generate correct output file name", func() {
- s := Service{Name: "SubsonicAPI"}
- Expect(s.OutputFileName()).To(Equal("subsonicapi_gen.go"))
- })
-
- It("should generate correct export prefix", func() {
- s := Service{Name: "SubsonicAPI"}
- Expect(s.ExportPrefix()).To(Equal("subsonicapi"))
- })
- })
-})
diff --git a/plugins/cmd/hostgen/internal/templates/client.go.tmpl b/plugins/cmd/hostgen/internal/templates/client.go.tmpl
deleted file mode 100644
index 5cc53640a..000000000
--- a/plugins/cmd/hostgen/internal/templates/client.go.tmpl
+++ /dev/null
@@ -1,108 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the {{.Service.Name}} host service.
-// It is intended for use in Navidrome plugins built with TinyGo.
-//
-//go:build wasip1
-
-package ndhost
-
-import (
- "encoding/json"
- "errors"
-
- "github.com/extism/go-pdk"
-)
-
-{{- /* Generate struct definitions */ -}}
-{{- range .Service.Structs}}
-
-// {{.Name}} represents the {{.Name}} data structure.
-{{- if .Doc}}
-{{formatDoc .Doc}}
-{{- end}}
-type {{.Name}} struct {
-{{- range .Fields}}
- {{.Name}} {{.Type}} `json:"{{.JSONTag}}"`
-{{- end}}
-}
-{{- end}}
-
-{{- /* Generate wasmimport declarations for each method */ -}}
-{{range .Service.Methods}}
-
-// {{exportName .}} is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user {{exportName .}}
-func {{exportName .}}(uint64) uint64
-{{- end}}
-
-{{- /* Generate request/response types for all methods */ -}}
-{{range .Service.Methods}}
-{{- if .HasParams}}
-
-// {{requestType .}} is the request type for {{$.Service.Name}}.{{.Name}}.
-type {{requestType .}} struct {
-{{- range .Params}}
- {{title .Name}} {{.Type}} `json:"{{.JSONName}}"`
-{{- end}}
-}
-{{- end}}
-
-// {{responseType .}} is the response type for {{$.Service.Name}}.{{.Name}}.
-type {{responseType .}} struct {
-{{- range .Returns}}
- {{title .Name}} {{.Type}} `json:"{{.JSONName}},omitempty"`
-{{- end}}
- Error string `json:"error,omitempty"`
-}
-{{- end}}
-
-{{- /* Generate wrapper functions */ -}}
-{{range .Service.Methods}}
-
-// {{$.Service.Name}}{{.Name}} calls the {{exportName .}} host function.
-{{- if .Doc}}
-{{formatDoc .Doc}}
-{{- end}}
-func {{$.Service.Name}}{{.Name}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.Name}} {{$p.Type}}{{end}}) (*{{responseType .}}, error) {
-{{- if .HasParams}}
- // Marshal request to JSON
- req := {{requestType .}}{
-{{- range .Params}}
- {{title .Name}}: {{.Name}},
-{{- end}}
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-{{- else}}
- // No parameters - allocate empty JSON object
- reqMem := pdk.AllocateBytes([]byte("{}"))
- defer reqMem.Free()
-{{- end}}
-
- // Call the host function
- responsePtr := {{exportName .}}(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response {{responseType .}}
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-{{- end}}
diff --git a/plugins/cmd/hostgen/internal/templates/client.py.tmpl b/plugins/cmd/hostgen/internal/templates/client.py.tmpl
deleted file mode 100644
index 4ad6ae01e..000000000
--- a/plugins/cmd/hostgen/internal/templates/client.py.tmpl
+++ /dev/null
@@ -1,95 +0,0 @@
-# Code generated by hostgen. DO NOT EDIT.
-#
-# This file contains client wrappers for the {{.Service.Name}} host service.
-# It is intended for use in Navidrome plugins built with extism-py.
-#
-# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly.
-# The @extism.import_fn decorators are only detected when defined in the plugin's
-# main __init__.py file. Copy the needed functions from this file into your plugin.
-
-from dataclasses import dataclass
-from typing import Any
-
-import extism
-import json
-
-
-class HostFunctionError(Exception):
- """Raised when a host function returns an error."""
- pass
-
-{{- /* Generate raw host function imports */ -}}
-{{range .Service.Methods}}
-
-
-@extism.import_fn("extism:host/user", "{{exportName .}}")
-def _{{exportName .}}(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-{{- end}}
-{{- /* Generate dataclasses for multi-value returns */ -}}
-{{range .Service.Methods}}
-{{- if .NeedsResultClass}}
-
-
-@dataclass
-class {{pythonResultType .}}:
- """Result type for {{pythonFunc .}}."""
-{{- range .Returns}}
- {{.PythonName}}: {{.PythonType}}
-{{- end}}
-{{- end}}
-{{- end}}
-{{- /* Generate wrapper functions */ -}}
-{{range .Service.Methods}}
-
-
-def {{pythonFunc .}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.PythonName}}: {{$p.PythonType}}{{end}}){{if .NeedsResultClass}} -> {{pythonResultType .}}{{else if .HasReturns}} -> {{(index .Returns 0).PythonType}}{{else}} -> None{{end}}:
- """{{if .Doc}}{{.Doc}}{{else}}Call the {{exportName .}} host function.{{end}}
-{{- if .HasParams}}
-
- Args:
-{{- range .Params}}
- {{.PythonName}}: {{.PythonType}} parameter.
-{{- end}}
-{{- end}}
-{{- if .HasReturns}}
-
- Returns:
-{{- if .NeedsResultClass}}
- {{pythonResultType .}} containing{{range .Returns}} {{.PythonName}},{{end}}.
-{{- else}}
- {{(index .Returns 0).PythonType}}: The result value.
-{{- end}}
-{{- end}}
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
-{{- if .HasParams}}
- request = {
-{{- range .Params}}
- "{{.JSONName}}": {{.PythonName}},
-{{- end}}
- }
- request_bytes = json.dumps(request).encode("utf-8")
-{{- else}}
- request_bytes = b"{}"
-{{- end}}
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _{{exportName .}}(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-{{if .NeedsResultClass}}
- return {{pythonResultType .}}(
-{{- range .Returns}}
- {{.PythonName}}=response.get("{{.JSONName}}"{{pythonDefault .}}),
-{{- end}}
- )
-{{- else if .HasReturns}}
- return response.get("{{(index .Returns 0).JSONName}}"{{pythonDefault (index .Returns 0)}})
-{{- end}}
-{{- end}}
diff --git a/plugins/cmd/hostgen/internal/templates/client.rs.tmpl b/plugins/cmd/hostgen/internal/templates/client.rs.tmpl
deleted file mode 100644
index 3bd86779d..000000000
--- a/plugins/cmd/hostgen/internal/templates/client.rs.tmpl
+++ /dev/null
@@ -1,103 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the {{.Service.Name}} host service.
-// It is intended for use in Navidrome plugins built with extism-pdk.
-
-use extism_pdk::*;
-use serde::{Deserialize, Serialize};
-{{- /* Generate struct definitions */ -}}
-{{- range .Service.Structs}}
-{{if .Doc}}
-{{rustDocComment .Doc}}
-{{else}}
-{{end}}#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct {{.Name}} {
-{{- range .Fields}}
-{{- if .NeedsDefault}}
- #[serde(default)]
-{{- end}}
- pub {{.RustName}}: {{fieldRustType .}},
-{{- end}}
-}
-{{- end}}
-{{- /* Generate request/response types */ -}}
-{{- range .Service.Methods}}
-{{- if .HasParams}}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct {{requestType .}} {
-{{- range .Params}}
- {{.RustName}}: {{rustType .}},
-{{- end}}
-}
-{{- end}}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct {{responseType .}} {
-{{- range .Returns}}
- #[serde(default)]
- {{.RustName}}: {{rustType .}},
-{{- end}}
- #[serde(default)]
- error: Option,
-}
-{{- end}}
-
-#[host_fn]
-extern "ExtismHost" {
-{{- range .Service.Methods}}
- fn {{exportName .}}(input: Json<{{if .HasParams}}{{requestType .}}{{else}}serde_json::Value{{end}}>) -> Json<{{responseType .}}>;
-{{- end}}
-}
-
-{{- /* Generate wrapper functions */ -}}
-{{range .Service.Methods}}
-
-{{if .Doc}}{{rustDocComment .Doc}}{{else}}/// Calls the {{exportName .}} host function.{{end}}
-{{- if .HasParams}}
-///
-/// # Arguments
-{{- range .Params}}
-/// * `{{.RustName}}` - {{rustType .}} parameter.
-{{- end}}
-{{- end}}
-{{- if .HasReturns}}
-///
-/// # Returns
-{{- if eq (len .Returns) 1}}
-/// The {{(index .Returns 0).RustName}} value.
-{{- else}}
-/// A tuple of ({{range $i, $r := .Returns}}{{if $i}}, {{end}}{{$r.RustName}}{{end}}).
-{{- end}}
-{{- end}}
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn {{rustFunc .}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.RustName}}: {{rustParamType $p}}{{end}}) -> Result<{{if eq (len .Returns) 0}}(){{else if eq (len .Returns) 1}}{{rustType (index .Returns 0)}}{{else}}({{range $i, $r := .Returns}}{{if $i}}, {{end}}{{rustType $r}}{{end}}){{end}}, Error> {
- let response = unsafe {
-{{- if .HasParams}}
- {{exportName .}}(Json({{requestType .}} {
-{{- range .Params}}
- {{.RustName}}: {{.RustName}}{{if .NeedsToOwned}}.to_owned(){{end}},
-{{- end}}
- }))?
-{{- else}}
- {{exportName .}}(Json(serde_json::json!({})))?
-{{- end}}
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-{{if eq (len .Returns) 0}}
- Ok(())
-{{- else if eq (len .Returns) 1}}
- Ok(response.0.{{(index .Returns 0).RustName}})
-{{- else}}
- Ok(({{range $i, $r := .Returns}}{{if $i}}, {{end}}response.0.{{$r.RustName}}{{end}}))
-{{- end}}
-}
-{{- end}}
diff --git a/plugins/cmd/hostgen/internal/templates/client_stub.go.tmpl b/plugins/cmd/hostgen/internal/templates/client_stub.go.tmpl
deleted file mode 100644
index 69802e717..000000000
--- a/plugins/cmd/hostgen/internal/templates/client_stub.go.tmpl
+++ /dev/null
@@ -1,56 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains stub implementations for non-WASM builds.
-// These stubs allow IDE support and compilation on non-WASM platforms.
-// They panic at runtime since host functions are only available in WASM plugins.
-//
-//go:build !wasip1
-
-package ndhost
-
-{{- /* Generate struct definitions (same as main file, needed for type references) */ -}}
-{{- range .Service.Structs}}
-
-// {{.Name}} represents the {{.Name}} data structure.
-{{- if .Doc}}
-{{formatDoc .Doc}}
-{{- end}}
-type {{.Name}} struct {
-{{- range .Fields}}
- {{.Name}} {{.Type}} `json:"{{.JSONTag}}"`
-{{- end}}
-}
-{{- end}}
-
-{{- /* Generate request/response types (same as main file) */ -}}
-{{range .Service.Methods}}
-{{- if .HasParams}}
-
-// {{requestType .}} is the request type for {{$.Service.Name}}.{{.Name}}.
-type {{requestType .}} struct {
-{{- range .Params}}
- {{title .Name}} {{.Type}} `json:"{{.JSONName}}"`
-{{- end}}
-}
-{{- end}}
-
-// {{responseType .}} is the response type for {{$.Service.Name}}.{{.Name}}.
-type {{responseType .}} struct {
-{{- range .Returns}}
- {{title .Name}} {{.Type}} `json:"{{.JSONName}},omitempty"`
-{{- end}}
- Error string `json:"error,omitempty"`
-}
-{{- end}}
-
-{{- /* Generate stub wrapper functions that panic */ -}}
-{{range .Service.Methods}}
-
-// {{$.Service.Name}}{{.Name}} is a stub that panics on non-WASM platforms.
-{{- if .Doc}}
-{{formatDoc .Doc}}
-{{- end}}
-func {{$.Service.Name}}{{.Name}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.Name}} {{$p.Type}}{{end}}) (*{{responseType .}}, error) {
- panic("ndhost: {{$.Service.Name}}{{.Name}} is only available in WASM plugins")
-}
-{{- end}}
diff --git a/plugins/cmd/hostgen/internal/templates/doc.go.tmpl b/plugins/cmd/hostgen/internal/templates/doc.go.tmpl
deleted file mode 100644
index 2a1ecc4b2..000000000
--- a/plugins/cmd/hostgen/internal/templates/doc.go.tmpl
+++ /dev/null
@@ -1,49 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-
-/*
-Package ndhost provides Navidrome host function wrappers for Go/TinyGo plugins.
-
-This package is auto-generated by the hostgen tool and should not be edited manually.
-
-# Usage
-
-Add this module as a dependency in your plugin's go.mod:
-
- require github.com/navidrome/navidrome/plugins/host/go v0.0.0
-
-Then import the package in your plugin code:
-
- import ndhost "github.com/navidrome/navidrome/plugins/host/go"
-
- func myPluginFunction() error {
- // Use the cache service
- _, err := ndhost.CacheSetString("my_key", "my_value", 3600)
- if err != nil {
- return err
- }
-
- // Schedule a recurring task
- _, err = ndhost.SchedulerScheduleRecurring("@every 5m", "payload", "task_id")
- if err != nil {
- return err
- }
-
- return nil
- }
-
-# Available Services
-
-The following host services are available:
-{{range .Services}}
- - {{.Name}}: {{if .Doc}}{{.Doc | firstLine}}{{else}}{{.Name}} service{{end}}
-{{- end}}
-
-# Building Plugins
-
-Go plugins must be compiled to WebAssembly using TinyGo:
-
- tinygo build -o plugin.wasm -target=wasip1 -buildmode=c-shared .
-
-See the examples directory for complete plugin implementations.
-*/
-package ndhost
diff --git a/plugins/cmd/hostgen/internal/templates/go.mod.tmpl b/plugins/cmd/hostgen/internal/templates/go.mod.tmpl
deleted file mode 100644
index 954c98071..000000000
--- a/plugins/cmd/hostgen/internal/templates/go.mod.tmpl
+++ /dev/null
@@ -1,5 +0,0 @@
-module github.com/navidrome/navidrome/plugins/host/go
-
-go 1.24
-
-require github.com/extism/go-pdk v1.1.3
diff --git a/plugins/cmd/hostgen/internal/templates/host.go.tmpl b/plugins/cmd/hostgen/internal/templates/host.go.tmpl
deleted file mode 100644
index 4a961bd56..000000000
--- a/plugins/cmd/hostgen/internal/templates/host.go.tmpl
+++ /dev/null
@@ -1,119 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-
-package {{.Package}}
-
-import (
- "context"
- "encoding/json"
-
- extism "github.com/extism/go-sdk"
-)
-
-{{- /* Generate request/response types for all methods */ -}}
-{{range .Service.Methods}}
-{{- if .HasParams}}
-
-// {{requestType .}} is the request type for {{$.Service.Name}}.{{.Name}}.
-type {{requestType .}} struct {
-{{- range .Params}}
- {{title .Name}} {{.Type}} `json:"{{.JSONName}}"`
-{{- end}}
-}
-{{- end}}
-
-// {{responseType .}} is the response type for {{$.Service.Name}}.{{.Name}}.
-type {{responseType .}} struct {
-{{- range .Returns}}
- {{title .Name}} {{.Type}} `json:"{{.JSONName}},omitempty"`
-{{- end}}
- Error string `json:"error,omitempty"`
-}
-{{end}}
-
-// Register{{.Service.Name}}HostFunctions registers {{.Service.Name}} service host functions.
-// The returned host functions should be added to the plugin's configuration.
-func Register{{.Service.Name}}HostFunctions(service {{.Service.Interface}}) []extism.HostFunction {
- return []extism.HostFunction{
-{{- range .Service.Methods}}
- new{{$.Service.Name}}{{.Name}}HostFunction(service),
-{{- end}}
- }
-}
-{{range .Service.Methods}}
-
-func new{{$.Service.Name}}{{.Name}}HostFunction(service {{$.Service.Interface}}) extism.HostFunction {
- return extism.NewHostFunctionWithStack(
- "{{exportName .}}",
- func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
-{{- if .HasParams}}
- // Read JSON request from plugin memory
- reqBytes, err := p.ReadBytes(stack[0])
- if err != nil {
- {{$.Service.Name | lower}}WriteError(p, stack, err)
- return
- }
- var req {{requestType .}}
- if err := json.Unmarshal(reqBytes, &req); err != nil {
- {{$.Service.Name | lower}}WriteError(p, stack, err)
- return
- }
-{{- end}}
-
- // Call the service method
-{{- if .HasReturns}}
-{{- if .HasError}}
- {{range $i, $r := .Returns}}{{if $i}}, {{end}}{{lower $r.Name}}{{end}}, svcErr := service.{{.Name}}(ctx{{range .Params}}, req.{{title .Name}}{{end}})
- if svcErr != nil {
- {{$.Service.Name | lower}}WriteError(p, stack, svcErr)
- return
- }
-{{- else}}
- {{range $i, $r := .Returns}}{{if $i}}, {{end}}{{lower $r.Name}}{{end}} := service.{{.Name}}(ctx{{range .Params}}, req.{{title .Name}}{{end}})
-{{- end}}
-{{- else if .HasError}}
- if svcErr := service.{{.Name}}(ctx{{range .Params}}, req.{{title .Name}}{{end}}); svcErr != nil {
- {{$.Service.Name | lower}}WriteError(p, stack, svcErr)
- return
- }
-{{- else}}
- service.{{.Name}}(ctx{{range .Params}}, req.{{title .Name}}{{end}})
-{{- end}}
-
- // Write JSON response to plugin memory
- resp := {{responseType .}}{
-{{- range .Returns}}
- {{title .Name}}: {{lower .Name}},
-{{- end}}
- }
- {{$.Service.Name | lower}}WriteResponse(p, stack, resp)
- },
- []extism.ValueType{extism.ValueTypePTR},
- []extism.ValueType{extism.ValueTypePTR},
- )
-}
-{{end}}
-
-// {{.Service.Name | lower}}WriteResponse writes a JSON response to plugin memory.
-func {{.Service.Name | lower}}WriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
- respBytes, err := json.Marshal(resp)
- if err != nil {
- {{.Service.Name | lower}}WriteError(p, stack, err)
- return
- }
- respPtr, err := p.WriteBytes(respBytes)
- if err != nil {
- stack[0] = 0
- return
- }
- stack[0] = respPtr
-}
-
-// {{.Service.Name | lower}}WriteError writes an error response to plugin memory.
-func {{.Service.Name | lower}}WriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
- errResp := struct {
- Error string `json:"error"`
- }{Error: err.Error()}
- respBytes, _ := json.Marshal(errResp)
- respPtr, _ := p.WriteBytes(respBytes)
- stack[0] = respPtr
-}
diff --git a/plugins/cmd/hostgen/internal/templates/lib.rs.tmpl b/plugins/cmd/hostgen/internal/templates/lib.rs.tmpl
deleted file mode 100644
index 7edbb0f62..000000000
--- a/plugins/cmd/hostgen/internal/templates/lib.rs.tmpl
+++ /dev/null
@@ -1,43 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-//! Navidrome Host Function Wrappers for Rust Plugins
-//!
-//! This crate provides idiomatic Rust wrappers for all Navidrome host services.
-//! It is auto-generated by the hostgen tool and should not be edited manually.
-//!
-//! # Usage
-//!
-//! Add this crate as a dependency in your plugin's Cargo.toml:
-//!
-//! ```toml
-//! [dependencies]
-//! nd-host = { path = "../../host/rust" }
-//! ```
-//!
-//! Then import the services you need:
-//!
-//! ```ignore
-//! use nd_host::{cache, scheduler};
-//!
-//! fn my_plugin_function() -> Result<(), extism_pdk::Error> {
-//! // Use the cache service
-//! cache::set_string("my_key", "my_value", 3600)?;
-//!
-//! // Schedule a recurring task
-//! scheduler::schedule_recurring("@every 5m", "payload", "task_id")?;
-//!
-//! Ok(())
-//! }
-//! ```
-//!
-//! # Available Services
-//!
-{{- range .Services}}
-//! - [`{{.Name | lower}}`] - {{if .Doc}}{{.Doc | firstLine}}{{else}}{{.Name}} service{{end}}
-{{- end}}
-{{range .Services}}
-#[path = "nd_host_{{.Name | lower}}.rs"]
-pub mod {{.Name | lower}};
-{{end}}
-// Re-export commonly used types from extism-pdk for convenience
-pub use extism_pdk::Error;
diff --git a/plugins/cmd/hostgen/internal/types.go b/plugins/cmd/hostgen/internal/types.go
deleted file mode 100644
index af5ccfb6b..000000000
--- a/plugins/cmd/hostgen/internal/types.go
+++ /dev/null
@@ -1,384 +0,0 @@
-package internal
-
-import (
- "strings"
- "unicode"
-)
-
-// Service represents a parsed host service interface.
-type Service struct {
- Name string // Service name from annotation (e.g., "SubsonicAPI")
- Permission string // Manifest permission key (e.g., "subsonicapi")
- Interface string // Go interface name (e.g., "SubsonicAPIService")
- Methods []Method // Methods marked with //nd:hostfunc
- Doc string // Documentation comment for the service
- Structs []StructDef // Structs used by this service
-}
-
-// StructDef represents a Go struct type definition.
-type StructDef struct {
- Name string // Go struct name (e.g., "Library")
- Fields []FieldDef // Struct fields
- Doc string // Documentation comment
-}
-
-// FieldDef represents a field within a struct.
-type FieldDef struct {
- Name string // Go field name (e.g., "TotalSongs")
- Type string // Go type (e.g., "int32", "*string", "[]User")
- JSONTag string // JSON tag value (e.g., "totalSongs,omitempty")
- OmitEmpty bool // Whether the field has omitempty tag
- Doc string // Field documentation
-}
-
-// OutputFileName returns the generated file name for this service.
-func (s Service) OutputFileName() string {
- return strings.ToLower(s.Name) + "_gen.go"
-}
-
-// ExportPrefix returns the prefix for exported host function names.
-func (s Service) ExportPrefix() string {
- return strings.ToLower(s.Name)
-}
-
-// KnownStructs returns a map of struct names defined in this service.
-func (s Service) KnownStructs() map[string]bool {
- result := make(map[string]bool)
- for _, st := range s.Structs {
- result[st.Name] = true
- }
- return result
-}
-
-// Method represents a host function method within a service.
-type Method struct {
- Name string // Go method name (e.g., "Call")
- ExportName string // Optional override for export name
- Params []Param // Method parameters (excluding context.Context)
- Returns []Param // Return values (excluding error)
- HasError bool // Whether the method returns an error
- Doc string // Documentation comment for the method
-}
-
-// FunctionName returns the Extism host function export name.
-func (m Method) FunctionName(servicePrefix string) string {
- if m.ExportName != "" {
- return m.ExportName
- }
- return servicePrefix + "_" + strings.ToLower(m.Name)
-}
-
-// RequestTypeName returns the generated request type name.
-func (m Method) RequestTypeName(serviceName string) string {
- return serviceName + m.Name + "Request"
-}
-
-// ResponseTypeName returns the generated response type name.
-func (m Method) ResponseTypeName(serviceName string) string {
- return serviceName + m.Name + "Response"
-}
-
-// HasParams returns true if the method has input parameters.
-func (m Method) HasParams() bool {
- return len(m.Params) > 0
-}
-
-// HasReturns returns true if the method has return values (excluding error).
-func (m Method) HasReturns() bool {
- return len(m.Returns) > 0
-}
-
-// Param represents a method parameter or return value.
-type Param struct {
- Name string // Parameter name
- Type string // Go type (e.g., "string", "int32", "[]byte")
- JSONName string // JSON field name (camelCase)
-}
-
-// NewParam creates a Param with auto-generated JSON name.
-func NewParam(name, typ string) Param {
- return Param{
- Name: name,
- Type: typ,
- JSONName: toJSONName(name),
- }
-}
-
-// toJSONName converts a Go identifier to camelCase JSON field name.
-// This matches Rust serde's rename_all = "camelCase" behavior.
-// Examples: "ConnectionID" -> "connectionId", "NewConnectionID" -> "newConnectionId"
-func toJSONName(name string) string {
- if name == "" {
- return ""
- }
-
- runes := []rune(name)
- result := make([]rune, 0, len(runes))
-
- for i, r := range runes {
- if i == 0 {
- // First character is always lowercase
- result = append(result, unicode.ToLower(r))
- } else if unicode.IsUpper(r) {
- // Check if this is part of an acronym (consecutive uppercase)
- // or a word boundary
- prevIsUpper := unicode.IsUpper(runes[i-1])
- nextIsLower := i+1 < len(runes) && unicode.IsLower(runes[i+1])
-
- if prevIsUpper && !nextIsLower {
- // Middle of an acronym - lowercase it
- result = append(result, unicode.ToLower(r))
- } else if prevIsUpper && nextIsLower {
- // End of acronym followed by lowercase - this starts a new word
- // Keep uppercase
- result = append(result, r)
- } else {
- // Regular word boundary - keep uppercase
- result = append(result, r)
- }
- } else {
- result = append(result, r)
- }
- }
-
- return string(result)
-}
-
-// ToPythonType converts a Go type to its Python equivalent.
-func ToPythonType(goType string) string {
- switch goType {
- case "string":
- return "str"
- case "int", "int32", "int64":
- return "int"
- case "float32", "float64":
- return "float"
- case "bool":
- return "bool"
- case "[]byte":
- return "bytes"
- default:
- return "Any"
- }
-}
-
-// ToSnakeCase converts a PascalCase or camelCase string to snake_case.
-// It handles consecutive uppercase letters correctly (e.g., "ScheduleID" -> "schedule_id").
-func ToSnakeCase(s string) string {
- var result strings.Builder
- runes := []rune(s)
- for i, r := range runes {
- if i > 0 && r >= 'A' && r <= 'Z' {
- // Add underscore before uppercase, but not if:
- // - Previous char was uppercase AND next char is uppercase or end of string
- // (this handles acronyms like "ID" in "NewScheduleID")
- prevUpper := runes[i-1] >= 'A' && runes[i-1] <= 'Z'
- nextUpper := i+1 < len(runes) && runes[i+1] >= 'A' && runes[i+1] <= 'Z'
- atEnd := i+1 == len(runes)
-
- // Only skip underscore if we're in the middle of an acronym
- if !prevUpper || (!nextUpper && !atEnd) {
- result.WriteByte('_')
- }
- }
- result.WriteRune(r)
- }
- return strings.ToLower(result.String())
-}
-
-// PythonFunctionName returns the Python function name for a method.
-func (m Method) PythonFunctionName(servicePrefix string) string {
- return ToSnakeCase(servicePrefix + m.Name)
-}
-
-// PythonResultTypeName returns the Python dataclass name for multi-value returns.
-func (m Method) PythonResultTypeName(serviceName string) string {
- return serviceName + m.Name + "Result"
-}
-
-// NeedsResultClass returns true if the method needs a dataclass for returns.
-func (m Method) NeedsResultClass() bool {
- return len(m.Returns) > 1
-}
-
-// PythonType returns the Python type for this parameter.
-func (p Param) PythonType() string {
- return ToPythonType(p.Type)
-}
-
-// PythonName returns the snake_case Python name for this parameter.
-func (p Param) PythonName() string {
- return ToSnakeCase(p.Name)
-}
-
-// ToRustType converts a Go type to its Rust equivalent.
-func ToRustType(goType string) string {
- return ToRustTypeWithStructs(goType, nil)
-}
-
-// RustParamType returns the Rust type for a function parameter (uses &str for strings).
-func RustParamType(goType string) string {
- if goType == "string" {
- return "&str"
- }
- return ToRustType(goType)
-}
-
-// RustDefaultValue returns the default value for a Rust type.
-func RustDefaultValue(goType string) string {
- switch goType {
- case "string":
- return `String::new()`
- case "int", "int32":
- return "0"
- case "int64":
- return "0"
- case "float32", "float64":
- return "0.0"
- case "bool":
- return "false"
- default:
- if strings.HasPrefix(goType, "[]") {
- return "Vec::new()"
- }
- if strings.HasPrefix(goType, "map[") {
- return "std::collections::HashMap::new()"
- }
- if strings.HasPrefix(goType, "*") {
- return "None"
- }
- return "serde_json::Value::Null"
- }
-}
-
-// RustFunctionName returns the Rust function name for a method (snake_case).
-// Uses just the method name without service prefix since the module provides namespacing.
-func (m Method) RustFunctionName(_ string) string {
- return ToSnakeCase(m.Name)
-}
-
-// RustDocComment returns a properly formatted Rust doc comment.
-// Each line of the input doc string is prefixed with "/// ".
-func RustDocComment(doc string) string {
- if doc == "" {
- return ""
- }
- lines := strings.Split(doc, "\n")
- var result []string
- for _, line := range lines {
- result = append(result, "/// "+line)
- }
- return strings.Join(result, "\n")
-}
-
-// RustType returns the Rust type for this parameter.
-func (p Param) RustType() string {
- return ToRustType(p.Type)
-}
-
-// RustTypeWithStructs returns the Rust type using known struct names.
-func (p Param) RustTypeWithStructs(knownStructs map[string]bool) string {
- return ToRustTypeWithStructs(p.Type, knownStructs)
-}
-
-// RustParamType returns the Rust type for this parameter when used as a function argument.
-func (p Param) RustParamType() string {
- return RustParamType(p.Type)
-}
-
-// RustParamTypeWithStructs returns the Rust param type using known struct names.
-func (p Param) RustParamTypeWithStructs(knownStructs map[string]bool) string {
- if p.Type == "string" {
- return "&str"
- }
- return ToRustTypeWithStructs(p.Type, knownStructs)
-}
-
-// RustName returns the snake_case Rust name for this parameter.
-func (p Param) RustName() string {
- return ToSnakeCase(p.Name)
-}
-
-// NeedsToOwned returns true if the parameter needs .to_owned() when used.
-func (p Param) NeedsToOwned() bool {
- return p.Type == "string"
-}
-
-// RustType returns the Rust type for this field, using known struct names.
-func (f FieldDef) RustType(knownStructs map[string]bool) string {
- return ToRustTypeWithStructs(f.Type, knownStructs)
-}
-
-// RustName returns the snake_case Rust name for this field.
-func (f FieldDef) RustName() string {
- return ToSnakeCase(f.Name)
-}
-
-// NeedsDefault returns true if the field needs #[serde(default)] attribute.
-// This is true for fields with omitempty tag.
-func (f FieldDef) NeedsDefault() bool {
- return f.OmitEmpty
-}
-
-// ToRustTypeWithStructs converts a Go type to its Rust equivalent,
-// using known struct names instead of serde_json::Value.
-func ToRustTypeWithStructs(goType string, knownStructs map[string]bool) string {
- // Handle pointer types
- if strings.HasPrefix(goType, "*") {
- inner := ToRustTypeWithStructs(goType[1:], knownStructs)
- return "Option<" + inner + ">"
- }
- // Handle slice types
- if strings.HasPrefix(goType, "[]") {
- if goType == "[]byte" {
- return "Vec"
- }
- inner := ToRustTypeWithStructs(goType[2:], knownStructs)
- return "Vec<" + inner + ">"
- }
- // Handle map types
- if strings.HasPrefix(goType, "map[") {
- // Extract key and value types from map[K]V
- rest := goType[4:] // Remove "map["
- depth := 1
- keyEnd := 0
- for i, r := range rest {
- if r == '[' {
- depth++
- } else if r == ']' {
- depth--
- if depth == 0 {
- keyEnd = i
- break
- }
- }
- }
- keyType := rest[:keyEnd]
- valueType := rest[keyEnd+1:]
- return "std::collections::HashMap<" + ToRustTypeWithStructs(keyType, knownStructs) + ", " + ToRustTypeWithStructs(valueType, knownStructs) + ">"
- }
-
- switch goType {
- case "string":
- return "String"
- case "int", "int32":
- return "i32"
- case "int64":
- return "i64"
- case "float32":
- return "f32"
- case "float64":
- return "f64"
- case "bool":
- return "bool"
- case "interface{}", "any":
- return "serde_json::Value"
- default:
- // Check if this is a known struct type
- if knownStructs != nil && knownStructs[goType] {
- return goType
- }
- // For unknown custom types, fall back to Value
- return "serde_json::Value"
- }
-}
diff --git a/plugins/cmd/hostgen/main.go b/plugins/cmd/hostgen/main.go
deleted file mode 100644
index 5fc35569d..000000000
--- a/plugins/cmd/hostgen/main.go
+++ /dev/null
@@ -1,455 +0,0 @@
-// hostgen generates Extism host function wrappers from annotated Go interfaces.
-//
-// Usage:
-//
-// hostgen -input=./plugins/host -output=./plugins/host
-//
-// Flags:
-//
-// -input Input directory containing Go source files with annotated interfaces
-// -output Output directory for generated files (default: same as input)
-// -package Output package name (default: inferred from output directory)
-// -host-only Generate only host-side code (default: false)
-// -plugin-only Generate only plugin/client-side code (default: false)
-// -go Generate Go client wrappers (default: true when not using -python/-rust)
-// -python Generate Python client wrappers (default: false)
-// -rust Generate Rust client wrappers (default: false)
-// -v Verbose output
-// -dry-run Preview generated code without writing files
-package main
-
-import (
- "flag"
- "fmt"
- "go/format"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/navidrome/navidrome/plugins/cmd/hostgen/internal"
-)
-
-// config holds the parsed command-line configuration.
-type config struct {
- inputDir string
- outputDir string
- pkgName string
- generateHost bool
- generateGoClient bool
- generatePyClient bool
- generateRsClient bool
- verbose bool
- dryRun bool
-}
-
-func main() {
- cfg, err := parseConfig()
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
-
- services, err := parseServices(cfg)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
- if len(services) == 0 {
- return
- }
-
- if err := generateAllCode(cfg, services); err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
-}
-
-// parseConfig parses command-line flags and returns the configuration.
-func parseConfig() (*config, error) {
- var (
- inputDir = flag.String("input", ".", "Input directory containing Go source files")
- outputDir = flag.String("output", "", "Output directory for generated files (default: same as input)")
- pkgName = flag.String("package", "", "Output package name (default: inferred from output directory)")
- hostOnly = flag.Bool("host-only", false, "Generate only host-side code")
- pluginOnly = flag.Bool("plugin-only", false, "Generate only plugin/client-side code")
- goClient = flag.Bool("go", false, "Generate Go client wrappers")
- pyClient = flag.Bool("python", false, "Generate Python client wrappers")
- rsClient = flag.Bool("rust", false, "Generate Rust client wrappers")
- verbose = flag.Bool("v", false, "Verbose output")
- dryRun = flag.Bool("dry-run", false, "Preview generated code without writing files")
- )
- flag.Parse()
-
- if *hostOnly && *pluginOnly {
- return nil, fmt.Errorf("-host-only and -plugin-only cannot be used together")
- }
-
- if *outputDir == "" {
- *outputDir = *inputDir
- }
-
- absInput, err := filepath.Abs(*inputDir)
- if err != nil {
- return nil, fmt.Errorf("resolving input path: %w", err)
- }
- absOutput, err := filepath.Abs(*outputDir)
- if err != nil {
- return nil, fmt.Errorf("resolving output path: %w", err)
- }
-
- if *pkgName == "" {
- *pkgName = filepath.Base(absOutput)
- }
-
- // Determine what to generate
- // Default: generate Go clients if no language flag is specified
- anyLangFlag := *goClient || *pyClient || *rsClient
-
- return &config{
- inputDir: absInput,
- outputDir: absOutput,
- pkgName: *pkgName,
- generateHost: !*pluginOnly,
- generateGoClient: !*hostOnly && (*goClient || !anyLangFlag),
- generatePyClient: !*hostOnly && *pyClient,
- generateRsClient: !*hostOnly && *rsClient,
- verbose: *verbose,
- dryRun: *dryRun,
- }, nil
-}
-
-// parseServices parses source files and returns discovered services.
-func parseServices(cfg *config) ([]internal.Service, error) {
- if cfg.verbose {
- fmt.Printf("Input directory: %s\n", cfg.inputDir)
- fmt.Printf("Output directory: %s\n", cfg.outputDir)
- fmt.Printf("Package name: %s\n", cfg.pkgName)
- fmt.Printf("Generate host code: %v\n", cfg.generateHost)
- fmt.Printf("Generate Go client code: %v\n", cfg.generateGoClient)
- fmt.Printf("Generate Python client code: %v\n", cfg.generatePyClient)
- fmt.Printf("Generate Rust client code: %v\n", cfg.generateRsClient)
- }
-
- services, err := internal.ParseDirectory(cfg.inputDir)
- if err != nil {
- return nil, fmt.Errorf("parsing source files: %w", err)
- }
-
- if len(services) == 0 {
- if cfg.verbose {
- fmt.Println("No host services found")
- }
- return nil, nil
- }
-
- if cfg.verbose {
- fmt.Printf("Found %d host service(s)\n", len(services))
- for _, svc := range services {
- fmt.Printf(" - %s (%d methods)\n", svc.Name, len(svc.Methods))
- }
- }
-
- return services, nil
-}
-
-// generateAllCode generates all requested code for the services.
-func generateAllCode(cfg *config, services []internal.Service) error {
- for _, svc := range services {
- if cfg.generateHost {
- if err := generateHostCode(svc, cfg.pkgName, cfg.outputDir, cfg.dryRun, cfg.verbose); err != nil {
- return fmt.Errorf("generating host code for %s: %w", svc.Name, err)
- }
- }
- if cfg.generateGoClient {
- if err := generateGoClientCode(svc, cfg.outputDir, cfg.dryRun, cfg.verbose); err != nil {
- return fmt.Errorf("generating Go client code for %s: %w", svc.Name, err)
- }
- }
- if cfg.generatePyClient {
- if err := generatePythonClientCode(svc, cfg.outputDir, cfg.dryRun, cfg.verbose); err != nil {
- return fmt.Errorf("generating Python client code for %s: %w", svc.Name, err)
- }
- }
- if cfg.generateRsClient {
- if err := generateRustClientCode(svc, cfg.outputDir, cfg.dryRun, cfg.verbose); err != nil {
- return fmt.Errorf("generating Rust client code for %s: %w", svc.Name, err)
- }
- }
- }
-
- if cfg.generateRsClient && len(services) > 0 {
- if err := generateRustLibFile(services, cfg.outputDir, cfg.dryRun, cfg.verbose); err != nil {
- return fmt.Errorf("generating Rust lib.rs: %w", err)
- }
- }
-
- if cfg.generateGoClient && len(services) > 0 {
- if err := generateGoDocFile(services, cfg.outputDir, cfg.dryRun, cfg.verbose); err != nil {
- return fmt.Errorf("generating Go doc.go: %w", err)
- }
- if err := generateGoModFile(cfg.outputDir, cfg.dryRun, cfg.verbose); err != nil {
- return fmt.Errorf("generating Go go.mod: %w", err)
- }
- }
-
- return nil
-}
-
-// generateHostCode generates host-side code for a service.
-func generateHostCode(svc internal.Service, pkgName, outputDir string, dryRun, verbose bool) error {
- code, err := internal.GenerateHost(svc, pkgName)
- if err != nil {
- return fmt.Errorf("generating code: %w", err)
- }
-
- formatted, err := format.Source(code)
- if err != nil {
- return fmt.Errorf("formatting code: %w\nRaw code:\n%s", err, code)
- }
-
- outputFile := filepath.Join(outputDir, svc.OutputFileName())
-
- if dryRun {
- fmt.Printf("=== %s ===\n%s\n", outputFile, formatted)
- return nil
- }
-
- if err := os.WriteFile(outputFile, formatted, 0600); err != nil {
- return fmt.Errorf("writing file: %w", err)
- }
-
- if verbose {
- fmt.Printf("Generated host code: %s\n", outputFile)
- }
- return nil
-}
-
-// generateGoClientCode generates Go client-side code for a service.
-func generateGoClientCode(svc internal.Service, outputDir string, dryRun, verbose bool) error {
- code, err := internal.GenerateClientGo(svc)
- if err != nil {
- return fmt.Errorf("generating code: %w", err)
- }
-
- formatted, err := format.Source(code)
- if err != nil {
- return fmt.Errorf("formatting code: %w\nRaw code:\n%s", err, code)
- }
-
- // Client code goes in go/ subdirectory
- clientDir := filepath.Join(outputDir, "go")
- clientFile := filepath.Join(clientDir, "nd_host_"+strings.ToLower(svc.Name)+".go")
-
- if dryRun {
- fmt.Printf("=== %s ===\n%s\n", clientFile, formatted)
- } else {
- // Create go/ subdirectory if needed
- if err := os.MkdirAll(clientDir, 0755); err != nil {
- return fmt.Errorf("creating client directory: %w", err)
- }
-
- if err := os.WriteFile(clientFile, formatted, 0600); err != nil {
- return fmt.Errorf("writing file: %w", err)
- }
-
- if verbose {
- fmt.Printf("Generated Go client code: %s\n", clientFile)
- }
- }
-
- // Also generate stub file for non-WASM platforms
- return generateGoClientStubCode(svc, outputDir, dryRun, verbose)
-}
-
-// generateGoClientStubCode generates stub code for non-WASM platforms.
-func generateGoClientStubCode(svc internal.Service, outputDir string, dryRun, verbose bool) error {
- code, err := internal.GenerateClientGoStub(svc)
- if err != nil {
- return fmt.Errorf("generating stub code: %w", err)
- }
-
- formatted, err := format.Source(code)
- if err != nil {
- return fmt.Errorf("formatting stub code: %w\nRaw code:\n%s", err, code)
- }
-
- // Stub code goes in go/ subdirectory with _stub suffix
- clientDir := filepath.Join(outputDir, "go")
- stubFile := filepath.Join(clientDir, "nd_host_"+strings.ToLower(svc.Name)+"_stub.go")
-
- if dryRun {
- fmt.Printf("=== %s ===\n%s\n", stubFile, formatted)
- return nil
- }
-
- // Create go/ subdirectory if needed
- if err := os.MkdirAll(clientDir, 0755); err != nil {
- return fmt.Errorf("creating client directory: %w", err)
- }
-
- if err := os.WriteFile(stubFile, formatted, 0600); err != nil {
- return fmt.Errorf("writing stub file: %w", err)
- }
-
- if verbose {
- fmt.Printf("Generated Go client stub: %s\n", stubFile)
- }
- return nil
-}
-
-// generatePythonClientCode generates Python client-side code for a service.
-func generatePythonClientCode(svc internal.Service, outputDir string, dryRun, verbose bool) error {
- code, err := internal.GenerateClientPython(svc)
- if err != nil {
- return fmt.Errorf("generating code: %w", err)
- }
-
- // Python code goes in python/ subdirectory
- clientDir := filepath.Join(outputDir, "python")
- clientFile := filepath.Join(clientDir, "nd_host_"+strings.ToLower(svc.Name)+".py")
-
- if dryRun {
- fmt.Printf("=== %s ===\n%s\n", clientFile, code)
- return nil
- }
-
- // Create python/ subdirectory if needed
- if err := os.MkdirAll(clientDir, 0755); err != nil {
- return fmt.Errorf("creating python client directory: %w", err)
- }
-
- if err := os.WriteFile(clientFile, code, 0600); err != nil {
- return fmt.Errorf("writing file: %w", err)
- }
-
- if verbose {
- fmt.Printf("Generated Python client code: %s\n", clientFile)
- }
- return nil
-}
-
-// generateRustClientCode generates Rust client-side code for a service.
-func generateRustClientCode(svc internal.Service, outputDir string, dryRun, verbose bool) error {
- code, err := internal.GenerateClientRust(svc)
- if err != nil {
- return fmt.Errorf("generating code: %w", err)
- }
-
- // Rust code goes in rust/ subdirectory
- clientDir := filepath.Join(outputDir, "rust")
- clientFile := filepath.Join(clientDir, "nd_host_"+strings.ToLower(svc.Name)+".rs")
-
- if dryRun {
- fmt.Printf("=== %s ===\n%s\n", clientFile, code)
- return nil
- }
-
- // Create rust/ subdirectory if needed
- if err := os.MkdirAll(clientDir, 0755); err != nil {
- return fmt.Errorf("creating rust client directory: %w", err)
- }
-
- if err := os.WriteFile(clientFile, code, 0600); err != nil {
- return fmt.Errorf("writing file: %w", err)
- }
-
- if verbose {
- fmt.Printf("Generated Rust client code: %s\n", clientFile)
- }
- return nil
-}
-
-// generateRustLibFile generates the lib.rs file that exposes all Rust modules.
-func generateRustLibFile(services []internal.Service, outputDir string, dryRun, verbose bool) error {
- code, err := internal.GenerateRustLib(services)
- if err != nil {
- return fmt.Errorf("generating lib.rs: %w", err)
- }
-
- clientDir := filepath.Join(outputDir, "rust")
- libFile := filepath.Join(clientDir, "lib.rs")
-
- if dryRun {
- fmt.Printf("=== %s ===\n%s\n", libFile, code)
- return nil
- }
-
- // Create rust/ subdirectory if needed
- if err := os.MkdirAll(clientDir, 0755); err != nil {
- return fmt.Errorf("creating rust client directory: %w", err)
- }
-
- if err := os.WriteFile(libFile, code, 0600); err != nil {
- return fmt.Errorf("writing file: %w", err)
- }
-
- if verbose {
- fmt.Printf("Generated Rust lib.rs: %s\n", libFile)
- }
- return nil
-}
-
-// generateGoDocFile generates the doc.go file for the Go library.
-func generateGoDocFile(services []internal.Service, outputDir string, dryRun, verbose bool) error {
- code, err := internal.GenerateGoDoc(services)
- if err != nil {
- return fmt.Errorf("generating doc.go: %w", err)
- }
-
- formatted, err := format.Source(code)
- if err != nil {
- return fmt.Errorf("formatting doc.go: %w\nRaw code:\n%s", err, code)
- }
-
- clientDir := filepath.Join(outputDir, "go")
- docFile := filepath.Join(clientDir, "doc.go")
-
- if dryRun {
- fmt.Printf("=== %s ===\n%s\n", docFile, formatted)
- return nil
- }
-
- // Create go/ subdirectory if needed
- if err := os.MkdirAll(clientDir, 0755); err != nil {
- return fmt.Errorf("creating go client directory: %w", err)
- }
-
- if err := os.WriteFile(docFile, formatted, 0600); err != nil {
- return fmt.Errorf("writing file: %w", err)
- }
-
- if verbose {
- fmt.Printf("Generated Go doc.go: %s\n", docFile)
- }
- return nil
-}
-
-// generateGoModFile generates the go.mod file for the Go library.
-func generateGoModFile(outputDir string, dryRun, verbose bool) error {
- code, err := internal.GenerateGoMod()
- if err != nil {
- return fmt.Errorf("generating go.mod: %w", err)
- }
-
- clientDir := filepath.Join(outputDir, "go")
- modFile := filepath.Join(clientDir, "go.mod")
-
- if dryRun {
- fmt.Printf("=== %s ===\n%s\n", modFile, code)
- return nil
- }
-
- // Create go/ subdirectory if needed
- if err := os.MkdirAll(clientDir, 0755); err != nil {
- return fmt.Errorf("creating go client directory: %w", err)
- }
-
- if err := os.WriteFile(modFile, code, 0600); err != nil {
- return fmt.Errorf("writing file: %w", err)
- }
-
- if verbose {
- fmt.Printf("Generated Go go.mod: %s\n", modFile)
- }
- return nil
-}
diff --git a/plugins/cmd/ndpgen/integration_test.go b/plugins/cmd/ndpgen/integration_test.go
index 8c9d899bd..20093ef81 100644
--- a/plugins/cmd/ndpgen/integration_test.go
+++ b/plugins/cmd/ndpgen/integration_test.go
@@ -12,12 +12,9 @@ import (
. "github.com/onsi/gomega"
)
-// normalizeGeneratedCode normalizes differences between hostgen and ndpgen output
-// so we can reuse hostgen's testdata for ndpgen verification.
+// normalizeGeneratedCode normalizes generated code for comparison with expected output.
func normalizeGeneratedCode(code string) string {
- // Replace tool name references
- code = strings.ReplaceAll(code, "Code generated by hostgen.", "Code generated by ndpgen.")
- // Replace package names
+ // Replace package names (generated uses ndpdk, testdata may use ndhost)
code = strings.ReplaceAll(code, "package ndhost", "package ndpdk")
return code
}
@@ -30,8 +27,8 @@ var _ = Describe("ndpgen CLI", Ordered, func() {
)
BeforeAll(func() {
- // Set testdata directory (reuse hostgen's testdata)
- testdataDir = filepath.Join(mustGetWd(GinkgoT()), "plugins", "cmd", "hostgen", "testdata")
+ // Set testdata directory
+ testdataDir = filepath.Join(mustGetWd(GinkgoT()), "plugins", "cmd", "ndpgen", "testdata")
// Build the ndpgen binary
ndpgenBin = filepath.Join(os.TempDir(), "ndpgen-test")
@@ -252,38 +249,38 @@ type ServiceB interface {
},
Entry("simple string params",
- "echo_service.go", "echo_client_expected.go", "echo_client_expected.py", "echo_client_expected.rs"),
+ "echo_service.go.txt", "echo_client_expected.go.txt", "echo_client_expected.py", "echo_client_expected.rs"),
Entry("multiple simple params (int32)",
- "math_service.go", "math_client_expected.go", "math_client_expected.py", "math_client_expected.rs"),
+ "math_service.go.txt", "math_client_expected.go.txt", "math_client_expected.py", "math_client_expected.rs"),
Entry("struct param with request type",
- "store_service.go", "store_client_expected.go", "store_client_expected.py", "store_client_expected.rs"),
+ "store_service.go.txt", "store_client_expected.go.txt", "store_client_expected.py", "store_client_expected.rs"),
Entry("mixed simple and complex params",
- "list_service.go", "list_client_expected.go", "list_client_expected.py", "list_client_expected.rs"),
+ "list_service.go.txt", "list_client_expected.go.txt", "list_client_expected.py", "list_client_expected.rs"),
Entry("method without error",
- "counter_service.go", "counter_client_expected.go", "counter_client_expected.py", "counter_client_expected.rs"),
+ "counter_service.go.txt", "counter_client_expected.go.txt", "counter_client_expected.py", "counter_client_expected.rs"),
Entry("no params, error only",
- "ping_service.go", "ping_client_expected.go", "ping_client_expected.py", "ping_client_expected.rs"),
+ "ping_service.go.txt", "ping_client_expected.go.txt", "ping_client_expected.py", "ping_client_expected.rs"),
Entry("map and interface types",
- "meta_service.go", "meta_client_expected.go", "meta_client_expected.py", "meta_client_expected.rs"),
+ "meta_service.go.txt", "meta_client_expected.go.txt", "meta_client_expected.py", "meta_client_expected.rs"),
Entry("pointer types",
- "users_service.go", "users_client_expected.go", "users_client_expected.py", "users_client_expected.rs"),
+ "users_service.go.txt", "users_client_expected.go.txt", "users_client_expected.py", "users_client_expected.rs"),
Entry("multiple returns",
- "search_service.go", "search_client_expected.go", "search_client_expected.py", "search_client_expected.rs"),
+ "search_service.go.txt", "search_client_expected.go.txt", "search_client_expected.py", "search_client_expected.rs"),
Entry("bytes",
- "codec_service.go", "codec_client_expected.go", "codec_client_expected.py", "codec_client_expected.rs"),
+ "codec_service.go.txt", "codec_client_expected.go.txt", "codec_client_expected.py", "codec_client_expected.rs"),
)
It("generates compilable client code for comprehensive service", func() {
- serviceCode := readTestdata("comprehensive_service.go")
+ serviceCode := readTestdata("comprehensive_service.go.txt")
Expect(os.WriteFile(filepath.Join(testDir, "service.go"), []byte(serviceCode), 0600)).To(Succeed())
@@ -409,7 +406,7 @@ type TestService interface {
Expect(err).ToNot(HaveOccurred())
contentStr := string(content)
- Expect(contentStr).To(ContainSubstring("Code generated by hostgen. DO NOT EDIT."))
+ Expect(contentStr).To(ContainSubstring("Code generated by ndpgen. DO NOT EDIT."))
Expect(contentStr).To(ContainSubstring("class HostFunctionError(Exception):"))
Expect(contentStr).To(ContainSubstring(`@extism.import_fn("extism:host/user", "test_doaction")`))
Expect(contentStr).To(ContainSubstring("def test_do_action(input: str) -> str:"))
diff --git a/plugins/cmd/ndpgen/internal/generator_test.go b/plugins/cmd/ndpgen/internal/generator_test.go
index 68dcedee9..8cf0a65ca 100644
--- a/plugins/cmd/ndpgen/internal/generator_test.go
+++ b/plugins/cmd/ndpgen/internal/generator_test.go
@@ -36,7 +36,7 @@ var _ = Describe("Generator", func() {
codeStr := string(code)
// Check for generated header
- Expect(codeStr).To(ContainSubstring("Code generated by hostgen. DO NOT EDIT."))
+ Expect(codeStr).To(ContainSubstring("Code generated by ndpgen. DO NOT EDIT."))
// Check for package declaration
Expect(codeStr).To(ContainSubstring("package host"))
@@ -390,7 +390,7 @@ var _ = Describe("Generator", func() {
codeStr := string(code)
// Check for generated header
- Expect(codeStr).To(ContainSubstring("Code generated by hostgen. DO NOT EDIT."))
+ Expect(codeStr).To(ContainSubstring("Code generated by ndpgen. DO NOT EDIT."))
// Check for imports
Expect(codeStr).To(ContainSubstring("from dataclasses import dataclass"))
diff --git a/plugins/cmd/ndpgen/internal/templates/client.py.tmpl b/plugins/cmd/ndpgen/internal/templates/client.py.tmpl
index 4ad6ae01e..67053e134 100644
--- a/plugins/cmd/ndpgen/internal/templates/client.py.tmpl
+++ b/plugins/cmd/ndpgen/internal/templates/client.py.tmpl
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the {{.Service.Name}} host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/ndpgen/internal/templates/client.rs.tmpl b/plugins/cmd/ndpgen/internal/templates/client.rs.tmpl
index 3bd86779d..8fc01c1f3 100644
--- a/plugins/cmd/ndpgen/internal/templates/client.rs.tmpl
+++ b/plugins/cmd/ndpgen/internal/templates/client.rs.tmpl
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the {{.Service.Name}} host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/ndpgen/internal/templates/host.go.tmpl b/plugins/cmd/ndpgen/internal/templates/host.go.tmpl
index 4a961bd56..39e6ad0ce 100644
--- a/plugins/cmd/ndpgen/internal/templates/host.go.tmpl
+++ b/plugins/cmd/ndpgen/internal/templates/host.go.tmpl
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package {{.Package}}
diff --git a/plugins/cmd/ndpgen/internal/templates/lib.rs.tmpl b/plugins/cmd/ndpgen/internal/templates/lib.rs.tmpl
index 7edbb0f62..50f2603bf 100644
--- a/plugins/cmd/ndpgen/internal/templates/lib.rs.tmpl
+++ b/plugins/cmd/ndpgen/internal/templates/lib.rs.tmpl
@@ -1,9 +1,9 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
//! Navidrome Host Function Wrappers for Rust Plugins
//!
//! This crate provides idiomatic Rust wrappers for all Navidrome host services.
-//! It is auto-generated by the hostgen tool and should not be edited manually.
+//! It is auto-generated by the ndpgen tool and should not be edited manually.
//!
//! # Usage
//!
diff --git a/plugins/cmd/hostgen/testdata/codec_client_expected.go b/plugins/cmd/ndpgen/testdata/codec_client_expected.go.txt
similarity index 97%
rename from plugins/cmd/hostgen/testdata/codec_client_expected.go
rename to plugins/cmd/ndpgen/testdata/codec_client_expected.go.txt
index 4ae8795d8..04a349ad4 100644
--- a/plugins/cmd/hostgen/testdata/codec_client_expected.go
+++ b/plugins/cmd/ndpgen/testdata/codec_client_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Codec host service.
// It is intended for use in Navidrome plugins built with TinyGo.
diff --git a/plugins/cmd/hostgen/testdata/codec_client_expected.py b/plugins/cmd/ndpgen/testdata/codec_client_expected.py
similarity index 97%
rename from plugins/cmd/hostgen/testdata/codec_client_expected.py
rename to plugins/cmd/ndpgen/testdata/codec_client_expected.py
index 5521f1bbf..e1eb92501 100644
--- a/plugins/cmd/hostgen/testdata/codec_client_expected.py
+++ b/plugins/cmd/ndpgen/testdata/codec_client_expected.py
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the Codec host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/hostgen/testdata/codec_client_expected.rs b/plugins/cmd/ndpgen/testdata/codec_client_expected.rs
similarity index 96%
rename from plugins/cmd/hostgen/testdata/codec_client_expected.rs
rename to plugins/cmd/ndpgen/testdata/codec_client_expected.rs
index c4b5e7e7c..ff61294d0 100644
--- a/plugins/cmd/hostgen/testdata/codec_client_expected.rs
+++ b/plugins/cmd/ndpgen/testdata/codec_client_expected.rs
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Codec host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/hostgen/testdata/codec_expected.go b/plugins/cmd/ndpgen/testdata/codec_expected.go.txt
similarity index 98%
rename from plugins/cmd/hostgen/testdata/codec_expected.go
rename to plugins/cmd/ndpgen/testdata/codec_expected.go.txt
index 28eb22139..8655e49c8 100644
--- a/plugins/cmd/hostgen/testdata/codec_expected.go
+++ b/plugins/cmd/ndpgen/testdata/codec_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package testpkg
diff --git a/plugins/cmd/hostgen/testdata/codec_service.go b/plugins/cmd/ndpgen/testdata/codec_service.go.txt
similarity index 100%
rename from plugins/cmd/hostgen/testdata/codec_service.go
rename to plugins/cmd/ndpgen/testdata/codec_service.go.txt
diff --git a/plugins/cmd/hostgen/testdata/comprehensive_client_expected.py b/plugins/cmd/ndpgen/testdata/comprehensive_client_expected.py
similarity index 99%
rename from plugins/cmd/hostgen/testdata/comprehensive_client_expected.py
rename to plugins/cmd/ndpgen/testdata/comprehensive_client_expected.py
index bcb6e2f12..0fdbfb0f2 100644
--- a/plugins/cmd/hostgen/testdata/comprehensive_client_expected.py
+++ b/plugins/cmd/ndpgen/testdata/comprehensive_client_expected.py
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the Comprehensive host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/hostgen/testdata/comprehensive_client_expected.rs b/plugins/cmd/ndpgen/testdata/comprehensive_client_expected.rs
similarity index 99%
rename from plugins/cmd/hostgen/testdata/comprehensive_client_expected.rs
rename to plugins/cmd/ndpgen/testdata/comprehensive_client_expected.rs
index a78d17609..efcc1b8ef 100644
--- a/plugins/cmd/hostgen/testdata/comprehensive_client_expected.rs
+++ b/plugins/cmd/ndpgen/testdata/comprehensive_client_expected.rs
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Comprehensive host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/hostgen/testdata/comprehensive_service.go b/plugins/cmd/ndpgen/testdata/comprehensive_service.go.txt
similarity index 100%
rename from plugins/cmd/hostgen/testdata/comprehensive_service.go
rename to plugins/cmd/ndpgen/testdata/comprehensive_service.go.txt
diff --git a/plugins/cmd/hostgen/testdata/counter_client_expected.go b/plugins/cmd/ndpgen/testdata/counter_client_expected.go.txt
similarity index 97%
rename from plugins/cmd/hostgen/testdata/counter_client_expected.go
rename to plugins/cmd/ndpgen/testdata/counter_client_expected.go.txt
index ee9bea22a..73ca80085 100644
--- a/plugins/cmd/hostgen/testdata/counter_client_expected.go
+++ b/plugins/cmd/ndpgen/testdata/counter_client_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Counter host service.
// It is intended for use in Navidrome plugins built with TinyGo.
diff --git a/plugins/cmd/hostgen/testdata/counter_client_expected.py b/plugins/cmd/ndpgen/testdata/counter_client_expected.py
similarity index 97%
rename from plugins/cmd/hostgen/testdata/counter_client_expected.py
rename to plugins/cmd/ndpgen/testdata/counter_client_expected.py
index 6bdbc61dc..2af5b3b38 100644
--- a/plugins/cmd/hostgen/testdata/counter_client_expected.py
+++ b/plugins/cmd/ndpgen/testdata/counter_client_expected.py
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the Counter host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/hostgen/testdata/counter_client_expected.rs b/plugins/cmd/ndpgen/testdata/counter_client_expected.rs
similarity index 96%
rename from plugins/cmd/hostgen/testdata/counter_client_expected.rs
rename to plugins/cmd/ndpgen/testdata/counter_client_expected.rs
index 11842689c..37b8f8605 100644
--- a/plugins/cmd/hostgen/testdata/counter_client_expected.rs
+++ b/plugins/cmd/ndpgen/testdata/counter_client_expected.rs
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Counter host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/hostgen/testdata/counter_expected.go b/plugins/cmd/ndpgen/testdata/counter_expected.go.txt
similarity index 98%
rename from plugins/cmd/hostgen/testdata/counter_expected.go
rename to plugins/cmd/ndpgen/testdata/counter_expected.go.txt
index 036ff4523..f15f3c2a7 100644
--- a/plugins/cmd/hostgen/testdata/counter_expected.go
+++ b/plugins/cmd/ndpgen/testdata/counter_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package testpkg
diff --git a/plugins/cmd/hostgen/testdata/counter_service.go b/plugins/cmd/ndpgen/testdata/counter_service.go.txt
similarity index 100%
rename from plugins/cmd/hostgen/testdata/counter_service.go
rename to plugins/cmd/ndpgen/testdata/counter_service.go.txt
diff --git a/plugins/cmd/hostgen/testdata/echo_client_expected.go b/plugins/cmd/ndpgen/testdata/echo_client_expected.go.txt
similarity index 97%
rename from plugins/cmd/hostgen/testdata/echo_client_expected.go
rename to plugins/cmd/ndpgen/testdata/echo_client_expected.go.txt
index 6adbe2bb6..4858488bf 100644
--- a/plugins/cmd/hostgen/testdata/echo_client_expected.go
+++ b/plugins/cmd/ndpgen/testdata/echo_client_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Echo host service.
// It is intended for use in Navidrome plugins built with TinyGo.
diff --git a/plugins/cmd/hostgen/testdata/echo_client_expected.py b/plugins/cmd/ndpgen/testdata/echo_client_expected.py
similarity index 97%
rename from plugins/cmd/hostgen/testdata/echo_client_expected.py
rename to plugins/cmd/ndpgen/testdata/echo_client_expected.py
index d3144c347..06565b0d6 100644
--- a/plugins/cmd/hostgen/testdata/echo_client_expected.py
+++ b/plugins/cmd/ndpgen/testdata/echo_client_expected.py
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the Echo host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/hostgen/testdata/echo_client_expected.rs b/plugins/cmd/ndpgen/testdata/echo_client_expected.rs
similarity index 96%
rename from plugins/cmd/hostgen/testdata/echo_client_expected.rs
rename to plugins/cmd/ndpgen/testdata/echo_client_expected.rs
index 4c4fd672a..0b97ca1cd 100644
--- a/plugins/cmd/hostgen/testdata/echo_client_expected.rs
+++ b/plugins/cmd/ndpgen/testdata/echo_client_expected.rs
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Echo host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/hostgen/testdata/echo_expected.go b/plugins/cmd/ndpgen/testdata/echo_expected.go.txt
similarity index 98%
rename from plugins/cmd/hostgen/testdata/echo_expected.go
rename to plugins/cmd/ndpgen/testdata/echo_expected.go.txt
index da0dc931e..d67854abf 100644
--- a/plugins/cmd/hostgen/testdata/echo_expected.go
+++ b/plugins/cmd/ndpgen/testdata/echo_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package testpkg
diff --git a/plugins/cmd/hostgen/testdata/echo_service.go b/plugins/cmd/ndpgen/testdata/echo_service.go.txt
similarity index 100%
rename from plugins/cmd/hostgen/testdata/echo_service.go
rename to plugins/cmd/ndpgen/testdata/echo_service.go.txt
diff --git a/plugins/cmd/hostgen/testdata/list_client_expected.go b/plugins/cmd/ndpgen/testdata/list_client_expected.go.txt
similarity index 97%
rename from plugins/cmd/hostgen/testdata/list_client_expected.go
rename to plugins/cmd/ndpgen/testdata/list_client_expected.go.txt
index 3bf4244a9..b0f76a013 100644
--- a/plugins/cmd/hostgen/testdata/list_client_expected.go
+++ b/plugins/cmd/ndpgen/testdata/list_client_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the List host service.
// It is intended for use in Navidrome plugins built with TinyGo.
diff --git a/plugins/cmd/hostgen/testdata/list_client_expected.py b/plugins/cmd/ndpgen/testdata/list_client_expected.py
similarity index 97%
rename from plugins/cmd/hostgen/testdata/list_client_expected.py
rename to plugins/cmd/ndpgen/testdata/list_client_expected.py
index 3a19ac476..58ccad146 100644
--- a/plugins/cmd/hostgen/testdata/list_client_expected.py
+++ b/plugins/cmd/ndpgen/testdata/list_client_expected.py
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the List host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/hostgen/testdata/list_client_expected.rs b/plugins/cmd/ndpgen/testdata/list_client_expected.rs
similarity index 96%
rename from plugins/cmd/hostgen/testdata/list_client_expected.rs
rename to plugins/cmd/ndpgen/testdata/list_client_expected.rs
index ec3ec2d57..9b54f7544 100644
--- a/plugins/cmd/hostgen/testdata/list_client_expected.rs
+++ b/plugins/cmd/ndpgen/testdata/list_client_expected.rs
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the List host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/hostgen/testdata/list_expected.go b/plugins/cmd/ndpgen/testdata/list_expected.go.txt
similarity index 98%
rename from plugins/cmd/hostgen/testdata/list_expected.go
rename to plugins/cmd/ndpgen/testdata/list_expected.go.txt
index 8a7dee4e7..778f3a409 100644
--- a/plugins/cmd/hostgen/testdata/list_expected.go
+++ b/plugins/cmd/ndpgen/testdata/list_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package testpkg
diff --git a/plugins/cmd/hostgen/testdata/list_service.go b/plugins/cmd/ndpgen/testdata/list_service.go.txt
similarity index 100%
rename from plugins/cmd/hostgen/testdata/list_service.go
rename to plugins/cmd/ndpgen/testdata/list_service.go.txt
diff --git a/plugins/cmd/hostgen/testdata/math_client_expected.go b/plugins/cmd/ndpgen/testdata/math_client_expected.go.txt
similarity index 97%
rename from plugins/cmd/hostgen/testdata/math_client_expected.go
rename to plugins/cmd/ndpgen/testdata/math_client_expected.go.txt
index f7b30579b..455cc8f41 100644
--- a/plugins/cmd/hostgen/testdata/math_client_expected.go
+++ b/plugins/cmd/ndpgen/testdata/math_client_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Math host service.
// It is intended for use in Navidrome plugins built with TinyGo.
diff --git a/plugins/cmd/hostgen/testdata/math_client_expected.py b/plugins/cmd/ndpgen/testdata/math_client_expected.py
similarity index 97%
rename from plugins/cmd/hostgen/testdata/math_client_expected.py
rename to plugins/cmd/ndpgen/testdata/math_client_expected.py
index e714fbc02..f3ea53335 100644
--- a/plugins/cmd/hostgen/testdata/math_client_expected.py
+++ b/plugins/cmd/ndpgen/testdata/math_client_expected.py
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the Math host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/hostgen/testdata/math_client_expected.rs b/plugins/cmd/ndpgen/testdata/math_client_expected.rs
similarity index 96%
rename from plugins/cmd/hostgen/testdata/math_client_expected.rs
rename to plugins/cmd/ndpgen/testdata/math_client_expected.rs
index 4bfa3bd12..fde6a7cb9 100644
--- a/plugins/cmd/hostgen/testdata/math_client_expected.rs
+++ b/plugins/cmd/ndpgen/testdata/math_client_expected.rs
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Math host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/hostgen/testdata/math_expected.go b/plugins/cmd/ndpgen/testdata/math_expected.go.txt
similarity index 98%
rename from plugins/cmd/hostgen/testdata/math_expected.go
rename to plugins/cmd/ndpgen/testdata/math_expected.go.txt
index 6fc55aeef..48a2bf875 100644
--- a/plugins/cmd/hostgen/testdata/math_expected.go
+++ b/plugins/cmd/ndpgen/testdata/math_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package testpkg
diff --git a/plugins/cmd/hostgen/testdata/math_service.go b/plugins/cmd/ndpgen/testdata/math_service.go.txt
similarity index 100%
rename from plugins/cmd/hostgen/testdata/math_service.go
rename to plugins/cmd/ndpgen/testdata/math_service.go.txt
diff --git a/plugins/cmd/hostgen/testdata/meta_client_expected.go b/plugins/cmd/ndpgen/testdata/meta_client_expected.go.txt
similarity index 98%
rename from plugins/cmd/hostgen/testdata/meta_client_expected.go
rename to plugins/cmd/ndpgen/testdata/meta_client_expected.go.txt
index fb61c327e..40f4d7ecf 100644
--- a/plugins/cmd/hostgen/testdata/meta_client_expected.go
+++ b/plugins/cmd/ndpgen/testdata/meta_client_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Meta host service.
// It is intended for use in Navidrome plugins built with TinyGo.
diff --git a/plugins/cmd/hostgen/testdata/meta_client_expected.py b/plugins/cmd/ndpgen/testdata/meta_client_expected.py
similarity index 98%
rename from plugins/cmd/hostgen/testdata/meta_client_expected.py
rename to plugins/cmd/ndpgen/testdata/meta_client_expected.py
index e132fb334..4d20c73ff 100644
--- a/plugins/cmd/hostgen/testdata/meta_client_expected.py
+++ b/plugins/cmd/ndpgen/testdata/meta_client_expected.py
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the Meta host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/hostgen/testdata/meta_client_expected.rs b/plugins/cmd/ndpgen/testdata/meta_client_expected.rs
similarity index 97%
rename from plugins/cmd/hostgen/testdata/meta_client_expected.rs
rename to plugins/cmd/ndpgen/testdata/meta_client_expected.rs
index 807b4895d..79b95ffb3 100644
--- a/plugins/cmd/hostgen/testdata/meta_client_expected.rs
+++ b/plugins/cmd/ndpgen/testdata/meta_client_expected.rs
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Meta host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/hostgen/testdata/meta_expected.go b/plugins/cmd/ndpgen/testdata/meta_expected.go.txt
similarity index 98%
rename from plugins/cmd/hostgen/testdata/meta_expected.go
rename to plugins/cmd/ndpgen/testdata/meta_expected.go.txt
index cea15a200..6f660fd65 100644
--- a/plugins/cmd/hostgen/testdata/meta_expected.go
+++ b/plugins/cmd/ndpgen/testdata/meta_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package testpkg
diff --git a/plugins/cmd/hostgen/testdata/meta_service.go b/plugins/cmd/ndpgen/testdata/meta_service.go.txt
similarity index 100%
rename from plugins/cmd/hostgen/testdata/meta_service.go
rename to plugins/cmd/ndpgen/testdata/meta_service.go.txt
diff --git a/plugins/cmd/hostgen/testdata/ping_client_expected.go b/plugins/cmd/ndpgen/testdata/ping_client_expected.go.txt
similarity index 96%
rename from plugins/cmd/hostgen/testdata/ping_client_expected.go
rename to plugins/cmd/ndpgen/testdata/ping_client_expected.go.txt
index 6adc4c798..e94e7e735 100644
--- a/plugins/cmd/hostgen/testdata/ping_client_expected.go
+++ b/plugins/cmd/ndpgen/testdata/ping_client_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Ping host service.
// It is intended for use in Navidrome plugins built with TinyGo.
diff --git a/plugins/cmd/hostgen/testdata/ping_client_expected.py b/plugins/cmd/ndpgen/testdata/ping_client_expected.py
similarity index 96%
rename from plugins/cmd/hostgen/testdata/ping_client_expected.py
rename to plugins/cmd/ndpgen/testdata/ping_client_expected.py
index 492cc436c..4c7d41d8e 100644
--- a/plugins/cmd/hostgen/testdata/ping_client_expected.py
+++ b/plugins/cmd/ndpgen/testdata/ping_client_expected.py
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the Ping host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/hostgen/testdata/ping_client_expected.rs b/plugins/cmd/ndpgen/testdata/ping_client_expected.rs
similarity index 94%
rename from plugins/cmd/hostgen/testdata/ping_client_expected.rs
rename to plugins/cmd/ndpgen/testdata/ping_client_expected.rs
index 0b5e11eba..a40f10843 100644
--- a/plugins/cmd/hostgen/testdata/ping_client_expected.rs
+++ b/plugins/cmd/ndpgen/testdata/ping_client_expected.rs
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Ping host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/hostgen/testdata/ping_expected.go b/plugins/cmd/ndpgen/testdata/ping_expected.go.txt
similarity index 97%
rename from plugins/cmd/hostgen/testdata/ping_expected.go
rename to plugins/cmd/ndpgen/testdata/ping_expected.go.txt
index dff52b03e..0b0253817 100644
--- a/plugins/cmd/hostgen/testdata/ping_expected.go
+++ b/plugins/cmd/ndpgen/testdata/ping_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package testpkg
diff --git a/plugins/cmd/hostgen/testdata/ping_service.go b/plugins/cmd/ndpgen/testdata/ping_service.go.txt
similarity index 100%
rename from plugins/cmd/hostgen/testdata/ping_service.go
rename to plugins/cmd/ndpgen/testdata/ping_service.go.txt
diff --git a/plugins/cmd/hostgen/testdata/search_client_expected.go b/plugins/cmd/ndpgen/testdata/search_client_expected.go.txt
similarity index 97%
rename from plugins/cmd/hostgen/testdata/search_client_expected.go
rename to plugins/cmd/ndpgen/testdata/search_client_expected.go.txt
index 7e311856a..c4d707bef 100644
--- a/plugins/cmd/hostgen/testdata/search_client_expected.go
+++ b/plugins/cmd/ndpgen/testdata/search_client_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Search host service.
// It is intended for use in Navidrome plugins built with TinyGo.
diff --git a/plugins/cmd/hostgen/testdata/search_client_expected.py b/plugins/cmd/ndpgen/testdata/search_client_expected.py
similarity index 97%
rename from plugins/cmd/hostgen/testdata/search_client_expected.py
rename to plugins/cmd/ndpgen/testdata/search_client_expected.py
index 87cb3f9cd..aa2e98a36 100644
--- a/plugins/cmd/hostgen/testdata/search_client_expected.py
+++ b/plugins/cmd/ndpgen/testdata/search_client_expected.py
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the Search host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/hostgen/testdata/search_client_expected.rs b/plugins/cmd/ndpgen/testdata/search_client_expected.rs
similarity index 96%
rename from plugins/cmd/hostgen/testdata/search_client_expected.rs
rename to plugins/cmd/ndpgen/testdata/search_client_expected.rs
index a6fa1bfc1..b0ab2505a 100644
--- a/plugins/cmd/hostgen/testdata/search_client_expected.rs
+++ b/plugins/cmd/ndpgen/testdata/search_client_expected.rs
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Search host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/hostgen/testdata/search_expected.go b/plugins/cmd/ndpgen/testdata/search_expected.go.txt
similarity index 98%
rename from plugins/cmd/hostgen/testdata/search_expected.go
rename to plugins/cmd/ndpgen/testdata/search_expected.go.txt
index 17f634085..6c316266f 100644
--- a/plugins/cmd/hostgen/testdata/search_expected.go
+++ b/plugins/cmd/ndpgen/testdata/search_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package testpkg
diff --git a/plugins/cmd/hostgen/testdata/search_service.go b/plugins/cmd/ndpgen/testdata/search_service.go.txt
similarity index 100%
rename from plugins/cmd/hostgen/testdata/search_service.go
rename to plugins/cmd/ndpgen/testdata/search_service.go.txt
diff --git a/plugins/cmd/hostgen/testdata/store_client_expected.go b/plugins/cmd/ndpgen/testdata/store_client_expected.go.txt
similarity index 97%
rename from plugins/cmd/hostgen/testdata/store_client_expected.go
rename to plugins/cmd/ndpgen/testdata/store_client_expected.go.txt
index 153813e95..eec5156d6 100644
--- a/plugins/cmd/hostgen/testdata/store_client_expected.go
+++ b/plugins/cmd/ndpgen/testdata/store_client_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Store host service.
// It is intended for use in Navidrome plugins built with TinyGo.
diff --git a/plugins/cmd/hostgen/testdata/store_client_expected.py b/plugins/cmd/ndpgen/testdata/store_client_expected.py
similarity index 97%
rename from plugins/cmd/hostgen/testdata/store_client_expected.py
rename to plugins/cmd/ndpgen/testdata/store_client_expected.py
index 5f1e6a2b0..4a964a497 100644
--- a/plugins/cmd/hostgen/testdata/store_client_expected.py
+++ b/plugins/cmd/ndpgen/testdata/store_client_expected.py
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the Store host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/hostgen/testdata/store_client_expected.rs b/plugins/cmd/ndpgen/testdata/store_client_expected.rs
similarity index 96%
rename from plugins/cmd/hostgen/testdata/store_client_expected.rs
rename to plugins/cmd/ndpgen/testdata/store_client_expected.rs
index 5ad8cdafe..25d2af2e0 100644
--- a/plugins/cmd/hostgen/testdata/store_client_expected.rs
+++ b/plugins/cmd/ndpgen/testdata/store_client_expected.rs
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Store host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/hostgen/testdata/store_expected.go b/plugins/cmd/ndpgen/testdata/store_expected.go.txt
similarity index 98%
rename from plugins/cmd/hostgen/testdata/store_expected.go
rename to plugins/cmd/ndpgen/testdata/store_expected.go.txt
index 9242c8c4b..07537ca41 100644
--- a/plugins/cmd/hostgen/testdata/store_expected.go
+++ b/plugins/cmd/ndpgen/testdata/store_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package testpkg
diff --git a/plugins/cmd/hostgen/testdata/store_service.go b/plugins/cmd/ndpgen/testdata/store_service.go.txt
similarity index 100%
rename from plugins/cmd/hostgen/testdata/store_service.go
rename to plugins/cmd/ndpgen/testdata/store_service.go.txt
diff --git a/plugins/cmd/hostgen/testdata/users_client_expected.go b/plugins/cmd/ndpgen/testdata/users_client_expected.go.txt
similarity index 97%
rename from plugins/cmd/hostgen/testdata/users_client_expected.go
rename to plugins/cmd/ndpgen/testdata/users_client_expected.go.txt
index 9f61bc447..43769d890 100644
--- a/plugins/cmd/hostgen/testdata/users_client_expected.go
+++ b/plugins/cmd/ndpgen/testdata/users_client_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Users host service.
// It is intended for use in Navidrome plugins built with TinyGo.
diff --git a/plugins/cmd/hostgen/testdata/users_client_expected.py b/plugins/cmd/ndpgen/testdata/users_client_expected.py
similarity index 97%
rename from plugins/cmd/hostgen/testdata/users_client_expected.py
rename to plugins/cmd/ndpgen/testdata/users_client_expected.py
index c89abb134..468b87b98 100644
--- a/plugins/cmd/hostgen/testdata/users_client_expected.py
+++ b/plugins/cmd/ndpgen/testdata/users_client_expected.py
@@ -1,4 +1,4 @@
-# Code generated by hostgen. DO NOT EDIT.
+# Code generated by ndpgen. DO NOT EDIT.
#
# This file contains client wrappers for the Users host service.
# It is intended for use in Navidrome plugins built with extism-py.
diff --git a/plugins/cmd/hostgen/testdata/users_client_expected.rs b/plugins/cmd/ndpgen/testdata/users_client_expected.rs
similarity index 96%
rename from plugins/cmd/hostgen/testdata/users_client_expected.rs
rename to plugins/cmd/ndpgen/testdata/users_client_expected.rs
index 081db0356..40daa9cfd 100644
--- a/plugins/cmd/hostgen/testdata/users_client_expected.rs
+++ b/plugins/cmd/ndpgen/testdata/users_client_expected.rs
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains client wrappers for the Users host service.
// It is intended for use in Navidrome plugins built with extism-pdk.
diff --git a/plugins/cmd/hostgen/testdata/users_expected.go b/plugins/cmd/ndpgen/testdata/users_expected.go.txt
similarity index 98%
rename from plugins/cmd/hostgen/testdata/users_expected.go
rename to plugins/cmd/ndpgen/testdata/users_expected.go.txt
index 417252426..1b0fbfa93 100644
--- a/plugins/cmd/hostgen/testdata/users_expected.go
+++ b/plugins/cmd/ndpgen/testdata/users_expected.go.txt
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package testpkg
diff --git a/plugins/cmd/hostgen/testdata/users_service.go b/plugins/cmd/ndpgen/testdata/users_service.go.txt
similarity index 100%
rename from plugins/cmd/hostgen/testdata/users_service.go
rename to plugins/cmd/ndpgen/testdata/users_service.go.txt
diff --git a/plugins/examples/crypto-ticker/go.mod b/plugins/examples/crypto-ticker/go.mod
index 78f28a7e7..c126c2d0a 100755
--- a/plugins/examples/crypto-ticker/go.mod
+++ b/plugins/examples/crypto-ticker/go.mod
@@ -4,7 +4,7 @@ go 1.24
require (
github.com/extism/go-pdk v1.1.3
- github.com/navidrome/navidrome/plugins/host/go v0.0.0
+ github.com/navidrome/navidrome/plugins/pdk/go/host v0.0.0
)
-replace github.com/navidrome/navidrome/plugins/host/go => ../../host/go
+replace github.com/navidrome/navidrome/plugins/pdk/go/host => ../../pdk/go/host
diff --git a/plugins/examples/crypto-ticker/main.go b/plugins/examples/crypto-ticker/main.go
index 400c8ca60..364a9a451 100755
--- a/plugins/examples/crypto-ticker/main.go
+++ b/plugins/examples/crypto-ticker/main.go
@@ -13,7 +13,7 @@ import (
"strings"
pdk "github.com/extism/go-pdk"
- ndhost "github.com/navidrome/navidrome/plugins/host/go"
+ host "github.com/navidrome/navidrome/plugins/pdk/go/host"
)
const (
@@ -116,7 +116,7 @@ func parseTickerSymbols(tickerConfig string) []string {
// connectAndSubscribe connects to Coinbase WebSocket and subscribes to tickers
func connectAndSubscribe(tickers []string) error {
// Connect to WebSocket using host function
- resp, err := ndhost.WebSocketConnect(coinbaseWSEndpoint, nil, connectionID)
+ resp, err := host.WebSocketConnect(coinbaseWSEndpoint, nil, connectionID)
if err != nil {
return fmt.Errorf("WebSocket connection error: %w", err)
}
@@ -135,7 +135,7 @@ func connectAndSubscribe(tickers []string) error {
}
// Send subscription message
- _, err = ndhost.WebSocketSendText(connectionID, string(subscriptionJSON))
+ _, err = host.WebSocketSendText(connectionID, string(subscriptionJSON))
if err != nil {
return fmt.Errorf("WebSocket send error: %w", err)
}
@@ -206,7 +206,7 @@ func NdWebsocketOnClose(input OnCloseInput) (OnCloseOutput, error) {
pdk.Log(pdk.LogInfo, "Scheduling reconnection attempt in 5 seconds...")
// Schedule a one-time reconnection attempt
- _, err := ndhost.SchedulerScheduleOneTime(5, "reconnect", reconnectScheduleID)
+ _, err := host.SchedulerScheduleOneTime(5, "reconnect", reconnectScheduleID)
if err != nil {
pdk.Log(pdk.LogError, fmt.Sprintf("Failed to schedule reconnection: %v", err))
}
@@ -257,7 +257,7 @@ func ndSchedulerCallback() int32 {
pdk.Log(pdk.LogError, fmt.Sprintf("Reconnection failed: %v - will retry in 10 seconds", err))
// Schedule another attempt
- _, err := ndhost.SchedulerScheduleOneTime(10, "reconnect", reconnectScheduleID)
+ _, err := host.SchedulerScheduleOneTime(10, "reconnect", reconnectScheduleID)
if err != nil {
pdk.Log(pdk.LogError, fmt.Sprintf("Failed to schedule retry: %v", err))
}
diff --git a/plugins/examples/discord-rich-presence-rs/Cargo.toml b/plugins/examples/discord-rich-presence-rs/Cargo.toml
index 44ec844e9..ed9f32249 100644
--- a/plugins/examples/discord-rich-presence-rs/Cargo.toml
+++ b/plugins/examples/discord-rich-presence-rs/Cargo.toml
@@ -13,4 +13,4 @@ crate-type = ["cdylib"]
extism-pdk = "1.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
-nd-host = { path = "../../host/rust" }
+nd-host = { path = "../../pdk/rust/host" }
diff --git a/plugins/examples/discord-rich-presence/go.mod b/plugins/examples/discord-rich-presence/go.mod
index bc6bf30cc..2e18b9784 100644
--- a/plugins/examples/discord-rich-presence/go.mod
+++ b/plugins/examples/discord-rich-presence/go.mod
@@ -4,7 +4,7 @@ go 1.24
require (
github.com/extism/go-pdk v1.1.3
- github.com/navidrome/navidrome/plugins/host/go v0.0.0
+ github.com/navidrome/navidrome/plugins/pdk/go/host v0.0.0
)
-replace github.com/navidrome/navidrome/plugins/host/go => ../../host/go
+replace github.com/navidrome/navidrome/plugins/pdk/go/host => ../../pdk/go/host
diff --git a/plugins/examples/discord-rich-presence/main.go b/plugins/examples/discord-rich-presence/main.go
index 8c8be5101..d2817b1ec 100644
--- a/plugins/examples/discord-rich-presence/main.go
+++ b/plugins/examples/discord-rich-presence/main.go
@@ -16,7 +16,7 @@ import (
"time"
"github.com/extism/go-pdk"
- ndhost "github.com/navidrome/navidrome/plugins/host/go"
+ host "github.com/navidrome/navidrome/plugins/pdk/go/host"
)
// Configuration keys
@@ -52,7 +52,7 @@ func getConfig() (clientID string, users map[string]string, err error) {
// getImageURL retrieves the track artwork URL.
func getImageURL(trackID string) string {
- resp, err := ndhost.ArtworkGetTrackUrl(trackID, 300)
+ resp, err := host.ArtworkGetTrackUrl(trackID, 300)
if err != nil {
pdk.Log(pdk.LogWarn, fmt.Sprintf("Failed to get artwork URL: %v", err))
return ""
@@ -105,7 +105,7 @@ func NdScrobblerNowPlaying(input NowPlayingInput) (ScrobblerOutput, error) {
}
// Cancel any existing completion schedule
- _, _ = ndhost.SchedulerCancelSchedule(fmt.Sprintf("%s-clear", input.Username))
+ _, _ = host.SchedulerCancelSchedule(fmt.Sprintf("%s-clear", input.Username))
// Calculate timestamps
now := time.Now().Unix()
@@ -134,7 +134,7 @@ func NdScrobblerNowPlaying(input NowPlayingInput) (ScrobblerOutput, error) {
// Schedule a timer to clear the activity after the track completes
remainingSeconds := int32(input.Track.Duration) - input.Position + 5
- _, err = ndhost.SchedulerScheduleOneTime(remainingSeconds, payloadClearActivity, fmt.Sprintf("%s-clear", input.Username))
+ _, err = host.SchedulerScheduleOneTime(remainingSeconds, payloadClearActivity, fmt.Sprintf("%s-clear", input.Username))
if err != nil {
pdk.Log(pdk.LogWarn, fmt.Sprintf("Failed to schedule completion timer: %v", err))
}
diff --git a/plugins/examples/discord-rich-presence/rpc.go b/plugins/examples/discord-rich-presence/rpc.go
index e189a5aab..cbc0da6ff 100644
--- a/plugins/examples/discord-rich-presence/rpc.go
+++ b/plugins/examples/discord-rich-presence/rpc.go
@@ -11,7 +11,7 @@ import (
"time"
"github.com/extism/go-pdk"
- ndhost "github.com/navidrome/navidrome/plugins/host/go"
+ host "github.com/navidrome/navidrome/plugins/pdk/go/host"
)
// Discord WebSocket Gateway constants
@@ -89,7 +89,7 @@ func processImage(imageURL, clientID, token string, isDefaultImage bool) (string
// Check cache first
cacheKey := fmt.Sprintf("discord.image.%x", imageURL)
- cacheResp, err := ndhost.CacheGetString(cacheKey)
+ cacheResp, err := host.CacheGetString(cacheKey)
if err == nil && cacheResp.Exists {
pdk.Log(pdk.LogDebug, fmt.Sprintf("Cache hit for image URL: %s", imageURL))
return cacheResp.Value, nil
@@ -141,7 +141,7 @@ func processImage(imageURL, clientID, token string, isDefaultImage bool) (string
ttl = 48 * 60 * 60 // 48 hours for default image
}
- _, _ = ndhost.CacheSetString(cacheKey, processedImage, ttl)
+ _, _ = host.CacheSetString(cacheKey, processedImage, ttl)
pdk.Log(pdk.LogDebug, fmt.Sprintf("Cached processed image URL for %s (TTL: %ds)", imageURL, ttl))
return processedImage, nil
@@ -184,7 +184,7 @@ func sendMessage(username string, opCode int, payload any) error {
return fmt.Errorf("failed to marshal message: %w", err)
}
- _, err = ndhost.WebSocketSendText(username, string(b))
+ _, err = host.WebSocketSendText(username, string(b))
if err != nil {
return fmt.Errorf("failed to send message: %w", err)
}
@@ -208,7 +208,7 @@ func getDiscordGateway() (string, error) {
// sendHeartbeat sends a heartbeat to Discord.
func sendHeartbeat(username string) error {
- cacheResp, err := ndhost.CacheGetInt(fmt.Sprintf("discord.seq.%s", username))
+ cacheResp, err := host.CacheGetInt(fmt.Sprintf("discord.seq.%s", username))
if err != nil {
return fmt.Errorf("failed to get sequence number: %w", err)
}
@@ -222,17 +222,17 @@ func cleanupFailedConnection(username string) {
pdk.Log(pdk.LogInfo, fmt.Sprintf("Cleaning up failed connection for user %s", username))
// Cancel the heartbeat schedule
- if _, err := ndhost.SchedulerCancelSchedule(username); err != nil {
+ if _, err := host.SchedulerCancelSchedule(username); err != nil {
pdk.Log(pdk.LogWarn, fmt.Sprintf("Failed to cancel heartbeat schedule for user %s: %v", username, err))
}
// Close the WebSocket connection
- if _, err := ndhost.WebSocketCloseConnection(username, 1000, "Connection lost"); err != nil {
+ if _, err := host.WebSocketCloseConnection(username, 1000, "Connection lost"); err != nil {
pdk.Log(pdk.LogWarn, fmt.Sprintf("Failed to close WebSocket connection for user %s: %v", username, err))
}
// Clean up cache entries
- _, _ = ndhost.CacheRemove(fmt.Sprintf("discord.seq.%s", username))
+ _, _ = host.CacheRemove(fmt.Sprintf("discord.seq.%s", username))
pdk.Log(pdk.LogInfo, fmt.Sprintf("Cleaned up connection for user %s", username))
}
@@ -263,7 +263,7 @@ func connect(username, token string) error {
pdk.Log(pdk.LogDebug, fmt.Sprintf("Using gateway: %s", gateway))
// Connect to Discord Gateway
- _, err = ndhost.WebSocketConnect(gateway, nil, username)
+ _, err = host.WebSocketConnect(gateway, nil, username)
if err != nil {
return fmt.Errorf("failed to connect to WebSocket: %w", err)
}
@@ -284,7 +284,7 @@ func connect(username, token string) error {
// Schedule heartbeats for this user/connection
cronExpr := fmt.Sprintf("@every %ds", heartbeatInterval)
- schedResp, err := ndhost.SchedulerScheduleRecurring(cronExpr, payloadHeartbeat, username)
+ schedResp, err := host.SchedulerScheduleRecurring(cronExpr, payloadHeartbeat, username)
if err != nil {
return fmt.Errorf("failed to schedule heartbeat: %w", err)
}
@@ -296,11 +296,11 @@ func connect(username, token string) error {
// disconnect closes the Discord connection for a user.
func disconnect(username string) error {
- if _, err := ndhost.SchedulerCancelSchedule(username); err != nil {
+ if _, err := host.SchedulerCancelSchedule(username); err != nil {
return fmt.Errorf("failed to cancel schedule: %w", err)
}
- if _, err := ndhost.WebSocketCloseConnection(username, 1000, "Navidrome disconnect"); err != nil {
+ if _, err := host.WebSocketCloseConnection(username, 1000, "Navidrome disconnect"); err != nil {
return fmt.Errorf("failed to close WebSocket connection: %w", err)
}
return nil
@@ -324,7 +324,7 @@ func handleWebSocketMessage(connectionID, message string) error {
if v := msg["s"]; v != nil {
seq := int64(v.(float64))
pdk.Log(pdk.LogTrace, fmt.Sprintf("Received sequence number for connection '%s': %d", connectionID, seq))
- if _, err := ndhost.CacheSetInt(fmt.Sprintf("discord.seq.%s", connectionID), seq, int64(heartbeatInterval*2)); err != nil {
+ if _, err := host.CacheSetInt(fmt.Sprintf("discord.seq.%s", connectionID), seq, int64(heartbeatInterval*2)); err != nil {
return fmt.Errorf("failed to store sequence number for user %s: %w", connectionID, err)
}
}
diff --git a/plugins/examples/library-inspector/Cargo.toml b/plugins/examples/library-inspector/Cargo.toml
index 2d4b5e70c..f2915e8f6 100644
--- a/plugins/examples/library-inspector/Cargo.toml
+++ b/plugins/examples/library-inspector/Cargo.toml
@@ -11,6 +11,6 @@ crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1.2"
-nd-host = { path = "../../host/rust" }
+nd-host = { path = "../../pdk/rust/host" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
diff --git a/plugins/host/artwork_gen.go b/plugins/host/artwork_gen.go
index ff76ba7cf..fbf807351 100644
--- a/plugins/host/artwork_gen.go
+++ b/plugins/host/artwork_gen.go
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package host
diff --git a/plugins/host/cache_gen.go b/plugins/host/cache_gen.go
index 792f7420c..5645c5495 100644
--- a/plugins/host/cache_gen.go
+++ b/plugins/host/cache_gen.go
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package host
diff --git a/plugins/host/doc.go b/plugins/host/doc.go
index 905060405..34917d74e 100644
--- a/plugins/host/doc.go
+++ b/plugins/host/doc.go
@@ -27,7 +27,7 @@
//
// # Generated Code
//
-// The hostgen tool reads annotated interfaces and generates Extism host function wrappers
+// The ndpgen tool reads annotated interfaces and generates Extism host function wrappers
// that handle:
// - JSON serialization/deserialization of request/response types
// - Memory operations (ReadBytes, WriteBytes, Alloc)
@@ -37,5 +37,5 @@
// Generated files follow the pattern _gen.go and include a header comment
// indicating they should not be edited manually.
//
-//go:generate go run ../cmd/hostgen -input=. -output=. -python -go -rust
+//go:generate go run ../cmd/ndpgen -input=. -output=../pdk -go -python -rust
package host
diff --git a/plugins/host/go/doc.go b/plugins/host/go/doc.go
deleted file mode 100644
index 211368063..000000000
--- a/plugins/host/go/doc.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-
-/*
-Package ndhost provides Navidrome host function wrappers for Go/TinyGo plugins.
-
-This package is auto-generated by the hostgen tool and should not be edited manually.
-
-# Usage
-
-Add this module as a dependency in your plugin's go.mod:
-
- require github.com/navidrome/navidrome/plugins/host/go v0.0.0
-
-Then import the package in your plugin code:
-
- import ndhost "github.com/navidrome/navidrome/plugins/host/go"
-
- func myPluginFunction() error {
- // Use the cache service
- _, err := ndhost.CacheSetString("my_key", "my_value", 3600)
- if err != nil {
- return err
- }
-
- // Schedule a recurring task
- _, err = ndhost.SchedulerScheduleRecurring("@every 5m", "payload", "task_id")
- if err != nil {
- return err
- }
-
- return nil
- }
-
-# Available Services
-
-The following host services are available:
-
- - Artwork: provides artwork URL generation capabilities for plugins.
- - Cache: provides in-memory TTL-based caching capabilities for plugins.
- - KVStore: provides persistent key-value storage for plugins.
- - Library: provides access to music library metadata for plugins.
- - Scheduler: provides task scheduling capabilities for plugins.
- - SubsonicAPI: provides access to Navidrome's Subsonic API from plugins.
- - WebSocket: provides WebSocket communication capabilities for plugins.
-
-# Building Plugins
-
-Go plugins must be compiled to WebAssembly using TinyGo:
-
- tinygo build -o plugin.wasm -target=wasip1 -buildmode=c-shared .
-
-See the examples directory for complete plugin implementations.
-*/
-package ndhost
diff --git a/plugins/host/go/go.mod b/plugins/host/go/go.mod
deleted file mode 100644
index 954c98071..000000000
--- a/plugins/host/go/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module github.com/navidrome/navidrome/plugins/host/go
-
-go 1.24
-
-require github.com/extism/go-pdk v1.1.3
diff --git a/plugins/host/go/go.sum b/plugins/host/go/go.sum
deleted file mode 100644
index bf78d8808..000000000
--- a/plugins/host/go/go.sum
+++ /dev/null
@@ -1,3 +0,0 @@
-github.com/extism/go-pdk v1.1.0 h1:K2On6XOERxrYdsgu0uLzCxeu/FYRHE8jId/hdEVSYoY=
-github.com/extism/go-pdk v1.1.0/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4=
-github.com/extism/go-pdk v1.1.3/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4=
diff --git a/plugins/host/go/nd_host_artwork.go b/plugins/host/go/nd_host_artwork.go
deleted file mode 100644
index 51811ea5a..000000000
--- a/plugins/host/go/nd_host_artwork.go
+++ /dev/null
@@ -1,251 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the Artwork host service.
-// It is intended for use in Navidrome plugins built with TinyGo.
-//
-//go:build wasip1
-
-package ndhost
-
-import (
- "encoding/json"
- "errors"
-
- "github.com/extism/go-pdk"
-)
-
-// artwork_getartisturl is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user artwork_getartisturl
-func artwork_getartisturl(uint64) uint64
-
-// artwork_getalbumurl is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user artwork_getalbumurl
-func artwork_getalbumurl(uint64) uint64
-
-// artwork_gettrackurl is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user artwork_gettrackurl
-func artwork_gettrackurl(uint64) uint64
-
-// artwork_getplaylisturl is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user artwork_getplaylisturl
-func artwork_getplaylisturl(uint64) uint64
-
-// ArtworkGetArtistUrlRequest is the request type for Artwork.GetArtistUrl.
-type ArtworkGetArtistUrlRequest struct {
- Id string `json:"id"`
- Size int32 `json:"size"`
-}
-
-// ArtworkGetArtistUrlResponse is the response type for Artwork.GetArtistUrl.
-type ArtworkGetArtistUrlResponse struct {
- Url string `json:"url,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// ArtworkGetAlbumUrlRequest is the request type for Artwork.GetAlbumUrl.
-type ArtworkGetAlbumUrlRequest struct {
- Id string `json:"id"`
- Size int32 `json:"size"`
-}
-
-// ArtworkGetAlbumUrlResponse is the response type for Artwork.GetAlbumUrl.
-type ArtworkGetAlbumUrlResponse struct {
- Url string `json:"url,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// ArtworkGetTrackUrlRequest is the request type for Artwork.GetTrackUrl.
-type ArtworkGetTrackUrlRequest struct {
- Id string `json:"id"`
- Size int32 `json:"size"`
-}
-
-// ArtworkGetTrackUrlResponse is the response type for Artwork.GetTrackUrl.
-type ArtworkGetTrackUrlResponse struct {
- Url string `json:"url,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// ArtworkGetPlaylistUrlRequest is the request type for Artwork.GetPlaylistUrl.
-type ArtworkGetPlaylistUrlRequest struct {
- Id string `json:"id"`
- Size int32 `json:"size"`
-}
-
-// ArtworkGetPlaylistUrlResponse is the response type for Artwork.GetPlaylistUrl.
-type ArtworkGetPlaylistUrlResponse struct {
- Url string `json:"url,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// ArtworkGetArtistUrl calls the artwork_getartisturl host function.
-// GetArtistUrl generates a public URL for an artist's artwork.
-//
-// Parameters:
-// - id: The artist's unique identifier
-// - size: Desired image size in pixels (0 for original size)
-//
-// Returns the public URL for the artwork, or an error if generation fails.
-func ArtworkGetArtistUrl(id string, size int32) (*ArtworkGetArtistUrlResponse, error) {
- // Marshal request to JSON
- req := ArtworkGetArtistUrlRequest{
- Id: id,
- Size: size,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := artwork_getartisturl(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response ArtworkGetArtistUrlResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// ArtworkGetAlbumUrl calls the artwork_getalbumurl host function.
-// GetAlbumUrl generates a public URL for an album's artwork.
-//
-// Parameters:
-// - id: The album's unique identifier
-// - size: Desired image size in pixels (0 for original size)
-//
-// Returns the public URL for the artwork, or an error if generation fails.
-func ArtworkGetAlbumUrl(id string, size int32) (*ArtworkGetAlbumUrlResponse, error) {
- // Marshal request to JSON
- req := ArtworkGetAlbumUrlRequest{
- Id: id,
- Size: size,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := artwork_getalbumurl(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response ArtworkGetAlbumUrlResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// ArtworkGetTrackUrl calls the artwork_gettrackurl host function.
-// GetTrackUrl generates a public URL for a track's artwork.
-//
-// Parameters:
-// - id: The track's (media file) unique identifier
-// - size: Desired image size in pixels (0 for original size)
-//
-// Returns the public URL for the artwork, or an error if generation fails.
-func ArtworkGetTrackUrl(id string, size int32) (*ArtworkGetTrackUrlResponse, error) {
- // Marshal request to JSON
- req := ArtworkGetTrackUrlRequest{
- Id: id,
- Size: size,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := artwork_gettrackurl(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response ArtworkGetTrackUrlResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// ArtworkGetPlaylistUrl calls the artwork_getplaylisturl host function.
-// GetPlaylistUrl generates a public URL for a playlist's artwork.
-//
-// Parameters:
-// - id: The playlist's unique identifier
-// - size: Desired image size in pixels (0 for original size)
-//
-// Returns the public URL for the artwork, or an error if generation fails.
-func ArtworkGetPlaylistUrl(id string, size int32) (*ArtworkGetPlaylistUrlResponse, error) {
- // Marshal request to JSON
- req := ArtworkGetPlaylistUrlRequest{
- Id: id,
- Size: size,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := artwork_getplaylisturl(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response ArtworkGetPlaylistUrlResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
diff --git a/plugins/host/go/nd_host_artwork_stub.go b/plugins/host/go/nd_host_artwork_stub.go
deleted file mode 100644
index 9bf0b1352..000000000
--- a/plugins/host/go/nd_host_artwork_stub.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains stub implementations for non-WASM builds.
-// These stubs allow IDE support and compilation on non-WASM platforms.
-// They panic at runtime since host functions are only available in WASM plugins.
-//
-//go:build !wasip1
-
-package ndhost
-
-// ArtworkGetArtistUrlRequest is the request type for Artwork.GetArtistUrl.
-type ArtworkGetArtistUrlRequest struct {
- Id string `json:"id"`
- Size int32 `json:"size"`
-}
-
-// ArtworkGetArtistUrlResponse is the response type for Artwork.GetArtistUrl.
-type ArtworkGetArtistUrlResponse struct {
- Url string `json:"url,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// ArtworkGetAlbumUrlRequest is the request type for Artwork.GetAlbumUrl.
-type ArtworkGetAlbumUrlRequest struct {
- Id string `json:"id"`
- Size int32 `json:"size"`
-}
-
-// ArtworkGetAlbumUrlResponse is the response type for Artwork.GetAlbumUrl.
-type ArtworkGetAlbumUrlResponse struct {
- Url string `json:"url,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// ArtworkGetTrackUrlRequest is the request type for Artwork.GetTrackUrl.
-type ArtworkGetTrackUrlRequest struct {
- Id string `json:"id"`
- Size int32 `json:"size"`
-}
-
-// ArtworkGetTrackUrlResponse is the response type for Artwork.GetTrackUrl.
-type ArtworkGetTrackUrlResponse struct {
- Url string `json:"url,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// ArtworkGetPlaylistUrlRequest is the request type for Artwork.GetPlaylistUrl.
-type ArtworkGetPlaylistUrlRequest struct {
- Id string `json:"id"`
- Size int32 `json:"size"`
-}
-
-// ArtworkGetPlaylistUrlResponse is the response type for Artwork.GetPlaylistUrl.
-type ArtworkGetPlaylistUrlResponse struct {
- Url string `json:"url,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// ArtworkGetArtistUrl is a stub that panics on non-WASM platforms.
-// GetArtistUrl generates a public URL for an artist's artwork.
-//
-// Parameters:
-// - id: The artist's unique identifier
-// - size: Desired image size in pixels (0 for original size)
-//
-// Returns the public URL for the artwork, or an error if generation fails.
-func ArtworkGetArtistUrl(id string, size int32) (*ArtworkGetArtistUrlResponse, error) {
- panic("ndhost: ArtworkGetArtistUrl is only available in WASM plugins")
-}
-
-// ArtworkGetAlbumUrl is a stub that panics on non-WASM platforms.
-// GetAlbumUrl generates a public URL for an album's artwork.
-//
-// Parameters:
-// - id: The album's unique identifier
-// - size: Desired image size in pixels (0 for original size)
-//
-// Returns the public URL for the artwork, or an error if generation fails.
-func ArtworkGetAlbumUrl(id string, size int32) (*ArtworkGetAlbumUrlResponse, error) {
- panic("ndhost: ArtworkGetAlbumUrl is only available in WASM plugins")
-}
-
-// ArtworkGetTrackUrl is a stub that panics on non-WASM platforms.
-// GetTrackUrl generates a public URL for a track's artwork.
-//
-// Parameters:
-// - id: The track's (media file) unique identifier
-// - size: Desired image size in pixels (0 for original size)
-//
-// Returns the public URL for the artwork, or an error if generation fails.
-func ArtworkGetTrackUrl(id string, size int32) (*ArtworkGetTrackUrlResponse, error) {
- panic("ndhost: ArtworkGetTrackUrl is only available in WASM plugins")
-}
-
-// ArtworkGetPlaylistUrl is a stub that panics on non-WASM platforms.
-// GetPlaylistUrl generates a public URL for a playlist's artwork.
-//
-// Parameters:
-// - id: The playlist's unique identifier
-// - size: Desired image size in pixels (0 for original size)
-//
-// Returns the public URL for the artwork, or an error if generation fails.
-func ArtworkGetPlaylistUrl(id string, size int32) (*ArtworkGetPlaylistUrlResponse, error) {
- panic("ndhost: ArtworkGetPlaylistUrl is only available in WASM plugins")
-}
diff --git a/plugins/host/go/nd_host_cache.go b/plugins/host/go/nd_host_cache.go
deleted file mode 100644
index ee4183820..000000000
--- a/plugins/host/go/nd_host_cache.go
+++ /dev/null
@@ -1,602 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the Cache host service.
-// It is intended for use in Navidrome plugins built with TinyGo.
-//
-//go:build wasip1
-
-package ndhost
-
-import (
- "encoding/json"
- "errors"
-
- "github.com/extism/go-pdk"
-)
-
-// cache_setstring is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user cache_setstring
-func cache_setstring(uint64) uint64
-
-// cache_getstring is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user cache_getstring
-func cache_getstring(uint64) uint64
-
-// cache_setint is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user cache_setint
-func cache_setint(uint64) uint64
-
-// cache_getint is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user cache_getint
-func cache_getint(uint64) uint64
-
-// cache_setfloat is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user cache_setfloat
-func cache_setfloat(uint64) uint64
-
-// cache_getfloat is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user cache_getfloat
-func cache_getfloat(uint64) uint64
-
-// cache_setbytes is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user cache_setbytes
-func cache_setbytes(uint64) uint64
-
-// cache_getbytes is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user cache_getbytes
-func cache_getbytes(uint64) uint64
-
-// cache_has is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user cache_has
-func cache_has(uint64) uint64
-
-// cache_remove is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user cache_remove
-func cache_remove(uint64) uint64
-
-// CacheSetStringRequest is the request type for Cache.SetString.
-type CacheSetStringRequest struct {
- Key string `json:"key"`
- Value string `json:"value"`
- TtlSeconds int64 `json:"ttlSeconds"`
-}
-
-// CacheSetStringResponse is the response type for Cache.SetString.
-type CacheSetStringResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// CacheGetStringRequest is the request type for Cache.GetString.
-type CacheGetStringRequest struct {
- Key string `json:"key"`
-}
-
-// CacheGetStringResponse is the response type for Cache.GetString.
-type CacheGetStringResponse struct {
- Value string `json:"value,omitempty"`
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// CacheSetIntRequest is the request type for Cache.SetInt.
-type CacheSetIntRequest struct {
- Key string `json:"key"`
- Value int64 `json:"value"`
- TtlSeconds int64 `json:"ttlSeconds"`
-}
-
-// CacheSetIntResponse is the response type for Cache.SetInt.
-type CacheSetIntResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// CacheGetIntRequest is the request type for Cache.GetInt.
-type CacheGetIntRequest struct {
- Key string `json:"key"`
-}
-
-// CacheGetIntResponse is the response type for Cache.GetInt.
-type CacheGetIntResponse struct {
- Value int64 `json:"value,omitempty"`
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// CacheSetFloatRequest is the request type for Cache.SetFloat.
-type CacheSetFloatRequest struct {
- Key string `json:"key"`
- Value float64 `json:"value"`
- TtlSeconds int64 `json:"ttlSeconds"`
-}
-
-// CacheSetFloatResponse is the response type for Cache.SetFloat.
-type CacheSetFloatResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// CacheGetFloatRequest is the request type for Cache.GetFloat.
-type CacheGetFloatRequest struct {
- Key string `json:"key"`
-}
-
-// CacheGetFloatResponse is the response type for Cache.GetFloat.
-type CacheGetFloatResponse struct {
- Value float64 `json:"value,omitempty"`
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// CacheSetBytesRequest is the request type for Cache.SetBytes.
-type CacheSetBytesRequest struct {
- Key string `json:"key"`
- Value []byte `json:"value"`
- TtlSeconds int64 `json:"ttlSeconds"`
-}
-
-// CacheSetBytesResponse is the response type for Cache.SetBytes.
-type CacheSetBytesResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// CacheGetBytesRequest is the request type for Cache.GetBytes.
-type CacheGetBytesRequest struct {
- Key string `json:"key"`
-}
-
-// CacheGetBytesResponse is the response type for Cache.GetBytes.
-type CacheGetBytesResponse struct {
- Value []byte `json:"value,omitempty"`
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// CacheHasRequest is the request type for Cache.Has.
-type CacheHasRequest struct {
- Key string `json:"key"`
-}
-
-// CacheHasResponse is the response type for Cache.Has.
-type CacheHasResponse struct {
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// CacheRemoveRequest is the request type for Cache.Remove.
-type CacheRemoveRequest struct {
- Key string `json:"key"`
-}
-
-// CacheRemoveResponse is the response type for Cache.Remove.
-type CacheRemoveResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// CacheSetString calls the cache_setstring host function.
-// SetString stores a string value in the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-// - value: The string value to store
-// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-//
-// Returns an error if the operation fails.
-func CacheSetString(key string, value string, ttlSeconds int64) (*CacheSetStringResponse, error) {
- // Marshal request to JSON
- req := CacheSetStringRequest{
- Key: key,
- Value: value,
- TtlSeconds: ttlSeconds,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := cache_setstring(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response CacheSetStringResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// CacheGetString calls the cache_getstring host function.
-// GetString retrieves a string value from the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns the value and whether the key exists. If the key doesn't exist
-// or the stored value is not a string, exists will be false.
-func CacheGetString(key string) (*CacheGetStringResponse, error) {
- // Marshal request to JSON
- req := CacheGetStringRequest{
- Key: key,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := cache_getstring(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response CacheGetStringResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// CacheSetInt calls the cache_setint host function.
-// SetInt stores an integer value in the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-// - value: The integer value to store
-// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-//
-// Returns an error if the operation fails.
-func CacheSetInt(key string, value int64, ttlSeconds int64) (*CacheSetIntResponse, error) {
- // Marshal request to JSON
- req := CacheSetIntRequest{
- Key: key,
- Value: value,
- TtlSeconds: ttlSeconds,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := cache_setint(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response CacheSetIntResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// CacheGetInt calls the cache_getint host function.
-// GetInt retrieves an integer value from the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns the value and whether the key exists. If the key doesn't exist
-// or the stored value is not an integer, exists will be false.
-func CacheGetInt(key string) (*CacheGetIntResponse, error) {
- // Marshal request to JSON
- req := CacheGetIntRequest{
- Key: key,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := cache_getint(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response CacheGetIntResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// CacheSetFloat calls the cache_setfloat host function.
-// SetFloat stores a float value in the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-// - value: The float value to store
-// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-//
-// Returns an error if the operation fails.
-func CacheSetFloat(key string, value float64, ttlSeconds int64) (*CacheSetFloatResponse, error) {
- // Marshal request to JSON
- req := CacheSetFloatRequest{
- Key: key,
- Value: value,
- TtlSeconds: ttlSeconds,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := cache_setfloat(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response CacheSetFloatResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// CacheGetFloat calls the cache_getfloat host function.
-// GetFloat retrieves a float value from the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns the value and whether the key exists. If the key doesn't exist
-// or the stored value is not a float, exists will be false.
-func CacheGetFloat(key string) (*CacheGetFloatResponse, error) {
- // Marshal request to JSON
- req := CacheGetFloatRequest{
- Key: key,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := cache_getfloat(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response CacheGetFloatResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// CacheSetBytes calls the cache_setbytes host function.
-// SetBytes stores a byte slice in the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-// - value: The byte slice to store
-// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-//
-// Returns an error if the operation fails.
-func CacheSetBytes(key string, value []byte, ttlSeconds int64) (*CacheSetBytesResponse, error) {
- // Marshal request to JSON
- req := CacheSetBytesRequest{
- Key: key,
- Value: value,
- TtlSeconds: ttlSeconds,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := cache_setbytes(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response CacheSetBytesResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// CacheGetBytes calls the cache_getbytes host function.
-// GetBytes retrieves a byte slice from the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns the value and whether the key exists. If the key doesn't exist
-// or the stored value is not a byte slice, exists will be false.
-func CacheGetBytes(key string) (*CacheGetBytesResponse, error) {
- // Marshal request to JSON
- req := CacheGetBytesRequest{
- Key: key,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := cache_getbytes(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response CacheGetBytesResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// CacheHas calls the cache_has host function.
-// Has checks if a key exists in the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns true if the key exists and has not expired.
-func CacheHas(key string) (*CacheHasResponse, error) {
- // Marshal request to JSON
- req := CacheHasRequest{
- Key: key,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := cache_has(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response CacheHasResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// CacheRemove calls the cache_remove host function.
-// Remove deletes a value from the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns an error if the operation fails. Does not return an error if the key doesn't exist.
-func CacheRemove(key string) (*CacheRemoveResponse, error) {
- // Marshal request to JSON
- req := CacheRemoveRequest{
- Key: key,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := cache_remove(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response CacheRemoveResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
diff --git a/plugins/host/go/nd_host_cache_stub.go b/plugins/host/go/nd_host_cache_stub.go
deleted file mode 100644
index d5a8932a9..000000000
--- a/plugins/host/go/nd_host_cache_stub.go
+++ /dev/null
@@ -1,248 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains stub implementations for non-WASM builds.
-// These stubs allow IDE support and compilation on non-WASM platforms.
-// They panic at runtime since host functions are only available in WASM plugins.
-//
-//go:build !wasip1
-
-package ndhost
-
-// CacheSetStringRequest is the request type for Cache.SetString.
-type CacheSetStringRequest struct {
- Key string `json:"key"`
- Value string `json:"value"`
- TtlSeconds int64 `json:"ttlSeconds"`
-}
-
-// CacheSetStringResponse is the response type for Cache.SetString.
-type CacheSetStringResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// CacheGetStringRequest is the request type for Cache.GetString.
-type CacheGetStringRequest struct {
- Key string `json:"key"`
-}
-
-// CacheGetStringResponse is the response type for Cache.GetString.
-type CacheGetStringResponse struct {
- Value string `json:"value,omitempty"`
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// CacheSetIntRequest is the request type for Cache.SetInt.
-type CacheSetIntRequest struct {
- Key string `json:"key"`
- Value int64 `json:"value"`
- TtlSeconds int64 `json:"ttlSeconds"`
-}
-
-// CacheSetIntResponse is the response type for Cache.SetInt.
-type CacheSetIntResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// CacheGetIntRequest is the request type for Cache.GetInt.
-type CacheGetIntRequest struct {
- Key string `json:"key"`
-}
-
-// CacheGetIntResponse is the response type for Cache.GetInt.
-type CacheGetIntResponse struct {
- Value int64 `json:"value,omitempty"`
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// CacheSetFloatRequest is the request type for Cache.SetFloat.
-type CacheSetFloatRequest struct {
- Key string `json:"key"`
- Value float64 `json:"value"`
- TtlSeconds int64 `json:"ttlSeconds"`
-}
-
-// CacheSetFloatResponse is the response type for Cache.SetFloat.
-type CacheSetFloatResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// CacheGetFloatRequest is the request type for Cache.GetFloat.
-type CacheGetFloatRequest struct {
- Key string `json:"key"`
-}
-
-// CacheGetFloatResponse is the response type for Cache.GetFloat.
-type CacheGetFloatResponse struct {
- Value float64 `json:"value,omitempty"`
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// CacheSetBytesRequest is the request type for Cache.SetBytes.
-type CacheSetBytesRequest struct {
- Key string `json:"key"`
- Value []byte `json:"value"`
- TtlSeconds int64 `json:"ttlSeconds"`
-}
-
-// CacheSetBytesResponse is the response type for Cache.SetBytes.
-type CacheSetBytesResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// CacheGetBytesRequest is the request type for Cache.GetBytes.
-type CacheGetBytesRequest struct {
- Key string `json:"key"`
-}
-
-// CacheGetBytesResponse is the response type for Cache.GetBytes.
-type CacheGetBytesResponse struct {
- Value []byte `json:"value,omitempty"`
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// CacheHasRequest is the request type for Cache.Has.
-type CacheHasRequest struct {
- Key string `json:"key"`
-}
-
-// CacheHasResponse is the response type for Cache.Has.
-type CacheHasResponse struct {
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// CacheRemoveRequest is the request type for Cache.Remove.
-type CacheRemoveRequest struct {
- Key string `json:"key"`
-}
-
-// CacheRemoveResponse is the response type for Cache.Remove.
-type CacheRemoveResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// CacheSetString is a stub that panics on non-WASM platforms.
-// SetString stores a string value in the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-// - value: The string value to store
-// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-//
-// Returns an error if the operation fails.
-func CacheSetString(key string, value string, ttlSeconds int64) (*CacheSetStringResponse, error) {
- panic("ndhost: CacheSetString is only available in WASM plugins")
-}
-
-// CacheGetString is a stub that panics on non-WASM platforms.
-// GetString retrieves a string value from the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns the value and whether the key exists. If the key doesn't exist
-// or the stored value is not a string, exists will be false.
-func CacheGetString(key string) (*CacheGetStringResponse, error) {
- panic("ndhost: CacheGetString is only available in WASM plugins")
-}
-
-// CacheSetInt is a stub that panics on non-WASM platforms.
-// SetInt stores an integer value in the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-// - value: The integer value to store
-// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-//
-// Returns an error if the operation fails.
-func CacheSetInt(key string, value int64, ttlSeconds int64) (*CacheSetIntResponse, error) {
- panic("ndhost: CacheSetInt is only available in WASM plugins")
-}
-
-// CacheGetInt is a stub that panics on non-WASM platforms.
-// GetInt retrieves an integer value from the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns the value and whether the key exists. If the key doesn't exist
-// or the stored value is not an integer, exists will be false.
-func CacheGetInt(key string) (*CacheGetIntResponse, error) {
- panic("ndhost: CacheGetInt is only available in WASM plugins")
-}
-
-// CacheSetFloat is a stub that panics on non-WASM platforms.
-// SetFloat stores a float value in the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-// - value: The float value to store
-// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-//
-// Returns an error if the operation fails.
-func CacheSetFloat(key string, value float64, ttlSeconds int64) (*CacheSetFloatResponse, error) {
- panic("ndhost: CacheSetFloat is only available in WASM plugins")
-}
-
-// CacheGetFloat is a stub that panics on non-WASM platforms.
-// GetFloat retrieves a float value from the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns the value and whether the key exists. If the key doesn't exist
-// or the stored value is not a float, exists will be false.
-func CacheGetFloat(key string) (*CacheGetFloatResponse, error) {
- panic("ndhost: CacheGetFloat is only available in WASM plugins")
-}
-
-// CacheSetBytes is a stub that panics on non-WASM platforms.
-// SetBytes stores a byte slice in the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-// - value: The byte slice to store
-// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-//
-// Returns an error if the operation fails.
-func CacheSetBytes(key string, value []byte, ttlSeconds int64) (*CacheSetBytesResponse, error) {
- panic("ndhost: CacheSetBytes is only available in WASM plugins")
-}
-
-// CacheGetBytes is a stub that panics on non-WASM platforms.
-// GetBytes retrieves a byte slice from the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns the value and whether the key exists. If the key doesn't exist
-// or the stored value is not a byte slice, exists will be false.
-func CacheGetBytes(key string) (*CacheGetBytesResponse, error) {
- panic("ndhost: CacheGetBytes is only available in WASM plugins")
-}
-
-// CacheHas is a stub that panics on non-WASM platforms.
-// Has checks if a key exists in the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns true if the key exists and has not expired.
-func CacheHas(key string) (*CacheHasResponse, error) {
- panic("ndhost: CacheHas is only available in WASM plugins")
-}
-
-// CacheRemove is a stub that panics on non-WASM platforms.
-// Remove deletes a value from the cache.
-//
-// Parameters:
-// - key: The cache key (will be namespaced with plugin ID)
-//
-// Returns an error if the operation fails. Does not return an error if the key doesn't exist.
-func CacheRemove(key string) (*CacheRemoveResponse, error) {
- panic("ndhost: CacheRemove is only available in WASM plugins")
-}
diff --git a/plugins/host/go/nd_host_kvstore.go b/plugins/host/go/nd_host_kvstore.go
deleted file mode 100644
index 52e9da911..000000000
--- a/plugins/host/go/nd_host_kvstore.go
+++ /dev/null
@@ -1,336 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the KVStore host service.
-// It is intended for use in Navidrome plugins built with TinyGo.
-//
-//go:build wasip1
-
-package ndhost
-
-import (
- "encoding/json"
- "errors"
-
- "github.com/extism/go-pdk"
-)
-
-// kvstore_set is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user kvstore_set
-func kvstore_set(uint64) uint64
-
-// kvstore_get is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user kvstore_get
-func kvstore_get(uint64) uint64
-
-// kvstore_delete is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user kvstore_delete
-func kvstore_delete(uint64) uint64
-
-// kvstore_has is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user kvstore_has
-func kvstore_has(uint64) uint64
-
-// kvstore_list is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user kvstore_list
-func kvstore_list(uint64) uint64
-
-// kvstore_getstorageused is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user kvstore_getstorageused
-func kvstore_getstorageused(uint64) uint64
-
-// KVStoreSetRequest is the request type for KVStore.Set.
-type KVStoreSetRequest struct {
- Key string `json:"key"`
- Value []byte `json:"value"`
-}
-
-// KVStoreSetResponse is the response type for KVStore.Set.
-type KVStoreSetResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreGetRequest is the request type for KVStore.Get.
-type KVStoreGetRequest struct {
- Key string `json:"key"`
-}
-
-// KVStoreGetResponse is the response type for KVStore.Get.
-type KVStoreGetResponse struct {
- Value []byte `json:"value,omitempty"`
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreDeleteRequest is the request type for KVStore.Delete.
-type KVStoreDeleteRequest struct {
- Key string `json:"key"`
-}
-
-// KVStoreDeleteResponse is the response type for KVStore.Delete.
-type KVStoreDeleteResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreHasRequest is the request type for KVStore.Has.
-type KVStoreHasRequest struct {
- Key string `json:"key"`
-}
-
-// KVStoreHasResponse is the response type for KVStore.Has.
-type KVStoreHasResponse struct {
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreListRequest is the request type for KVStore.List.
-type KVStoreListRequest struct {
- Prefix string `json:"prefix"`
-}
-
-// KVStoreListResponse is the response type for KVStore.List.
-type KVStoreListResponse struct {
- Keys []string `json:"keys,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreGetStorageUsedResponse is the response type for KVStore.GetStorageUsed.
-type KVStoreGetStorageUsedResponse struct {
- Bytes int64 `json:"bytes,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreSet calls the kvstore_set host function.
-// Set stores a byte value with the given key.
-//
-// Parameters:
-// - key: The storage key (max 256 bytes, UTF-8)
-// - value: The byte slice to store
-//
-// Returns an error if the storage limit would be exceeded or the operation fails.
-func KVStoreSet(key string, value []byte) (*KVStoreSetResponse, error) {
- // Marshal request to JSON
- req := KVStoreSetRequest{
- Key: key,
- Value: value,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := kvstore_set(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response KVStoreSetResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// KVStoreGet calls the kvstore_get host function.
-// Get retrieves a byte value from storage.
-//
-// Parameters:
-// - key: The storage key
-//
-// Returns the value and whether the key exists.
-func KVStoreGet(key string) (*KVStoreGetResponse, error) {
- // Marshal request to JSON
- req := KVStoreGetRequest{
- Key: key,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := kvstore_get(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response KVStoreGetResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// KVStoreDelete calls the kvstore_delete host function.
-// Delete removes a value from storage.
-//
-// Parameters:
-// - key: The storage key
-//
-// Returns an error if the operation fails. Does not return an error if the key doesn't exist.
-func KVStoreDelete(key string) (*KVStoreDeleteResponse, error) {
- // Marshal request to JSON
- req := KVStoreDeleteRequest{
- Key: key,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := kvstore_delete(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response KVStoreDeleteResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// KVStoreHas calls the kvstore_has host function.
-// Has checks if a key exists in storage.
-//
-// Parameters:
-// - key: The storage key
-//
-// Returns true if the key exists.
-func KVStoreHas(key string) (*KVStoreHasResponse, error) {
- // Marshal request to JSON
- req := KVStoreHasRequest{
- Key: key,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := kvstore_has(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response KVStoreHasResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// KVStoreList calls the kvstore_list host function.
-// List returns all keys matching the given prefix.
-//
-// Parameters:
-// - prefix: Key prefix to filter by (empty string returns all keys)
-//
-// Returns a slice of matching keys.
-func KVStoreList(prefix string) (*KVStoreListResponse, error) {
- // Marshal request to JSON
- req := KVStoreListRequest{
- Prefix: prefix,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := kvstore_list(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response KVStoreListResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// KVStoreGetStorageUsed calls the kvstore_getstorageused host function.
-// GetStorageUsed returns the total storage used by this plugin in bytes.
-func KVStoreGetStorageUsed() (*KVStoreGetStorageUsedResponse, error) {
- // No parameters - allocate empty JSON object
- reqMem := pdk.AllocateBytes([]byte("{}"))
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := kvstore_getstorageused(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response KVStoreGetStorageUsedResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
diff --git a/plugins/host/go/nd_host_kvstore_stub.go b/plugins/host/go/nd_host_kvstore_stub.go
deleted file mode 100644
index d90e4071e..000000000
--- a/plugins/host/go/nd_host_kvstore_stub.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains stub implementations for non-WASM builds.
-// These stubs allow IDE support and compilation on non-WASM platforms.
-// They panic at runtime since host functions are only available in WASM plugins.
-//
-//go:build !wasip1
-
-package ndhost
-
-// KVStoreSetRequest is the request type for KVStore.Set.
-type KVStoreSetRequest struct {
- Key string `json:"key"`
- Value []byte `json:"value"`
-}
-
-// KVStoreSetResponse is the response type for KVStore.Set.
-type KVStoreSetResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreGetRequest is the request type for KVStore.Get.
-type KVStoreGetRequest struct {
- Key string `json:"key"`
-}
-
-// KVStoreGetResponse is the response type for KVStore.Get.
-type KVStoreGetResponse struct {
- Value []byte `json:"value,omitempty"`
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreDeleteRequest is the request type for KVStore.Delete.
-type KVStoreDeleteRequest struct {
- Key string `json:"key"`
-}
-
-// KVStoreDeleteResponse is the response type for KVStore.Delete.
-type KVStoreDeleteResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreHasRequest is the request type for KVStore.Has.
-type KVStoreHasRequest struct {
- Key string `json:"key"`
-}
-
-// KVStoreHasResponse is the response type for KVStore.Has.
-type KVStoreHasResponse struct {
- Exists bool `json:"exists,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreListRequest is the request type for KVStore.List.
-type KVStoreListRequest struct {
- Prefix string `json:"prefix"`
-}
-
-// KVStoreListResponse is the response type for KVStore.List.
-type KVStoreListResponse struct {
- Keys []string `json:"keys,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreGetStorageUsedResponse is the response type for KVStore.GetStorageUsed.
-type KVStoreGetStorageUsedResponse struct {
- Bytes int64 `json:"bytes,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// KVStoreSet is a stub that panics on non-WASM platforms.
-// Set stores a byte value with the given key.
-//
-// Parameters:
-// - key: The storage key (max 256 bytes, UTF-8)
-// - value: The byte slice to store
-//
-// Returns an error if the storage limit would be exceeded or the operation fails.
-func KVStoreSet(key string, value []byte) (*KVStoreSetResponse, error) {
- panic("ndhost: KVStoreSet is only available in WASM plugins")
-}
-
-// KVStoreGet is a stub that panics on non-WASM platforms.
-// Get retrieves a byte value from storage.
-//
-// Parameters:
-// - key: The storage key
-//
-// Returns the value and whether the key exists.
-func KVStoreGet(key string) (*KVStoreGetResponse, error) {
- panic("ndhost: KVStoreGet is only available in WASM plugins")
-}
-
-// KVStoreDelete is a stub that panics on non-WASM platforms.
-// Delete removes a value from storage.
-//
-// Parameters:
-// - key: The storage key
-//
-// Returns an error if the operation fails. Does not return an error if the key doesn't exist.
-func KVStoreDelete(key string) (*KVStoreDeleteResponse, error) {
- panic("ndhost: KVStoreDelete is only available in WASM plugins")
-}
-
-// KVStoreHas is a stub that panics on non-WASM platforms.
-// Has checks if a key exists in storage.
-//
-// Parameters:
-// - key: The storage key
-//
-// Returns true if the key exists.
-func KVStoreHas(key string) (*KVStoreHasResponse, error) {
- panic("ndhost: KVStoreHas is only available in WASM plugins")
-}
-
-// KVStoreList is a stub that panics on non-WASM platforms.
-// List returns all keys matching the given prefix.
-//
-// Parameters:
-// - prefix: Key prefix to filter by (empty string returns all keys)
-//
-// Returns a slice of matching keys.
-func KVStoreList(prefix string) (*KVStoreListResponse, error) {
- panic("ndhost: KVStoreList is only available in WASM plugins")
-}
-
-// KVStoreGetStorageUsed is a stub that panics on non-WASM platforms.
-// GetStorageUsed returns the total storage used by this plugin in bytes.
-func KVStoreGetStorageUsed() (*KVStoreGetStorageUsedResponse, error) {
- panic("ndhost: KVStoreGetStorageUsed is only available in WASM plugins")
-}
diff --git a/plugins/host/go/nd_host_library.go b/plugins/host/go/nd_host_library.go
deleted file mode 100644
index 2d7da44c7..000000000
--- a/plugins/host/go/nd_host_library.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the Library host service.
-// It is intended for use in Navidrome plugins built with TinyGo.
-//
-//go:build wasip1
-
-package ndhost
-
-import (
- "encoding/json"
- "errors"
-
- "github.com/extism/go-pdk"
-)
-
-// Library represents the Library data structure.
-// Library represents a music library with metadata.
-type Library struct {
- ID int32 `json:"id"`
- Name string `json:"name"`
- Path string `json:"path"`
- MountPoint string `json:"mountPoint"`
- LastScanAt int64 `json:"lastScanAt"`
- TotalSongs int32 `json:"totalSongs"`
- TotalAlbums int32 `json:"totalAlbums"`
- TotalArtists int32 `json:"totalArtists"`
- TotalSize int64 `json:"totalSize"`
- TotalDuration float64 `json:"totalDuration"`
-}
-
-// library_getlibrary is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user library_getlibrary
-func library_getlibrary(uint64) uint64
-
-// library_getalllibraries is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user library_getalllibraries
-func library_getalllibraries(uint64) uint64
-
-// LibraryGetLibraryRequest is the request type for Library.GetLibrary.
-type LibraryGetLibraryRequest struct {
- Id int32 `json:"id"`
-}
-
-// LibraryGetLibraryResponse is the response type for Library.GetLibrary.
-type LibraryGetLibraryResponse struct {
- Result *Library `json:"result,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// LibraryGetAllLibrariesResponse is the response type for Library.GetAllLibraries.
-type LibraryGetAllLibrariesResponse struct {
- Result []Library `json:"result,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// LibraryGetLibrary calls the library_getlibrary host function.
-// GetLibrary retrieves metadata for a specific library by ID.
-//
-// Parameters:
-// - id: The library's unique identifier
-//
-// Returns the library metadata, or an error if the library is not found.
-func LibraryGetLibrary(id int32) (*LibraryGetLibraryResponse, error) {
- // Marshal request to JSON
- req := LibraryGetLibraryRequest{
- Id: id,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := library_getlibrary(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response LibraryGetLibraryResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// LibraryGetAllLibraries calls the library_getalllibraries host function.
-// GetAllLibraries retrieves metadata for all configured libraries.
-//
-// Returns a slice of all libraries with their metadata.
-func LibraryGetAllLibraries() (*LibraryGetAllLibrariesResponse, error) {
- // No parameters - allocate empty JSON object
- reqMem := pdk.AllocateBytes([]byte("{}"))
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := library_getalllibraries(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response LibraryGetAllLibrariesResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
diff --git a/plugins/host/go/nd_host_library_stub.go b/plugins/host/go/nd_host_library_stub.go
deleted file mode 100644
index c71a30cd1..000000000
--- a/plugins/host/go/nd_host_library_stub.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains stub implementations for non-WASM builds.
-// These stubs allow IDE support and compilation on non-WASM platforms.
-// They panic at runtime since host functions are only available in WASM plugins.
-//
-//go:build !wasip1
-
-package ndhost
-
-// Library represents the Library data structure.
-// Library represents a music library with metadata.
-type Library struct {
- ID int32 `json:"id"`
- Name string `json:"name"`
- Path string `json:"path"`
- MountPoint string `json:"mountPoint"`
- LastScanAt int64 `json:"lastScanAt"`
- TotalSongs int32 `json:"totalSongs"`
- TotalAlbums int32 `json:"totalAlbums"`
- TotalArtists int32 `json:"totalArtists"`
- TotalSize int64 `json:"totalSize"`
- TotalDuration float64 `json:"totalDuration"`
-}
-
-// LibraryGetLibraryRequest is the request type for Library.GetLibrary.
-type LibraryGetLibraryRequest struct {
- Id int32 `json:"id"`
-}
-
-// LibraryGetLibraryResponse is the response type for Library.GetLibrary.
-type LibraryGetLibraryResponse struct {
- Result *Library `json:"result,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// LibraryGetAllLibrariesResponse is the response type for Library.GetAllLibraries.
-type LibraryGetAllLibrariesResponse struct {
- Result []Library `json:"result,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// LibraryGetLibrary is a stub that panics on non-WASM platforms.
-// GetLibrary retrieves metadata for a specific library by ID.
-//
-// Parameters:
-// - id: The library's unique identifier
-//
-// Returns the library metadata, or an error if the library is not found.
-func LibraryGetLibrary(id int32) (*LibraryGetLibraryResponse, error) {
- panic("ndhost: LibraryGetLibrary is only available in WASM plugins")
-}
-
-// LibraryGetAllLibraries is a stub that panics on non-WASM platforms.
-// GetAllLibraries retrieves metadata for all configured libraries.
-//
-// Returns a slice of all libraries with their metadata.
-func LibraryGetAllLibraries() (*LibraryGetAllLibrariesResponse, error) {
- panic("ndhost: LibraryGetAllLibraries is only available in WASM plugins")
-}
diff --git a/plugins/host/go/nd_host_scheduler.go b/plugins/host/go/nd_host_scheduler.go
deleted file mode 100644
index 89411f2d1..000000000
--- a/plugins/host/go/nd_host_scheduler.go
+++ /dev/null
@@ -1,196 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the Scheduler host service.
-// It is intended for use in Navidrome plugins built with TinyGo.
-//
-//go:build wasip1
-
-package ndhost
-
-import (
- "encoding/json"
- "errors"
-
- "github.com/extism/go-pdk"
-)
-
-// scheduler_scheduleonetime is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user scheduler_scheduleonetime
-func scheduler_scheduleonetime(uint64) uint64
-
-// scheduler_schedulerecurring is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user scheduler_schedulerecurring
-func scheduler_schedulerecurring(uint64) uint64
-
-// scheduler_cancelschedule is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user scheduler_cancelschedule
-func scheduler_cancelschedule(uint64) uint64
-
-// SchedulerScheduleOneTimeRequest is the request type for Scheduler.ScheduleOneTime.
-type SchedulerScheduleOneTimeRequest struct {
- DelaySeconds int32 `json:"delaySeconds"`
- Payload string `json:"payload"`
- ScheduleID string `json:"scheduleId"`
-}
-
-// SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime.
-type SchedulerScheduleOneTimeResponse struct {
- NewScheduleID string `json:"newScheduleId,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// SchedulerScheduleRecurringRequest is the request type for Scheduler.ScheduleRecurring.
-type SchedulerScheduleRecurringRequest struct {
- CronExpression string `json:"cronExpression"`
- Payload string `json:"payload"`
- ScheduleID string `json:"scheduleId"`
-}
-
-// SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring.
-type SchedulerScheduleRecurringResponse struct {
- NewScheduleID string `json:"newScheduleId,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// SchedulerCancelScheduleRequest is the request type for Scheduler.CancelSchedule.
-type SchedulerCancelScheduleRequest struct {
- ScheduleID string `json:"scheduleId"`
-}
-
-// SchedulerCancelScheduleResponse is the response type for Scheduler.CancelSchedule.
-type SchedulerCancelScheduleResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// SchedulerScheduleOneTime calls the scheduler_scheduleonetime host function.
-// ScheduleOneTime schedules a one-time event to be triggered after the specified delay.
-// Plugins that use this function must also implement the SchedulerCallback capability
-//
-// Parameters:
-// - delaySeconds: Number of seconds to wait before triggering the event
-// - payload: Data to be passed to the scheduled event handler
-// - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated
-//
-// Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails.
-func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID string) (*SchedulerScheduleOneTimeResponse, error) {
- // Marshal request to JSON
- req := SchedulerScheduleOneTimeRequest{
- DelaySeconds: delaySeconds,
- Payload: payload,
- ScheduleID: scheduleID,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := scheduler_scheduleonetime(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response SchedulerScheduleOneTimeResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// SchedulerScheduleRecurring calls the scheduler_schedulerecurring host function.
-// ScheduleRecurring schedules a recurring event using a cron expression.
-// Plugins that use this function must also implement the SchedulerCallback capability
-//
-// Parameters:
-// - cronExpression: Standard cron format expression (e.g., "0 0 * * *" for daily at midnight)
-// - payload: Data to be passed to each scheduled event handler invocation
-// - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated
-//
-// Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails.
-func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleID string) (*SchedulerScheduleRecurringResponse, error) {
- // Marshal request to JSON
- req := SchedulerScheduleRecurringRequest{
- CronExpression: cronExpression,
- Payload: payload,
- ScheduleID: scheduleID,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := scheduler_schedulerecurring(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response SchedulerScheduleRecurringResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// SchedulerCancelSchedule calls the scheduler_cancelschedule host function.
-// CancelSchedule cancels a scheduled job identified by its schedule ID.
-//
-// This works for both one-time and recurring schedules. Once cancelled, the job will not trigger
-// any future events.
-//
-// Returns an error if the schedule ID is not found or if cancellation fails.
-func SchedulerCancelSchedule(scheduleID string) (*SchedulerCancelScheduleResponse, error) {
- // Marshal request to JSON
- req := SchedulerCancelScheduleRequest{
- ScheduleID: scheduleID,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := scheduler_cancelschedule(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response SchedulerCancelScheduleResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
diff --git a/plugins/host/go/nd_host_scheduler_stub.go b/plugins/host/go/nd_host_scheduler_stub.go
deleted file mode 100644
index 4a6029429..000000000
--- a/plugins/host/go/nd_host_scheduler_stub.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains stub implementations for non-WASM builds.
-// These stubs allow IDE support and compilation on non-WASM platforms.
-// They panic at runtime since host functions are only available in WASM plugins.
-//
-//go:build !wasip1
-
-package ndhost
-
-// SchedulerScheduleOneTimeRequest is the request type for Scheduler.ScheduleOneTime.
-type SchedulerScheduleOneTimeRequest struct {
- DelaySeconds int32 `json:"delaySeconds"`
- Payload string `json:"payload"`
- ScheduleID string `json:"scheduleId"`
-}
-
-// SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime.
-type SchedulerScheduleOneTimeResponse struct {
- NewScheduleID string `json:"newScheduleId,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// SchedulerScheduleRecurringRequest is the request type for Scheduler.ScheduleRecurring.
-type SchedulerScheduleRecurringRequest struct {
- CronExpression string `json:"cronExpression"`
- Payload string `json:"payload"`
- ScheduleID string `json:"scheduleId"`
-}
-
-// SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring.
-type SchedulerScheduleRecurringResponse struct {
- NewScheduleID string `json:"newScheduleId,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// SchedulerCancelScheduleRequest is the request type for Scheduler.CancelSchedule.
-type SchedulerCancelScheduleRequest struct {
- ScheduleID string `json:"scheduleId"`
-}
-
-// SchedulerCancelScheduleResponse is the response type for Scheduler.CancelSchedule.
-type SchedulerCancelScheduleResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// SchedulerScheduleOneTime is a stub that panics on non-WASM platforms.
-// ScheduleOneTime schedules a one-time event to be triggered after the specified delay.
-// Plugins that use this function must also implement the SchedulerCallback capability
-//
-// Parameters:
-// - delaySeconds: Number of seconds to wait before triggering the event
-// - payload: Data to be passed to the scheduled event handler
-// - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated
-//
-// Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails.
-func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID string) (*SchedulerScheduleOneTimeResponse, error) {
- panic("ndhost: SchedulerScheduleOneTime is only available in WASM plugins")
-}
-
-// SchedulerScheduleRecurring is a stub that panics on non-WASM platforms.
-// ScheduleRecurring schedules a recurring event using a cron expression.
-// Plugins that use this function must also implement the SchedulerCallback capability
-//
-// Parameters:
-// - cronExpression: Standard cron format expression (e.g., "0 0 * * *" for daily at midnight)
-// - payload: Data to be passed to each scheduled event handler invocation
-// - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated
-//
-// Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails.
-func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleID string) (*SchedulerScheduleRecurringResponse, error) {
- panic("ndhost: SchedulerScheduleRecurring is only available in WASM plugins")
-}
-
-// SchedulerCancelSchedule is a stub that panics on non-WASM platforms.
-// CancelSchedule cancels a scheduled job identified by its schedule ID.
-//
-// This works for both one-time and recurring schedules. Once cancelled, the job will not trigger
-// any future events.
-//
-// Returns an error if the schedule ID is not found or if cancellation fails.
-func SchedulerCancelSchedule(scheduleID string) (*SchedulerCancelScheduleResponse, error) {
- panic("ndhost: SchedulerCancelSchedule is only available in WASM plugins")
-}
diff --git a/plugins/host/go/nd_host_subsonicapi.go b/plugins/host/go/nd_host_subsonicapi.go
deleted file mode 100644
index e845e617a..000000000
--- a/plugins/host/go/nd_host_subsonicapi.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the SubsonicAPI host service.
-// It is intended for use in Navidrome plugins built with TinyGo.
-//
-//go:build wasip1
-
-package ndhost
-
-import (
- "encoding/json"
- "errors"
-
- "github.com/extism/go-pdk"
-)
-
-// subsonicapi_call is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user subsonicapi_call
-func subsonicapi_call(uint64) uint64
-
-// SubsonicAPICallRequest is the request type for SubsonicAPI.Call.
-type SubsonicAPICallRequest struct {
- Uri string `json:"uri"`
-}
-
-// SubsonicAPICallResponse is the response type for SubsonicAPI.Call.
-type SubsonicAPICallResponse struct {
- ResponseJSON string `json:"responseJson,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// SubsonicAPICall calls the subsonicapi_call host function.
-// Call executes a Subsonic API request and returns the JSON response.
-//
-// The uri parameter should be the Subsonic API path without the server prefix,
-// e.g., "getAlbumList2?type=random&size=10". The response is returned as raw JSON.
-func SubsonicAPICall(uri string) (*SubsonicAPICallResponse, error) {
- // Marshal request to JSON
- req := SubsonicAPICallRequest{
- Uri: uri,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := subsonicapi_call(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response SubsonicAPICallResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
diff --git a/plugins/host/go/nd_host_subsonicapi_stub.go b/plugins/host/go/nd_host_subsonicapi_stub.go
deleted file mode 100644
index e99f52ebb..000000000
--- a/plugins/host/go/nd_host_subsonicapi_stub.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains stub implementations for non-WASM builds.
-// These stubs allow IDE support and compilation on non-WASM platforms.
-// They panic at runtime since host functions are only available in WASM plugins.
-//
-//go:build !wasip1
-
-package ndhost
-
-// SubsonicAPICallRequest is the request type for SubsonicAPI.Call.
-type SubsonicAPICallRequest struct {
- Uri string `json:"uri"`
-}
-
-// SubsonicAPICallResponse is the response type for SubsonicAPI.Call.
-type SubsonicAPICallResponse struct {
- ResponseJSON string `json:"responseJson,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// SubsonicAPICall is a stub that panics on non-WASM platforms.
-// Call executes a Subsonic API request and returns the JSON response.
-//
-// The uri parameter should be the Subsonic API path without the server prefix,
-// e.g., "getAlbumList2?type=random&size=10". The response is returned as raw JSON.
-func SubsonicAPICall(uri string) (*SubsonicAPICallResponse, error) {
- panic("ndhost: SubsonicAPICall is only available in WASM plugins")
-}
diff --git a/plugins/host/go/nd_host_websocket.go b/plugins/host/go/nd_host_websocket.go
deleted file mode 100644
index d4ce707d7..000000000
--- a/plugins/host/go/nd_host_websocket.go
+++ /dev/null
@@ -1,258 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the WebSocket host service.
-// It is intended for use in Navidrome plugins built with TinyGo.
-//
-//go:build wasip1
-
-package ndhost
-
-import (
- "encoding/json"
- "errors"
-
- "github.com/extism/go-pdk"
-)
-
-// websocket_connect is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user websocket_connect
-func websocket_connect(uint64) uint64
-
-// websocket_sendtext is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user websocket_sendtext
-func websocket_sendtext(uint64) uint64
-
-// websocket_sendbinary is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user websocket_sendbinary
-func websocket_sendbinary(uint64) uint64
-
-// websocket_closeconnection is the host function provided by Navidrome.
-//
-//go:wasmimport extism:host/user websocket_closeconnection
-func websocket_closeconnection(uint64) uint64
-
-// WebSocketConnectRequest is the request type for WebSocket.Connect.
-type WebSocketConnectRequest struct {
- Url string `json:"url"`
- Headers map[string]string `json:"headers"`
- ConnectionID string `json:"connectionId"`
-}
-
-// WebSocketConnectResponse is the response type for WebSocket.Connect.
-type WebSocketConnectResponse struct {
- NewConnectionID string `json:"newConnectionId,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// WebSocketSendTextRequest is the request type for WebSocket.SendText.
-type WebSocketSendTextRequest struct {
- ConnectionID string `json:"connectionId"`
- Message string `json:"message"`
-}
-
-// WebSocketSendTextResponse is the response type for WebSocket.SendText.
-type WebSocketSendTextResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// WebSocketSendBinaryRequest is the request type for WebSocket.SendBinary.
-type WebSocketSendBinaryRequest struct {
- ConnectionID string `json:"connectionId"`
- Data []byte `json:"data"`
-}
-
-// WebSocketSendBinaryResponse is the response type for WebSocket.SendBinary.
-type WebSocketSendBinaryResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// WebSocketCloseConnectionRequest is the request type for WebSocket.CloseConnection.
-type WebSocketCloseConnectionRequest struct {
- ConnectionID string `json:"connectionId"`
- Code int32 `json:"code"`
- Reason string `json:"reason"`
-}
-
-// WebSocketCloseConnectionResponse is the response type for WebSocket.CloseConnection.
-type WebSocketCloseConnectionResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// WebSocketConnect calls the websocket_connect host function.
-// Connect establishes a WebSocket connection to the specified URL.
-//
-// Plugins that use this function must also implement the WebSocketCallback capability
-// to receive incoming messages and connection events.
-//
-// Parameters:
-// - url: The WebSocket URL to connect to (ws:// or wss://)
-// - headers: Optional HTTP headers to include in the handshake request
-// - connectionID: Optional unique identifier for the connection. If empty, one will be generated
-//
-// Returns the connection ID that can be used to send messages or close the connection,
-// or an error if the connection fails.
-func WebSocketConnect(url string, headers map[string]string, connectionID string) (*WebSocketConnectResponse, error) {
- // Marshal request to JSON
- req := WebSocketConnectRequest{
- Url: url,
- Headers: headers,
- ConnectionID: connectionID,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := websocket_connect(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response WebSocketConnectResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// WebSocketSendText calls the websocket_sendtext host function.
-// SendText sends a text message over an established WebSocket connection.
-//
-// Parameters:
-// - connectionID: The connection identifier returned by Connect
-// - message: The text message to send
-//
-// Returns an error if the connection is not found or if sending fails.
-func WebSocketSendText(connectionID string, message string) (*WebSocketSendTextResponse, error) {
- // Marshal request to JSON
- req := WebSocketSendTextRequest{
- ConnectionID: connectionID,
- Message: message,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := websocket_sendtext(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response WebSocketSendTextResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// WebSocketSendBinary calls the websocket_sendbinary host function.
-// SendBinary sends binary data over an established WebSocket connection.
-//
-// Parameters:
-// - connectionID: The connection identifier returned by Connect
-// - data: The binary data to send
-//
-// Returns an error if the connection is not found or if sending fails.
-func WebSocketSendBinary(connectionID string, data []byte) (*WebSocketSendBinaryResponse, error) {
- // Marshal request to JSON
- req := WebSocketSendBinaryRequest{
- ConnectionID: connectionID,
- Data: data,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := websocket_sendbinary(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response WebSocketSendBinaryResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
-
-// WebSocketCloseConnection calls the websocket_closeconnection host function.
-// CloseConnection gracefully closes a WebSocket connection.
-//
-// Parameters:
-// - connectionID: The connection identifier returned by Connect
-// - code: WebSocket close status code (e.g., 1000 for normal closure)
-// - reason: Optional human-readable reason for closing
-//
-// Returns an error if the connection is not found or if closing fails.
-func WebSocketCloseConnection(connectionID string, code int32, reason string) (*WebSocketCloseConnectionResponse, error) {
- // Marshal request to JSON
- req := WebSocketCloseConnectionRequest{
- ConnectionID: connectionID,
- Code: code,
- Reason: reason,
- }
- reqBytes, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- reqMem := pdk.AllocateBytes(reqBytes)
- defer reqMem.Free()
-
- // Call the host function
- responsePtr := websocket_closeconnection(reqMem.Offset())
-
- // Read the response from memory
- responseMem := pdk.FindMemory(responsePtr)
- responseBytes := responseMem.ReadBytes()
-
- // Parse the response
- var response WebSocketCloseConnectionResponse
- if err := json.Unmarshal(responseBytes, &response); err != nil {
- return nil, err
- }
-
- // Convert Error field to Go error
- if response.Error != "" {
- return nil, errors.New(response.Error)
- }
-
- return &response, nil
-}
diff --git a/plugins/host/go/nd_host_websocket_stub.go b/plugins/host/go/nd_host_websocket_stub.go
deleted file mode 100644
index 75b4ae0f9..000000000
--- a/plugins/host/go/nd_host_websocket_stub.go
+++ /dev/null
@@ -1,110 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains stub implementations for non-WASM builds.
-// These stubs allow IDE support and compilation on non-WASM platforms.
-// They panic at runtime since host functions are only available in WASM plugins.
-//
-//go:build !wasip1
-
-package ndhost
-
-// WebSocketConnectRequest is the request type for WebSocket.Connect.
-type WebSocketConnectRequest struct {
- Url string `json:"url"`
- Headers map[string]string `json:"headers"`
- ConnectionID string `json:"connectionId"`
-}
-
-// WebSocketConnectResponse is the response type for WebSocket.Connect.
-type WebSocketConnectResponse struct {
- NewConnectionID string `json:"newConnectionId,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-// WebSocketSendTextRequest is the request type for WebSocket.SendText.
-type WebSocketSendTextRequest struct {
- ConnectionID string `json:"connectionId"`
- Message string `json:"message"`
-}
-
-// WebSocketSendTextResponse is the response type for WebSocket.SendText.
-type WebSocketSendTextResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// WebSocketSendBinaryRequest is the request type for WebSocket.SendBinary.
-type WebSocketSendBinaryRequest struct {
- ConnectionID string `json:"connectionId"`
- Data []byte `json:"data"`
-}
-
-// WebSocketSendBinaryResponse is the response type for WebSocket.SendBinary.
-type WebSocketSendBinaryResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// WebSocketCloseConnectionRequest is the request type for WebSocket.CloseConnection.
-type WebSocketCloseConnectionRequest struct {
- ConnectionID string `json:"connectionId"`
- Code int32 `json:"code"`
- Reason string `json:"reason"`
-}
-
-// WebSocketCloseConnectionResponse is the response type for WebSocket.CloseConnection.
-type WebSocketCloseConnectionResponse struct {
- Error string `json:"error,omitempty"`
-}
-
-// WebSocketConnect is a stub that panics on non-WASM platforms.
-// Connect establishes a WebSocket connection to the specified URL.
-//
-// Plugins that use this function must also implement the WebSocketCallback capability
-// to receive incoming messages and connection events.
-//
-// Parameters:
-// - url: The WebSocket URL to connect to (ws:// or wss://)
-// - headers: Optional HTTP headers to include in the handshake request
-// - connectionID: Optional unique identifier for the connection. If empty, one will be generated
-//
-// Returns the connection ID that can be used to send messages or close the connection,
-// or an error if the connection fails.
-func WebSocketConnect(url string, headers map[string]string, connectionID string) (*WebSocketConnectResponse, error) {
- panic("ndhost: WebSocketConnect is only available in WASM plugins")
-}
-
-// WebSocketSendText is a stub that panics on non-WASM platforms.
-// SendText sends a text message over an established WebSocket connection.
-//
-// Parameters:
-// - connectionID: The connection identifier returned by Connect
-// - message: The text message to send
-//
-// Returns an error if the connection is not found or if sending fails.
-func WebSocketSendText(connectionID string, message string) (*WebSocketSendTextResponse, error) {
- panic("ndhost: WebSocketSendText is only available in WASM plugins")
-}
-
-// WebSocketSendBinary is a stub that panics on non-WASM platforms.
-// SendBinary sends binary data over an established WebSocket connection.
-//
-// Parameters:
-// - connectionID: The connection identifier returned by Connect
-// - data: The binary data to send
-//
-// Returns an error if the connection is not found or if sending fails.
-func WebSocketSendBinary(connectionID string, data []byte) (*WebSocketSendBinaryResponse, error) {
- panic("ndhost: WebSocketSendBinary is only available in WASM plugins")
-}
-
-// WebSocketCloseConnection is a stub that panics on non-WASM platforms.
-// CloseConnection gracefully closes a WebSocket connection.
-//
-// Parameters:
-// - connectionID: The connection identifier returned by Connect
-// - code: WebSocket close status code (e.g., 1000 for normal closure)
-// - reason: Optional human-readable reason for closing
-//
-// Returns an error if the connection is not found or if closing fails.
-func WebSocketCloseConnection(connectionID string, code int32, reason string) (*WebSocketCloseConnectionResponse, error) {
- panic("ndhost: WebSocketCloseConnection is only available in WASM plugins")
-}
diff --git a/plugins/host/kvstore_gen.go b/plugins/host/kvstore_gen.go
index 422199f23..2ad24959d 100644
--- a/plugins/host/kvstore_gen.go
+++ b/plugins/host/kvstore_gen.go
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package host
diff --git a/plugins/host/library_gen.go b/plugins/host/library_gen.go
index 95409ab36..27195e85c 100644
--- a/plugins/host/library_gen.go
+++ b/plugins/host/library_gen.go
@@ -1,4 +1,4 @@
-// Code generated by hostgen. DO NOT EDIT.
+// Code generated by ndpgen. DO NOT EDIT.
package host
diff --git a/plugins/host/python/nd_host_artwork.py b/plugins/host/python/nd_host_artwork.py
deleted file mode 100644
index 3919dbbe9..000000000
--- a/plugins/host/python/nd_host_artwork.py
+++ /dev/null
@@ -1,183 +0,0 @@
-# Code generated by hostgen. DO NOT EDIT.
-#
-# This file contains client wrappers for the Artwork host service.
-# It is intended for use in Navidrome plugins built with extism-py.
-#
-# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly.
-# The @extism.import_fn decorators are only detected when defined in the plugin's
-# main __init__.py file. Copy the needed functions from this file into your plugin.
-
-from dataclasses import dataclass
-from typing import Any
-
-import extism
-import json
-
-
-class HostFunctionError(Exception):
- """Raised when a host function returns an error."""
- pass
-
-
-@extism.import_fn("extism:host/user", "artwork_getartisturl")
-def _artwork_getartisturl(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "artwork_getalbumurl")
-def _artwork_getalbumurl(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "artwork_gettrackurl")
-def _artwork_gettrackurl(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "artwork_getplaylisturl")
-def _artwork_getplaylisturl(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-def artwork_get_artist_url(id: str, size: int) -> str:
- """GetArtistUrl generates a public URL for an artist's artwork.
-
-Parameters:
- - id: The artist's unique identifier
- - size: Desired image size in pixels (0 for original size)
-
-Returns the public URL for the artwork, or an error if generation fails.
-
- Args:
- id: str parameter.
- size: int parameter.
-
- Returns:
- str: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "id": id,
- "size": size,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _artwork_getartisturl(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("url", "")
-
-
-def artwork_get_album_url(id: str, size: int) -> str:
- """GetAlbumUrl generates a public URL for an album's artwork.
-
-Parameters:
- - id: The album's unique identifier
- - size: Desired image size in pixels (0 for original size)
-
-Returns the public URL for the artwork, or an error if generation fails.
-
- Args:
- id: str parameter.
- size: int parameter.
-
- Returns:
- str: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "id": id,
- "size": size,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _artwork_getalbumurl(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("url", "")
-
-
-def artwork_get_track_url(id: str, size: int) -> str:
- """GetTrackUrl generates a public URL for a track's artwork.
-
-Parameters:
- - id: The track's (media file) unique identifier
- - size: Desired image size in pixels (0 for original size)
-
-Returns the public URL for the artwork, or an error if generation fails.
-
- Args:
- id: str parameter.
- size: int parameter.
-
- Returns:
- str: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "id": id,
- "size": size,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _artwork_gettrackurl(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("url", "")
-
-
-def artwork_get_playlist_url(id: str, size: int) -> str:
- """GetPlaylistUrl generates a public URL for a playlist's artwork.
-
-Parameters:
- - id: The playlist's unique identifier
- - size: Desired image size in pixels (0 for original size)
-
-Returns the public URL for the artwork, or an error if generation fails.
-
- Args:
- id: str parameter.
- size: int parameter.
-
- Returns:
- str: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "id": id,
- "size": size,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _artwork_getplaylisturl(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("url", "")
diff --git a/plugins/host/python/nd_host_cache.py b/plugins/host/python/nd_host_cache.py
deleted file mode 100644
index f06b2ad0c..000000000
--- a/plugins/host/python/nd_host_cache.py
+++ /dev/null
@@ -1,447 +0,0 @@
-# Code generated by hostgen. DO NOT EDIT.
-#
-# This file contains client wrappers for the Cache host service.
-# It is intended for use in Navidrome plugins built with extism-py.
-#
-# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly.
-# The @extism.import_fn decorators are only detected when defined in the plugin's
-# main __init__.py file. Copy the needed functions from this file into your plugin.
-
-from dataclasses import dataclass
-from typing import Any
-
-import extism
-import json
-
-
-class HostFunctionError(Exception):
- """Raised when a host function returns an error."""
- pass
-
-
-@extism.import_fn("extism:host/user", "cache_setstring")
-def _cache_setstring(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "cache_getstring")
-def _cache_getstring(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "cache_setint")
-def _cache_setint(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "cache_getint")
-def _cache_getint(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "cache_setfloat")
-def _cache_setfloat(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "cache_getfloat")
-def _cache_getfloat(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "cache_setbytes")
-def _cache_setbytes(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "cache_getbytes")
-def _cache_getbytes(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "cache_has")
-def _cache_has(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "cache_remove")
-def _cache_remove(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@dataclass
-class CacheGetStringResult:
- """Result type for cache_get_string."""
- value: str
- exists: bool
-
-
-@dataclass
-class CacheGetIntResult:
- """Result type for cache_get_int."""
- value: int
- exists: bool
-
-
-@dataclass
-class CacheGetFloatResult:
- """Result type for cache_get_float."""
- value: float
- exists: bool
-
-
-@dataclass
-class CacheGetBytesResult:
- """Result type for cache_get_bytes."""
- value: bytes
- exists: bool
-
-
-def cache_set_string(key: str, value: str, ttl_seconds: int) -> None:
- """SetString stores a string value in the cache.
-
-Parameters:
- - key: The cache key (will be namespaced with plugin ID)
- - value: The string value to store
- - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-
-Returns an error if the operation fails.
-
- Args:
- key: str parameter.
- value: str parameter.
- ttl_seconds: int parameter.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- "value": value,
- "ttlSeconds": ttl_seconds,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _cache_setstring(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
-
-
-def cache_get_string(key: str) -> CacheGetStringResult:
- """GetString retrieves a string value from the cache.
-
-Parameters:
- - key: The cache key (will be namespaced with plugin ID)
-
-Returns the value and whether the key exists. If the key doesn't exist
-or the stored value is not a string, exists will be false.
-
- Args:
- key: str parameter.
-
- Returns:
- CacheGetStringResult containing value, exists,.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _cache_getstring(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return CacheGetStringResult(
- value=response.get("value", ""),
- exists=response.get("exists", False),
- )
-
-
-def cache_set_int(key: str, value: int, ttl_seconds: int) -> None:
- """SetInt stores an integer value in the cache.
-
-Parameters:
- - key: The cache key (will be namespaced with plugin ID)
- - value: The integer value to store
- - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-
-Returns an error if the operation fails.
-
- Args:
- key: str parameter.
- value: int parameter.
- ttl_seconds: int parameter.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- "value": value,
- "ttlSeconds": ttl_seconds,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _cache_setint(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
-
-
-def cache_get_int(key: str) -> CacheGetIntResult:
- """GetInt retrieves an integer value from the cache.
-
-Parameters:
- - key: The cache key (will be namespaced with plugin ID)
-
-Returns the value and whether the key exists. If the key doesn't exist
-or the stored value is not an integer, exists will be false.
-
- Args:
- key: str parameter.
-
- Returns:
- CacheGetIntResult containing value, exists,.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _cache_getint(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return CacheGetIntResult(
- value=response.get("value", 0),
- exists=response.get("exists", False),
- )
-
-
-def cache_set_float(key: str, value: float, ttl_seconds: int) -> None:
- """SetFloat stores a float value in the cache.
-
-Parameters:
- - key: The cache key (will be namespaced with plugin ID)
- - value: The float value to store
- - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-
-Returns an error if the operation fails.
-
- Args:
- key: str parameter.
- value: float parameter.
- ttl_seconds: int parameter.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- "value": value,
- "ttlSeconds": ttl_seconds,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _cache_setfloat(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
-
-
-def cache_get_float(key: str) -> CacheGetFloatResult:
- """GetFloat retrieves a float value from the cache.
-
-Parameters:
- - key: The cache key (will be namespaced with plugin ID)
-
-Returns the value and whether the key exists. If the key doesn't exist
-or the stored value is not a float, exists will be false.
-
- Args:
- key: str parameter.
-
- Returns:
- CacheGetFloatResult containing value, exists,.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _cache_getfloat(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return CacheGetFloatResult(
- value=response.get("value", 0.0),
- exists=response.get("exists", False),
- )
-
-
-def cache_set_bytes(key: str, value: bytes, ttl_seconds: int) -> None:
- """SetBytes stores a byte slice in the cache.
-
-Parameters:
- - key: The cache key (will be namespaced with plugin ID)
- - value: The byte slice to store
- - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-
-Returns an error if the operation fails.
-
- Args:
- key: str parameter.
- value: bytes parameter.
- ttl_seconds: int parameter.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- "value": value,
- "ttlSeconds": ttl_seconds,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _cache_setbytes(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
-
-
-def cache_get_bytes(key: str) -> CacheGetBytesResult:
- """GetBytes retrieves a byte slice from the cache.
-
-Parameters:
- - key: The cache key (will be namespaced with plugin ID)
-
-Returns the value and whether the key exists. If the key doesn't exist
-or the stored value is not a byte slice, exists will be false.
-
- Args:
- key: str parameter.
-
- Returns:
- CacheGetBytesResult containing value, exists,.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _cache_getbytes(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return CacheGetBytesResult(
- value=response.get("value", b""),
- exists=response.get("exists", False),
- )
-
-
-def cache_has(key: str) -> bool:
- """Has checks if a key exists in the cache.
-
-Parameters:
- - key: The cache key (will be namespaced with plugin ID)
-
-Returns true if the key exists and has not expired.
-
- Args:
- key: str parameter.
-
- Returns:
- bool: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _cache_has(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("exists", False)
-
-
-def cache_remove(key: str) -> None:
- """Remove deletes a value from the cache.
-
-Parameters:
- - key: The cache key (will be namespaced with plugin ID)
-
-Returns an error if the operation fails. Does not return an error if the key doesn't exist.
-
- Args:
- key: str parameter.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _cache_remove(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
diff --git a/plugins/host/python/nd_host_kvstore.py b/plugins/host/python/nd_host_kvstore.py
deleted file mode 100644
index f1a6e5724..000000000
--- a/plugins/host/python/nd_host_kvstore.py
+++ /dev/null
@@ -1,241 +0,0 @@
-# Code generated by hostgen. DO NOT EDIT.
-#
-# This file contains client wrappers for the KVStore host service.
-# It is intended for use in Navidrome plugins built with extism-py.
-#
-# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly.
-# The @extism.import_fn decorators are only detected when defined in the plugin's
-# main __init__.py file. Copy the needed functions from this file into your plugin.
-
-from dataclasses import dataclass
-from typing import Any
-
-import extism
-import json
-
-
-class HostFunctionError(Exception):
- """Raised when a host function returns an error."""
- pass
-
-
-@extism.import_fn("extism:host/user", "kvstore_set")
-def _kvstore_set(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "kvstore_get")
-def _kvstore_get(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "kvstore_delete")
-def _kvstore_delete(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "kvstore_has")
-def _kvstore_has(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "kvstore_list")
-def _kvstore_list(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "kvstore_getstorageused")
-def _kvstore_getstorageused(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@dataclass
-class KVStoreGetResult:
- """Result type for kvstore_get."""
- value: bytes
- exists: bool
-
-
-def kvstore_set(key: str, value: bytes) -> None:
- """Set stores a byte value with the given key.
-
-Parameters:
- - key: The storage key (max 256 bytes, UTF-8)
- - value: The byte slice to store
-
-Returns an error if the storage limit would be exceeded or the operation fails.
-
- Args:
- key: str parameter.
- value: bytes parameter.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- "value": value,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _kvstore_set(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
-
-
-def kvstore_get(key: str) -> KVStoreGetResult:
- """Get retrieves a byte value from storage.
-
-Parameters:
- - key: The storage key
-
-Returns the value and whether the key exists.
-
- Args:
- key: str parameter.
-
- Returns:
- KVStoreGetResult containing value, exists,.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _kvstore_get(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return KVStoreGetResult(
- value=response.get("value", b""),
- exists=response.get("exists", False),
- )
-
-
-def kvstore_delete(key: str) -> None:
- """Delete removes a value from storage.
-
-Parameters:
- - key: The storage key
-
-Returns an error if the operation fails. Does not return an error if the key doesn't exist.
-
- Args:
- key: str parameter.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _kvstore_delete(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
-
-
-def kvstore_has(key: str) -> bool:
- """Has checks if a key exists in storage.
-
-Parameters:
- - key: The storage key
-
-Returns true if the key exists.
-
- Args:
- key: str parameter.
-
- Returns:
- bool: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "key": key,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _kvstore_has(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("exists", False)
-
-
-def kvstore_list(prefix: str) -> Any:
- """List returns all keys matching the given prefix.
-
-Parameters:
- - prefix: Key prefix to filter by (empty string returns all keys)
-
-Returns a slice of matching keys.
-
- Args:
- prefix: str parameter.
-
- Returns:
- Any: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "prefix": prefix,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _kvstore_list(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("keys", None)
-
-
-def kvstore_get_storage_used() -> int:
- """GetStorageUsed returns the total storage used by this plugin in bytes.
-
- Returns:
- int: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request_bytes = b"{}"
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _kvstore_getstorageused(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("bytes", 0)
diff --git a/plugins/host/python/nd_host_library.py b/plugins/host/python/nd_host_library.py
deleted file mode 100644
index 4111df2b9..000000000
--- a/plugins/host/python/nd_host_library.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# Code generated by hostgen. DO NOT EDIT.
-#
-# This file contains client wrappers for the Library host service.
-# It is intended for use in Navidrome plugins built with extism-py.
-#
-# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly.
-# The @extism.import_fn decorators are only detected when defined in the plugin's
-# main __init__.py file. Copy the needed functions from this file into your plugin.
-
-from dataclasses import dataclass
-from typing import Any
-
-import extism
-import json
-
-
-class HostFunctionError(Exception):
- """Raised when a host function returns an error."""
- pass
-
-
-@extism.import_fn("extism:host/user", "library_getlibrary")
-def _library_getlibrary(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "library_getalllibraries")
-def _library_getalllibraries(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-def library_get_library(id: int) -> Any:
- """GetLibrary retrieves metadata for a specific library by ID.
-
-Parameters:
- - id: The library's unique identifier
-
-Returns the library metadata, or an error if the library is not found.
-
- Args:
- id: int parameter.
-
- Returns:
- Any: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "id": id,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _library_getlibrary(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("result", None)
-
-
-def library_get_all_libraries() -> Any:
- """GetAllLibraries retrieves metadata for all configured libraries.
-
-Returns a slice of all libraries with their metadata.
-
- Returns:
- Any: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request_bytes = b"{}"
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _library_getalllibraries(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("result", None)
diff --git a/plugins/host/python/nd_host_scheduler.py b/plugins/host/python/nd_host_scheduler.py
deleted file mode 100644
index 3a94da94d..000000000
--- a/plugins/host/python/nd_host_scheduler.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# Code generated by hostgen. DO NOT EDIT.
-#
-# This file contains client wrappers for the Scheduler host service.
-# It is intended for use in Navidrome plugins built with extism-py.
-#
-# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly.
-# The @extism.import_fn decorators are only detected when defined in the plugin's
-# main __init__.py file. Copy the needed functions from this file into your plugin.
-
-from dataclasses import dataclass
-from typing import Any
-
-import extism
-import json
-
-
-class HostFunctionError(Exception):
- """Raised when a host function returns an error."""
- pass
-
-
-@extism.import_fn("extism:host/user", "scheduler_scheduleonetime")
-def _scheduler_scheduleonetime(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "scheduler_schedulerecurring")
-def _scheduler_schedulerecurring(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "scheduler_cancelschedule")
-def _scheduler_cancelschedule(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-def scheduler_schedule_one_time(delay_seconds: int, payload: str, schedule_id: str) -> str:
- """ScheduleOneTime schedules a one-time event to be triggered after the specified delay.
-Plugins that use this function must also implement the SchedulerCallback capability
-
-Parameters:
- - delaySeconds: Number of seconds to wait before triggering the event
- - payload: Data to be passed to the scheduled event handler
- - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated
-
-Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails.
-
- Args:
- delay_seconds: int parameter.
- payload: str parameter.
- schedule_id: str parameter.
-
- Returns:
- str: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "delaySeconds": delay_seconds,
- "payload": payload,
- "scheduleId": schedule_id,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _scheduler_scheduleonetime(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("newScheduleId", "")
-
-
-def scheduler_schedule_recurring(cron_expression: str, payload: str, schedule_id: str) -> str:
- """ScheduleRecurring schedules a recurring event using a cron expression.
-Plugins that use this function must also implement the SchedulerCallback capability
-
-Parameters:
- - cronExpression: Standard cron format expression (e.g., "0 0 * * *" for daily at midnight)
- - payload: Data to be passed to each scheduled event handler invocation
- - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated
-
-Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails.
-
- Args:
- cron_expression: str parameter.
- payload: str parameter.
- schedule_id: str parameter.
-
- Returns:
- str: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "cronExpression": cron_expression,
- "payload": payload,
- "scheduleId": schedule_id,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _scheduler_schedulerecurring(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("newScheduleId", "")
-
-
-def scheduler_cancel_schedule(schedule_id: str) -> None:
- """CancelSchedule cancels a scheduled job identified by its schedule ID.
-
-This works for both one-time and recurring schedules. Once cancelled, the job will not trigger
-any future events.
-
-Returns an error if the schedule ID is not found or if cancellation fails.
-
- Args:
- schedule_id: str parameter.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "scheduleId": schedule_id,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _scheduler_cancelschedule(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
diff --git a/plugins/host/python/nd_host_subsonicapi.py b/plugins/host/python/nd_host_subsonicapi.py
deleted file mode 100644
index 4eaf06d4c..000000000
--- a/plugins/host/python/nd_host_subsonicapi.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# Code generated by hostgen. DO NOT EDIT.
-#
-# This file contains client wrappers for the SubsonicAPI host service.
-# It is intended for use in Navidrome plugins built with extism-py.
-#
-# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly.
-# The @extism.import_fn decorators are only detected when defined in the plugin's
-# main __init__.py file. Copy the needed functions from this file into your plugin.
-
-from dataclasses import dataclass
-from typing import Any
-
-import extism
-import json
-
-
-class HostFunctionError(Exception):
- """Raised when a host function returns an error."""
- pass
-
-
-@extism.import_fn("extism:host/user", "subsonicapi_call")
-def _subsonicapi_call(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-def subsonicapi_call(uri: str) -> str:
- """Call executes a Subsonic API request and returns the JSON response.
-
-The uri parameter should be the Subsonic API path without the server prefix,
-e.g., "getAlbumList2?type=random&size=10". The response is returned as raw JSON.
-
- Args:
- uri: str parameter.
-
- Returns:
- str: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "uri": uri,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _subsonicapi_call(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("responseJson", "")
diff --git a/plugins/host/python/nd_host_websocket.py b/plugins/host/python/nd_host_websocket.py
deleted file mode 100644
index 67938f41f..000000000
--- a/plugins/host/python/nd_host_websocket.py
+++ /dev/null
@@ -1,181 +0,0 @@
-# Code generated by hostgen. DO NOT EDIT.
-#
-# This file contains client wrappers for the WebSocket host service.
-# It is intended for use in Navidrome plugins built with extism-py.
-#
-# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly.
-# The @extism.import_fn decorators are only detected when defined in the plugin's
-# main __init__.py file. Copy the needed functions from this file into your plugin.
-
-from dataclasses import dataclass
-from typing import Any
-
-import extism
-import json
-
-
-class HostFunctionError(Exception):
- """Raised when a host function returns an error."""
- pass
-
-
-@extism.import_fn("extism:host/user", "websocket_connect")
-def _websocket_connect(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "websocket_sendtext")
-def _websocket_sendtext(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "websocket_sendbinary")
-def _websocket_sendbinary(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-@extism.import_fn("extism:host/user", "websocket_closeconnection")
-def _websocket_closeconnection(offset: int) -> int:
- """Raw host function - do not call directly."""
- ...
-
-
-def websocket_connect(url: str, headers: Any, connection_id: str) -> str:
- """Connect establishes a WebSocket connection to the specified URL.
-
-Plugins that use this function must also implement the WebSocketCallback capability
-to receive incoming messages and connection events.
-
-Parameters:
- - url: The WebSocket URL to connect to (ws:// or wss://)
- - headers: Optional HTTP headers to include in the handshake request
- - connectionID: Optional unique identifier for the connection. If empty, one will be generated
-
-Returns the connection ID that can be used to send messages or close the connection,
-or an error if the connection fails.
-
- Args:
- url: str parameter.
- headers: Any parameter.
- connection_id: str parameter.
-
- Returns:
- str: The result value.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "url": url,
- "headers": headers,
- "connectionId": connection_id,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _websocket_connect(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
- return response.get("newConnectionId", "")
-
-
-def websocket_send_text(connection_id: str, message: str) -> None:
- """SendText sends a text message over an established WebSocket connection.
-
-Parameters:
- - connectionID: The connection identifier returned by Connect
- - message: The text message to send
-
-Returns an error if the connection is not found or if sending fails.
-
- Args:
- connection_id: str parameter.
- message: str parameter.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "connectionId": connection_id,
- "message": message,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _websocket_sendtext(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
-
-
-def websocket_send_binary(connection_id: str, data: bytes) -> None:
- """SendBinary sends binary data over an established WebSocket connection.
-
-Parameters:
- - connectionID: The connection identifier returned by Connect
- - data: The binary data to send
-
-Returns an error if the connection is not found or if sending fails.
-
- Args:
- connection_id: str parameter.
- data: bytes parameter.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "connectionId": connection_id,
- "data": data,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _websocket_sendbinary(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
-
-
-def websocket_close_connection(connection_id: str, code: int, reason: str) -> None:
- """CloseConnection gracefully closes a WebSocket connection.
-
-Parameters:
- - connectionID: The connection identifier returned by Connect
- - code: WebSocket close status code (e.g., 1000 for normal closure)
- - reason: Optional human-readable reason for closing
-
-Returns an error if the connection is not found or if closing fails.
-
- Args:
- connection_id: str parameter.
- code: int parameter.
- reason: str parameter.
-
- Raises:
- HostFunctionError: If the host function returns an error.
- """
- request = {
- "connectionId": connection_id,
- "code": code,
- "reason": reason,
- }
- request_bytes = json.dumps(request).encode("utf-8")
- request_mem = extism.memory.alloc(request_bytes)
- response_offset = _websocket_closeconnection(request_mem.offset)
- response_mem = extism.memory.find(response_offset)
- response = json.loads(extism.memory.string(response_mem))
-
- if response.get("error"):
- raise HostFunctionError(response["error"])
-
diff --git a/plugins/host/rust/lib.rs b/plugins/host/rust/lib.rs
deleted file mode 100644
index b71861398..000000000
--- a/plugins/host/rust/lib.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-//! Navidrome Host Function Wrappers for Rust Plugins
-//!
-//! This crate provides idiomatic Rust wrappers for all Navidrome host services.
-//! It is auto-generated by the hostgen tool and should not be edited manually.
-//!
-//! # Usage
-//!
-//! Add this crate as a dependency in your plugin's Cargo.toml:
-//!
-//! ```toml
-//! [dependencies]
-//! nd-host = { path = "../../host/rust" }
-//! ```
-//!
-//! Then import the services you need:
-//!
-//! ```ignore
-//! use nd_host::{cache, scheduler};
-//!
-//! fn my_plugin_function() -> Result<(), extism_pdk::Error> {
-//! // Use the cache service
-//! cache::set_string("my_key", "my_value", 3600)?;
-//!
-//! // Schedule a recurring task
-//! scheduler::schedule_recurring("@every 5m", "payload", "task_id")?;
-//!
-//! Ok(())
-//! }
-//! ```
-//!
-//! # Available Services
-//!
-//! - [`artwork`] - provides artwork URL generation capabilities for plugins.
-//! - [`cache`] - provides in-memory TTL-based caching capabilities for plugins.
-//! - [`kvstore`] - provides persistent key-value storage for plugins.
-//! - [`library`] - provides access to music library metadata for plugins.
-//! - [`scheduler`] - provides task scheduling capabilities for plugins.
-//! - [`subsonicapi`] - provides access to Navidrome's Subsonic API from plugins.
-//! - [`websocket`] - provides WebSocket communication capabilities for plugins.
-
-#[path = "nd_host_artwork.rs"]
-pub mod artwork;
-
-#[path = "nd_host_cache.rs"]
-pub mod cache;
-
-#[path = "nd_host_kvstore.rs"]
-pub mod kvstore;
-
-#[path = "nd_host_library.rs"]
-pub mod library;
-
-#[path = "nd_host_scheduler.rs"]
-pub mod scheduler;
-
-#[path = "nd_host_subsonicapi.rs"]
-pub mod subsonicapi;
-
-#[path = "nd_host_websocket.rs"]
-pub mod websocket;
-
-// Re-export commonly used types from extism-pdk for convenience
-pub use extism_pdk::Error;
diff --git a/plugins/host/rust/nd_host_artwork.rs b/plugins/host/rust/nd_host_artwork.rs
deleted file mode 100644
index 31ccb4370..000000000
--- a/plugins/host/rust/nd_host_artwork.rs
+++ /dev/null
@@ -1,207 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the Artwork host service.
-// It is intended for use in Navidrome plugins built with extism-pdk.
-
-use extism_pdk::*;
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct ArtworkGetArtistUrlRequest {
- id: String,
- size: i32,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct ArtworkGetArtistUrlResponse {
- #[serde(default)]
- url: String,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct ArtworkGetAlbumUrlRequest {
- id: String,
- size: i32,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct ArtworkGetAlbumUrlResponse {
- #[serde(default)]
- url: String,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct ArtworkGetTrackUrlRequest {
- id: String,
- size: i32,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct ArtworkGetTrackUrlResponse {
- #[serde(default)]
- url: String,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct ArtworkGetPlaylistUrlRequest {
- id: String,
- size: i32,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct ArtworkGetPlaylistUrlResponse {
- #[serde(default)]
- url: String,
- #[serde(default)]
- error: Option,
-}
-
-#[host_fn]
-extern "ExtismHost" {
- fn artwork_getartisturl(input: Json) -> Json;
- fn artwork_getalbumurl(input: Json) -> Json;
- fn artwork_gettrackurl(input: Json) -> Json;
- fn artwork_getplaylisturl(input: Json) -> Json;
-}
-
-/// GetArtistUrl generates a public URL for an artist's artwork.
-///
-/// Parameters:
-/// - id: The artist's unique identifier
-/// - size: Desired image size in pixels (0 for original size)
-///
-/// Returns the public URL for the artwork, or an error if generation fails.
-///
-/// # Arguments
-/// * `id` - String parameter.
-/// * `size` - i32 parameter.
-///
-/// # Returns
-/// The url value.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn get_artist_url(id: &str, size: i32) -> Result {
- let response = unsafe {
- artwork_getartisturl(Json(ArtworkGetArtistUrlRequest {
- id: id.to_owned(),
- size: size,
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(response.0.url)
-}
-
-/// GetAlbumUrl generates a public URL for an album's artwork.
-///
-/// Parameters:
-/// - id: The album's unique identifier
-/// - size: Desired image size in pixels (0 for original size)
-///
-/// Returns the public URL for the artwork, or an error if generation fails.
-///
-/// # Arguments
-/// * `id` - String parameter.
-/// * `size` - i32 parameter.
-///
-/// # Returns
-/// The url value.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn get_album_url(id: &str, size: i32) -> Result {
- let response = unsafe {
- artwork_getalbumurl(Json(ArtworkGetAlbumUrlRequest {
- id: id.to_owned(),
- size: size,
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(response.0.url)
-}
-
-/// GetTrackUrl generates a public URL for a track's artwork.
-///
-/// Parameters:
-/// - id: The track's (media file) unique identifier
-/// - size: Desired image size in pixels (0 for original size)
-///
-/// Returns the public URL for the artwork, or an error if generation fails.
-///
-/// # Arguments
-/// * `id` - String parameter.
-/// * `size` - i32 parameter.
-///
-/// # Returns
-/// The url value.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn get_track_url(id: &str, size: i32) -> Result {
- let response = unsafe {
- artwork_gettrackurl(Json(ArtworkGetTrackUrlRequest {
- id: id.to_owned(),
- size: size,
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(response.0.url)
-}
-
-/// GetPlaylistUrl generates a public URL for a playlist's artwork.
-///
-/// Parameters:
-/// - id: The playlist's unique identifier
-/// - size: Desired image size in pixels (0 for original size)
-///
-/// Returns the public URL for the artwork, or an error if generation fails.
-///
-/// # Arguments
-/// * `id` - String parameter.
-/// * `size` - i32 parameter.
-///
-/// # Returns
-/// The url value.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn get_playlist_url(id: &str, size: i32) -> Result {
- let response = unsafe {
- artwork_getplaylisturl(Json(ArtworkGetPlaylistUrlRequest {
- id: id.to_owned(),
- size: size,
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(response.0.url)
-}
diff --git a/plugins/host/rust/nd_host_cache.rs b/plugins/host/rust/nd_host_cache.rs
deleted file mode 100644
index f223c8de8..000000000
--- a/plugins/host/rust/nd_host_cache.rs
+++ /dev/null
@@ -1,480 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the Cache host service.
-// It is intended for use in Navidrome plugins built with extism-pdk.
-
-use extism_pdk::*;
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheSetStringRequest {
- key: String,
- value: String,
- ttl_seconds: i64,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheSetStringResponse {
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheGetStringRequest {
- key: String,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheGetStringResponse {
- #[serde(default)]
- value: String,
- #[serde(default)]
- exists: bool,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheSetIntRequest {
- key: String,
- value: i64,
- ttl_seconds: i64,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheSetIntResponse {
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheGetIntRequest {
- key: String,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheGetIntResponse {
- #[serde(default)]
- value: i64,
- #[serde(default)]
- exists: bool,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheSetFloatRequest {
- key: String,
- value: f64,
- ttl_seconds: i64,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheSetFloatResponse {
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheGetFloatRequest {
- key: String,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheGetFloatResponse {
- #[serde(default)]
- value: f64,
- #[serde(default)]
- exists: bool,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheSetBytesRequest {
- key: String,
- value: Vec,
- ttl_seconds: i64,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheSetBytesResponse {
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheGetBytesRequest {
- key: String,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheGetBytesResponse {
- #[serde(default)]
- value: Vec,
- #[serde(default)]
- exists: bool,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheHasRequest {
- key: String,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheHasResponse {
- #[serde(default)]
- exists: bool,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheRemoveRequest {
- key: String,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct CacheRemoveResponse {
- #[serde(default)]
- error: Option,
-}
-
-#[host_fn]
-extern "ExtismHost" {
- fn cache_setstring(input: Json) -> Json;
- fn cache_getstring(input: Json) -> Json;
- fn cache_setint(input: Json) -> Json;
- fn cache_getint(input: Json) -> Json;
- fn cache_setfloat(input: Json) -> Json;
- fn cache_getfloat(input: Json) -> Json;
- fn cache_setbytes(input: Json) -> Json;
- fn cache_getbytes(input: Json) -> Json;
- fn cache_has(input: Json) -> Json;
- fn cache_remove(input: Json) -> Json;
-}
-
-/// SetString stores a string value in the cache.
-///
-/// Parameters:
-/// - key: The cache key (will be namespaced with plugin ID)
-/// - value: The string value to store
-/// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-///
-/// Returns an error if the operation fails.
-///
-/// # Arguments
-/// * `key` - String parameter.
-/// * `value` - String parameter.
-/// * `ttl_seconds` - i64 parameter.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn set_string(key: &str, value: &str, ttl_seconds: i64) -> Result<(), Error> {
- let response = unsafe {
- cache_setstring(Json(CacheSetStringRequest {
- key: key.to_owned(),
- value: value.to_owned(),
- ttl_seconds: ttl_seconds,
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(())
-}
-
-/// GetString retrieves a string value from the cache.
-///
-/// Parameters:
-/// - key: The cache key (will be namespaced with plugin ID)
-///
-/// Returns the value and whether the key exists. If the key doesn't exist
-/// or the stored value is not a string, exists will be false.
-///
-/// # Arguments
-/// * `key` - String parameter.
-///
-/// # Returns
-/// A tuple of (value, exists).
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn get_string(key: &str) -> Result<(String, bool), Error> {
- let response = unsafe {
- cache_getstring(Json(CacheGetStringRequest {
- key: key.to_owned(),
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok((response.0.value, response.0.exists))
-}
-
-/// SetInt stores an integer value in the cache.
-///
-/// Parameters:
-/// - key: The cache key (will be namespaced with plugin ID)
-/// - value: The integer value to store
-/// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-///
-/// Returns an error if the operation fails.
-///
-/// # Arguments
-/// * `key` - String parameter.
-/// * `value` - i64 parameter.
-/// * `ttl_seconds` - i64 parameter.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn set_int(key: &str, value: i64, ttl_seconds: i64) -> Result<(), Error> {
- let response = unsafe {
- cache_setint(Json(CacheSetIntRequest {
- key: key.to_owned(),
- value: value,
- ttl_seconds: ttl_seconds,
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(())
-}
-
-/// GetInt retrieves an integer value from the cache.
-///
-/// Parameters:
-/// - key: The cache key (will be namespaced with plugin ID)
-///
-/// Returns the value and whether the key exists. If the key doesn't exist
-/// or the stored value is not an integer, exists will be false.
-///
-/// # Arguments
-/// * `key` - String parameter.
-///
-/// # Returns
-/// A tuple of (value, exists).
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn get_int(key: &str) -> Result<(i64, bool), Error> {
- let response = unsafe {
- cache_getint(Json(CacheGetIntRequest {
- key: key.to_owned(),
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok((response.0.value, response.0.exists))
-}
-
-/// SetFloat stores a float value in the cache.
-///
-/// Parameters:
-/// - key: The cache key (will be namespaced with plugin ID)
-/// - value: The float value to store
-/// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-///
-/// Returns an error if the operation fails.
-///
-/// # Arguments
-/// * `key` - String parameter.
-/// * `value` - f64 parameter.
-/// * `ttl_seconds` - i64 parameter.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn set_float(key: &str, value: f64, ttl_seconds: i64) -> Result<(), Error> {
- let response = unsafe {
- cache_setfloat(Json(CacheSetFloatRequest {
- key: key.to_owned(),
- value: value,
- ttl_seconds: ttl_seconds,
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(())
-}
-
-/// GetFloat retrieves a float value from the cache.
-///
-/// Parameters:
-/// - key: The cache key (will be namespaced with plugin ID)
-///
-/// Returns the value and whether the key exists. If the key doesn't exist
-/// or the stored value is not a float, exists will be false.
-///
-/// # Arguments
-/// * `key` - String parameter.
-///
-/// # Returns
-/// A tuple of (value, exists).
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn get_float(key: &str) -> Result<(f64, bool), Error> {
- let response = unsafe {
- cache_getfloat(Json(CacheGetFloatRequest {
- key: key.to_owned(),
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok((response.0.value, response.0.exists))
-}
-
-/// SetBytes stores a byte slice in the cache.
-///
-/// Parameters:
-/// - key: The cache key (will be namespaced with plugin ID)
-/// - value: The byte slice to store
-/// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
-///
-/// Returns an error if the operation fails.
-///
-/// # Arguments
-/// * `key` - String parameter.
-/// * `value` - Vec parameter.
-/// * `ttl_seconds` - i64 parameter.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn set_bytes(key: &str, value: Vec, ttl_seconds: i64) -> Result<(), Error> {
- let response = unsafe {
- cache_setbytes(Json(CacheSetBytesRequest {
- key: key.to_owned(),
- value: value,
- ttl_seconds: ttl_seconds,
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(())
-}
-
-/// GetBytes retrieves a byte slice from the cache.
-///
-/// Parameters:
-/// - key: The cache key (will be namespaced with plugin ID)
-///
-/// Returns the value and whether the key exists. If the key doesn't exist
-/// or the stored value is not a byte slice, exists will be false.
-///
-/// # Arguments
-/// * `key` - String parameter.
-///
-/// # Returns
-/// A tuple of (value, exists).
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn get_bytes(key: &str) -> Result<(Vec, bool), Error> {
- let response = unsafe {
- cache_getbytes(Json(CacheGetBytesRequest {
- key: key.to_owned(),
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok((response.0.value, response.0.exists))
-}
-
-/// Has checks if a key exists in the cache.
-///
-/// Parameters:
-/// - key: The cache key (will be namespaced with plugin ID)
-///
-/// Returns true if the key exists and has not expired.
-///
-/// # Arguments
-/// * `key` - String parameter.
-///
-/// # Returns
-/// The exists value.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn has(key: &str) -> Result {
- let response = unsafe {
- cache_has(Json(CacheHasRequest {
- key: key.to_owned(),
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(response.0.exists)
-}
-
-/// Remove deletes a value from the cache.
-///
-/// Parameters:
-/// - key: The cache key (will be namespaced with plugin ID)
-///
-/// Returns an error if the operation fails. Does not return an error if the key doesn't exist.
-///
-/// # Arguments
-/// * `key` - String parameter.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn remove(key: &str) -> Result<(), Error> {
- let response = unsafe {
- cache_remove(Json(CacheRemoveRequest {
- key: key.to_owned(),
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(())
-}
diff --git a/plugins/host/rust/nd_host_kvstore.rs b/plugins/host/rust/nd_host_kvstore.rs
deleted file mode 100644
index 5c25a7e9b..000000000
--- a/plugins/host/rust/nd_host_kvstore.rs
+++ /dev/null
@@ -1,261 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the KVStore host service.
-// It is intended for use in Navidrome plugins built with extism-pdk.
-
-use extism_pdk::*;
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct KVStoreSetRequest {
- key: String,
- value: Vec,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct KVStoreSetResponse {
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct KVStoreGetRequest {
- key: String,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct KVStoreGetResponse {
- #[serde(default)]
- value: Vec,
- #[serde(default)]
- exists: bool,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct KVStoreDeleteRequest {
- key: String,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct KVStoreDeleteResponse {
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct KVStoreHasRequest {
- key: String,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct KVStoreHasResponse {
- #[serde(default)]
- exists: bool,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct KVStoreListRequest {
- prefix: String,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct KVStoreListResponse {
- #[serde(default)]
- keys: Vec,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct KVStoreGetStorageUsedResponse {
- #[serde(default)]
- bytes: i64,
- #[serde(default)]
- error: Option,
-}
-
-#[host_fn]
-extern "ExtismHost" {
- fn kvstore_set(input: Json) -> Json;
- fn kvstore_get(input: Json) -> Json;
- fn kvstore_delete(input: Json) -> Json;
- fn kvstore_has(input: Json) -> Json;
- fn kvstore_list(input: Json) -> Json;
- fn kvstore_getstorageused(input: Json) -> Json;
-}
-
-/// Set stores a byte value with the given key.
-///
-/// Parameters:
-/// - key: The storage key (max 256 bytes, UTF-8)
-/// - value: The byte slice to store
-///
-/// Returns an error if the storage limit would be exceeded or the operation fails.
-///
-/// # Arguments
-/// * `key` - String parameter.
-/// * `value` - Vec parameter.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn set(key: &str, value: Vec) -> Result<(), Error> {
- let response = unsafe {
- kvstore_set(Json(KVStoreSetRequest {
- key: key.to_owned(),
- value: value,
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(())
-}
-
-/// Get retrieves a byte value from storage.
-///
-/// Parameters:
-/// - key: The storage key
-///
-/// Returns the value and whether the key exists.
-///
-/// # Arguments
-/// * `key` - String parameter.
-///
-/// # Returns
-/// A tuple of (value, exists).
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn get(key: &str) -> Result<(Vec, bool), Error> {
- let response = unsafe {
- kvstore_get(Json(KVStoreGetRequest {
- key: key.to_owned(),
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok((response.0.value, response.0.exists))
-}
-
-/// Delete removes a value from storage.
-///
-/// Parameters:
-/// - key: The storage key
-///
-/// Returns an error if the operation fails. Does not return an error if the key doesn't exist.
-///
-/// # Arguments
-/// * `key` - String parameter.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn delete(key: &str) -> Result<(), Error> {
- let response = unsafe {
- kvstore_delete(Json(KVStoreDeleteRequest {
- key: key.to_owned(),
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(())
-}
-
-/// Has checks if a key exists in storage.
-///
-/// Parameters:
-/// - key: The storage key
-///
-/// Returns true if the key exists.
-///
-/// # Arguments
-/// * `key` - String parameter.
-///
-/// # Returns
-/// The exists value.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn has(key: &str) -> Result {
- let response = unsafe {
- kvstore_has(Json(KVStoreHasRequest {
- key: key.to_owned(),
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(response.0.exists)
-}
-
-/// List returns all keys matching the given prefix.
-///
-/// Parameters:
-/// - prefix: Key prefix to filter by (empty string returns all keys)
-///
-/// Returns a slice of matching keys.
-///
-/// # Arguments
-/// * `prefix` - String parameter.
-///
-/// # Returns
-/// The keys value.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn list(prefix: &str) -> Result, Error> {
- let response = unsafe {
- kvstore_list(Json(KVStoreListRequest {
- prefix: prefix.to_owned(),
- }))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(response.0.keys)
-}
-
-/// GetStorageUsed returns the total storage used by this plugin in bytes.
-///
-/// # Returns
-/// The bytes value.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn get_storage_used() -> Result {
- let response = unsafe {
- kvstore_getstorageused(Json(serde_json::json!({})))?
- };
-
- if let Some(err) = response.0.error {
- return Err(Error::msg(err));
- }
-
- Ok(response.0.bytes)
-}
diff --git a/plugins/host/rust/nd_host_library.rs b/plugins/host/rust/nd_host_library.rs
deleted file mode 100644
index ca6bf958d..000000000
--- a/plugins/host/rust/nd_host_library.rs
+++ /dev/null
@@ -1,105 +0,0 @@
-// Code generated by hostgen. DO NOT EDIT.
-//
-// This file contains client wrappers for the Library host service.
-// It is intended for use in Navidrome plugins built with extism-pdk.
-
-use extism_pdk::*;
-use serde::{Deserialize, Serialize};
-
-/// Library represents a music library with metadata.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Library {
- pub id: i32,
- pub name: String,
- #[serde(default)]
- pub path: String,
- #[serde(default)]
- pub mount_point: String,
- pub last_scan_at: i64,
- pub total_songs: i32,
- pub total_albums: i32,
- pub total_artists: i32,
- pub total_size: i64,
- pub total_duration: f64,
-}
-
-#[derive(Debug, Clone, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct LibraryGetLibraryRequest {
- id: i32,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct LibraryGetLibraryResponse {
- #[serde(default)]
- result: Option,
- #[serde(default)]
- error: Option,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct LibraryGetAllLibrariesResponse {
- #[serde(default)]
- result: Vec,
- #[serde(default)]
- error: Option,
-}
-
-#[host_fn]
-extern "ExtismHost" {
- fn library_getlibrary(input: Json) -> Json;
- fn library_getalllibraries(input: Json) -> Json;
-}
-
-/// GetLibrary retrieves metadata for a specific library by ID.
-///
-/// Parameters:
-/// - id: The library's unique identifier
-///
-/// Returns the library metadata, or an error if the library is not found.
-///
-/// # Arguments
-/// * `id` - i32 parameter.
-///
-/// # Returns
-/// The result value.
-///
-/// # Errors
-/// Returns an error if the host function call fails.
-pub fn get_library(id: i32) -> Result