├── .github └── workflows │ └── golangci-lint.yml ├── .golangci.yaml ├── Dockerfile ├── LICENSE ├── README.md ├── cache.go ├── clean.go ├── cloud ├── api │ ├── handlers │ │ ├── clean.go │ │ ├── delete.go │ │ ├── exists.go │ │ ├── fulltext.go │ │ ├── get.go │ │ ├── indices.go │ │ ├── info.go │ │ ├── init.go │ │ ├── keys.go │ │ ├── length.go │ │ ├── search.go │ │ ├── set.go │ │ └── values.go │ ├── routes.go │ └── utils │ │ ├── decode.go │ │ ├── http.go │ │ └── params.go ├── app │ ├── dev │ │ └── .gitkeep │ ├── prod │ │ └── .gitkeep │ └── server │ │ ├── app.go │ │ ├── go.mod │ │ ├── go.sum │ │ └── utils │ │ └── args.go ├── database │ └── .gitkeep ├── socket │ ├── errors.go │ ├── funcs.go │ ├── handlers │ │ ├── clean.go │ │ ├── delete.go │ │ ├── exists.go │ │ ├── fulltext.go │ │ ├── get.go │ │ ├── indices.go │ │ ├── info.go │ │ ├── init.go │ │ ├── keys.go │ │ ├── length.go │ │ ├── search.go │ │ ├── set.go │ │ └── values.go │ ├── router.go │ ├── socket.go │ └── utils │ │ ├── decode.go │ │ ├── errors.go │ │ ├── http.go │ │ └── params.go └── wrappers │ ├── go │ └── main.go │ ├── npm │ └── main.ts │ ├── python │ ├── LICENSE │ ├── README.md │ ├── dist │ │ ├── hermescloud-0.0.6-py3-none-any.whl │ │ └── hermescloud-0.0.6.tar.gz │ ├── pyproject.toml │ ├── setup.cfg │ └── src │ │ ├── hermescloud.egg-info │ │ ├── PKG-INFO │ │ ├── SOURCES.txt │ │ ├── dependency_links.txt │ │ └── top_level.txt │ │ ├── hermescloud │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-310.pyc │ │ │ ├── cache.cpython-310.pyc │ │ │ ├── hermes.cpython-310.pyc │ │ │ └── utils.cpython-310.pyc │ │ ├── cache.py │ │ └── utils.py │ │ ├── test_base.py │ │ └── test_withjson.py │ └── rust │ ├── Cargo.lock │ ├── Cargo.toml │ ├── notes.txt │ ├── src │ └── main.rs │ └── target │ ├── .rustc_info.json │ ├── CACHEDIR.TAG │ └── debug │ ├── .cargo-lock │ ├── .fingerprint │ ├── hermes-515e19fc54440f23 │ │ ├── dep-test-bin-hermes │ │ ├── invoked.timestamp │ │ ├── test-bin-hermes │ │ └── test-bin-hermes.json │ └── hermes-d80b4823bd3ba10f │ │ ├── bin-hermes │ │ ├── bin-hermes.json │ │ ├── dep-bin-hermes │ │ └── invoked.timestamp │ ├── deps │ ├── hermes-515e19fc54440f23.d │ ├── hermes-d80b4823bd3ba10f.d │ ├── libhermes-515e19fc54440f23.rmeta │ └── libhermes-d80b4823bd3ba10f.rmeta │ └── incremental │ ├── hermes-1b6yi2bw8b899 │ ├── s-gkiytxaoyx-n4pevb-2nsnbq1tpmfaw │ │ ├── dep-graph.bin │ │ ├── query-cache.bin │ │ └── work-products.bin │ └── s-gkiytxaoyx-n4pevb.lock │ └── hermes-1msokizf0w59t │ ├── s-gkiytxaoyx-61kh8a-385m7v9kh6g64 │ ├── dep-graph.bin │ ├── query-cache.bin │ └── work-products.bin │ └── s-gkiytxaoyx-61kh8a.lock ├── compression ├── gzip │ └── gzip.go └── zlib │ └── zlib.go ├── delete.go ├── docker-compose.yml ├── examples ├── basic │ └── basic.go └── router │ └── router.go ├── exists.go ├── fulltext.go ├── get.go ├── go.mod ├── go.sum ├── go.work ├── go.work.sum ├── indices.go ├── info.go ├── init.go ├── keys.go ├── length.go ├── nocache ├── examples │ ├── basic │ │ └── basic.go │ └── router │ │ └── router.go ├── fulltext.go ├── init.go ├── insert.go ├── search.go ├── searchoneword.go ├── searchparams.go ├── searchvalues.go ├── searchwithkey.go └── withft.go ├── search.go ├── searchoneword.go ├── searchparams.go ├── searchvalues.go ├── searchwithkey.go ├── set.go ├── tempstorage.go ├── testing ├── api │ ├── request.py │ └── router.go ├── cache │ └── cache.go ├── compression │ └── compression.go ├── data │ ├── data_array.json │ ├── data_hash.json │ └── data_stats.py ├── fulltext │ ├── clean.go │ ├── delete.go │ ├── ft.go │ ├── insert_key_merge.go │ ├── main.go │ ├── search.go │ └── set.go ├── nocache │ └── speed │ │ └── speed.go ├── socket │ └── router.go └── speed │ ├── func_speeds │ └── fnspeeds.go │ └── search_speeds │ └── searchspeeds.go ├── utils ├── buffer.go ├── json.go ├── slices.go └── strings.go ├── values.go ├── website ├── .editorconfig ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── tasks.json ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── code-example │ │ │ ├── code-example.component.css │ │ │ ├── code-example.component.html │ │ │ ├── code-example.component.spec.ts │ │ │ └── code-example.component.ts │ │ ├── laptop │ │ │ ├── laptop.component.html │ │ │ ├── laptop.component.scss │ │ │ ├── laptop.component.spec.ts │ │ │ └── laptop.component.ts │ │ ├── navbar-button │ │ │ ├── navbar-button.component.css │ │ │ ├── navbar-button.component.html │ │ │ ├── navbar-button.component.spec.ts │ │ │ └── navbar-button.component.ts │ │ ├── navbar │ │ │ ├── navbar.component.css │ │ │ ├── navbar.component.html │ │ │ ├── navbar.component.spec.ts │ │ │ └── navbar.component.ts │ │ └── terminal │ │ │ ├── terminal.component.css │ │ │ ├── terminal.component.html │ │ │ ├── terminal.component.spec.ts │ │ │ └── terminal.component.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ ├── github.png │ │ │ └── logo.png │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ └── styles.css ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json └── withft.go /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | pull_request: 9 | permissions: 10 | contents: read 11 | jobs: 12 | golangci: 13 | name: lint 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-go@v4 18 | with: 19 | go-version: '1.20' 20 | cache: false 21 | - name: golangci-lint 22 | uses: golangci/golangci-lint-action@v3 23 | with: 24 | version: latest 25 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable: 3 | - gosec 4 | - govet 5 | presets: 6 | - bugs 7 | - performance 8 | run: 9 | go: '1.20' 10 | issues: 11 | exclude-rules: 12 | - path: testing 13 | linters: 14 | - errcheck 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the offical golang image to create a binary. 2 | # This is based on Debian and sets the GOPATH to /go. 3 | # https://hub.docker.com/_/golang 4 | FROM golang:1.20-buster as builder 5 | 6 | # Create and change to the app directory. 7 | WORKDIR /app 8 | 9 | # Retrieve application dependencies. 10 | # This allows the container build to reuse cached dependencies. 11 | # Expecting to copy go.mod and if present go.sum. 12 | COPY /cloud/app/server/go.* ./ 13 | RUN go mod download 14 | 15 | # Copy local code to the container image. 16 | COPY . ./ 17 | 18 | # Build the binary. 19 | RUN go build -v -o server 20 | 21 | # Use the official Debian slim image for a lean production container. 22 | # https://hub.docker.com/_/debian 23 | # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds 24 | FROM debian:buster-slim 25 | RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ 26 | ca-certificates && \ 27 | rm -rf /var/lib/apt/lists/* 28 | 29 | # Copy the binary to the production image from the builder stage. 30 | COPY --from=builder /app/server /app/server 31 | 32 | # Run the web service on container startup. 33 | CMD ["/app/server serve -p 3000"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tristan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Cache is a struct that represents an in-memory cache of key-value pairs. 8 | // The cache can be used to store arbitrary data, and supports concurrent access through a mutex. 9 | // Additionally, the cache can be configured to support full-text search using a FullText index. 10 | // Fields: 11 | // - data (map[string]map[string]any): A map that stores the data in the cache. The keys of the map are strings that represent the cache keys, and the values are sub-maps that store the actual data under string keys. 12 | // - mutex (*sync.RWMutex): A RWMutex that guards access to the cache data. 13 | // - ft (*FullText): A FullText index that can be used for full-text search. If nil, full-text search is disabled. 14 | type Cache struct { 15 | data map[string]map[string]any 16 | mutex *sync.RWMutex 17 | ft *FullText 18 | } 19 | -------------------------------------------------------------------------------- /clean.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | import "errors" 4 | 5 | // Clean is a method of the Cache struct that clears the cache contents. 6 | // If the full-text index is initialized, it is also cleared. 7 | // This method is thread-safe. 8 | // 9 | // Parameters: 10 | // - None 11 | // 12 | // Returns: 13 | // - None 14 | func (c *Cache) Clean() { 15 | c.mutex.Lock() 16 | defer c.mutex.Unlock() 17 | c.clean() 18 | } 19 | 20 | // clean is a method of the Cache struct that clears the cache contents. 21 | // If the full-text index is initialized, it is also cleared. 22 | // This method is not thread-safe and should only be called from an exported function. 23 | // 24 | // Parameters: 25 | // - None 26 | // 27 | // Returns: 28 | // - None 29 | func (c *Cache) clean() { 30 | if c.ft != nil { 31 | c.ft.clean() 32 | } 33 | c.data = map[string]map[string]any{} 34 | } 35 | 36 | // FTClean is a method of the Cache struct that clears the full-text cache contents. 37 | // If the full-text index is not initialized, this method returns an error. 38 | // Otherwise, the full-text index storage and indices are cleared, and this method returns nil. 39 | // This method is thread-safe. 40 | // 41 | // Returns: 42 | // - error: An error object. If no error occurs, this will be nil. 43 | func (c *Cache) FTClean() error { 44 | c.mutex.Lock() 45 | defer c.mutex.Unlock() 46 | 47 | // Verify that the full text is initialized 48 | if c.ft == nil { 49 | return errors.New("full text is not initialized") 50 | } 51 | 52 | // Clean the ft cache 53 | c.ft.clean() 54 | 55 | // Return no error 56 | return nil 57 | } 58 | 59 | // clean is a method of the FullText struct that clears the full-text index storage and indices. 60 | // This method initializes a new empty storage map and indices map with the maximum length specified in the FullText struct. 61 | // 62 | // Parameters: 63 | // - None 64 | // 65 | // Returns: 66 | // - None 67 | func (ft *FullText) clean() { 68 | ft.storage = make(map[string]any) 69 | ft.indices = make(map[int]string) 70 | } 71 | -------------------------------------------------------------------------------- /cloud/api/handlers/clean.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | hermes "github.com/realTristan/hermes" 6 | utils "github.com/realTristan/hermes/cloud/api/utils" 7 | ) 8 | 9 | // Clean is a function that returns a fiber context handler function for cleaning the regular cache. 10 | // Parameters: 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that cleans the regular cache and returns a success message. 15 | func Clean(c *hermes.Cache) func(ctx *fiber.Ctx) error { 16 | return func(ctx *fiber.Ctx) error { 17 | c.Clean() 18 | return ctx.Send(utils.Success("null")) 19 | } 20 | } 21 | 22 | // FTClean is a function that returns a fiber context handler function for cleaning the full-text cache. 23 | // Parameters: 24 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 25 | // 26 | // Returns: 27 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that cleans the full-text cache and returns a success message or an error message if the cleaning fails. 28 | func FTClean(c *hermes.Cache) func(ctx *fiber.Ctx) error { 29 | return func(ctx *fiber.Ctx) error { 30 | if err := c.FTClean(); err != nil { 31 | return ctx.Send(utils.Error(err)) 32 | } 33 | return ctx.Send(utils.Success("null")) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cloud/api/handlers/delete.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | hermes "github.com/realTristan/hermes" 6 | utils "github.com/realTristan/hermes/cloud/api/utils" 7 | ) 8 | 9 | // Delete is a handler function that returns a fiber context handler function for deleting a key from the cache. 10 | // Parameters: 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that deletes a key from the cache and returns a success message or an error message if the key is not provided. 15 | func Delete(c *hermes.Cache) func(ctx *fiber.Ctx) error { 16 | return func(ctx *fiber.Ctx) error { 17 | // Get the key from the query 18 | var key string 19 | if key = ctx.Query("key"); len(key) == 0 { 20 | return ctx.Send(utils.Error("key not provided")) 21 | } 22 | 23 | // Delete the key from the cache 24 | c.Delete(key) 25 | return ctx.Send(utils.Success("null")) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cloud/api/handlers/exists.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | hermes "github.com/realTristan/hermes" 6 | utils "github.com/realTristan/hermes/cloud/api/utils" 7 | ) 8 | 9 | // Exists is a handler function that returns a fiber context handler function for checking if a key exists in the cache. 10 | // Parameters: 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that checks if a key exists in the cache and returns a success message with a boolean value indicating whether the key exists or an error message if the key is not provided. 15 | func Exists(c *hermes.Cache) func(ctx *fiber.Ctx) error { 16 | return func(ctx *fiber.Ctx) error { 17 | // Get the key from the query 18 | var key string 19 | if key = ctx.Query("key"); len(key) == 0 { 20 | return ctx.Send(utils.Error("key not provided")) 21 | } 22 | 23 | // Return whether the key exists 24 | return ctx.Send(utils.Success(c.Exists(key))) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /cloud/api/handlers/fulltext.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | hermes "github.com/realTristan/hermes" 8 | utils "github.com/realTristan/hermes/cloud/api/utils" 9 | ) 10 | 11 | // FTIsInitialized is a handler function that returns a fiber context handler function for checking if the full-text search is initialized. 12 | // Parameters: 13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 14 | // 15 | // Returns: 16 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that checks if the full-text search is initialized and returns a success message with a boolean value indicating whether it is initialized. 17 | func FTIsInitialized(c *hermes.Cache) func(ctx *fiber.Ctx) error { 18 | return func(ctx *fiber.Ctx) error { 19 | return ctx.Send(utils.Success(c.FTIsInitialized())) 20 | } 21 | } 22 | 23 | // FTSetMaxBytes is a handler function that returns a fiber context handler function for setting the maximum number of bytes for full-text search. 24 | // Parameters: 25 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 26 | // 27 | // Returns: 28 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that sets the maximum number of bytes for full-text search and returns a success message or an error message if the value is not provided or if the setting fails. 29 | func FTSetMaxBytes(c *hermes.Cache) func(ctx *fiber.Ctx) error { 30 | return func(ctx *fiber.Ctx) error { 31 | // Get the value from the query 32 | var value int 33 | if err := utils.GetMaxBytesParam(ctx, &value); err != nil { 34 | return ctx.Send(utils.Error(err)) 35 | } 36 | 37 | // Set the max bytes 38 | if err := c.FTSetMaxBytes(value); err != nil { 39 | return ctx.Send(utils.Error(err)) 40 | } 41 | return ctx.Send(utils.Success("null")) 42 | } 43 | } 44 | 45 | // FTSetMaxSize is a handler function that returns a fiber context handler function for setting the maximum length for full-text search. 46 | // Parameters: 47 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 48 | // 49 | // Returns: 50 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that sets the maximum length for full-text search and returns a success message or an error message if the value is not provided or if the setting fails. 51 | func FTSetMaxSize(c *hermes.Cache) func(ctx *fiber.Ctx) error { 52 | return func(ctx *fiber.Ctx) error { 53 | // Get the value from the query 54 | var value int 55 | if err := utils.GetMaxSizeParam(ctx, &value); err != nil { 56 | return ctx.Send(utils.Error(err)) 57 | } 58 | 59 | // Set the max length 60 | if err := c.FTSetMaxSize(value); err != nil { 61 | return ctx.Send(utils.Error(err)) 62 | } 63 | return ctx.Send(utils.Success("null")) 64 | } 65 | } 66 | 67 | // FTStorage is a handler function that returns a fiber context handler function for getting the full-text storage. 68 | // Parameters: 69 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 70 | // 71 | // Returns: 72 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets the full-text storage and returns a JSON-encoded string of the data or an error message if the retrieval or encoding fails. 73 | func FTStorage(c *hermes.Cache) func(ctx *fiber.Ctx) error { 74 | return func(ctx *fiber.Ctx) error { 75 | if data, err := c.FTStorage(); err != nil { 76 | return ctx.Send(utils.Error(err)) 77 | } else if data, err := json.Marshal(data); err != nil { 78 | return ctx.Send(utils.Error(err)) 79 | } else { 80 | return ctx.Send(data) 81 | } 82 | } 83 | } 84 | 85 | // FTStorageLength is a handler function that returns a fiber context handler function for getting the length of the full-text storage. 86 | // Parameters: 87 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 88 | // 89 | // Returns: 90 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets the length of the full-text storage and returns a success message with the length or an error message if the retrieval fails. 91 | func FTStorageLength(c *hermes.Cache) func(ctx *fiber.Ctx) error { 92 | return func(ctx *fiber.Ctx) error { 93 | if length, err := c.FTStorageLength(); err != nil { 94 | return ctx.Send(utils.Error(err)) 95 | } else { 96 | return ctx.Send(utils.Success(length)) 97 | } 98 | } 99 | } 100 | 101 | // FTStorageSize is a handler function that returns a fiber context handler function for getting the size of the full-text storage. 102 | // Parameters: 103 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 104 | // 105 | // Returns: 106 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets the size of the full-text storage and returns a success message with the size or an error message if the retrieval fails. 107 | func FTStorageSize(c *hermes.Cache) func(ctx *fiber.Ctx) error { 108 | return func(ctx *fiber.Ctx) error { 109 | if size, err := c.FTStorageSize(); err != nil { 110 | return ctx.Send(utils.Error(err)) 111 | } else { 112 | return ctx.Send(utils.Success(size)) 113 | } 114 | } 115 | } 116 | 117 | // FTSetMinWordLength is a handler function that returns a fiber context handler function for setting the minimum word length for full-text search. 118 | // Parameters: 119 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 120 | // 121 | // Returns: 122 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that sets the minimum word length for full-text search and returns a success message or an error message if the value is not provided or if the setting fails. 123 | func FTSetMinWordLength(c *hermes.Cache) func(ctx *fiber.Ctx) error { 124 | return func(ctx *fiber.Ctx) error { 125 | // Get the min word length from the query 126 | var minWordLength int 127 | if err := utils.GetMinWordLengthParam(ctx, &minWordLength); err != nil { 128 | return ctx.Send(utils.Error(err)) 129 | } 130 | 131 | // Update the min word length 132 | if err := c.FTSetMinWordLength(minWordLength); err != nil { 133 | return ctx.Send(utils.Error(err)) 134 | } 135 | 136 | // Return null 137 | return ctx.Send(utils.Success("null")) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /cloud/api/handlers/get.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | hermes "github.com/realTristan/hermes" 8 | utils "github.com/realTristan/hermes/cloud/api/utils" 9 | ) 10 | 11 | // Get is a handler function that returns a fiber context handler function for getting a value from the cache. 12 | // Parameters: 13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 14 | // 15 | // Returns: 16 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets a value from the cache using a key provided in the query string and returns a JSON-encoded string of the value or an error message if the key is not provided or if the retrieval or encoding fails. 17 | func Get(c *hermes.Cache) func(ctx *fiber.Ctx) error { 18 | return func(ctx *fiber.Ctx) error { 19 | // Get the key from the query 20 | var key string 21 | if key = ctx.Query("key"); len(key) == 0 { 22 | return ctx.Send(utils.Error("key not provided")) 23 | } 24 | 25 | // Get the value from the cache 26 | if data, err := json.Marshal(c.Get(key)); err != nil { 27 | return ctx.Send(utils.Error(err)) 28 | } else { 29 | return ctx.Send(data) 30 | } 31 | } 32 | } 33 | 34 | // GetAll is a handler function that returns a fiber context handler function for getting all the data from the cache. 35 | // Parameters: 36 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 37 | // 38 | // Returns: 39 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets all the data from the cache and returns a success message with a JSON-encoded string of the data or an error message if the retrieval or encoding fails. 40 | func GetAll(c *hermes.Cache) func(ctx *fiber.Ctx) error { 41 | return func(ctx *fiber.Ctx) error { 42 | return nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cloud/api/handlers/indices.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | hermes "github.com/realTristan/hermes" 6 | utils "github.com/realTristan/hermes/cloud/api/utils" 7 | ) 8 | 9 | // FTSequenceIndices is a handler function that returns a fiber context handler function for sequencing the full-text storage indices. 10 | // Parameters: 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that sequences the full-text storage indices and returns a success message or an error message if the sequencing fails. 15 | func FTSequenceIndices(c *hermes.Cache) func(ctx *fiber.Ctx) error { 16 | return func(ctx *fiber.Ctx) error { 17 | c.FTSequenceIndices() 18 | return ctx.Send(utils.Success("null")) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cloud/api/handlers/info.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | hermes "github.com/realTristan/hermes" 6 | utils "github.com/realTristan/hermes/cloud/api/utils" 7 | ) 8 | 9 | // Info is a function that returns information about the cache. 10 | // 11 | // Parameters: 12 | // - c: A pointer to a hermes.Cache struct representing the cache to get information from. 13 | // 14 | // Returns: 15 | // - A function that takes a pointer to a fiber.Ctx struct and returns an error. 16 | // The function returns information about the cache. 17 | func Info(c *hermes.Cache) func(ctx *fiber.Ctx) error { 18 | return func(ctx *fiber.Ctx) error { 19 | if info, err := c.Info(); err != nil { 20 | return ctx.Send(utils.Error(err)) 21 | } else { 22 | return ctx.Send(utils.Success(info)) 23 | } 24 | } 25 | } 26 | 27 | // InfoForTesting is a function that returns information about the cache for testing purposes. 28 | // 29 | // Parameters: 30 | // - c: A pointer to a hermes.Cache struct representing the cache to get information from. 31 | // 32 | // Returns: 33 | // - A function that takes a pointer to a fiber.Ctx struct and returns an error. 34 | // The function returns information about the cache for testing purposes. 35 | func InfoForTesting(c *hermes.Cache) func(ctx *fiber.Ctx) error { 36 | return func(ctx *fiber.Ctx) error { 37 | if info, err := c.InfoForTesting(); err != nil { 38 | return ctx.Send(utils.Error(err)) 39 | } else { 40 | return ctx.Send(utils.Success(info)) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cloud/api/handlers/init.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | hermes "github.com/realTristan/hermes" 6 | utils "github.com/realTristan/hermes/cloud/api/utils" 7 | ) 8 | 9 | // FTInit is a handler function that returns a fiber context handler function for initializing the full-text search cache. 10 | // Parameters: 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that initializes the full-text search cache using the max length, max bytes, and min word length parameters provided in the query string and returns a success message or an error message if the parameters are not provided or if the initialization fails. 15 | func FTInit(c *hermes.Cache) func(ctx *fiber.Ctx) error { 16 | return func(ctx *fiber.Ctx) error { 17 | var ( 18 | maxSize int 19 | maxBytes int 20 | minWordLength int 21 | ) 22 | 23 | // Get the max length parameter 24 | if err := utils.GetMaxSizeParam(ctx, &maxSize); err != nil { 25 | return ctx.Send(utils.Error(err)) 26 | } 27 | 28 | // Get the max bytes parameter 29 | if err := utils.GetMaxBytesParam(ctx, &maxBytes); err != nil { 30 | return ctx.Send(utils.Error(err)) 31 | } 32 | 33 | // Get the min word length parameter 34 | if err := utils.GetMinWordLengthParam(ctx, &minWordLength); err != nil { 35 | return ctx.Send(utils.Error(err)) 36 | } 37 | 38 | // Initialize the full-text cache 39 | if err := c.FTInit(maxSize, maxBytes, minWordLength); err != nil { 40 | return ctx.Send(utils.Error(err)) 41 | } 42 | return ctx.Send(utils.Success("null")) 43 | } 44 | } 45 | 46 | // FTInitJson is a handler function that returns a fiber context handler function for initializing the full-text search cache with a JSON object. 47 | // Parameters: 48 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 49 | // 50 | // Returns: 51 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that initializes the full-text search cache using a JSON object, max length, max bytes, and min word length parameters provided in the query string and returns a success message or an error message if the parameters are not provided or if the initialization fails. 52 | func FTInitJson(c *hermes.Cache) func(ctx *fiber.Ctx) error { 53 | return func(ctx *fiber.Ctx) error { 54 | var ( 55 | maxSize int 56 | maxBytes int 57 | minWordLength int 58 | json map[string]map[string]interface{} 59 | ) 60 | 61 | // Get the max length from the query 62 | if err := utils.GetMaxSizeParam(ctx, &maxSize); err != nil { 63 | return ctx.Send(utils.Error(err)) 64 | } 65 | 66 | // Get the max bytes from the query 67 | if err := utils.GetMaxBytesParam(ctx, &maxBytes); err != nil { 68 | return ctx.Send(utils.Error(err)) 69 | } 70 | 71 | // Get the min word length from the query 72 | if err := utils.GetMinWordLengthParam(ctx, &minWordLength); err != nil { 73 | return ctx.Send(utils.Error(err)) 74 | } 75 | 76 | // Get the JSON from the query 77 | if err := utils.GetJSONParam(ctx, &json); err != nil { 78 | return ctx.Send(utils.Error(err)) 79 | } 80 | 81 | // Initialize the full-text cache 82 | if err := c.FTInitWithMap(json, maxSize, maxBytes, minWordLength); err != nil { 83 | return ctx.Send(utils.Error(err)) 84 | } 85 | 86 | // Return success message 87 | return ctx.Send(utils.Success("null")) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /cloud/api/handlers/keys.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | hermes "github.com/realTristan/hermes" 8 | utils "github.com/realTristan/hermes/cloud/api/utils" 9 | ) 10 | 11 | // Keys is a handler function that returns a fiber context handler function for getting all the keys from the cache. 12 | // Parameters: 13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 14 | // 15 | // Returns: 16 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets all the keys from the cache and returns a JSON-encoded string of the keys or an error message if the retrieval or encoding fails. 17 | func Keys(c *hermes.Cache) func(ctx *fiber.Ctx) error { 18 | return func(ctx *fiber.Ctx) error { 19 | if keys, err := json.Marshal(c.Keys()); err != nil { 20 | return ctx.Send(utils.Error(err)) 21 | } else { 22 | return ctx.Send(keys) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cloud/api/handlers/length.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | hermes "github.com/realTristan/hermes" 6 | utils "github.com/realTristan/hermes/cloud/api/utils" 7 | ) 8 | 9 | // Length is a handler function that returns a fiber context handler function for getting the length of the cache. 10 | // Parameters: 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets the length of the cache and returns a success message with the length or an error message if the retrieval fails. 15 | func Length(c *hermes.Cache) func(ctx *fiber.Ctx) error { 16 | return func(ctx *fiber.Ctx) error { 17 | return ctx.Send(utils.Success(c.Length())) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cloud/api/handlers/set.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | hermes "github.com/realTristan/hermes" 6 | utils "github.com/realTristan/hermes/cloud/api/utils" 7 | ) 8 | 9 | // Set is a handler function that returns a fiber context handler function for setting a value in the cache. 10 | // Parameters: 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that sets a value in the cache using the key and value parameters provided in the query string and returns a success message or an error message if the set fails or if the parameters are not provided. 15 | func Set(c *hermes.Cache) func(ctx *fiber.Ctx) error { 16 | return func(ctx *fiber.Ctx) error { 17 | var ( 18 | key string 19 | value map[string]interface{} 20 | ) 21 | // Get the key from the query 22 | if key = ctx.Query("key"); len(key) == 0 { 23 | return ctx.Send(utils.Error("invalid key")) 24 | } 25 | 26 | // Get the value from the query 27 | if err := utils.GetValueParam(ctx, &value); err != nil { 28 | return ctx.Send(utils.Error(err)) 29 | } 30 | 31 | // Set the value in the cache 32 | if err := c.Set(key, value); err != nil { 33 | return ctx.Send(utils.Error(err)) 34 | } 35 | return ctx.Send(utils.Success("null")) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cloud/api/handlers/values.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | hermes "github.com/realTristan/hermes" 8 | utils "github.com/realTristan/hermes/cloud/api/utils" 9 | ) 10 | 11 | // Values is a handler function that returns a fiber context handler function for getting all values from the cache. 12 | // Parameters: 13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 14 | // 15 | // Returns: 16 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets all values from the cache and returns a JSON-encoded string of the values or an error message if the retrieval fails. 17 | func Values(c *hermes.Cache) func(ctx *fiber.Ctx) error { 18 | return func(ctx *fiber.Ctx) error { 19 | if values, err := json.Marshal(c.Values()); err != nil { 20 | return ctx.Send(utils.Error(err)) 21 | } else { 22 | return ctx.Send(values) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cloud/api/routes.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | hermes "github.com/realTristan/hermes" 6 | "github.com/realTristan/hermes/cloud/api/handlers" 7 | ) 8 | 9 | // SetRoutes is a function that sets the routes for the hermes Cache API. 10 | // Parameters: 11 | // - app (*fiber.App): A pointer to a fiber.App struct. 12 | // - cache (*hermes.Cache): A pointer to a hermes.Cache struct. 13 | // 14 | // Returns: 15 | // - void: This function does not return anything. 16 | func SetRoutes(app *fiber.App, cache *hermes.Cache) { 17 | // Dev Testing Handler 18 | app.Get("/dev/hermes", func(c *fiber.Ctx) error { 19 | return c.SendString("hermes Cache API Successfully Running!") 20 | }) 21 | 22 | // Cache Handlers 23 | app.Get("/cache/values", handlers.Values(cache)) 24 | app.Get("/cache/length", handlers.Length(cache)) 25 | app.Post("/cache/clean", handlers.Clean(cache)) 26 | app.Post("/cache/set", handlers.Set(cache)) 27 | app.Delete("/cache/delete", handlers.Delete(cache)) 28 | app.Get("/cache/get", handlers.Get(cache)) 29 | app.Get("/cache/get/all", handlers.GetAll(cache)) 30 | app.Get("/cache/keys", handlers.Keys(cache)) 31 | app.Get("/cache/info", handlers.Info(cache)) 32 | app.Get("/cache/info/testing", handlers.InfoForTesting(cache)) 33 | app.Get("/cache/exists", handlers.Exists(cache)) 34 | 35 | // Full-text Cache Handlers 36 | app.Post("/ft/init", handlers.FTInit(cache)) 37 | app.Post("/ft/init/json", handlers.FTInitJson(cache)) 38 | app.Post("/ft/clean", handlers.FTClean(cache)) 39 | app.Get("/ft/search", handlers.Search(cache)) 40 | app.Get("/ft/search/oneword", handlers.SearchOneWord(cache)) 41 | app.Get("/ft/search/values", handlers.SearchValues(cache)) 42 | app.Get("/ft/search/withkey", handlers.SearchWithKey(cache)) 43 | app.Post("/ft/maxbytes", handlers.FTSetMaxBytes(cache)) 44 | app.Post("/ft/maxsize", handlers.FTSetMaxSize(cache)) 45 | app.Post("/ft/minwordlength", handlers.FTSetMinWordLength(cache)) 46 | app.Get("/ft/storage", handlers.FTStorage(cache)) 47 | app.Get("/ft/storage/size", handlers.FTStorageSize(cache)) 48 | app.Get("/ft/storage/length", handlers.FTStorageLength(cache)) 49 | app.Get("/ft/isinitialized", handlers.FTIsInitialized(cache)) 50 | app.Post("/ft/indices/sequence", handlers.FTSequenceIndices(cache)) 51 | } 52 | -------------------------------------------------------------------------------- /cloud/api/utils/decode.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | ) 7 | 8 | // Decode is a function that decodes a base64-encoded string and then decodes the resulting JSON into a value of type T. 9 | // Parameters: 10 | // - s (string): The base64-encoded string to decode. 11 | // - v (*T): A pointer to a value of type T to store the decoded JSON. 12 | // 13 | // Returns: 14 | // - error: An error message if the decoding fails, or nil if the decoding is successful. 15 | func Decode[T any](s string, v *T) error { 16 | if data, err := base64.StdEncoding.DecodeString(s); err != nil { 17 | return err 18 | } else if err := json.Unmarshal(data, v); err != nil { 19 | return err 20 | } 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /cloud/api/utils/http.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Error is a function that returns an error message with the provided error. 8 | // Parameters: 9 | // - err (T): The error to include in the error message. 10 | // 11 | // Returns: 12 | // - []byte: A byte slice containing the error message with the provided error. 13 | func Error[T any](err T) []byte { 14 | return []byte(fmt.Sprintf(`{"success":false,"error":"%v"}`, err)) 15 | } 16 | 17 | // Success is a function that returns a success message with the provided data. 18 | // Parameters: 19 | // - v (T): The data to include in the success message. 20 | // 21 | // Returns: 22 | // - []byte: A byte slice containing the success message with the provided data. 23 | func Success[T any](v T) []byte { 24 | return []byte(fmt.Sprintf(`{"success":true,"data":%v}`, v)) 25 | } 26 | -------------------------------------------------------------------------------- /cloud/api/utils/params.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/gofiber/fiber/v2" 9 | ) 10 | 11 | // GetValueParam is a function that retrieves a value from a query parameter in a Fiber context and decodes it into a value of type T. 12 | // Parameters: 13 | // - ctx (*fiber.Ctx): A pointer to a Fiber context. 14 | // - value (*T): A pointer to a value of type T to store the decoded value. 15 | // 16 | // Returns: 17 | // - error: An error message if the decoding fails or the query parameter is invalid, or nil if the decoding is successful. 18 | func GetValueParam[T any](ctx *fiber.Ctx, value *T) error { 19 | if v := ctx.Query("value"); len(v) == 0 { 20 | return errors.New("invalid value") 21 | } else if err := Decode(v, &value); err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | 27 | // GetMaxSizeParam is a function that retrieves the "maxsize" query parameter from a Fiber context and stores it in an integer pointer. 28 | // Parameters: 29 | // - ctx (*fiber.Ctx): A pointer to a Fiber context. 30 | // - maxSize (*int): A pointer to an integer to store the "maxsize" query parameter. 31 | // 32 | // Returns: 33 | // - error: An error message if the "maxsize" query parameter is invalid or cannot be converted to an integer, or nil if the retrieval is successful. 34 | func GetMaxSizeParam(ctx *fiber.Ctx, maxSize *int) error { 35 | if s := ctx.Query("maxsize"); len(s) == 0 { 36 | fmt.Println(s) 37 | return errors.New("invalid maxsize") 38 | } else if i, err := strconv.Atoi(s); err != nil { 39 | return err 40 | } else { 41 | *maxSize = i 42 | } 43 | return nil 44 | } 45 | 46 | // GetMaxBytesParam is a function that retrieves the "maxbytes" query parameter from a Fiber context and stores it in an integer pointer. 47 | // Parameters: 48 | // - ctx (*fiber.Ctx): A pointer to a Fiber context. 49 | // - maxBytes (*int): A pointer to an integer to store the "maxbytes" query parameter. 50 | // 51 | // Returns: 52 | // - error: An error message if the "maxbytes" query parameter is invalid or cannot be converted to an integer, or nil if the retrieval is successful. 53 | func GetMaxBytesParam(ctx *fiber.Ctx, maxBytes *int) error { 54 | if s := ctx.Query("maxbytes"); len(s) == 0 { 55 | return errors.New("invalid maxbytes") 56 | } else if i, err := strconv.Atoi(s); err != nil { 57 | return err 58 | } else { 59 | *maxBytes = i 60 | } 61 | return nil 62 | } 63 | 64 | // Get the min word length url parameter 65 | func GetMinWordLengthParam(ctx *fiber.Ctx, minWordLength *int) error { 66 | if s := ctx.Query("minwordlength"); len(s) == 0 { 67 | return errors.New("invalid minwordlength") 68 | } else if i, err := strconv.Atoi(s); err != nil { 69 | return err 70 | } else { 71 | *minWordLength = i 72 | } 73 | return nil 74 | } 75 | 76 | // GetJSONParam is a function that retrieves a JSON-encoded value from a query parameter in a Fiber context and decodes it into a value of type T. 77 | // Parameters: 78 | // - ctx (*fiber.Ctx): A pointer to a Fiber context. 79 | // - json (*T): A pointer to a value of type T to store the decoded JSON. 80 | // 81 | // Returns: 82 | // - error: An error message if the decoding fails or the query parameter is invalid, or nil if the decoding is successful. 83 | func GetJSONParam[T any](ctx *fiber.Ctx, json *T) error { 84 | if s := ctx.Query("json"); len(s) == 0 { 85 | return errors.New("invalid json") 86 | } else if err := Decode(s, &json); err != nil { 87 | return err 88 | } 89 | return nil 90 | } 91 | 92 | // GetSchemaParam is a function that retrieves a schema from a query parameter in a Fiber context and decodes it into a map of string keys and boolean values. 93 | // Parameters: 94 | // - ctx (*fiber.Ctx): A pointer to a Fiber context. 95 | // - schema (*map[string]bool): A pointer to a map of string keys and boolean values to store the decoded schema. 96 | // 97 | // Returns: 98 | // - error: An error message if the decoding fails or the query parameter is invalid, or nil if the decoding is successful. 99 | func GetSchemaParam(ctx *fiber.Ctx, schema *map[string]bool) error { 100 | // Get the schema from the url params 101 | if s := ctx.Query("schema"); len(s) == 0 { 102 | return errors.New("invalid schema") 103 | } else if err := Decode(s, schema); err != nil { 104 | return err 105 | } 106 | return nil 107 | } 108 | 109 | // GetLimitParam is a function that retrieves the "limit" query parameter from a Fiber context and stores it in an integer pointer. 110 | // Parameters: 111 | // - ctx (*fiber.Ctx): A pointer to a Fiber context. 112 | // - limit (*int): A pointer to an integer to store the "limit" query parameter. 113 | // 114 | // Returns: 115 | // - error: An error message if the "limit" query parameter is invalid or cannot be converted to an integer, or nil if the retrieval is successful. 116 | func GetLimitParam(ctx *fiber.Ctx, limit *int) error { 117 | // Get the limit from the url params 118 | if s := ctx.Query("limit"); len(s) == 0 { 119 | return errors.New("invalid limit") 120 | } else if i, err := strconv.Atoi(s); err != nil { 121 | return err 122 | } else { 123 | *limit = i 124 | } 125 | return nil 126 | } 127 | 128 | // GetLimitParam is a function that retrieves the "limit" query parameter from a Fiber context and stores it in an integer pointer. 129 | // Parameters: 130 | // - ctx (*fiber.Ctx): A pointer to a Fiber context. 131 | // - limit (*int): A pointer to an integer to store the "limit" query parameter. 132 | // 133 | // Returns: 134 | // - error: An error message if the "limit" query parameter is invalid or cannot be converted to an integer, or nil if the retrieval is successful. 135 | func GetStrictParam(ctx *fiber.Ctx, strict *bool) error { 136 | // Get whether strict mode is enabled/disabled 137 | if s := ctx.Query("strict"); len(s) == 0 { 138 | return errors.New("invalid strict") 139 | } else if b, err := strconv.ParseBool(s); err != nil { 140 | return err 141 | } else { 142 | *strict = b 143 | } 144 | return nil 145 | } 146 | -------------------------------------------------------------------------------- /cloud/app/dev/.gitkeep: -------------------------------------------------------------------------------- 1 | use a go gui framework -------------------------------------------------------------------------------- /cloud/app/prod/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/app/prod/.gitkeep -------------------------------------------------------------------------------- /cloud/app/server/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | utils "hermes/utils" 8 | 9 | "github.com/gofiber/fiber/v2" 10 | hermes "github.com/realTristan/hermes" 11 | Socket "github.com/realTristan/hermes/cloud/socket" 12 | ) 13 | 14 | // Main function 15 | func main() { 16 | // Verify that the user is trying to serve the cache 17 | if len(os.Args) < 1 || os.Args[1] != "serve" { 18 | panic("incorrect usage. example: ./hermes serve -p {port}") 19 | } 20 | 21 | // Get the arg data 22 | var args, err = utils.GetArgData(os.Args) 23 | if err != nil || args.Port() == nil { 24 | panic("incorrect usage. example: ./hermes serve -p {port}") 25 | } 26 | 27 | // Get the port and json file 28 | var cache *hermes.Cache = hermes.InitCache() 29 | 30 | // Initialize a new fiber app 31 | var app *fiber.App = fiber.New(fiber.Config{ 32 | Prefork: false, 33 | ServerHeader: "hermes", 34 | }) 35 | Socket.SetRouter(app, cache) 36 | 37 | // Listen on the port 38 | log.Fatal(app.Listen(args.Port().(string))) 39 | } 40 | -------------------------------------------------------------------------------- /cloud/app/server/go.mod: -------------------------------------------------------------------------------- 1 | module hermes 2 | 3 | go 1.20 4 | 5 | require github.com/realTristan/Hermes v1.6.7 6 | 7 | require ( 8 | github.com/andybalholm/brotli v1.0.5 // indirect 9 | github.com/fasthttp/websocket v1.5.3 // indirect 10 | github.com/gofiber/fiber/v2 v2.45.0 11 | github.com/gofiber/websocket/v2 v2.2.0 // indirect 12 | github.com/google/uuid v1.3.0 // indirect 13 | github.com/klauspost/compress v1.16.5 // indirect 14 | github.com/mattn/go-colorable v0.1.13 // indirect 15 | github.com/mattn/go-isatty v0.0.18 // indirect 16 | github.com/mattn/go-runewidth v0.0.14 // indirect 17 | github.com/philhofer/fwd v1.1.2 // indirect 18 | github.com/rivo/uniseg v0.2.0 // indirect 19 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect 20 | github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect 21 | github.com/tinylib/msgp v1.1.8 // indirect 22 | github.com/valyala/bytebufferpool v1.0.0 // indirect 23 | github.com/valyala/fasthttp v1.47.0 // indirect 24 | github.com/valyala/tcplisten v1.0.0 // indirect 25 | golang.org/x/sys v0.8.0 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /cloud/app/server/utils/args.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // Data struct 9 | type Data struct { 10 | port any 11 | } 12 | 13 | // Get the port 14 | func (d *Data) Port() any { 15 | var copy any = d.port 16 | return copy 17 | } 18 | 19 | // Get the argument data in a map 20 | func GetArgData(args []string) (*Data, error) { 21 | var data *Data = &Data{ 22 | port: nil, 23 | } 24 | 25 | // Iterate over the args 26 | for i := 2; i < len(args); i++ { 27 | // Port arg 28 | if args[i] == "-port" || args[i] == "-p" { 29 | if i+1 >= len(args) { 30 | return data, errors.New("invalid port") 31 | } 32 | 33 | // Add a ':' to the port 34 | data.port = ":" + strings.ReplaceAll(args[i+1], ":", "") 35 | 36 | // If all past index 1 isnt a number 37 | if !isNum(data.port.(string)[1:]) { 38 | return data, errors.New("invalid port") 39 | } 40 | 41 | // Increment i then continue 42 | i = i + 1 43 | continue 44 | } 45 | } 46 | return data, nil 47 | } 48 | 49 | // Check if a string is a number 50 | func isNum(s string) bool { 51 | for _, c := range s { 52 | if c < '0' || c > '9' { 53 | return false 54 | } 55 | } 56 | return true 57 | } 58 | -------------------------------------------------------------------------------- /cloud/database/.gitkeep: -------------------------------------------------------------------------------- 1 | compress and store data in sqlite db -------------------------------------------------------------------------------- /cloud/socket/errors.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Check whether the error is a close error by converting 8 | // the error to a string and checking if it contains the 9 | // word "close". 10 | func IsCloseError(err error) bool { 11 | return strings.Contains(err.Error(), "close") 12 | } 13 | -------------------------------------------------------------------------------- /cloud/socket/funcs.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | hermes "github.com/realTristan/hermes" 5 | "github.com/realTristan/hermes/cloud/socket/handlers" 6 | utils "github.com/realTristan/hermes/cloud/socket/utils" 7 | ) 8 | 9 | // Map of functions that can be called from the client 10 | var Functions = map[string]func(*utils.Params, *hermes.Cache) []byte{ 11 | "cache.length": handlers.Length, 12 | "cache.clean": handlers.Clean, 13 | "cache.set": handlers.Set, 14 | "cache.delete": handlers.Delete, 15 | "cache.get": handlers.Get, 16 | "cache.get.all": handlers.GetAll, 17 | "cache.keys": handlers.Keys, 18 | "cache.info": handlers.Info, 19 | "cache.info.testing": handlers.InfoForTesting, 20 | "cache.exists": handlers.Exists, 21 | "ft.init": handlers.FTInit, 22 | "ft.init.json": handlers.FTInitJson, 23 | "ft.clean": handlers.FTClean, 24 | "ft.search": handlers.Search, 25 | "ft.search.oneword": handlers.SearchOneWord, 26 | "ft.search.values": handlers.SearchValues, 27 | "ft.search.withkey": handlers.SearchWithKey, 28 | "ft.maxbytes.set": handlers.FTSetMaxBytes, 29 | "ft.maxsize.set": handlers.FTSetMaxSize, 30 | "ft.storage": handlers.FTStorage, 31 | "ft.storage.size": handlers.FTStorageSize, 32 | "ft.storage.length": handlers.FTStorageLength, 33 | "ft.isinitialized": handlers.FTIsInitialized, 34 | "ft.indices.sequence": handlers.FTSequenceIndices, 35 | } 36 | -------------------------------------------------------------------------------- /cloud/socket/handlers/clean.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | hermes "github.com/realTristan/hermes" 5 | utils "github.com/realTristan/hermes/cloud/socket/utils" 6 | ) 7 | 8 | // Clean is a handler function that returns a fiber context handler function for cleaning the cache. 9 | // Parameters: 10 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused). 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the cleaning fails. 15 | func Clean(_ *utils.Params, c *hermes.Cache) []byte { 16 | c.Clean() 17 | return utils.Success("null") 18 | } 19 | 20 | // FTClean is a handler function that returns a fiber context handler function for cleaning the full-text storage. 21 | // Parameters: 22 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused). 23 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 24 | // 25 | // Returns: 26 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the cleaning fails. 27 | func FTClean(_ *utils.Params, c *hermes.Cache) []byte { 28 | if err := c.FTClean(); err != nil { 29 | return utils.Error(err) 30 | } 31 | return utils.Success("null") 32 | } 33 | -------------------------------------------------------------------------------- /cloud/socket/handlers/delete.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | hermes "github.com/realTristan/hermes" 5 | utils "github.com/realTristan/hermes/cloud/socket/utils" 6 | ) 7 | 8 | // Delete is a handler function that returns a fiber context handler function for deleting a key from the cache. 9 | // Parameters: 10 | // - p (*utils.Params): A pointer to a utils.Params struct. 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the key is not provided or the deletion fails. 15 | func Delete(p *utils.Params, c *hermes.Cache) []byte { 16 | // Get the key from the query 17 | var ( 18 | key string 19 | err error 20 | ) 21 | if key, err = utils.GetKeyParam(p); err != nil { 22 | return utils.Error("key not provided") 23 | } 24 | 25 | // Delete the key from the cache 26 | c.Delete(key) 27 | return utils.Success("null") 28 | } 29 | -------------------------------------------------------------------------------- /cloud/socket/handlers/exists.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | hermes "github.com/realTristan/hermes" 5 | utils "github.com/realTristan/hermes/cloud/socket/utils" 6 | ) 7 | 8 | // Exists is a handler function that returns a fiber context handler function for checking if a key exists in the cache. 9 | // Parameters: 10 | // - p (*utils.Params): A pointer to a utils.Params struct. 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - []byte: A JSON-encoded byte slice containing a boolean value indicating whether the key exists in the cache or an error message if the key is not provided. 15 | func Exists(p *utils.Params, c *hermes.Cache) []byte { 16 | // Get the key from the query 17 | var ( 18 | key string 19 | err error 20 | ) 21 | if key, err = utils.GetKeyParam(p); err != nil { 22 | return utils.Error("key not provided") 23 | } 24 | 25 | // Return whether the key exists 26 | return utils.Success(c.Exists(key)) 27 | } 28 | -------------------------------------------------------------------------------- /cloud/socket/handlers/fulltext.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | hermes "github.com/realTristan/hermes" 7 | utils "github.com/realTristan/hermes/cloud/socket/utils" 8 | ) 9 | 10 | // FTIsInitialized is a handler function that returns a fiber context handler function for checking if the full-text storage is initialized. 11 | // Parameters: 12 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused). 13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 14 | // 15 | // Returns: 16 | // - []byte: A JSON-encoded byte slice containing a boolean value indicating whether the full-text storage is initialized. 17 | func FTIsInitialized(_ *utils.Params, c *hermes.Cache) []byte { 18 | return utils.Success(c.FTIsInitialized()) 19 | } 20 | 21 | // FTSetMaxBytes is a handler function that returns a fiber context handler function for setting the maximum number of bytes for the full-text storage. 22 | // Parameters: 23 | // - p (*utils.Params): A pointer to a utils.Params struct. 24 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 25 | // 26 | // Returns: 27 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the value is invalid or the setting fails. 28 | func FTSetMaxBytes(p *utils.Params, c *hermes.Cache) []byte { 29 | // Get the value from the query 30 | var value int 31 | if err := utils.GetMaxBytesParam(p, &value); err != nil { 32 | return utils.Error(err) 33 | } 34 | 35 | // Set the max bytes 36 | if err := c.FTSetMaxBytes(value); err != nil { 37 | return utils.Error(err) 38 | } 39 | return utils.Success("null") 40 | } 41 | 42 | // FTSetMaxSize is a handler function that returns a fiber context handler function for setting the maximum length for the full-text storage. 43 | // Parameters: 44 | // - p (*utils.Params): A pointer to a utils.Params struct. 45 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 46 | // 47 | // Returns: 48 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the value is invalid or the setting fails. 49 | func FTSetMaxSize(p *utils.Params, c *hermes.Cache) []byte { 50 | // Get the value from the query 51 | var value int 52 | if err := utils.GetMaxSizeParam(p, &value); err != nil { 53 | return utils.Error(err) 54 | } 55 | 56 | // Set the max length 57 | if err := c.FTSetMaxSize(value); err != nil { 58 | return utils.Error(err) 59 | } 60 | return utils.Success("null") 61 | } 62 | 63 | // FTStorage is a handler function that returns a fiber context handler function for retrieving the full-text storage. 64 | // Parameters: 65 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused). 66 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 67 | // 68 | // Returns: 69 | // - []byte: A JSON-encoded byte slice containing the full-text storage or an error message if the retrieval fails. 70 | func FTStorage(_ *utils.Params, c *hermes.Cache) []byte { 71 | if data, err := c.FTStorage(); err != nil { 72 | return utils.Error(err) 73 | } else if data, err := json.Marshal(data); err != nil { 74 | return utils.Error(err) 75 | } else { 76 | return data 77 | } 78 | } 79 | 80 | // FTStorageLength is a handler function that returns a fiber context handler function for retrieving the length of the full-text storage. 81 | // Parameters: 82 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused). 83 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 84 | // 85 | // Returns: 86 | // - []byte: A JSON-encoded byte slice containing the length of the full-text storage or an error message if the retrieval fails. 87 | func FTStorageLength(_ *utils.Params, c *hermes.Cache) []byte { 88 | if length, err := c.FTStorageLength(); err != nil { 89 | return utils.Error(err) 90 | } else { 91 | return utils.Success(length) 92 | } 93 | } 94 | 95 | // FTStorageSize is a handler function that returns a fiber context handler function for retrieving the size of the full-text storage. 96 | // Parameters: 97 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused). 98 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 99 | // 100 | // Returns: 101 | // - []byte: A JSON-encoded byte slice containing the size of the full-text storage or an error message if the retrieval fails. 102 | func FTStorageSize(_ *utils.Params, c *hermes.Cache) []byte { 103 | if size, err := c.FTStorageSize(); err != nil { 104 | return utils.Error(err) 105 | } else { 106 | return utils.Success(size) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /cloud/socket/handlers/get.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | hermes "github.com/realTristan/hermes" 7 | utils "github.com/realTristan/hermes/cloud/socket/utils" 8 | ) 9 | 10 | // Get is a handler function that returns a fiber context handler function for retrieving a key from the cache. 11 | // Parameters: 12 | // - p (*utils.Params): A pointer to a utils.Params struct. 13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 14 | // 15 | // Returns: 16 | // - []byte: A JSON-encoded byte slice containing the value of the key or an error message if the key is not provided or the retrieval fails. 17 | func Get(p *utils.Params, c *hermes.Cache) []byte { 18 | // Get the key from the query 19 | var ( 20 | key string 21 | err error 22 | ) 23 | if key, err = utils.GetKeyParam(p); err != nil { 24 | return utils.Error("key not provided") 25 | } 26 | 27 | // Get the value from the cache 28 | if data, err := json.Marshal(c.Get(key)); err != nil { 29 | return utils.Error(err) 30 | } else { 31 | return data 32 | } 33 | } 34 | 35 | // GetAll is a handler function that returns a fiber context handler function for retrieving all data from the cache. 36 | // Parameters: 37 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused). 38 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 39 | // 40 | // Returns: 41 | // - []byte: A JSON-encoded byte slice containing all data from the cache or an error message if the retrieval fails. 42 | func GetAll(_ *utils.Params, c *hermes.Cache) []byte { 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /cloud/socket/handlers/indices.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | hermes "github.com/realTristan/hermes" 5 | utils "github.com/realTristan/hermes/cloud/socket/utils" 6 | ) 7 | 8 | // FTSequenceIndices is a handler function that returns a fiber context handler function for sequencing the full-text storage indices. 9 | // Parameters: 10 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused). 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the sequencing fails. 15 | func FTSequenceIndices(_ *utils.Params, c *hermes.Cache) []byte { 16 | c.FTSequenceIndices() 17 | return utils.Success("null") 18 | } 19 | -------------------------------------------------------------------------------- /cloud/socket/handlers/info.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | hermes "github.com/realTristan/hermes" 5 | utils "github.com/realTristan/hermes/cloud/socket/utils" 6 | ) 7 | 8 | // Info is a function that returns information about the cache. 9 | // 10 | // Parameters: 11 | // - _ : A pointer to a utils.Params struct representing the parameters of the request. This parameter is ignored. 12 | // - c: A pointer to a hermes.Cache struct representing the cache to get information from. 13 | // 14 | // Returns: 15 | // - A byte slice representing the information about the cache. 16 | func Info(_ *utils.Params, c *hermes.Cache) []byte { 17 | if info, err := c.Info(); err != nil { 18 | return utils.Error(err) 19 | } else { 20 | return utils.Success(info) 21 | } 22 | } 23 | 24 | // InfoForTesting is a function that returns information about the cache for testing purposes. 25 | // 26 | // Parameters: 27 | // - _ : A pointer to a utils.Params struct representing the parameters of the request. This parameter is ignored. 28 | // - c: A pointer to a hermes.Cache struct representing the cache to get information from. 29 | // 30 | // Returns: 31 | // - A byte slice representing the information about the cache for testing purposes. 32 | func InfoForTesting(_ *utils.Params, c *hermes.Cache) []byte { 33 | if info, err := c.InfoForTesting(); err != nil { 34 | return utils.Error(err) 35 | } else { 36 | return utils.Success(info) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cloud/socket/handlers/init.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | hermes "github.com/realTristan/hermes" 5 | utils "github.com/realTristan/hermes/cloud/socket/utils" 6 | ) 7 | 8 | // FTInit is a handler function that returns a fiber context handler function for initializing the full-text search cache. 9 | // Parameters: 10 | // - p (*utils.Params): A pointer to a utils.Params struct. 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the initialization fails. 15 | func FTInit(p *utils.Params, c *hermes.Cache) []byte { 16 | var ( 17 | maxSize int 18 | maxBytes int 19 | minWordLength int 20 | ) 21 | 22 | // Get the max length parameter 23 | if err := utils.GetMaxSizeParam(p, &maxSize); err != nil { 24 | return utils.Error(err) 25 | } 26 | 27 | // Get the max bytes parameter 28 | if err := utils.GetMaxBytesParam(p, &maxBytes); err != nil { 29 | return utils.Error(err) 30 | } 31 | 32 | // Get the min word length parameter 33 | if err := utils.GetMinWordLengthParam(p, &minWordLength); err != nil { 34 | return utils.Error(err) 35 | } 36 | 37 | // Initialize the full-text cache 38 | if err := c.FTInit(maxSize, maxBytes, minWordLength); err != nil { 39 | return utils.Error(err) 40 | } 41 | return utils.Success("null") 42 | } 43 | 44 | // FTInitJson is a handler function that returns a fiber context handler function for initializing the full-text search cache with a JSON object. 45 | // Parameters: 46 | // - p (*utils.Params): A pointer to a utils.Params struct. 47 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 48 | // 49 | // Returns: 50 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the initialization fails. 51 | func FTInitJson(p *utils.Params, c *hermes.Cache) []byte { 52 | var ( 53 | maxSize int 54 | maxBytes int 55 | minWordLength int 56 | json map[string]map[string]any 57 | ) 58 | 59 | // Get the max length from the query 60 | if err := utils.GetMaxSizeParam(p, &maxSize); err != nil { 61 | return utils.Error(err) 62 | } 63 | 64 | // Get the max bytes from the query 65 | if err := utils.GetMaxBytesParam(p, &maxBytes); err != nil { 66 | return utils.Error(err) 67 | } 68 | 69 | // Get the min word length from the query 70 | if err := utils.GetMinWordLengthParam(p, &minWordLength); err != nil { 71 | return utils.Error(err) 72 | } 73 | 74 | // Get the JSON from the query 75 | if err := utils.GetJSONParam(p, &json); err != nil { 76 | return utils.Error(err) 77 | } 78 | 79 | // Initialize the full-text cache 80 | if err := c.FTInitWithMap(json, maxSize, maxBytes, minWordLength); err != nil { 81 | return utils.Error(err) 82 | } 83 | 84 | // Return success message 85 | return utils.Success("null") 86 | } 87 | -------------------------------------------------------------------------------- /cloud/socket/handlers/keys.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | hermes "github.com/realTristan/hermes" 7 | utils "github.com/realTristan/hermes/cloud/socket/utils" 8 | ) 9 | 10 | // Keys is a handler function that returns a fiber context handler function for retrieving all keys from the cache. 11 | // Parameters: 12 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused). 13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 14 | // 15 | // Returns: 16 | // - []byte: A JSON-encoded byte slice containing all keys from the cache or an error message if the retrieval fails. 17 | func Keys(_ *utils.Params, c *hermes.Cache) []byte { 18 | if keys, err := json.Marshal(c.Keys()); err != nil { 19 | return utils.Error(err) 20 | } else { 21 | return keys 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cloud/socket/handlers/length.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | hermes "github.com/realTristan/hermes" 5 | utils "github.com/realTristan/hermes/cloud/socket/utils" 6 | ) 7 | 8 | // Length is a handler function that returns a fiber context handler function for retrieving the length of the cache. 9 | // Parameters: 10 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused). 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - []byte: A JSON-encoded byte slice containing the length of the cache. 15 | func Length(_ *utils.Params, c *hermes.Cache) []byte { 16 | return utils.Success(c.Length()) 17 | } 18 | -------------------------------------------------------------------------------- /cloud/socket/handlers/search.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | hermes "github.com/realTristan/hermes" 7 | utils "github.com/realTristan/hermes/cloud/socket/utils" 8 | ) 9 | 10 | // Search is a handler function that returns a fiber context handler function for searching the cache for a query. 11 | // Parameters: 12 | // - p (*utils.Params): A pointer to a utils.Params struct. 13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 14 | // 15 | // Returns: 16 | // - []byte: A JSON-encoded byte slice containing the search results or an error message if the search fails. 17 | func Search(p *utils.Params, c *hermes.Cache) []byte { 18 | var ( 19 | strict bool 20 | query string 21 | limit int 22 | err error 23 | ) 24 | 25 | // Get the query from the params 26 | if query, err = utils.GetQueryParam(p); err != nil { 27 | return utils.Error("query not provided") 28 | } 29 | 30 | // Get the limit from the params 31 | if err := utils.GetLimitParam(p, &limit); err != nil { 32 | return utils.Error(err) 33 | } 34 | 35 | // Get the strict from the params 36 | if err := utils.GetStrictParam(p, &strict); err != nil { 37 | return utils.Error(err) 38 | } 39 | 40 | // Search for the query 41 | if res, err := c.Search(hermes.SearchParams{ 42 | Query: query, 43 | Limit: limit, 44 | Strict: strict, 45 | }); err != nil { 46 | return utils.Error(err) 47 | } else if data, err := json.Marshal(res); err != nil { 48 | return utils.Error(err) 49 | } else { 50 | return data 51 | } 52 | } 53 | 54 | // SearchOneWord is a handler function that returns a fiber context handler function for searching the cache for a single word query. 55 | // Parameters: 56 | // - p (*utils.Params): A pointer to a utils.Params struct. 57 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 58 | // 59 | // Returns: 60 | // - []byte: A JSON-encoded byte slice containing the search results or an error message if the search fails. 61 | func SearchOneWord(p *utils.Params, c *hermes.Cache) []byte { 62 | var ( 63 | strict bool 64 | query string 65 | err error 66 | limit int 67 | ) 68 | 69 | // Get the query from the params 70 | if query, err = utils.GetQueryParam(p); err != nil { 71 | return utils.Error("invalid query") 72 | } 73 | 74 | // Get the limit from the params 75 | if err := utils.GetLimitParam(p, &limit); err != nil { 76 | return utils.Error(err) 77 | } 78 | 79 | // Get the strict from the params 80 | if err := utils.GetStrictParam(p, &strict); err != nil { 81 | return utils.Error(err) 82 | } 83 | 84 | // Search for the query 85 | if res, err := c.SearchOneWord(hermes.SearchParams{ 86 | Query: query, 87 | Limit: limit, 88 | Strict: strict, 89 | }); err != nil { 90 | return utils.Error(err) 91 | } else { 92 | if data, err := json.Marshal(res); err != nil { 93 | return utils.Error(err) 94 | } else { 95 | return data 96 | } 97 | } 98 | } 99 | 100 | // SearchValues is a handler function that returns a fiber context handler function for searching the cache for a query in values. 101 | // Parameters: 102 | // - p (*utils.Params): A pointer to a utils.Params struct. 103 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 104 | // 105 | // Returns: 106 | // - []byte: A JSON-encoded byte slice containing the search results or an error message if the search fails. 107 | func SearchValues(p *utils.Params, c *hermes.Cache) []byte { 108 | var ( 109 | query string 110 | limit int 111 | err error 112 | schema map[string]bool 113 | ) 114 | 115 | // Get the query from the params 116 | if query, err = utils.GetQueryParam(p); err != nil { 117 | return utils.Error("invalid query") 118 | } 119 | 120 | // Get the limit from the params 121 | if err := utils.GetLimitParam(p, &limit); err != nil { 122 | return utils.Error(err) 123 | } 124 | 125 | // Get the schema from the params 126 | if err := utils.GetSchemaParam(p, &schema); err != nil { 127 | return utils.Error(err) 128 | } 129 | 130 | // Search for the query 131 | if res, err := c.SearchValues(hermes.SearchParams{ 132 | Query: query, 133 | Limit: limit, 134 | Schema: schema, 135 | }); err != nil { 136 | return utils.Error(err) 137 | } else { 138 | if data, err := json.Marshal(res); err != nil { 139 | return utils.Error(err) 140 | } else { 141 | return data 142 | } 143 | } 144 | } 145 | 146 | // SearchWithKey is a handler function that returns a fiber context handler function for searching the cache for a query with a specific key. 147 | // Parameters: 148 | // - p (*utils.Params): A pointer to a utils.Params struct. 149 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 150 | // 151 | // Returns: 152 | // - []byte: A JSON-encoded byte slice containing the search results or an error message if the search fails. 153 | func SearchWithKey(p *utils.Params, c *hermes.Cache) []byte { 154 | var ( 155 | key string 156 | query string 157 | err error 158 | limit int 159 | schema map[string]bool 160 | ) 161 | 162 | // Get the query from the params 163 | if query, err = utils.GetQueryParam(p); err != nil { 164 | return utils.Error("invalid query") 165 | } 166 | 167 | // Get the key from the params 168 | if key, err = utils.GetKeyParam(p); err != nil { 169 | return utils.Error("invalid key") 170 | } 171 | 172 | // Get the limit from the params 173 | if err := utils.GetLimitParam(p, &limit); err != nil { 174 | return utils.Error(err) 175 | } 176 | 177 | // Get the schema from the params 178 | if err := utils.GetSchemaParam(p, &schema); err != nil { 179 | return utils.Error(err) 180 | } 181 | 182 | // Search for the query 183 | if res, err := c.SearchWithKey(hermes.SearchParams{ 184 | Query: query, 185 | Key: key, 186 | Limit: limit, 187 | }); err != nil { 188 | return utils.Error(err) 189 | } else { 190 | if data, err := json.Marshal(res); err != nil { 191 | return utils.Error(err) 192 | } else { 193 | return data 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /cloud/socket/handlers/set.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | hermes "github.com/realTristan/hermes" 5 | utils "github.com/realTristan/hermes/cloud/socket/utils" 6 | ) 7 | 8 | // Set is a handler function that returns a fiber context handler function for setting a value in the cache. 9 | // Parameters: 10 | // - p (*utils.Params): A pointer to a utils.Params struct. 11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 12 | // 13 | // Returns: 14 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the set operation fails. 15 | func Set(p *utils.Params, c *hermes.Cache) []byte { 16 | var ( 17 | key string 18 | err error 19 | value map[string]any 20 | ) 21 | 22 | // Get the key from the query 23 | if key, err = utils.GetKeyParam(p); err != nil { 24 | return utils.Error("invalid key") 25 | } 26 | 27 | // Get the value from the query 28 | if err := utils.GetValueParam(p, &value); err != nil { 29 | return utils.Error(err) 30 | } 31 | 32 | // Set the value in the cache 33 | if err := c.Set(key, value); err != nil { 34 | return utils.Error(err) 35 | } 36 | return utils.Success("null") 37 | } 38 | -------------------------------------------------------------------------------- /cloud/socket/handlers/values.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | hermes "github.com/realTristan/hermes" 7 | utils "github.com/realTristan/hermes/cloud/socket/utils" 8 | ) 9 | 10 | // Values is a handler function that returns a fiber context handler function for retrieving all values from the cache. 11 | // Parameters: 12 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused). 13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct. 14 | // 15 | // Returns: 16 | // - []byte: A JSON-encoded byte slice containing all values from the cache or an error message if the retrieval fails. 17 | func Values(_ *utils.Params, c *hermes.Cache) []byte { 18 | if values, err := json.Marshal(c.Values()); err != nil { 19 | return utils.Error(err) 20 | } else { 21 | return values 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cloud/socket/router.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | 7 | "github.com/gofiber/fiber/v2" 8 | "github.com/gofiber/websocket/v2" 9 | hermes "github.com/realTristan/hermes" 10 | utils "github.com/realTristan/hermes/cloud/socket/utils" 11 | ) 12 | 13 | // Set the router for the socket 14 | func SetRouter(app *fiber.App, cache *hermes.Cache) { 15 | // Init a new socket 16 | var socket *Socket = &Socket{ 17 | active: false, 18 | mutex: &sync.Mutex{}, 19 | } 20 | 21 | // Middleware 22 | app.Use("/ws", func(c *fiber.Ctx) error { 23 | // Check if the socket is active 24 | if socket.IsActive() { 25 | return fiber.ErrLocked 26 | } 27 | 28 | // Check if the request is via socket 29 | if websocket.IsWebSocketUpgrade(c) { 30 | // Allow Locals 31 | c.Locals("allowed", true) 32 | 33 | // Set the socket to active 34 | socket.SetActive() 35 | 36 | // Return the next handler 37 | return c.Next() 38 | } 39 | 40 | // Return an error 41 | return fiber.ErrUpgradeRequired 42 | }) 43 | 44 | // Main websocket handler 45 | app.Get("/ws/hermes", websocket.New(func(c *websocket.Conn) { 46 | for { 47 | var ( 48 | msg []byte 49 | err error 50 | ) 51 | 52 | // Read the message 53 | if _, msg, err = c.ReadMessage(); err != nil { 54 | log.Println("read:", err) 55 | if IsCloseError(err) { 56 | socket.SetInactive() 57 | } 58 | break 59 | } 60 | 61 | // Get the data 62 | var p *utils.Params 63 | if p, err = utils.ParseParams(msg); err != nil { 64 | log.Println("parse:", err) 65 | break 66 | } 67 | 68 | // Get the function 69 | var function string 70 | if function, err = p.GetFunction(); err != nil { 71 | log.Println("function:", err) 72 | break 73 | } 74 | 75 | // Check if the function exists 76 | if fn, ok := Functions[function]; !ok { 77 | if c.WriteMessage(websocket.TextMessage, []byte("Function not found")) != nil { 78 | log.Println("write:", err) 79 | break 80 | } 81 | } else if c.WriteMessage(websocket.TextMessage, fn(p, cache)) != nil { 82 | log.Println("function:", err) 83 | break 84 | } 85 | } 86 | })) 87 | } 88 | -------------------------------------------------------------------------------- /cloud/socket/socket.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Socket struct for storing the socket state and fiber app 8 | type Socket struct { 9 | mutex *sync.Mutex 10 | active bool 11 | } 12 | 13 | // Set the socket to active 14 | func (s *Socket) SetActive() { 15 | s.mutex.Lock() 16 | defer s.mutex.Unlock() 17 | s.active = true 18 | } 19 | 20 | // Set the socket to inactive 21 | func (s *Socket) SetInactive() { 22 | s.mutex.Lock() 23 | defer s.mutex.Unlock() 24 | s.active = false 25 | } 26 | 27 | // Get the socket state 28 | func (s *Socket) IsActive() bool { 29 | s.mutex.Lock() 30 | defer s.mutex.Unlock() 31 | return s.active 32 | } 33 | -------------------------------------------------------------------------------- /cloud/socket/utils/decode.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | ) 7 | 8 | // Decode is a generic function that decodes a base64-encoded string and unmarshals the resulting JSON into a provided value. 9 | // Parameters: 10 | // - s (string): The base64-encoded string to decode and unmarshal. 11 | // - v (*T): A pointer to a value of type T to unmarshal the JSON into. 12 | // 13 | // Returns: 14 | // - error: An error if the decoding or unmarshaling fails, or nil if successful. 15 | func Decode[T any](s string, v *T) error { 16 | if data, err := base64.StdEncoding.DecodeString(s); err != nil { 17 | return err 18 | } else if err := json.Unmarshal(data, v); err != nil { 19 | return err 20 | } 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /cloud/socket/utils/errors.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/gofiber/websocket/v2" 7 | ) 8 | 9 | // IsCloseError is a function that checks if an error is a close error by converting 10 | // the error to a string and checking if it contains the word "close". 11 | // Parameters: 12 | // - err (error): The error to check. 13 | // 14 | // Returns: 15 | // - bool: true if the error is a close error, false otherwise. 16 | func IsCloseError(err error) bool { 17 | return strings.Contains(err.Error(), "close") 18 | } 19 | 20 | // IsSocketCloseError is a function that checks if an error is a WebSocket close error. 21 | // Parameters: 22 | // - err (error): The error to check. 23 | // 24 | // Returns: 25 | // - bool: true if the error is a WebSocket close error, false otherwise. 26 | func IsSocketCloseError(err error) bool { 27 | return websocket.IsCloseError( 28 | err, 29 | websocket.CloseNormalClosure, 30 | websocket.CloseGoingAway, 31 | websocket.CloseAbnormalClosure, 32 | websocket.CloseNoStatusReceived, 33 | websocket.CloseInvalidFramePayloadData, 34 | websocket.ClosePolicyViolation, 35 | websocket.CloseMessageTooBig, 36 | websocket.CloseMandatoryExtension, 37 | websocket.CloseInternalServerErr, 38 | websocket.CloseServiceRestart, 39 | websocket.CloseTryAgainLater, 40 | websocket.CloseTLSHandshake, 41 | websocket.CloseMessage, 42 | websocket.CloseProtocolError, 43 | websocket.CloseUnsupportedData, 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /cloud/socket/utils/http.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Error is a function that returns an error message with the provided error. 8 | // Parameters: 9 | // - err (T): The error to include in the error message. 10 | // 11 | // Returns: 12 | // - []byte: A byte slice containing the error message with the provided error. 13 | func Error[T any](err T) []byte { 14 | return []byte(fmt.Sprintf(`{"success":false,"error":"%v"}`, err)) 15 | } 16 | 17 | // Success is a function that returns a success message with the provided data. 18 | // Parameters: 19 | // - v (T): The data to include in the success message. 20 | // 21 | // Returns: 22 | // - []byte: A byte slice containing the success message with the provided data. 23 | func Success[T any](v T) []byte { 24 | return []byte(fmt.Sprintf(`{"success":true,"data":%v}`, v)) 25 | } 26 | -------------------------------------------------------------------------------- /cloud/wrappers/go/main.go: -------------------------------------------------------------------------------- 1 | package hermescloud 2 | 3 | func Get() { 4 | // send http request 5 | } 6 | -------------------------------------------------------------------------------- /cloud/wrappers/npm/main.ts: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | To do: 4 | 5 | 6 | 7 | 8 | 9 | */ 10 | 11 | // Get data from the cache function 12 | export const Get = (key: string): any => { 13 | // GET 14 | return fetch(`${host}/get`) 15 | .then(res => res.json()) 16 | .then(res => res); 17 | } 18 | 19 | // Set data in the cache function 20 | export const Set = (key: string, value: map[string]interface{}): any => { 21 | // POST 22 | let value = base64encode(jsonEncode(value)) 23 | return fetch(`${host}/set?key=${key}&value=${value}`) 24 | .then(res => res.json()) 25 | .then(res => res); 26 | } 27 | 28 | // Delete key from the cache 29 | export const Delete = (key: string): any => { 30 | // DELETE 31 | return fetch(`${host}/delete?key=${key}`) 32 | .then(res => res.json()) 33 | .then(res => res); 34 | } -------------------------------------------------------------------------------- /cloud/wrappers/python/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tristan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cloud/wrappers/python/dist/hermescloud-0.0.6-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/python/dist/hermescloud-0.0.6-py3-none-any.whl -------------------------------------------------------------------------------- /cloud/wrappers/python/dist/hermescloud-0.0.6.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/python/dist/hermescloud-0.0.6.tar.gz -------------------------------------------------------------------------------- /cloud/wrappers/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0"] 3 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /cloud/wrappers/python/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = hermescloud 3 | version = 0.0.6 4 | author = Tristan Simpson 5 | author_email = heytristaann@gmail.com 6 | description = Extremely fast full-text-search algorithm and caching system 7 | long_description = file: README.md 8 | long_description_content_type = text/markdown 9 | url = https://github.com/realTristan/Hermes 10 | project_urls = 11 | Bug Tracker = https://github.com/realTristan/Hermes/issues 12 | classifiers = 13 | Programming Language :: Python :: 3 14 | License :: OSI Approved :: MIT License 15 | Operating System :: OS Independent 16 | 17 | [options] 18 | package_dir = 19 | = src 20 | packages = find: 21 | python_requires = >=3.6 22 | 23 | [options.packages.find] 24 | where = src 25 | 26 | # python3 -m pip install --upgrade build 27 | # python3 -m pip install --upgrade twine 28 | # python3 -m build 29 | # python3 -m twine upload dist/* -------------------------------------------------------------------------------- /cloud/wrappers/python/src/hermescloud.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE 2 | README.md 3 | pyproject.toml 4 | setup.cfg 5 | src/hermescloud/__init__.py 6 | src/hermescloud/cache.py 7 | src/hermescloud/utils.py 8 | src/hermescloud.egg-info/PKG-INFO 9 | src/hermescloud.egg-info/SOURCES.txt 10 | src/hermescloud.egg-info/dependency_links.txt 11 | src/hermescloud.egg-info/top_level.txt -------------------------------------------------------------------------------- /cloud/wrappers/python/src/hermescloud.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cloud/wrappers/python/src/hermescloud.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | hermescloud 2 | -------------------------------------------------------------------------------- /cloud/wrappers/python/src/hermescloud/__init__.py: -------------------------------------------------------------------------------- 1 | from .cache import * -------------------------------------------------------------------------------- /cloud/wrappers/python/src/hermescloud/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/python/src/hermescloud/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /cloud/wrappers/python/src/hermescloud/__pycache__/cache.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/python/src/hermescloud/__pycache__/cache.cpython-310.pyc -------------------------------------------------------------------------------- /cloud/wrappers/python/src/hermescloud/__pycache__/hermes.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/python/src/hermescloud/__pycache__/hermes.cpython-310.pyc -------------------------------------------------------------------------------- /cloud/wrappers/python/src/hermescloud/__pycache__/utils.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/python/src/hermescloud/__pycache__/utils.cpython-310.pyc -------------------------------------------------------------------------------- /cloud/wrappers/python/src/hermescloud/utils.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | class Utils: 4 | # Convert a string to base64 5 | @staticmethod 6 | def tob64(value: str): 7 | return base64.b64encode(value.encode("utf-8")).decode("utf-8") 8 | -------------------------------------------------------------------------------- /cloud/wrappers/python/src/test_base.py: -------------------------------------------------------------------------------- 1 | from hermescloud import Cache 2 | import time 3 | 4 | # Create a new cache instance 5 | cache = Cache("localhost:3000") 6 | 7 | def main(): 8 | # Initialize the full-text search engine 9 | print(cache.ft_init(-1, -1)) 10 | 11 | # Set a value 12 | cache.set("user_id", { 13 | "name": { 14 | "$hermes.full_text": True, 15 | "$hermes.value": "tristan" 16 | } 17 | }) 18 | 19 | # Get a value 20 | print(cache.get("user_id")) 21 | 22 | # Track the start time 23 | start_time = time.time() 24 | 25 | # Search for a value 26 | print(cache.ft_search("tristan", False, 100, { 27 | "name": True 28 | })) 29 | 30 | # Print the duration (average: 0.0006s) 31 | print(f"Duration: {time.time() - start_time}s") 32 | 33 | # Delete a value 34 | print(cache.delete("user_id")) 35 | 36 | # Get a value 37 | print(cache.get("user_id")) 38 | 39 | # Run the main function 40 | if __name__ == "__main__": 41 | main() -------------------------------------------------------------------------------- /cloud/wrappers/python/src/test_withjson.py: -------------------------------------------------------------------------------- 1 | import hermescloud, json, base64, time 2 | 3 | # base64 encode a value 4 | def base64_encode(value): 5 | return base64.b64encode(value.encode("utf-8")).decode("utf-8") 6 | 7 | # Create a new cache instance 8 | cache = hermescloud.Cache("localhost:3000") 9 | 10 | # Initialize the full-text search engine 11 | #print(cache.ft_init(-1, -1)) 12 | 13 | # open the data/data_hash.json file 14 | def set_data(): 15 | with open("data/data_hash.json", "r") as file: 16 | # load the data_hash.json file 17 | data = json.loads(file.read()) 18 | 19 | # loop through the data 20 | for key in data: 21 | # set the key 22 | cache.set(key, data[key]) 23 | 24 | # set the data 25 | #set_data() 26 | 27 | # Track the start time 28 | start_time = time.time() 29 | 30 | # Search for a value 31 | r = cache.ft_search("computer", False, 100, { 32 | "id": False, 33 | "components": False, 34 | "units": False, 35 | "description": True, 36 | "name": True, 37 | "pre_requisites": True, 38 | "title": True 39 | }) 40 | 41 | # Print the duration 42 | print(time.time() - start_time) 43 | 44 | # print the results 45 | #print(r[0]) 46 | -------------------------------------------------------------------------------- /cloud/wrappers/rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "hermes" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /cloud/wrappers/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hermes" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /cloud/wrappers/rust/notes.txt: -------------------------------------------------------------------------------- 1 | make a rust crate for using Hermes -------------------------------------------------------------------------------- /cloud/wrappers/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/.rustc_info.json: -------------------------------------------------------------------------------- 1 | {"rustc_fingerprint":472984446891056861,"outputs":{"10376369925670944939":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/tristan/.rustup/toolchains/stable-x86_64-apple-darwin\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"ssse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"15697416045686424142":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.66.0 (69f9c33d7 2022-12-12)\nbinary: rustc\ncommit-hash: 69f9c33d71c871fc16ac445211281c6e7a340943\ncommit-date: 2022-12-12\nhost: x86_64-apple-darwin\nrelease: 1.66.0\nLLVM version: 15.0.2\n","stderr":""}},"successes":{}} -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/CACHEDIR.TAG: -------------------------------------------------------------------------------- 1 | Signature: 8a477f597d28d172789f06886806bc55 2 | # This file is a cache directory tag created by cargo. 3 | # For information about cache directory tags see https://bford.info/cachedir/ 4 | -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/.cargo-lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/rust/target/debug/.cargo-lock -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/.fingerprint/hermes-515e19fc54440f23/dep-test-bin-hermes: -------------------------------------------------------------------------------- 1 |  src/main.rs -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/.fingerprint/hermes-515e19fc54440f23/invoked.timestamp: -------------------------------------------------------------------------------- 1 | This file has an mtime of when this was started. -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/.fingerprint/hermes-515e19fc54440f23/test-bin-hermes: -------------------------------------------------------------------------------- 1 | 6fc47864a16005b5 -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/.fingerprint/hermes-515e19fc54440f23/test-bin-hermes.json: -------------------------------------------------------------------------------- 1 | {"rustc":5585755218383029683,"features":"[]","target":11031198021622862316,"profile":11506243869495082934,"path":1684066648322511884,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/hermes-515e19fc54440f23/dep-test-bin-hermes"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0} -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/.fingerprint/hermes-d80b4823bd3ba10f/bin-hermes: -------------------------------------------------------------------------------- 1 | 580e12857d03a55e -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/.fingerprint/hermes-d80b4823bd3ba10f/bin-hermes.json: -------------------------------------------------------------------------------- 1 | {"rustc":5585755218383029683,"features":"[]","target":11031198021622862316,"profile":17483045194147818835,"path":1684066648322511884,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/hermes-d80b4823bd3ba10f/dep-bin-hermes"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0} -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/.fingerprint/hermes-d80b4823bd3ba10f/dep-bin-hermes: -------------------------------------------------------------------------------- 1 |  src/main.rs -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/.fingerprint/hermes-d80b4823bd3ba10f/invoked.timestamp: -------------------------------------------------------------------------------- 1 | This file has an mtime of when this was started. -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/deps/hermes-515e19fc54440f23.d: -------------------------------------------------------------------------------- 1 | /Users/tristan/Desktop/Hermes/rust/target/debug/deps/hermes-515e19fc54440f23.rmeta: src/main.rs 2 | 3 | /Users/tristan/Desktop/Hermes/rust/target/debug/deps/hermes-515e19fc54440f23.d: src/main.rs 4 | 5 | src/main.rs: 6 | -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/deps/hermes-d80b4823bd3ba10f.d: -------------------------------------------------------------------------------- 1 | /Users/tristan/Desktop/Hermes/rust/target/debug/deps/hermes-d80b4823bd3ba10f.rmeta: src/main.rs 2 | 3 | /Users/tristan/Desktop/Hermes/rust/target/debug/deps/hermes-d80b4823bd3ba10f.d: src/main.rs 4 | 5 | src/main.rs: 6 | -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/deps/libhermes-515e19fc54440f23.rmeta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/rust/target/debug/deps/libhermes-515e19fc54440f23.rmeta -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/deps/libhermes-d80b4823bd3ba10f.rmeta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/rust/target/debug/deps/libhermes-d80b4823bd3ba10f.rmeta -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/incremental/hermes-1b6yi2bw8b899/s-gkiytxaoyx-n4pevb-2nsnbq1tpmfaw/dep-graph.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/rust/target/debug/incremental/hermes-1b6yi2bw8b899/s-gkiytxaoyx-n4pevb-2nsnbq1tpmfaw/dep-graph.bin -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/incremental/hermes-1b6yi2bw8b899/s-gkiytxaoyx-n4pevb-2nsnbq1tpmfaw/query-cache.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/rust/target/debug/incremental/hermes-1b6yi2bw8b899/s-gkiytxaoyx-n4pevb-2nsnbq1tpmfaw/query-cache.bin -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/incremental/hermes-1b6yi2bw8b899/s-gkiytxaoyx-n4pevb-2nsnbq1tpmfaw/work-products.bin: -------------------------------------------------------------------------------- 1 | RSIC1.66.0 (69f9c33d7 2022-12-12) -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/incremental/hermes-1b6yi2bw8b899/s-gkiytxaoyx-n4pevb.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/rust/target/debug/incremental/hermes-1b6yi2bw8b899/s-gkiytxaoyx-n4pevb.lock -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/incremental/hermes-1msokizf0w59t/s-gkiytxaoyx-61kh8a-385m7v9kh6g64/dep-graph.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/rust/target/debug/incremental/hermes-1msokizf0w59t/s-gkiytxaoyx-61kh8a-385m7v9kh6g64/dep-graph.bin -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/incremental/hermes-1msokizf0w59t/s-gkiytxaoyx-61kh8a-385m7v9kh6g64/query-cache.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/rust/target/debug/incremental/hermes-1msokizf0w59t/s-gkiytxaoyx-61kh8a-385m7v9kh6g64/query-cache.bin -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/incremental/hermes-1msokizf0w59t/s-gkiytxaoyx-61kh8a-385m7v9kh6g64/work-products.bin: -------------------------------------------------------------------------------- 1 | RSIC1.66.0 (69f9c33d7 2022-12-12) -------------------------------------------------------------------------------- /cloud/wrappers/rust/target/debug/incremental/hermes-1msokizf0w59t/s-gkiytxaoyx-61kh8a.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/cloud/wrappers/rust/target/debug/incremental/hermes-1msokizf0w59t/s-gkiytxaoyx-61kh8a.lock -------------------------------------------------------------------------------- /compression/gzip/gzip.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "io" 7 | ) 8 | 9 | // Compress is a function that compresses a string using gzip compression. 10 | // 11 | // Parameters: 12 | // - v: A string representing the value to compress. 13 | // 14 | // Returns: 15 | // - A byte slice representing the compressed value. 16 | // - An error if there was an error compressing the value. 17 | // 18 | // Example usage: 19 | // 20 | // compressed, err := Compress("value") // compressed == []byte{...}, err == nil 21 | func Compress(v []byte) ([]byte, error) { 22 | var ( 23 | b *bytes.Buffer = new(bytes.Buffer) 24 | gz *gzip.Writer = gzip.NewWriter(b) 25 | ) 26 | if _, err := gz.Write(v); err != nil { 27 | return nil, err 28 | } 29 | if err := gz.Close(); err != nil { 30 | return nil, err 31 | } 32 | return b.Bytes(), nil 33 | } 34 | 35 | // Decompress is a function that decompresses a byte slice using gzip decompression. 36 | // 37 | // Parameters: 38 | // - v: A byte slice representing the compressed value to decompress. 39 | // 40 | // Returns: 41 | // - A string representing the decompressed value. 42 | // - An error if there was an error decompressing the value. 43 | // 44 | // Example usage: 45 | // 46 | // decompressed, err := Decompress([]byte{...}) // decompressed == "value", err == nil 47 | func Decompress(v []byte) (string, error) { 48 | var b *bytes.Buffer = bytes.NewBuffer(v) 49 | if gz, err := gzip.NewReader(b); err != nil { 50 | return "", err 51 | } else if s, err := io.ReadAll(gz); err != nil { 52 | return "", err 53 | } else { 54 | return string(s), nil 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /compression/zlib/zlib.go: -------------------------------------------------------------------------------- 1 | package zlib 2 | 3 | import ( 4 | "bytes" 5 | "compress/zlib" 6 | "io" 7 | ) 8 | 9 | // Compress is a function that compresses a string using gzip compression. 10 | // 11 | // Parameters: 12 | // - v: A string representing the value to compress. 13 | // 14 | // Returns: 15 | // - A byte slice representing the compressed value. 16 | // - An error if there was an error compressing the value. 17 | // 18 | // Example usage: 19 | // 20 | // compressed, err := Compress("value") // compressed == []byte{...}, err == nil 21 | func Compress(v []byte) ([]byte, error) { 22 | var ( 23 | b *bytes.Buffer = new(bytes.Buffer) 24 | gz *zlib.Writer = zlib.NewWriter(b) 25 | ) 26 | if _, err := gz.Write(v); err != nil { 27 | return nil, err 28 | } 29 | if err := gz.Close(); err != nil { 30 | return nil, err 31 | } 32 | return b.Bytes(), nil 33 | } 34 | 35 | // Decompress is a function that decompresses a byte slice using gzip decompression. 36 | // 37 | // Parameters: 38 | // - v: A byte slice representing the compressed value to decompress. 39 | // 40 | // Returns: 41 | // - A string representing the decompressed value. 42 | // - An error if there was an error decompressing the value. 43 | // 44 | // Example usage: 45 | // 46 | // decompressed, err := Decompress([]byte{...}) // decompressed == "value", err == nil 47 | func Decompress(v []byte) (string, error) { 48 | var b *bytes.Buffer = bytes.NewBuffer(v) 49 | if gz, err := zlib.NewReader(b); err != nil { 50 | return "", err 51 | } else if s, err := io.ReadAll(gz); err != nil { 52 | return "", err 53 | } else { 54 | return string(s), nil 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /delete.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | // Delete is a method of the Cache struct that removes a key from the cache. 4 | // If the full-text index is initialized, it is also removed from there. 5 | // This method is thread-safe. 6 | // 7 | // Parameters: 8 | // - key: A string representing the key to remove from the cache. 9 | // 10 | // Returns: 11 | // - None 12 | func (c *Cache) Delete(key string) { 13 | c.mutex.Lock() 14 | defer c.mutex.Unlock() 15 | c.delete(key) 16 | } 17 | 18 | // delete is a method of the Cache struct that removes a key from the cache. 19 | // If the full-text index is initialized, it is also removed from there. 20 | // This method is not thread-safe and should only be called from an exported function. 21 | // 22 | // Parameters: 23 | // - key: A string representing the key to remove from the cache. 24 | // 25 | // Returns: 26 | // - None 27 | func (c *Cache) delete(key string) { 28 | // Delete the key from the FT cache 29 | if c.ft != nil { 30 | c.ft.delete(key) 31 | } 32 | 33 | // Delete the key from the cache 34 | delete(c.data, key) 35 | } 36 | 37 | // delete is a method of the FullText struct that removes a key from the full-text storage. 38 | // This function is not thread-safe and should only be called from an exported function. 39 | // 40 | // Parameters: 41 | // - key: A string representing the key to remove from the full-text storage. 42 | // 43 | // Returns: 44 | // - None 45 | func (ft *FullText) delete(key string) { 46 | // Remove the key from the ft.storage 47 | for word, data := range ft.storage { 48 | // Check if the data is []int or int 49 | if _, ok := data.(int); ok { 50 | delete(ft.storage, word) 51 | continue 52 | } 53 | 54 | // If the data is []int, loop through the slice 55 | if keys, ok := data.([]int); !ok { 56 | for i := 0; i < len(keys); i++ { 57 | if key != ft.indices[keys[i]] { 58 | continue 59 | } 60 | 61 | // Remove the key from the ft.storage slice 62 | keys = append(keys[:i], keys[i+1:]...) 63 | ft.storage[word] = keys 64 | break 65 | } 66 | 67 | // If keys is empty, remove it from the storage 68 | if len(keys) == 0 { 69 | delete(ft.storage, word) 70 | } else if len(keys) == 1 { 71 | ft.storage[word] = keys[0] 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | backend: 4 | build: . 5 | ports: 6 | - "3000:3000" -------------------------------------------------------------------------------- /examples/basic/basic.go: -------------------------------------------------------------------------------- 1 | // //////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Run Command: go run . 4 | // 5 | // Host URL: http://localhost:8000/courses?q=computer&limit=100&strict=false 6 | // 7 | // //////////////////////////////////////////////////////////////////////////// 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "time" 13 | 14 | hermes "github.com/realTristan/hermes" 15 | ) 16 | 17 | // Initialize the cache 18 | var cache *hermes.Cache = hermes.InitCache() 19 | 20 | func main() { 21 | // Initialize the full-text cache 22 | cache.FTInitWithJson("../../testing/data/data_hash.json", -1, -1, 3) 23 | 24 | // Search for a word in the cache 25 | var startTime time.Time = time.Now() 26 | var res, _ = cache.Search(hermes.SearchParams{ 27 | Query: "computer", 28 | Limit: 100, 29 | Strict: false, 30 | }) 31 | var duration time.Duration = time.Since(startTime) 32 | fmt.Println("Search took", duration) 33 | fmt.Println("Search results:", len(res)) 34 | } 35 | -------------------------------------------------------------------------------- /examples/router/router.go: -------------------------------------------------------------------------------- 1 | // ///////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Run Command: go run . 4 | // 5 | // Host URL: http://localhost:8000/courses?q=computer&limit=100&strict=false 6 | // 7 | // ///////////////////////////////////////////////////////////////////////////// 8 | package main 9 | 10 | import ( 11 | "encoding/json" 12 | "fmt" 13 | "net/http" 14 | "strconv" 15 | "time" 16 | 17 | hermes "github.com/realTristan/hermes" 18 | ) 19 | 20 | // Global full text variable 21 | var cache *hermes.Cache 22 | 23 | // Main function 24 | func main() { 25 | cache = hermes.InitCache() 26 | cache.FTInitWithJson("../../testing/data/data_hash.json", -1, -1, 3) 27 | 28 | // Print host 29 | fmt.Println(" >> Listening on: http://localhost:8000/") 30 | 31 | // Listen and serve on port 8000 32 | http.HandleFunc("/courses", Handler) 33 | http.ListenAndServe(":8000", nil) 34 | } 35 | 36 | // Handle the incoming http request 37 | func Handler(w http.ResponseWriter, r *http.Request) { 38 | w.Header().Set("Access-Control-Allow-Origin", "*") 39 | 40 | // Get the query parameter 41 | var query string = "CS" 42 | if _query := r.URL.Query().Get("q"); _query != "" { 43 | query = _query 44 | } 45 | 46 | // Get the limit parameter 47 | var limit int = 100 48 | if _limit := r.URL.Query().Get("limit"); _limit != "" { 49 | limit, _ = strconv.Atoi(_limit) 50 | } 51 | 52 | // Get the strict parameter 53 | var strict bool = false 54 | if _strict := r.URL.Query().Get("strict"); _strict != "" { 55 | strict, _ = strconv.ParseBool(_strict) 56 | } 57 | 58 | // Track the start time 59 | var start time.Time = time.Now() 60 | 61 | // Search for a word in the cache 62 | // Make sure the show which keys you do want to search through, 63 | // and which ones you don't 64 | var res, _ = cache.Search(hermes.SearchParams{ 65 | Query: query, 66 | Limit: limit, 67 | Strict: strict, 68 | }) 69 | 70 | // Print the duration 71 | fmt.Printf("\nFound %v results in %v", len(res), time.Since(start)) 72 | 73 | // Write the courses to the json response 74 | var response, _ = json.Marshal(res) 75 | w.Write(response) 76 | } 77 | -------------------------------------------------------------------------------- /exists.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | // Exists is a method of the Cache struct that checks if a key exists in the cache. 4 | // This method is thread-safe. 5 | // 6 | // Parameters: 7 | // - key: A string representing the key to check for existence in the cache. 8 | // 9 | // Returns: 10 | // - A boolean value indicating whether the key exists in the cache or not. 11 | func (c *Cache) Exists(key string) bool { 12 | c.mutex.RLock() 13 | defer c.mutex.RUnlock() 14 | return c.exists(key) 15 | } 16 | 17 | // exists is a method of the Cache struct that checks if a key exists in the cache. 18 | // This method is not thread-safe and should only be called from an exported function. 19 | // 20 | // Parameters: 21 | // - key: A string representing the key to check for existence in the cache. 22 | // 23 | // Returns: 24 | // - A boolean value indicating whether the key exists in the cache or not. 25 | func (c *Cache) exists(key string) bool { 26 | _, ok := c.data[key] 27 | return ok 28 | } 29 | -------------------------------------------------------------------------------- /get.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | // Get is a method of the Cache struct that retrieves the value associated with the given key from the cache. 4 | // This method is thread-safe. 5 | // 6 | // Parameters: 7 | // - key: A string representing the key to retrieve the value for. 8 | // 9 | // Returns: 10 | // - A map[string]any representing the value associated with the given key in the cache. 11 | func (c *Cache) Get(key string) map[string]any { 12 | c.mutex.RLock() 13 | defer c.mutex.RUnlock() 14 | return c.get(key) 15 | } 16 | 17 | // get is a method of the Cache struct that retrieves the value associated with the given key from the cache. 18 | // This method is not thread-safe and should only be called from an exported function. 19 | // 20 | // Parameters: 21 | // - key: A string representing the key to retrieve the value for. 22 | // 23 | // Returns: 24 | // - A map[string]any representing the value associated with the given key in the cache. 25 | func (c *Cache) get(key string) map[string]any { 26 | return c.data[key] 27 | } 28 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/realTristan/hermes 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/gofiber/fiber/v2 v2.45.0 7 | github.com/gofiber/websocket/v2 v2.2.0 8 | ) 9 | 10 | require ( 11 | github.com/andybalholm/brotli v1.0.5 // indirect 12 | github.com/fasthttp/websocket v1.5.3 // indirect 13 | github.com/google/uuid v1.3.0 // indirect 14 | github.com/klauspost/compress v1.16.5 // indirect 15 | github.com/mattn/go-colorable v0.1.13 // indirect 16 | github.com/mattn/go-isatty v0.0.18 // indirect 17 | github.com/mattn/go-runewidth v0.0.14 // indirect 18 | github.com/philhofer/fwd v1.1.2 // indirect 19 | github.com/rivo/uniseg v0.2.0 // indirect 20 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect 21 | github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect 22 | github.com/tinylib/msgp v1.1.8 // indirect 23 | github.com/valyala/bytebufferpool v1.0.0 // indirect 24 | github.com/valyala/fasthttp v1.47.0 // indirect 25 | github.com/valyala/tcplisten v1.0.0 // indirect 26 | golang.org/x/sys v0.8.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.20 2 | 3 | use ( 4 | ./ 5 | ./cloud/app/server 6 | ) 7 | -------------------------------------------------------------------------------- /go.work.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 2 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 3 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 4 | -------------------------------------------------------------------------------- /indices.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | // When you delete a number of keys from the cache, the index remains 4 | // the same. Over time, this number will grow to be very large, and will 5 | // cause the cache to use a lot of memory. This function resets the indices 6 | // to be sequential, starting from 0. 7 | // This function is thread-safe. 8 | func (c *Cache) FTSequenceIndices() { 9 | c.mutex.Lock() 10 | defer c.mutex.Unlock() 11 | c.ft.sequenceIndices() 12 | } 13 | 14 | // When you delete a number of keys from the cache, the index remains 15 | // the same. Over time, this number will grow to be very large, and will 16 | // cause the cache to use a lot of memory. This function resets the indices 17 | // to be sequential, starting from 0. 18 | // This function is not thread-safe, and should only be called from 19 | // an exported function. 20 | func (ft *FullText) sequenceIndices() { 21 | // Store the temp variables 22 | var ( 23 | tempIndices map[int]string = make(map[int]string) 24 | tempindex int = 0 25 | tempKeys map[string]int = make(map[string]int) 26 | ) 27 | 28 | // Fill the temp indices by iterating over the current 29 | // indices and adding them to the tempIndices map 30 | for _, value := range ft.indices { 31 | tempIndices[tempindex] = value 32 | tempindex++ 33 | } 34 | 35 | // Fill the temp keys with the opposites of ft.indices 36 | for key, value := range tempIndices { 37 | tempKeys[value] = key 38 | } 39 | 40 | // Iterate over the ft storage 41 | for word, data := range ft.storage { 42 | // Check if the data is []int or int 43 | if v, ok := data.(int); ok { 44 | ft.storage[word] = tempKeys[ft.indices[v]] 45 | continue 46 | } 47 | 48 | // If the data is []int, loop through the slice 49 | if keys, ok := data.([]int); !ok { 50 | for i := 0; i < len(keys); i++ { 51 | var index int = keys[i] 52 | 53 | // Get the key from the old indices 54 | var key string = ft.indices[index] 55 | 56 | // Set the new index 57 | ft.storage[word].([]int)[i] = tempKeys[key] 58 | } 59 | } 60 | } 61 | 62 | // Set the old variables to the new variables 63 | ft.indices = tempIndices 64 | ft.index = tempindex 65 | } 66 | -------------------------------------------------------------------------------- /info.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | import ( 4 | "errors" 5 | 6 | utils "github.com/realTristan/hermes/utils" 7 | ) 8 | 9 | // Info is a method of the Cache struct that returns a map with the cache and full-text info. 10 | // This method is thread-safe. 11 | // An error is returned if the full-text index is not initialized. 12 | // 13 | // Returns: 14 | // - A map[string]any representing the cache and full-text info. 15 | // - An error if the full-text index is not initialized. 16 | func (c *Cache) Info() (map[string]any, error) { 17 | c.mutex.RLock() 18 | defer c.mutex.RUnlock() 19 | return c.info() 20 | } 21 | 22 | // info is a method of the Cache struct that returns a map with the cache and full-text info. 23 | // This method is not thread-safe, and should only be called from an exported function. 24 | // An error is returned if the full-text index is not initialized. 25 | // 26 | // Returns: 27 | // - A map[string]any representing the cache and full-text info. 28 | // - An error if the full-text index is not initialized. 29 | func (c *Cache) info() (map[string]any, error) { 30 | var info map[string]any = map[string]any{ 31 | "keys": len(c.data), 32 | } 33 | 34 | // Check if the cache full-text has been initialized 35 | if c.ft == nil { 36 | return info, errors.New("full-text is not initialized") 37 | } 38 | 39 | // Add the full-text info to the map 40 | if size, err := utils.Size(c.ft.storage); err != nil { 41 | return info, err 42 | } else { 43 | // Add the full-text info to the map 44 | info["full-text"] = map[string]any{ 45 | "keys": len(c.ft.storage), 46 | "index": c.ft.index, 47 | "size": size, 48 | } 49 | } 50 | 51 | // Return the info map 52 | return info, nil 53 | } 54 | 55 | // InfoForTesting is a method of the Cache struct that returns a map with the cache and full-text info for testing purposes. 56 | // This method is thread-safe. 57 | // An error is returned if the full-text index is not initialized. 58 | // 59 | // Returns: 60 | // - A map[string]any representing the cache and full-text info for testing purposes. 61 | // - An error if the full-text index is not initialized. 62 | func (c *Cache) InfoForTesting() (map[string]any, error) { 63 | c.mutex.RLock() 64 | defer c.mutex.RUnlock() 65 | return c.infoForTesting() 66 | } 67 | 68 | // infoForTesting is a method of the Cache struct that returns a map with the cache and full-text info for testing purposes. 69 | // This method is not thread-safe, and should only be called from an exported function. 70 | // An error is returned if the full-text index is not initialized. 71 | // 72 | // Returns: 73 | // - A map[string]any representing the cache and full-text info for testing purposes. 74 | // - An error if the full-text index is not initialized. 75 | func (c *Cache) infoForTesting() (map[string]any, error) { 76 | var info map[string]any = map[string]any{ 77 | "keys": len(c.data), 78 | "data": c.data, 79 | } 80 | 81 | // Check if the cache full-text has been initialized 82 | if c.ft == nil { 83 | return info, errors.New("full-text is not initialized") 84 | } 85 | 86 | // Add the full-text info to the map 87 | if size, err := utils.Size(c.ft.storage); err != nil { 88 | return info, err 89 | } else { 90 | info["full-text"] = map[string]any{ 91 | "keys": len(c.ft.storage), 92 | "index": c.ft.index, 93 | "size": size, 94 | "storage": c.ft.storage, 95 | "indices": c.ft.indices, 96 | } 97 | } 98 | 99 | // Return the info map 100 | return info, nil 101 | } 102 | -------------------------------------------------------------------------------- /keys.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | // Keys is a method of the Cache struct that returns all the keys in the cache. 4 | // This function is thread-safe. 5 | // 6 | // Returns: 7 | // - A slice of strings containing all the keys in the cache. 8 | func (c *Cache) Keys() []string { 9 | c.mutex.RLock() 10 | defer c.mutex.RUnlock() 11 | return c.keys() 12 | } 13 | 14 | // keys is a method of the Cache struct that returns all the keys in the cache. 15 | // This function is not thread-safe, and should only be called from an exported function. 16 | // 17 | // Returns: 18 | // - A slice of strings containing all the keys in the cache. 19 | func (c *Cache) keys() []string { 20 | keys := make([]string, 0, len(c.data)) 21 | for key := range c.data { 22 | keys = append(keys, key) 23 | } 24 | return keys 25 | } 26 | -------------------------------------------------------------------------------- /length.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | // Length is a method of the Cache struct that returns the number of items stored in the cache. 4 | // This function is thread-safe. 5 | // 6 | // Returns: 7 | // - An integer representing the number of items stored in the cache. 8 | func (c *Cache) Length() int { 9 | c.mutex.RLock() 10 | defer c.mutex.RUnlock() 11 | return c.length() 12 | } 13 | 14 | // length is a method of the Cache struct that returns the number of items stored in the cache. 15 | // This function is not thread-safe, and should only be called from an exported function. 16 | // 17 | // Returns: 18 | // - An integer representing the number of items stored in the cache. 19 | func (c *Cache) length() int { 20 | return len(c.data) 21 | } 22 | -------------------------------------------------------------------------------- /nocache/examples/basic/basic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | hermes "github.com/realTristan/hermes/nocache" 8 | ) 9 | 10 | // Main function 11 | func main() { 12 | // Define variables 13 | var ( 14 | // Initialize the full text 15 | ft, _ = hermes.InitWithJson("../../../testing/data/data_array.json", 3) 16 | 17 | // Track the start time 18 | start time.Time = time.Now() 19 | 20 | // Search for a word in the cache 21 | // @params: query, limit, strict 22 | res, _ = ft.Search(hermes.SearchParams{ 23 | Query: "computer", 24 | Limit: 100, 25 | Strict: false, 26 | }) 27 | ) 28 | 29 | // Print the duration 30 | fmt.Printf("\nFound %v results in %v", len(res), time.Since(start)) 31 | 32 | // Search in values with key 33 | var ( 34 | // Track the start time 35 | start2 time.Time = time.Now() 36 | 37 | // Search for a word in the cache 38 | res2, _ = ft.SearchWithKey("CS", "title", 100) 39 | ) 40 | 41 | // Print the duration 42 | fmt.Printf("\nFound %v results in %v", len(res2), time.Since(start2)) 43 | } 44 | -------------------------------------------------------------------------------- /nocache/examples/router/router.go: -------------------------------------------------------------------------------- 1 | // ///////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Run Command: go run . 4 | // 5 | // Host URL: http://localhost:8000/courses?q=computer&limit=100&strict=false 6 | // 7 | // ///////////////////////////////////////////////////////////////////////////// 8 | package main 9 | 10 | import ( 11 | "encoding/json" 12 | "fmt" 13 | "net/http" 14 | "strconv" 15 | "time" 16 | 17 | hermes "github.com/realTristan/hermes/nocache" 18 | ) 19 | 20 | // Global full text variable 21 | var ft *hermes.FullText 22 | 23 | // Main function 24 | func main() { 25 | ft, _ = hermes.InitWithJson("../../../testing/data/data_array.json", 3) 26 | 27 | // Print host 28 | fmt.Println(" >> Listening on: http://localhost:8000/") 29 | 30 | // Listen and serve on port 8000 31 | http.HandleFunc("/courses", Handler) 32 | http.ListenAndServe(":8000", nil) 33 | } 34 | 35 | // Handle the incoming http request 36 | func Handler(w http.ResponseWriter, r *http.Request) { 37 | w.Header().Set("Access-Control-Allow-Origin", "*") 38 | 39 | // Get the query parameter 40 | var query string = "CS" 41 | if _query := r.URL.Query().Get("q"); _query != "" { 42 | query = _query 43 | } 44 | 45 | // Get the limit parameter 46 | var limit int = 100 47 | if _limit := r.URL.Query().Get("limit"); _limit != "" { 48 | limit, _ = strconv.Atoi(_limit) 49 | } 50 | 51 | // Get the strict parameter 52 | var strict bool = false 53 | if _strict := r.URL.Query().Get("strict"); _strict != "" { 54 | strict, _ = strconv.ParseBool(_strict) 55 | } 56 | 57 | // Track the start time 58 | var start time.Time = time.Now() 59 | 60 | // Search for a word in the cache 61 | // Make sure the show which keys you do want to search through, 62 | // and which ones you don't 63 | var res, _ = ft.Search(hermes.SearchParams{ 64 | Query: query, 65 | Limit: limit, 66 | Strict: strict, 67 | }) 68 | 69 | // Print the duration 70 | fmt.Printf("\nFound %v results in %v", len(res), time.Since(start)) 71 | 72 | // Write the courses to the json response 73 | var response, _ = json.Marshal(res) 74 | w.Write(response) 75 | } 76 | -------------------------------------------------------------------------------- /nocache/fulltext.go: -------------------------------------------------------------------------------- 1 | package nocache 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | /* 8 | FullText is a struct that represents a full text search cache. It has the following fields: 9 | - mutex (*sync.RWMutex): a pointer to a read-write mutex used to synchronize access to the cache 10 | - storage (map[string]any): a map where the keys are words and the values are arrays of integers representing the indices of the data items that contain the word 11 | - words ([]string): a slice of strings representing all the unique words in the cache 12 | - data ([]map[string]any): a slice of maps representing the data items in the cache, where the keys are the names of the fields and the values are the field values 13 | */ 14 | type FullText struct { 15 | mutex *sync.RWMutex 16 | storage map[string]any 17 | words []string 18 | data []map[string]any 19 | } 20 | -------------------------------------------------------------------------------- /nocache/init.go: -------------------------------------------------------------------------------- 1 | package nocache 2 | 3 | import ( 4 | "sync" 5 | 6 | utils "github.com/realTristan/hermes/utils" 7 | ) 8 | 9 | // Initialize the full-text cache with the provided data. 10 | // This function is thread safe. 11 | func InitWithMapSlice(data []map[string]any, minWordLength int) (*FullText, error) { 12 | var ft *FullText = &FullText{ 13 | mutex: &sync.RWMutex{}, 14 | storage: make(map[string]any), 15 | words: []string{}, 16 | data: []map[string]any{}, 17 | } 18 | 19 | // Load the cache data 20 | if err := ft.insert(data, minWordLength); err != nil { 21 | return nil, err 22 | } 23 | 24 | // Set the data 25 | ft.data = data 26 | 27 | // Return the full-text variable 28 | return ft, nil 29 | } 30 | 31 | // Initialize the full-text cache with the provided json file. 32 | // This function is thread safe. 33 | func InitWithJson(file string, minWordLength int) (*FullText, error) { 34 | // Read the json data 35 | if data, err := utils.ReadJson[[]map[string]any](file); err != nil { 36 | return nil, err 37 | } else { 38 | return InitWithMapSlice(data, minWordLength) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /nocache/insert.go: -------------------------------------------------------------------------------- 1 | package nocache 2 | 3 | import ( 4 | "strings" 5 | 6 | utils "github.com/realTristan/hermes/utils" 7 | ) 8 | 9 | // Insert data into the full-text cache. 10 | // This function is not thread-safe, and should only be called from 11 | // an exported function. 12 | func (ft *FullText) insert(data []map[string]any, minWordLength int) error { 13 | // Loop through the data 14 | for i, item := range data { 15 | // Loop through the map 16 | for key, value := range item { 17 | // Get the string value 18 | var ( 19 | strvNormal string 20 | strv string 21 | ) 22 | if _strv := WFTGetValueFromMap(value); len(_strv) > 0 { 23 | strv = _strv 24 | strvNormal = _strv 25 | } else { 26 | continue 27 | } 28 | 29 | // Clean the value 30 | strv = strings.TrimSpace(strv) 31 | strv = utils.RemoveDoubleSpaces(strv) 32 | strv = strings.ToLower(strv) 33 | 34 | // Loop through the words 35 | for _, word := range strings.Split(strv, " ") { 36 | if len(word) == 0 { 37 | continue 38 | } 39 | 40 | // Trim the word 41 | word = utils.TrimNonAlphaNum(word) 42 | var words []string = utils.SplitByAlphaNum(word) 43 | 44 | // Loop through the words 45 | for j := 0; j < len(words); j++ { 46 | if len(words[j]) < minWordLength { 47 | continue 48 | } 49 | if temp, ok := ft.storage[words[j]]; !ok { 50 | ft.storage[words[j]] = []int{i} 51 | ft.words = append(ft.words, words[j]) 52 | } else if indices, ok := temp.([]int); !ok { 53 | ft.storage[words[j]] = []int{temp.(int), i} 54 | } else { 55 | if utils.SliceContains(indices, i) { 56 | continue 57 | } 58 | ft.storage[words[j]] = append(indices, i) 59 | } 60 | } 61 | } 62 | 63 | // Iterate over the temp storage and set the values with len 1 to int 64 | for k, v := range ft.storage { 65 | if v, ok := v.([]int); ok && len(v) == 1 { 66 | ft.storage[k] = v[0] 67 | } 68 | } 69 | 70 | // Set the value 71 | data[i][key] = strvNormal 72 | } 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /nocache/search.go: -------------------------------------------------------------------------------- 1 | package nocache 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // Search searches for all occurrences of the given query string in the FullText object's data. 9 | // The search is done by splitting the query into separate words and looking for each of them in the data. 10 | // The search result is limited to the specified number of entries. 11 | // Parameters: 12 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 13 | // 14 | // Returns: 15 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query. 16 | // The keys of the map correspond to the column names of the data that were searched and returned in the result. 17 | // - error: An error if the query or limit is invalid. 18 | func (ft *FullText) Search(sp SearchParams) ([]map[string]any, error) { 19 | switch { 20 | case len(sp.Query) == 0: 21 | return []map[string]any{}, errors.New("invalid query") 22 | case sp.Limit < 1: 23 | return []map[string]any{}, errors.New("invalid limit") 24 | } 25 | 26 | // Convert the query to lowercase 27 | sp.Query = strings.ToLower(sp.Query) 28 | 29 | // Lock the mutex 30 | ft.mutex.RLock() 31 | defer ft.mutex.RUnlock() 32 | 33 | // Perform the search 34 | return ft.search(sp), nil 35 | } 36 | 37 | // search searches for all occurrences of the given query string in the FullText object's data. 38 | // The search is done by splitting the query into separate words and looking for each of them in the data. 39 | // The search result is limited to the specified number of entries. 40 | // Parameters: 41 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 42 | // 43 | // Returns: 44 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query. 45 | // The keys of the map correspond to the column names of the data that were searched and returned in the result. 46 | // - error: An error if the query or limit is invalid. 47 | func (ft *FullText) search(sp SearchParams) []map[string]any { 48 | // Split the query into separate words 49 | var words []string = strings.Split(strings.TrimSpace(sp.Query), " ") 50 | switch { 51 | // If the words array is empty 52 | case len(words) == 0: 53 | return []map[string]any{} 54 | // Get the search result of the first word 55 | case len(words) == 1: 56 | sp.Query = words[0] 57 | return ft.searchOneWord(sp) 58 | } 59 | 60 | // Check if the query is in the cache 61 | if _, ok := ft.storage[words[0]]; !ok { 62 | return []map[string]any{} 63 | } 64 | 65 | // Define variables 66 | var result []map[string]any = []map[string]any{} 67 | 68 | // Variables for storing the smallest words array 69 | var ( 70 | smallest int = 0 //nolint:ineffassign 71 | smallestIndex int = 0 72 | ) 73 | 74 | // Check if the query is in the cache 75 | if v, ok := ft.storage[words[0]]; !ok { 76 | return []map[string]any{} 77 | } else { 78 | if index, ok := v.(int); ok { 79 | return []map[string]any{ 80 | ft.data[index], 81 | } 82 | } 83 | smallest = len(v.([]int)) 84 | } 85 | 86 | // Find the smallest words array 87 | // Don't include the first or last words from the query 88 | for i := 1; i < len(words)-1; i++ { 89 | if v, ok := ft.storage[words[i]]; ok { 90 | if v, ok := v.(int); ok { 91 | return []map[string]any{ 92 | ft.data[v], 93 | } 94 | } 95 | if l := len(v.([]int)); l < smallest { 96 | smallest = l 97 | smallestIndex = i 98 | } 99 | } 100 | } 101 | 102 | // Loop through the indices 103 | var indices []int = ft.storage[words[smallestIndex]].([]int) 104 | for i := 0; i < len(indices); i++ { 105 | for _, value := range ft.data[indices[i]] { 106 | if v, ok := value.(string); !ok { 107 | continue 108 | } else if strings.Contains(strings.ToLower(v), sp.Query) { 109 | result = append(result, ft.data[indices[i]]) 110 | } 111 | } 112 | } 113 | 114 | // Return the result 115 | return result 116 | } 117 | -------------------------------------------------------------------------------- /nocache/searchoneword.go: -------------------------------------------------------------------------------- 1 | package nocache 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | utils "github.com/realTristan/hermes/utils" 8 | ) 9 | 10 | // SearchOneWord searches for a single query within the data using a full-text search approach. 11 | // The search result is limited to the specified number of entries, and can optionally be filtered to only include exact matches. 12 | // Parameters: 13 | // - query (string): The search query to use. This string will be searched for as a single word in any value associated with any key in each entry of the data. 14 | // - limit (int): The maximum number of search results to return. If the number of matching results exceeds this limit, the excess results will be ignored. 15 | // - strict (bool): If true, only exact matches will be returned. If false, partial matches will also be returned. 16 | // 17 | // Returns: 18 | // - []map[string]any: An array of maps representing the search results. Each map contains key-value pairs 19 | // from the entry in the data that matched the search query. If no results are found, an empty array is returned. 20 | // - error: An error object. If no error occurs, this will be nil. 21 | // 22 | // Note: The search is case-insensitive. 23 | func (ft *FullText) SearchOneWord(sp SearchParams) ([]map[string]any, error) { 24 | switch { 25 | case len(sp.Query) == 0: 26 | return []map[string]any{}, errors.New("invalid query") 27 | case sp.Limit < 1: 28 | return []map[string]any{}, errors.New("invalid limit") 29 | } 30 | 31 | // Set the query to lowercase 32 | sp.Query = strings.ToLower(sp.Query) 33 | 34 | // Lock the mutex 35 | ft.mutex.RLock() 36 | defer ft.mutex.RUnlock() 37 | 38 | // Search the data 39 | return ft.searchOneWord(sp), nil 40 | } 41 | 42 | // searchOneWord searches for a single query within the data using a full-text search approach. 43 | // The search result is limited to the specified number of entries, and can optionally be filtered to only include exact matches. 44 | // Parameters: 45 | // - query (string): The search query to use. This string will be searched for as a single word in any value associated with any key in each entry of the data. 46 | // - limit (int): The maximum number of search results to return. If the number of matching results exceeds this limit, the excess results will be ignored. 47 | // - strict (bool): If true, only exact matches will be returned. If false, partial matches will also be returned. 48 | // 49 | // Returns: 50 | // - []map[string]any: An array of maps representing the search results. Each map contains key-value pairs 51 | // from the entry in the data that matched the search query. If no results are found, an empty array is returned. 52 | // 53 | // Note: The search is case-insensitive. 54 | func (ft *FullText) searchOneWord(sp SearchParams) []map[string]any { 55 | // Define the result variable 56 | var result []map[string]any = []map[string]any{} 57 | 58 | // If the user wants a strict search, just return the result 59 | // straight from the cache 60 | if sp.Strict { 61 | return ft.searchOneWordStrict(result, sp) 62 | } 63 | 64 | // true for already checked 65 | var alreadyAdded map[int]int = map[int]int{} 66 | 67 | // Loop through the cache keys 68 | for i := 0; i < len(ft.words); i++ { 69 | switch { 70 | case len(result) >= sp.Limit: 71 | return result 72 | case !utils.Contains(ft.words[i], sp.Query): 73 | continue 74 | } 75 | 76 | // Loop through the cache indices 77 | if v, ok := ft.storage[ft.words[i]].(int); ok { 78 | if _, ok := alreadyAdded[v]; ok { 79 | continue 80 | } 81 | result = append(result, ft.data[v]) 82 | alreadyAdded[v] = 0 83 | continue 84 | } 85 | 86 | var indices []int = ft.storage[ft.words[i]].([]int) 87 | for j := 0; j < len(indices); j++ { 88 | if _, ok := alreadyAdded[indices[j]]; ok { 89 | continue 90 | } 91 | result = append(result, ft.data[indices[j]]) 92 | alreadyAdded[indices[j]] = 0 93 | } 94 | } 95 | 96 | // Return the result 97 | return result 98 | } 99 | 100 | // searchOneWordStrict is a method of the FullText struct that searches for a single word in the full-text cache and returns the results. 101 | // 102 | // Parameters: 103 | // - result: A slice of map[string]any representing the current search results. 104 | // - query: A string representing the word to search for. 105 | // - limit: An integer representing the maximum number of results to return. 106 | // 107 | // Returns: 108 | // - A slice of map[string]any representing the search results. 109 | func (ft *FullText) searchOneWordStrict(result []map[string]any, sp SearchParams) []map[string]any { 110 | // Check if the query is in the cache 111 | if _, ok := ft.storage[sp.Query]; !ok { 112 | return result 113 | } 114 | 115 | // Check if the cache value is an integer 116 | if v, ok := ft.storage[sp.Query].(int); ok { 117 | return []map[string]any{ft.data[v]} 118 | } 119 | 120 | // Loop through the indices 121 | var indices []int = ft.storage[sp.Query].([]int) 122 | for i := 0; i < len(indices); i++ { 123 | if len(result) >= sp.Limit { 124 | return result 125 | } 126 | result = append(result, ft.data[indices[i]]) 127 | } 128 | 129 | // Return the result 130 | return result 131 | } 132 | -------------------------------------------------------------------------------- /nocache/searchparams.go: -------------------------------------------------------------------------------- 1 | package nocache 2 | 3 | // SearchParams is a struct that contains the search parameters for the Cache search methods. 4 | type SearchParams struct { 5 | // The search query 6 | Query string 7 | // The limit of search results to return 8 | Limit int 9 | // A boolean to indicate whether the search should be strict or not 10 | Strict bool 11 | // A map containing the schema to search for 12 | Schema map[string]bool 13 | // Key to search in 14 | Key string 15 | } 16 | -------------------------------------------------------------------------------- /nocache/searchvalues.go: -------------------------------------------------------------------------------- 1 | package nocache 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // SearchValues searches for all occurrences of the given query string in the FullText object's data. 9 | // The search is done by splitting the query into separate words and looking for each of them in the data. 10 | // The search result is limited to the specified number of entries, and can optionally be filtered to only 11 | // include keys that match a given schema. 12 | // Parameters: 13 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 14 | // 15 | // Returns: 16 | // - []map[string]any: An array of maps representing the search results. Each map contains key-value pairs 17 | // from the entry in the data that matched the search query. If no results are found, an empty array is returned. 18 | // - error: An error object. If no error occurs, this will be nil. 19 | // 20 | // Note: The search is case-insensitive. 21 | func (ft *FullText) SearchValues(sp SearchParams) ([]map[string]any, error) { 22 | switch { 23 | case len(sp.Query) == 0: 24 | return []map[string]any{}, errors.New("invalid query") 25 | case sp.Limit < 1: 26 | return []map[string]any{}, errors.New("invalid limit") 27 | } 28 | 29 | // Set the query to lowercase 30 | sp.Query = strings.ToLower(sp.Query) 31 | 32 | // Lock the mutex 33 | ft.mutex.RLock() 34 | defer ft.mutex.RUnlock() 35 | 36 | // Search the data 37 | return ft.searchValues(sp), nil 38 | } 39 | 40 | // searchValues searches for all occurrences of the given query string in the FullText object's data. 41 | // The search is done by looking for the query as a substring in any value associated with any key in each entry of the data. 42 | // The search result is limited to the specified number of entries, and can optionally be filtered to only include keys that match a given schema. 43 | // Parameters: 44 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 45 | // 46 | // Returns: 47 | // - []map[string]any: An array of maps representing the search results. Each map contains key-value pairs 48 | // from the entry in the data that matched the search query. If no results are found, an empty array is returned. 49 | // 50 | // Note: The search is case-insensitive. 51 | func (ft *FullText) searchValues(sp SearchParams) []map[string]any { 52 | // Define variables 53 | var result []map[string]any = []map[string]any{} 54 | 55 | // Iterate over the query result 56 | for i := 0; i < len(ft.data); i++ { 57 | // Iterate over the keys and values for the data 58 | for key, value := range ft.data[i] { 59 | if v, ok := value.(string); !ok { 60 | continue 61 | } else { 62 | switch { 63 | case len(result) >= sp.Limit: 64 | return result 65 | case !sp.Schema[key]: 66 | continue 67 | case strings.Contains(strings.ToLower(v), sp.Query): 68 | result = append(result, ft.data[i]) 69 | } 70 | } 71 | } 72 | } 73 | 74 | // Return the result 75 | return result 76 | } 77 | -------------------------------------------------------------------------------- /nocache/searchwithkey.go: -------------------------------------------------------------------------------- 1 | package nocache 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // SearchWithKey searches for all records containing the given query in the specified key column with a limit of results to return. 9 | // The search result is limited to the specified number of entries. 10 | // Parameters: 11 | // - query (string): The search query to use. This string will be searched for as a substring in the data value associated with the given key. 12 | // - key (string): The name of the key in the data whose data value should be searched. 13 | // - limit (int): The maximum number of search results to return. If the number of matching results exceeds this limit, the excess results will be ignored. 14 | // 15 | // Returns: 16 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query. 17 | // The keys of the map correspond to the column names of the data that were searched and returned in the result. 18 | // - error: An error if the query, key or limit is invalid. 19 | func (ft *FullText) SearchWithKey(query string, key string, limit int) ([]map[string]any, error) { 20 | switch { 21 | case len(key) == 0: 22 | return []map[string]any{}, errors.New("invalid key") 23 | case len(query) == 0: 24 | return []map[string]any{}, errors.New("invalid query") 25 | case limit < 1: 26 | return []map[string]any{}, errors.New("invalid limit") 27 | } 28 | 29 | // Set the query to lowercase 30 | query = strings.ToLower(query) 31 | 32 | // Lock the mutex 33 | ft.mutex.RLock() 34 | defer ft.mutex.RUnlock() 35 | 36 | // Search for the query 37 | return ft.searchWithKey(query, key, limit), nil 38 | } 39 | 40 | // searchWithKey searches for all occurrences of the given query string in the FullText object's data associated with the specified key. 41 | // The search is done by looking for the query as a substring in the data value associated with the given key. 42 | // The search result is limited to the specified number of entries. 43 | // Parameters: 44 | // - query (string): The search query to use. This string will be searched for as a substring in the data value associated with the given key. 45 | // - key (string): The name of the key in the data whose data value should be searched. 46 | // - limit (int): The maximum number of search results to return. If the number of matching results exceeds this limit, the excess results will be ignored. 47 | // 48 | // Returns: 49 | // - []map[string]any: An array of maps representing the search results. Each map contains key-value pairs 50 | // from the entry in the data that matched the search query. If no results are found, an empty array is returned. 51 | // - error: An error object. If no error occurs, this will be nil. 52 | // 53 | // Note: The search is case-insensitive. 54 | func (ft *FullText) searchWithKey(query string, key string, limit int) []map[string]any { 55 | // Define variables 56 | var result []map[string]any = []map[string]any{} 57 | 58 | // Iterate over the query result 59 | for i := 0; i < len(ft.data); i++ { 60 | if v, ok := ft.data[i][key].(string); !ok { 61 | continue 62 | } else { 63 | switch { 64 | case len(result) >= limit: 65 | return result 66 | case strings.Contains(strings.ToLower(v), query): 67 | result = append(result, ft.data[i]) 68 | } 69 | } 70 | } 71 | 72 | // Return the result 73 | return result 74 | } 75 | -------------------------------------------------------------------------------- /nocache/withft.go: -------------------------------------------------------------------------------- 1 | package nocache 2 | 3 | // Get the full-text value from a map 4 | func WFTGetValueFromMap(value any) string { 5 | if _, ok := value.(map[string]any); !ok { 6 | return "" 7 | } 8 | 9 | // Verify that the map has the correct length 10 | var v map[string]any = value.(map[string]any) 11 | if len(v) != 2 { 12 | return "" 13 | } 14 | 15 | // Verify that the map has the correct keys 16 | if ft, ok := v["$hermes.full_text"]; ok { 17 | if ft, ok := ft.(bool); ok && ft { 18 | if v, ok := v["$hermes.value"]; ok { 19 | if v, ok := v.(string); ok { 20 | return v 21 | } 22 | } 23 | } 24 | } 25 | return "" 26 | } 27 | -------------------------------------------------------------------------------- /search.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // Search is a method of the Cache struct that searches for a query by splitting the query into separate words and returning the search results. 9 | // Parameters: 10 | // - c (c *Cache): A pointer to the Cache struct 11 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 12 | // 13 | // Returns: 14 | // - []map[string]any: A slice of maps containing the search results. 15 | // - error: An error if the query is invalid or if the smallest words array is not found in the cache. 16 | func (c *Cache) Search(sp SearchParams) ([]map[string]any, error) { 17 | // If the query is empty, return an error 18 | if len(sp.Query) == 0 { 19 | return []map[string]any{}, errors.New("invalid query") 20 | } 21 | 22 | // If no limit is provided, set it to 10 23 | if sp.Limit == 0 { 24 | sp.Limit = 10 25 | } 26 | 27 | // Lock the mutex 28 | c.mutex.RLock() 29 | defer c.mutex.RUnlock() 30 | 31 | // Check if the FT index is initialized 32 | if c.ft == nil { 33 | return []map[string]any{}, errors.New("full-text not initialized") 34 | } 35 | 36 | // Set the query to lowercase 37 | sp.Query = strings.ToLower(sp.Query) 38 | 39 | // Search for the query 40 | return c.search(sp), nil 41 | } 42 | 43 | // search is a method of the Cache struct that searches for a query by splitting the query into separate words and returning the search results. 44 | // Parameters: 45 | // - c (c *Cache): A pointer to the Cache struct 46 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 47 | // 48 | // Returns: 49 | // - []map[string]any: A slice of maps containing the search results. 50 | func (c *Cache) search(sp SearchParams) []map[string]any { 51 | // Split the query into separate words 52 | var words []string = strings.Split(strings.TrimSpace(sp.Query), " ") 53 | switch { 54 | // If the words array is empty 55 | case len(words) == 0: 56 | return []map[string]any{} 57 | // Get the search result of the first word 58 | case len(words) == 1: 59 | sp.Query = words[0] 60 | return c.searchOneWord(sp) 61 | } 62 | 63 | // Define variables 64 | var result []map[string]any = []map[string]any{} 65 | 66 | // Variables for storing the smallest words array 67 | // var smallestData []int = []int{} 68 | var ( 69 | smallestIndex int = 0 70 | smallest int = 0 71 | ) 72 | 73 | // Check if the query is in the cache 74 | if indices, ok := c.ft.storage[words[0]]; !ok { 75 | return []map[string]any{} 76 | } else { 77 | /*for { 78 | if v, ok := indices.(string); ok { 79 | indices = c.ft.storage[v] 80 | } else { 81 | break 82 | } 83 | }*/ 84 | if temp, ok := indices.(int); ok { 85 | return []map[string]any{ 86 | c.data[c.ft.indices[temp]], 87 | } 88 | } 89 | // smallestData = indices.([]int) 90 | smallest = len(indices.([]int)) 91 | } 92 | 93 | // Find the smallest words array 94 | // Don't include the first or last words from the query 95 | for i := 1; i < len(words)-1; i++ { 96 | if indices, ok := c.ft.storage[words[i]]; ok { 97 | /*for { 98 | if v, ok := indices.(string); ok { 99 | indices = c.ft.storage[v] 100 | } else { 101 | break 102 | } 103 | }*/ 104 | if index, ok := indices.(int); ok { 105 | return []map[string]any{ 106 | c.data[c.ft.indices[index]], 107 | } 108 | } 109 | /*if l := len(indices.([]int)); l < len(smallestData) { 110 | smallestData = indices.([]int) 111 | }*/ 112 | if l := len(indices.([]int)); l < smallest { 113 | smallest = l 114 | smallestIndex = i 115 | } 116 | } 117 | } 118 | 119 | // Loop through the indices 120 | /*for i := 0; i < len(smallestData); i++ { 121 | for _, value := range c.data[c.ft.indices[smallestData[i]]] { 122 | // Check if the value contains the query 123 | if v, ok := value.(string); ok { 124 | if strings.Contains(strings.ToLower(v), sp.Query) { 125 | result = append(result, c.data[c.ft.indices[smallestData[i]]]) 126 | } 127 | } 128 | } 129 | }*/ 130 | var keys []int = c.ft.storage[words[smallestIndex]].([]int) 131 | for i := 0; i < len(keys); i++ { 132 | for _, value := range c.data[c.ft.indices[keys[i]]] { 133 | // Check if the value contains the query 134 | if v, ok := value.(string); ok { 135 | if strings.Contains(strings.ToLower(v), sp.Query) { 136 | result = append(result, c.data[c.ft.indices[keys[i]]]) 137 | } 138 | } 139 | } 140 | } 141 | 142 | // Return the result 143 | return result 144 | } 145 | -------------------------------------------------------------------------------- /searchoneword.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | utils "github.com/realTristan/hermes/utils" 8 | ) 9 | 10 | // SearchOneWord searches for a single word in the FullText struct's data and returns a list of maps containing the search results. 11 | // Parameters: 12 | // - c (c *Cache): A pointer to the Cache struct 13 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 14 | // 15 | // Returns: 16 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query. 17 | // The keys of the map correspond to the column names of the data that were searched and returned in the result. 18 | // - error: An error if the query or limit is invalid or if the full-text is not initialized. 19 | func (c Cache) SearchOneWord(sp SearchParams) ([]map[string]any, error) { 20 | // If the query is empty, return an error 21 | if len(sp.Query) == 0 { 22 | return []map[string]any{}, errors.New("invalid query") 23 | } 24 | 25 | // If no limit is provided, set it to 10 26 | if sp.Limit == 0 { 27 | sp.Limit = 10 28 | } 29 | 30 | // Lock the mutex 31 | c.mutex.RLock() 32 | defer c.mutex.RUnlock() 33 | 34 | // Check if the full-text is initialized 35 | if c.ft == nil { 36 | return []map[string]any{}, errors.New("full-text is not initialized") 37 | } 38 | 39 | // Search the data 40 | return c.searchOneWord(sp), nil 41 | } 42 | 43 | // searchOneWord searches for a single word in the FullText struct's data and returns a list of maps containing the search results. 44 | // Parameters: 45 | // - c (c *Cache): A pointer to the Cache struct 46 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 47 | // 48 | // Returns: 49 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query. 50 | // The keys of the map correspond to the column names of the data that were searched and returned in the result. 51 | func (c *Cache) searchOneWord(sp SearchParams) []map[string]any { 52 | // Set the query to lowercase 53 | sp.Query = strings.ToLower(sp.Query) 54 | 55 | // Define variables 56 | var result []map[string]any = []map[string]any{} 57 | 58 | // If the user wants a strict search, just return the result 59 | // straight from the cache 60 | if sp.Strict { 61 | return c.searchOneWordStrict(result, sp) 62 | } 63 | 64 | // Define a map to store the indices that have already been added 65 | var alreadyAdded map[int]int = map[int]int{} 66 | 67 | // Loop through the cache keys 68 | for k, v := range c.ft.storage { 69 | switch { 70 | case len(result) >= sp.Limit: 71 | return result 72 | case !utils.Contains(k, sp.Query): 73 | continue 74 | } 75 | 76 | // Loop through the cache indices 77 | if index, ok := v.(int); ok { 78 | if _, ok := alreadyAdded[index]; ok { 79 | continue 80 | } 81 | result = append(result, c.data[c.ft.indices[index]]) 82 | alreadyAdded[index] = 0 83 | continue 84 | } 85 | 86 | var indices []int = v.([]int) 87 | for j := 0; j < len(indices); j++ { 88 | if _, ok := alreadyAdded[indices[j]]; ok { 89 | continue 90 | } 91 | 92 | // Else, append the index to the result 93 | result = append(result, c.data[c.ft.indices[indices[j]]]) 94 | alreadyAdded[indices[j]] = 0 95 | } 96 | } 97 | 98 | // Return the result 99 | return result 100 | } 101 | 102 | // searchOneWordStrict is a method of the Cache struct that searches for a single word in the cache and returns the results. 103 | // This function is not thread-safe. 104 | // 105 | // Parameters: 106 | // - c (c *Cache): A pointer to the Cache struct 107 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 108 | // 109 | // Returns: 110 | // - A slice of map[string]any representing the search results. 111 | func (c *Cache) searchOneWordStrict(result []map[string]any, sp SearchParams) []map[string]any { 112 | // Check if the query is in the cache 113 | if _, ok := c.ft.storage[sp.Query]; !ok { 114 | return result 115 | } 116 | 117 | // If there's only one result 118 | if v, ok := c.ft.storage[sp.Query].(int); ok { 119 | return []map[string]any{c.data[c.ft.indices[v]]} 120 | } 121 | 122 | // Loop through the indices 123 | for i := 0; i < len(c.ft.storage[sp.Query].([]int)); i++ { 124 | if len(result) >= sp.Limit { 125 | return result 126 | } 127 | var ( 128 | index int = c.ft.storage[sp.Query].([]int)[i] 129 | key string = c.ft.indices[index] 130 | ) 131 | result = append(result, c.data[key]) 132 | } 133 | 134 | // Return the result 135 | return result 136 | } 137 | -------------------------------------------------------------------------------- /searchparams.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | // SearchParams is a struct that contains the search parameters for the Cache search methods. 4 | type SearchParams struct { 5 | // The search query 6 | Query string 7 | // The limit of search results to return 8 | Limit int 9 | // A boolean to indicate whether the search should be strict or not 10 | Strict bool 11 | // A map containing the schema to search for 12 | Schema map[string]bool 13 | // Key to search in 14 | Key string 15 | } 16 | -------------------------------------------------------------------------------- /searchvalues.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // SearchValues searches for all records containing the given query in the specified schema with a limit of results to return. 9 | // Parameters: 10 | // - c (c *Cache): A pointer to the Cache struct 11 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 12 | // 13 | // Returns: 14 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query. 15 | // The keys of the map correspond to the column names of the data that were searched and returned in the result. 16 | // - error: An error if the query or limit is invalid 17 | func (c *Cache) SearchValues(sp SearchParams) ([]map[string]any, error) { 18 | // If the query is empty, return an error 19 | if len(sp.Query) == 0 { 20 | return []map[string]any{}, errors.New("invalid query") 21 | } 22 | 23 | // If no limit is provided, set it to 10 24 | if sp.Limit == 0 { 25 | sp.Limit = 10 26 | } 27 | 28 | // If no schema is provided, set it to all columns 29 | if len(sp.Schema) == 0 { 30 | sp.Schema = make(map[string]bool) 31 | } 32 | 33 | // Set the query to lowercase 34 | sp.Query = strings.ToLower(sp.Query) 35 | 36 | // Lock the mutex 37 | c.mutex.RLock() 38 | defer c.mutex.RUnlock() 39 | 40 | // Search the data 41 | return c.searchValues(sp), nil 42 | } 43 | 44 | // searchValues searches for all records containing the given query in the specified schema with a limit of results to return. 45 | // Parameters: 46 | // - c (c *Cache): A pointer to the Cache struct 47 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 48 | // 49 | // Returns: 50 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query. 51 | // The keys of the map correspond to the column names of the data that were searched and returned in the result. 52 | func (c *Cache) searchValues(sp SearchParams) []map[string]any { 53 | // Define variables 54 | var result []map[string]any = []map[string]any{} 55 | 56 | // Iterate over the query result 57 | for _, item := range c.data { 58 | // Iterate over the keys and values for the data for that index 59 | for key, value := range item { 60 | switch { 61 | case len(result) >= sp.Limit: 62 | return result 63 | case !sp.Schema[key]: 64 | continue 65 | } 66 | 67 | // Check if the value contains the query 68 | if v, ok := value.(string); ok { 69 | if strings.Contains(strings.ToLower(v), sp.Query) { 70 | result = append(result, item) 71 | } 72 | } 73 | } 74 | } 75 | 76 | // Return the result 77 | return result 78 | } 79 | -------------------------------------------------------------------------------- /searchwithkey.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // SearchWithKey searches for all records containing the given query in the specified key column with a limit of results to return. 9 | // Parameters: 10 | // - c (c *Cache): A pointer to the Cache struct 11 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 12 | // 13 | // Returns: 14 | // - []map[string]any: A slice of maps containing the search results 15 | // - error: An error if the key, query or limit is invalid 16 | func (c *Cache) SearchWithKey(sp SearchParams) ([]map[string]any, error) { 17 | switch { 18 | case len(sp.Key) == 0: 19 | return []map[string]any{}, errors.New("invalid key") 20 | case len(sp.Query) == 0: 21 | return []map[string]any{}, errors.New("invalid query") 22 | } 23 | 24 | // Set the query to lowercase 25 | sp.Query = strings.ToLower(sp.Query) 26 | 27 | // Lock the mutex 28 | c.mutex.RLock() 29 | defer c.mutex.RUnlock() 30 | 31 | // Search the data 32 | return c.searchWithKey(sp), nil 33 | } 34 | 35 | // searchWithKey searches for all records containing the given query in the specified key column with a limit of results to return. 36 | // Parameters: 37 | // - c (c *Cache): A pointer to the Cache struct 38 | // - sp (SearchParams): A SearchParams struct containing the search parameters. 39 | // 40 | // Returns: 41 | // - []map[string]any: A slice of maps containing the search results 42 | func (c *Cache) searchWithKey(sp SearchParams) []map[string]any { 43 | // Define variables 44 | var result []map[string]any = []map[string]any{} 45 | 46 | // Iterate over the query result 47 | for _, item := range c.data { 48 | for _, v := range item { 49 | if len(result) >= sp.Limit { 50 | return result 51 | } 52 | 53 | // Check if the value contains the query 54 | if v, ok := v.(string); ok { 55 | if strings.Contains(strings.ToLower(v), sp.Query) { 56 | result = append(result, item) 57 | } 58 | } 59 | } 60 | } 61 | 62 | // Return the result 63 | return result 64 | } 65 | -------------------------------------------------------------------------------- /set.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | import "fmt" 4 | 5 | // Set is a method of the Cache struct that sets a value in the cache for the specified key. 6 | // This function is thread-safe. 7 | // 8 | // Parameters: 9 | // - key: A string representing the key to set the value for. 10 | // - value: A map[string]any representing the value to set. 11 | // 12 | // Returns: 13 | // - Error 14 | func (c *Cache) Set(key string, value map[string]any) error { 15 | c.mutex.Lock() 16 | defer c.mutex.Unlock() 17 | return c.set(key, value) 18 | } 19 | 20 | // set is a method of the Cache struct that sets a value in the cache for the specified key. 21 | // This function is not thread-safe, and should only be called from an exported function. 22 | // If fullText is true, set the value in the full-text cache as well. 23 | // 24 | // Parameters: 25 | // - key: A string representing the key to set the value for. 26 | // - value: A map[string]any representing the value to set. 27 | // 28 | // Returns: 29 | // - An error if the full-text cache key already exists. Otherwise, nil. 30 | func (c *Cache) set(key string, value map[string]any) error { 31 | if _, ok := c.data[key]; ok { 32 | return fmt.Errorf("full-text cache key already exists (%s). delete it before setting it another value", key) 33 | } 34 | 35 | // Update the value in the FT cache 36 | if c.ft != nil { 37 | if err := c.ftSet(key, value); err != nil { 38 | return err 39 | } 40 | } 41 | 42 | // Update the value in the cache 43 | c.data[key] = value 44 | 45 | // Return nil for no error 46 | return nil 47 | } 48 | 49 | // ftSet is a method of the Cache struct that sets a value in the full-text cache for the specified key. 50 | // This function is not thread-safe, and should only be called from an exported function. 51 | // 52 | // Parameters: 53 | // - key: A string representing the key to set the value for. 54 | // - value: A map[string]any representing the value to set. 55 | // 56 | // Returns: 57 | // - An error if the full-text storage limit or byte-size limit is reached. Otherwise, nil. 58 | func (c *Cache) ftSet(key string, value map[string]any) error { 59 | var ts *TempStorage = NewTempStorage(c.ft) 60 | for k, v := range value { 61 | if ftv := WFTGetValue(v); len(ftv) == 0 { 62 | continue 63 | } else { 64 | // Update the value 65 | value[k] = ftv 66 | 67 | // Insert the value in the temp storage 68 | if err := ts.insert(c.ft, key, ftv); err != nil { 69 | return err 70 | } 71 | } 72 | } 73 | 74 | // Iterate over the temp storage and set the values with len 1 to int 75 | ts.cleanSingleArrays() 76 | 77 | // Set the full-text cache to the temp map 78 | ts.updateFullText(c.ft) 79 | 80 | // Return nil for no errors 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /testing/api/request.py: -------------------------------------------------------------------------------- 1 | import requests, json, base64, time 2 | 3 | def base64_encode(value): 4 | return base64.b64encode(value.encode("utf-8")).decode("utf-8") 5 | 6 | def test_search(): 7 | headers = { 8 | "Content-Type": "application/json" 9 | } 10 | 11 | # schema 12 | schema = base64_encode(json.dumps({ 13 | "id": False, 14 | "components": False, 15 | "units": False, 16 | "description": True, 17 | "name": True, 18 | "pre_requisites": True, 19 | "title": True 20 | })) 21 | 22 | # search for a value 23 | url = "http://localhost:3000/ft/search" 24 | params = "?query=computer&strict=false&limit=100&schema=" + schema 25 | 26 | # make the request 27 | r = requests.get(url+params, headers=headers) 28 | print(r.text) 29 | 30 | def test_set_json(data): 31 | headers = { 32 | "Content-Type": "application/json" 33 | } 34 | 35 | # set the data 36 | for key in data: 37 | # set the key 38 | url = "http://localhost:3000/cache/set" 39 | params = "?key=" + key + "&value=" + base64_encode(json.dumps(data[key])) 40 | 41 | # make the request 42 | r = requests.post(url+params, headers=headers) 43 | #print(r.text) 44 | 45 | def test_set(): 46 | headers = { 47 | "Content-Type": "application/json" 48 | } 49 | 50 | # set the data 51 | url = "http://localhost:3000/cache/set" 52 | params = "?key=testing&value=" + base64_encode(json.dumps({ 53 | "name": "tristan" 54 | })) 55 | 56 | # make the request 57 | r = requests.post(url+params, headers=headers) 58 | print(r.text) 59 | 60 | 61 | def test_init_ft_json(data): 62 | headers = { 63 | "Content-Type": "application/json" 64 | } 65 | 66 | # url and params 67 | _json = base64_encode(json.dumps(data)) 68 | url = "http://localhost:3000/ft/init/json" 69 | params = "?maxlength=-1&maxbytes=-1&json=" + _json 70 | 71 | # make the request 72 | r = requests.post(url+params, headers=headers) 73 | 74 | # print the response 75 | print(r.text) 76 | 77 | 78 | def test_init_ft(): 79 | headers = { 80 | "Content-Type": "application/json" 81 | } 82 | 83 | # url and params 84 | url = "http://localhost:3000/ft/init" 85 | params = "?maxlength=-1&maxbytes=-1" 86 | 87 | # make the request 88 | r = requests.post(url+params, headers=headers) 89 | 90 | # print the response 91 | print(r.text) 92 | 93 | 94 | def test_get(): 95 | headers = { 96 | "Content-Type": "application/json" 97 | } 98 | 99 | # get b4a3261059ea6f1b48eb8039e720e0b48d087583 100 | url = "http://localhost:3000/cache/get" 101 | params = "?key=b4a3261059ea6f1b48eb8039e720e0b48d087583" 102 | 103 | # make the request 104 | r = requests.get(url+params, headers=headers) 105 | print(r.text) 106 | 107 | def test_cache_info(): 108 | headers = { 109 | "Content-Type": "application/json" 110 | } 111 | 112 | # get b4a3261059ea6f1b48eb8039e720e0b48d087583 113 | url = "http://localhost:3000/cache/info/testing" 114 | 115 | # make the request 116 | r = requests.get(url, headers=headers) 117 | print(r.text) 118 | 119 | 120 | if __name__ == "__main__": 121 | # read the json file from the data folder 122 | with open("data/data_hash.json", "r") as file: 123 | # load the json file 124 | data = json.loads(file.read()) 125 | # test_init_ft_json(data) 126 | test_init_ft() 127 | # test_cache_info() 128 | # test_get() 129 | # test_set_json(data) 130 | # test_set() 131 | # test_cache_info() 132 | 133 | st = time.time() 134 | test_search() 135 | print(time.time() - st) -------------------------------------------------------------------------------- /testing/api/router.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | hermes "github.com/realTristan/hermes" 6 | api "github.com/realTristan/hermes/cloud/api" 7 | ) 8 | 9 | func main() { 10 | app := fiber.New() 11 | cache := hermes.InitCache() 12 | api.SetRoutes(app, cache) 13 | app.Listen(":3000") 14 | } 15 | -------------------------------------------------------------------------------- /testing/cache/cache.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | Hermes "github.com/realTristan/hermes" 7 | ) 8 | 9 | func main() { 10 | var cache *Hermes.Cache = Hermes.InitCache() 11 | cache.FTInit(3, -1, -1) 12 | 13 | // Test Set, Get, Delete, Clean, Length, Values, Keys, and Exists 14 | var data = map[string]any{ 15 | "name": cache.WithFT("Tristan"), 16 | "age": 17, 17 | } 18 | 19 | // Set data 20 | cache.Set("user_id1", data) 21 | cache.Set("user_id1", data) 22 | cache.Set("user_id2", data) 23 | cache.Set("user_id3", data) 24 | 25 | // Get data 26 | fmt.Println(cache.Get("user_id1")) 27 | fmt.Println(cache.Get("user_id2")) 28 | fmt.Println(cache.Get("user_id3")) 29 | 30 | // Exists 31 | fmt.Println(cache.Exists("user_id1")) 32 | fmt.Println(cache.Exists("user_id2")) 33 | 34 | // Length 35 | fmt.Println(cache.Length()) 36 | 37 | // Values 38 | fmt.Println(cache.Values()) 39 | 40 | // Keys 41 | fmt.Println(cache.Keys()) 42 | 43 | // Delete data 44 | cache.Delete("user_id1") 45 | 46 | // Exists 47 | fmt.Println(cache.Exists("user_id1")) 48 | fmt.Println(cache.Exists("user_id2")) 49 | 50 | // Get data 51 | fmt.Println(cache.Get("user_id1")) 52 | fmt.Println(cache.Get("user_id2")) 53 | fmt.Println(cache.Get("user_id3")) 54 | 55 | // Clean data 56 | cache.Clean() 57 | 58 | // Get data 59 | fmt.Println(cache.Get("user_id1")) 60 | fmt.Println(cache.Get("user_id2")) 61 | fmt.Println(cache.Get("user_id3")) 62 | 63 | // Length 64 | fmt.Println(cache.Length()) 65 | 66 | // Values 67 | fmt.Println(cache.Values()) 68 | 69 | // Keys 70 | fmt.Println(cache.Keys()) 71 | } 72 | -------------------------------------------------------------------------------- /testing/compression/compression.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | gzip "github.com/realTristan/hermes/compression/gzip" 9 | "github.com/realTristan/hermes/compression/zlib" 10 | utils "github.com/realTristan/hermes/utils" 11 | ) 12 | 13 | func main() { 14 | var v string = strings.Repeat("computer", 100) 15 | TestGzip(v) 16 | TestZlib(v) 17 | } 18 | 19 | // Test the zlib compression and decompression functions. 20 | func TestZlib(v string) { 21 | fmt.Println("zlib") 22 | var ( 23 | b []byte 24 | err error 25 | st time.Time = time.Now() 26 | ) 27 | if b, err = zlib.Compress([]byte(v)); err != nil { 28 | panic(err) 29 | } 30 | fmt.Println(time.Since(st)) 31 | st = time.Now() 32 | if v, err = zlib.Decompress(b); err != nil { 33 | panic(err) 34 | } 35 | fmt.Println(time.Since(st)) 36 | fmt.Println(utils.Size(v)) 37 | fmt.Println(utils.Size(b)) 38 | } 39 | 40 | // Test the gzip compression and decompression functions. 41 | func TestGzip(v string) { 42 | fmt.Println("gzip") 43 | var ( 44 | b []byte 45 | err error 46 | st time.Time = time.Now() 47 | ) 48 | if b, err = gzip.Compress([]byte(v)); err != nil { 49 | panic(err) 50 | } 51 | fmt.Println(time.Since(st)) 52 | st = time.Now() 53 | if v, err = gzip.Decompress(b); err != nil { 54 | panic(err) 55 | } 56 | fmt.Println(time.Since(st)) 57 | fmt.Println(utils.Size(v)) 58 | fmt.Println(utils.Size(b)) 59 | } 60 | -------------------------------------------------------------------------------- /testing/data/data_stats.py: -------------------------------------------------------------------------------- 1 | import json, sys 2 | 3 | # // Store the amount of words in the data set 4 | total_words: int = 0 5 | 6 | # // Load the json data 7 | data = json.load(open("data.json")) 8 | 9 | # // Iterate over the json itmes 10 | for item in data: 11 | for k, v in item.items(): 12 | for word in v.strip().split(): 13 | if word.isalnum(): 14 | total_words += 1 15 | 16 | # // Print the results 17 | print(f"Total words: {total_words}") 18 | print(f"Total keys: {len(data)}") 19 | print(f"Data size: {sys.getsizeof(data)} bytes") 20 | -------------------------------------------------------------------------------- /testing/fulltext/clean.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | hermes "github.com/realTristan/hermes" 7 | ) 8 | 9 | func clean() { 10 | var cache *hermes.Cache = hermes.InitCache() 11 | 12 | // Test CleanFT() 13 | var data = map[string]any{ 14 | "name": "tristan", 15 | "age": 17, 16 | } 17 | 18 | // Set data 19 | cache.Set("user_id1", data) 20 | cache.Set("user_id1", data) 21 | cache.Set("user_id2", data) 22 | 23 | // Initialize the FT cache 24 | cache.FTInit(-1, -1, 3) 25 | 26 | // Search for a word in the cache 27 | var result, _ = cache.SearchOneWord(hermes.SearchParams{ 28 | Query: "tristan", 29 | Limit: 100, 30 | Strict: false, 31 | }) 32 | fmt.Println(result) 33 | 34 | // Clean 35 | cache.FTClean() 36 | 37 | // Search for a word in the cache 38 | result, _ = cache.SearchOneWord(hermes.SearchParams{ 39 | Query: "tristan", 40 | Limit: 100, 41 | Strict: false, 42 | }) 43 | fmt.Println(result) 44 | } 45 | -------------------------------------------------------------------------------- /testing/fulltext/delete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | hermes "github.com/realTristan/hermes" 7 | ) 8 | 9 | func delete() { 10 | var cache *hermes.Cache = hermes.InitCache() 11 | 12 | // Initialize the FT cache 13 | cache.FTInit(-1, -1, 3) 14 | 15 | // Test Delete() 16 | var data = map[string]any{ 17 | "name": "tristan", 18 | "age": 17, 19 | } 20 | 21 | // print cache info 22 | fmt.Println(cache.InfoForTesting()) 23 | 24 | // Set data 25 | cache.Set("user_id1", data) 26 | cache.Set("user_id1", data) 27 | cache.Set("user_id2", data) 28 | 29 | // print cache info 30 | fmt.Println(cache.InfoForTesting()) 31 | 32 | // Delete data 33 | cache.Delete("user_id1") 34 | cache.Delete("user_id1") 35 | 36 | // Get data 37 | fmt.Println(cache.Get("user_id1")) 38 | fmt.Println(cache.Get("user_id2")) 39 | 40 | // Exists 41 | fmt.Println(cache.Exists("user_id1")) 42 | fmt.Println(cache.Exists("user_id2")) 43 | 44 | // Length 45 | fmt.Println(cache.Length()) 46 | 47 | // Values 48 | fmt.Println(cache.Values()) 49 | 50 | // Keys 51 | fmt.Println(cache.Keys()) 52 | 53 | // Print the cache info 54 | fmt.Println(cache.InfoForTesting()) 55 | } 56 | -------------------------------------------------------------------------------- /testing/fulltext/ft.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | hermes "github.com/realTristan/hermes" 7 | ) 8 | 9 | func ft() { 10 | var cache *hermes.Cache = hermes.InitCache() 11 | 12 | // Initialize the FT cache 13 | cache.FTInit(-1, -1, 3) 14 | 15 | // print cache info 16 | fmt.Println(cache.InfoForTesting()) 17 | } 18 | -------------------------------------------------------------------------------- /testing/fulltext/insert_key_merge.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | hermes "github.com/realTristan/hermes" 7 | ) 8 | 9 | func insert_key_merge() { 10 | var cache *hermes.Cache = hermes.InitCache() 11 | 12 | // Initialize the FT cache 13 | cache.FTInit(-1, -1, 3) 14 | cache.Set("user_id1", map[string]any{ 15 | "name": cache.WithFT("tristan"), 16 | "age": 17, 17 | }) 18 | cache.Set("user_id2", map[string]any{ 19 | "name": cache.WithFT("tris is cool"), 20 | "age": 17, 21 | }) 22 | 23 | // Search for tris 24 | var result, _ = cache.Search(hermes.SearchParams{ 25 | Query: "tris is", 26 | Limit: 100, 27 | Strict: false, 28 | }) 29 | fmt.Println(result) 30 | 31 | // print cache info 32 | fmt.Println(cache.InfoForTesting()) 33 | } 34 | -------------------------------------------------------------------------------- /testing/fulltext/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | insert_key_merge() 5 | //delete() 6 | //clean() 7 | //ft() 8 | //search() 9 | //set() 10 | } 11 | -------------------------------------------------------------------------------- /testing/fulltext/search.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | hermes "github.com/realTristan/hermes" 7 | ) 8 | 9 | func search() { 10 | var cache *hermes.Cache = hermes.InitCache() 11 | 12 | // Initialize the FT cache 13 | cache.FTInit(-1, -1, 3) 14 | 15 | // print cache info 16 | fmt.Println(cache.InfoForTesting()) 17 | } 18 | -------------------------------------------------------------------------------- /testing/fulltext/set.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | hermes "github.com/realTristan/hermes" 7 | ) 8 | 9 | func set() { 10 | var cache *hermes.Cache = hermes.InitCache() 11 | 12 | // Initialize the FT cache 13 | cache.FTInit(-1, -1, 3) 14 | 15 | // print cache info 16 | fmt.Println(cache.InfoForTesting()) 17 | } 18 | -------------------------------------------------------------------------------- /testing/nocache/speed/speed.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | hermes "github.com/realTristan/hermes/nocache" 11 | ) 12 | 13 | // Results: hermes is about 40x faster than a basic search 14 | func main() { 15 | BasicSearch() 16 | hermesSearch() 17 | } 18 | 19 | // Basic Search 20 | func BasicSearch() { 21 | // read the json data 22 | if data, err := readJson("../../../testing/data/data_array.json"); err != nil { 23 | panic(err) 24 | } else { 25 | var average int64 = 0 26 | for i := 0; i < 100; i++ { 27 | var startTime = time.Now() 28 | // Iterate over the data array 29 | for _, val := range data { 30 | // Iterate over the map 31 | for k, v := range val { 32 | var data map[string]interface{} 33 | if v, ok := v.(map[string]interface{}); !ok { 34 | continue 35 | } else { 36 | data = v 37 | } 38 | // Get the value 39 | var value string = data["$hermes.value"].(string) 40 | 41 | // Check if the value contains the search term 42 | if strings.Contains(strings.ToLower(value), strings.ToLower("computer")) { 43 | var _ = k 44 | } 45 | } 46 | } 47 | average += time.Since(startTime).Nanoseconds() 48 | } 49 | var averageNanos float64 = float64(average) / 100 50 | var averageMillis float64 = averageNanos / 1000000 51 | fmt.Println("\nBasic: Average time is: ", averageNanos, "ns or", averageMillis, "ms") 52 | } 53 | } 54 | 55 | // hermes Search 56 | func hermesSearch() { 57 | // Initialize the cache 58 | var cache, err = hermes.InitWithJson("../../../data/data_array.json", 3) 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | var average int64 = 0 64 | for i := 0; i < 100; i++ { 65 | // Track the start time 66 | var start time.Time = time.Now() 67 | 68 | // Search for a word in the cache 69 | cache.Search(hermes.SearchParams{ 70 | Query: "computer", 71 | Limit: 100, 72 | Strict: false, 73 | }) 74 | 75 | // Print the duration 76 | average += time.Since(start).Nanoseconds() 77 | } 78 | 79 | var averageNanos float32 = float32(average) / 100 80 | var averageMillis float32 = averageNanos / 1000000 81 | fmt.Println("\nhermes: Average time is: ", averageNanos, "ns or", averageMillis, "ms") 82 | } 83 | 84 | // Read a json file 85 | func readJson(file string) ([]map[string]interface{}, error) { 86 | var v []map[string]interface{} = []map[string]interface{}{} 87 | 88 | // Read the json data 89 | if data, err := os.ReadFile(file); err != nil { 90 | return nil, err 91 | } else { 92 | if err := json.Unmarshal(data, &v); err != nil { 93 | return nil, err 94 | } 95 | } 96 | return v, nil 97 | } 98 | -------------------------------------------------------------------------------- /testing/socket/router.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | hermes "github.com/realTristan/hermes" 6 | Socket "github.com/realTristan/hermes/cloud/socket" 7 | ) 8 | 9 | func main() { 10 | // Cache and fiber app 11 | cache := hermes.InitCache() 12 | app := fiber.New() 13 | 14 | // Set the router 15 | Socket.SetRouter(app, cache) 16 | 17 | // Listen on port 3000 18 | app.Listen(":3000") 19 | } 20 | -------------------------------------------------------------------------------- /testing/speed/func_speeds/fnspeeds.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | hermes "github.com/realTristan/hermes" 8 | ) 9 | 10 | func main() { 11 | // Cache 12 | var cache *hermes.Cache = hermes.InitCache() 13 | 14 | /* Initialize the FT cache 15 | if err := cache.InitFTWithJson("../../data/data_hash.json", maxWords, maxSizeBytes, schema); err != nil { 16 | fmt.Println(err) 17 | } 18 | */ 19 | // cache.Info() 20 | 21 | // Initialize the FT cache 22 | if err := cache.FTInit(-1, -1, 3); err != nil { 23 | fmt.Println(err) 24 | } 25 | 26 | // The data for the user_id and user_id2 key 27 | var data = map[string]any{ 28 | "name": cache.WithFT("tristan1"), 29 | "age": 17, 30 | } 31 | var data2 = map[string]any{ 32 | "name": map[string]any{ 33 | "$hermes.full_text": true, 34 | "value": "tristan2", 35 | }, 36 | "age": 17, 37 | } 38 | 39 | // Set the value in the cache 40 | duration("Set", func() { 41 | if err := cache.Set("user_id", data); err != nil { 42 | fmt.Println(err) 43 | } 44 | 45 | if err := cache.Set("user_id2", data2); err != nil { 46 | fmt.Println(err) 47 | } 48 | }) 49 | 50 | // Get the user_id value 51 | duration("Get", func() { 52 | var user = cache.Get("user_id") 53 | fmt.Println(user) 54 | }) 55 | 56 | // Search for a word in the cache 57 | duration("Search", func() { 58 | var result, _ = cache.SearchOneWord(hermes.SearchParams{ 59 | Query: "tristan", 60 | Limit: 100, 61 | Strict: false, 62 | }) 63 | fmt.Println(result) 64 | }) 65 | 66 | // Print all the cache info 67 | //cache.Info() 68 | 69 | /* Reset the FT cache 70 | if err := cache.ResetFT(maxWords, maxSizeBytes, schema); err != nil { 71 | fmt.Println(err) 72 | }*/ 73 | 74 | // Delete the user_id key 75 | duration("Delete", func() { 76 | cache.Delete("user_id") 77 | }) 78 | 79 | // Search for a word in the cache 80 | duration("Search", func() { 81 | var result, _ = cache.SearchOneWord(hermes.SearchParams{ 82 | Query: "tristan", 83 | Limit: 100, 84 | Strict: false, 85 | }) 86 | fmt.Println(result) 87 | }) 88 | 89 | // Print all the cache info 90 | //cache.Info() 91 | } 92 | 93 | // Track the duration of a function 94 | func duration(key string, f func()) { 95 | var start time.Time = time.Now() 96 | f() 97 | fmt.Printf("\nExecution Duration for %s: %s\n", key, time.Since(start)) 98 | } 99 | -------------------------------------------------------------------------------- /testing/speed/search_speeds/searchspeeds.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | hermes "github.com/realTristan/hermes" 11 | ) 12 | 13 | // Results: hermes is about 40x faster than a basic search 14 | func main() { 15 | BasicSearch() 16 | hermesSearch() 17 | } 18 | 19 | // Basic Search 20 | func BasicSearch() { 21 | // read the json data 22 | if data, err := readJson("../../data/data_hash.json"); err != nil { 23 | panic(err) 24 | } else { 25 | var average int64 = 0 26 | for i := 0; i < 100; i++ { 27 | var startTime = time.Now() 28 | // print the data 29 | for _, val := range data { 30 | for k, v := range val { 31 | var data map[string]any 32 | if v, ok := v.(map[string]any); !ok { 33 | continue 34 | } else { 35 | data = v 36 | } 37 | // Get the value 38 | var value string = data["$hermes.value"].(string) 39 | 40 | if strings.Contains(strings.ToLower(value), strings.ToLower("computer")) { 41 | var _ = k 42 | } 43 | } 44 | } 45 | average += time.Since(startTime).Nanoseconds() 46 | } 47 | var ( 48 | averageNanos float64 = float64(average) / 100 49 | averageMillis float64 = averageNanos / 1000000 50 | ) 51 | fmt.Println("Basic: Average time is: ", averageNanos, "ns or", averageMillis, "ms") 52 | } 53 | } 54 | 55 | // hermes Search 56 | func hermesSearch() { 57 | // Initialize the cache 58 | var cache *hermes.Cache = hermes.InitCache() 59 | 60 | // Initialize the FT cache with a json file 61 | cache.FTInitWithJson("../../data/data_hash.json", -1, -1, 3) 62 | var ( 63 | average int64 = 0 64 | total int = 0 65 | ) 66 | for i := 0; i < 100; i++ { 67 | // Track the start time 68 | var ( 69 | start time.Time = time.Now() 70 | 71 | // Search for a word in the cache 72 | res, _ = cache.Search(hermes.SearchParams{ 73 | Query: "computer science", 74 | Limit: 100, 75 | Strict: false, 76 | Schema: map[string]bool{ 77 | "name": true, 78 | "description": true, 79 | "title": true, 80 | }, 81 | }) 82 | ) 83 | 84 | // Print the duration 85 | average += time.Since(start).Nanoseconds() 86 | total += len(res) 87 | } 88 | var ( 89 | averageNanos float64 = float64(average) / 100 90 | averageMillis float64 = averageNanos / 1000000 91 | ) 92 | fmt.Println("hermes: Average time is: ", averageNanos, "ns or", averageMillis, "ms") 93 | fmt.Println("hermes: Results: ", total) 94 | } 95 | 96 | // Read a json file 97 | func readJson(file string) (map[string]map[string]any, error) { 98 | var v map[string]map[string]any = map[string]map[string]any{} 99 | 100 | // Read the json data 101 | if data, err := os.ReadFile(file); err != nil { 102 | return nil, err 103 | } else if err := json.Unmarshal(data, &v); err != nil { 104 | return nil, err 105 | } 106 | return v, nil 107 | } 108 | -------------------------------------------------------------------------------- /utils/buffer.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | ) 7 | 8 | // Size is a function that calculates the real size in memory of a given value by encoding it using the gob package and returning the length of the resulting byte buffer. 9 | // Parameters: 10 | // - v (any): The value to calculate the size of. 11 | // 12 | // Returns: 13 | // - int: The size of the value in memory. 14 | // - error: An error if the encoding fails, or nil if successful. 15 | func Size(v any) (int, error) { 16 | var b *bytes.Buffer = new(bytes.Buffer) 17 | if err := gob.NewEncoder(b).Encode(v); err != nil { 18 | return 0, err 19 | } 20 | return b.Len(), nil 21 | } 22 | -------------------------------------------------------------------------------- /utils/json.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | ) 7 | 8 | // ReadJson is a generic function that reads a JSON file and unmarshals its contents into a provided value of type T. 9 | // Parameters: 10 | // - file (string): The path to the JSON file to read. 11 | // 12 | // Returns: 13 | // - T: The unmarshalled value of type T. 14 | // - error: An error if the file cannot be read or the unmarshalling fails, or nil if successful. 15 | func ReadJson[T any](file string) (T, error) { 16 | var v T 17 | 18 | // Read the json data 19 | if data, err := os.ReadFile(file); err != nil { 20 | return *new(T), err 21 | } else if err := json.Unmarshal(data, &v); err != nil { 22 | return *new(T), err 23 | } 24 | return v, nil 25 | } 26 | -------------------------------------------------------------------------------- /utils/slices.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // SliceContains is a generic function that checks if a given value is present in a given slice of comparable values. 4 | // Parameters: 5 | // - array ([]T): The slice to search for the value. 6 | // - value (T): The value to search for in the slice. 7 | // 8 | // Returns: 9 | // - bool: true if the value is present in the slice, false otherwise. 10 | func SliceContains[T comparable](array []T, value T) bool { 11 | for i := 0; i < len(array); i++ { 12 | if array[i] == value { 13 | return true 14 | } 15 | } 16 | return false 17 | } 18 | -------------------------------------------------------------------------------- /utils/strings.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // IsAlphaNumChar is a function that checks if a given byte is an alphanumeric character. 8 | // Parameters: 9 | // - c (byte): The byte to check. 10 | // 11 | // Returns: 12 | // - bool: true if the byte is an alphanumeric character, false otherwise. 13 | func IsAlphaNumChar(c byte) bool { 14 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') 15 | } 16 | 17 | // IsAlphaNum is a function that checks if a given string consists entirely of alphanumeric characters. 18 | // Parameters: 19 | // - s (string): The string to check. 20 | // 21 | // Returns: 22 | // - bool: true if the string consists entirely of alphanumeric characters, false otherwise. 23 | func IsAlphaNum(s string) bool { 24 | for _, c := range s { 25 | if !IsAlphaNumChar(byte(c)) { 26 | return false 27 | } 28 | } 29 | return true 30 | } 31 | 32 | // TrimNonAlphaNum is a function that removes non-alphanumeric characters from the beginning and end of a given string. 33 | // Parameters: 34 | // - s (string): The string to trim. 35 | // 36 | // Returns: 37 | // - string: The trimmed string. 38 | func TrimNonAlphaNum(s string) string { 39 | if len(s) == 0 { 40 | return s 41 | } 42 | for !IsAlphaNumChar(s[0]) { 43 | if len(s) < 2 { 44 | return "" 45 | } 46 | s = s[1:] 47 | } 48 | for !IsAlphaNumChar(s[len(s)-1]) { 49 | if len(s) < 2 { 50 | return "" 51 | } 52 | s = s[:len(s)-1] 53 | } 54 | return s 55 | } 56 | 57 | // SplitByAlphaNum is a function that splits a given string into a slice of substrings by alphanumeric characters. 58 | // Parameters: 59 | // - s (string): The string to split. 60 | // 61 | // Returns: 62 | // - []string: A slice of substrings split by alphanumeric characters. 63 | func SplitByAlphaNum(s string) []string { 64 | var ( 65 | word string = "" 66 | words []string = make([]string, 0) 67 | ) 68 | for i := 0; i < len(s); i++ { 69 | if s[i] == '-' || s[i] == '.' || IsAlphaNumChar(s[i]) { 70 | word += string(s[i]) 71 | } else { 72 | if len(word) > 0 { 73 | words = append(words, word) 74 | word = "" 75 | } 76 | } 77 | } 78 | if len(word) > 0 { 79 | words = append(words, word) 80 | } 81 | return words 82 | } 83 | 84 | // RemoveDoubleSpaces is a function that removes double spaces from a given string and returns the modified string. 85 | // Parameters: 86 | // - s (string): The string to remove double spaces from. 87 | // 88 | // Returns: 89 | // - string: The modified string with double spaces removed. 90 | func RemoveDoubleSpaces(s string) string { 91 | for strings.Contains(s, " ") { 92 | s = strings.Replace(s, " ", " ", -1) 93 | } 94 | return s 95 | } 96 | 97 | // Contains is a function that checks if a given string contains another string as a substring. 98 | // Parameters: 99 | // - s1 (string): The string to search for the substring. 100 | // - s2 (string): The substring to search for in the string. 101 | // 102 | // Returns: 103 | // - bool: true if the substring is found in the string, false otherwise. 104 | func Contains(s1 string, s2 string) bool { 105 | var ( 106 | s1Len int = len(s1) 107 | s2Len int = len(s2) 108 | ) 109 | switch { 110 | case s1Len == s2Len: 111 | return s1 == s2 112 | case s1Len < s2Len: 113 | return false 114 | } 115 | for i := 0; i < s1Len-s2Len; i++ { 116 | if s1[i] == s2[0] { 117 | if s1[i:i+s2Len] == s2 { 118 | return true 119 | } 120 | } 121 | } 122 | return false 123 | } 124 | -------------------------------------------------------------------------------- /values.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | // Values is a method of the Cache struct that gets all the values in the cache. 4 | // This function is thread-safe. 5 | // 6 | // Returns: 7 | // - A slice of map[string]any representing all the values in the cache. 8 | func (c *Cache) Values() []map[string]any { 9 | c.mutex.RLock() 10 | defer c.mutex.RUnlock() 11 | return c.values() 12 | } 13 | 14 | // values is a method of the Cache struct that returns all the values in the cache. 15 | // This function is not thread-safe, and should only be called from an exported function. 16 | // 17 | // Returns: 18 | // - A slice of map[string]any representing all the values in the cache. 19 | func (c *Cache) values() []map[string]any { 20 | values := make([]map[string]any, 0, len(c.data)) 21 | for _, value := range c.data { 22 | values = append(values, value) 23 | } 24 | return values 25 | } 26 | -------------------------------------------------------------------------------- /website/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /website/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /website/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /website/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Hermes 2 | 3 | ## Documentation 4 | - API 5 | - Request, Response, etc. 6 | 7 | 8 | - Sockets 9 | - Database (in progress) 10 | - Hermes Go 11 | - Hermes Python, Rust, etc. 12 | - Compression/Decompression -------------------------------------------------------------------------------- /website/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "hermescloud": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/hermescloud", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": [ 20 | "zone.js" 21 | ], 22 | "tsConfig": "tsconfig.app.json", 23 | "assets": [ 24 | "src/favicon.ico", 25 | "src/assets" 26 | ], 27 | "styles": [ 28 | "src/styles.css" 29 | ], 30 | "scripts": [] 31 | }, 32 | "configurations": { 33 | "production": { 34 | "budgets": [ 35 | { 36 | "type": "initial", 37 | "maximumWarning": "500kb", 38 | "maximumError": "1mb" 39 | }, 40 | { 41 | "type": "anyComponentStyle", 42 | "maximumWarning": "2kb", 43 | "maximumError": "4kb" 44 | } 45 | ], 46 | "outputHashing": "all" 47 | }, 48 | "development": { 49 | "buildOptimizer": false, 50 | "optimization": false, 51 | "vendorChunk": true, 52 | "extractLicenses": false, 53 | "sourceMap": true, 54 | "namedChunks": true 55 | } 56 | }, 57 | "defaultConfiguration": "production" 58 | }, 59 | "serve": { 60 | "builder": "@angular-devkit/build-angular:dev-server", 61 | "configurations": { 62 | "production": { 63 | "browserTarget": "hermescloud:build:production" 64 | }, 65 | "development": { 66 | "browserTarget": "hermescloud:build:development" 67 | } 68 | }, 69 | "defaultConfiguration": "development" 70 | }, 71 | "extract-i18n": { 72 | "builder": "@angular-devkit/build-angular:extract-i18n", 73 | "options": { 74 | "browserTarget": "hermescloud:build" 75 | } 76 | }, 77 | "test": { 78 | "builder": "@angular-devkit/build-angular:karma", 79 | "options": { 80 | "polyfills": [ 81 | "zone.js", 82 | "zone.js/testing" 83 | ], 84 | "tsConfig": "tsconfig.spec.json", 85 | "assets": [ 86 | "src/favicon.ico", 87 | "src/assets" 88 | ], 89 | "styles": [ 90 | "src/styles.css" 91 | ], 92 | "scripts": [] 93 | } 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hermescloud", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "^16.0.0", 14 | "@angular/common": "^16.0.0", 15 | "@angular/compiler": "^16.0.0", 16 | "@angular/core": "^16.0.0", 17 | "@angular/forms": "^16.0.0", 18 | "@angular/platform-browser": "^16.0.0", 19 | "@angular/platform-browser-dynamic": "^16.0.0", 20 | "@angular/router": "^16.0.0", 21 | "rxjs": "~7.8.0", 22 | "tslib": "^2.3.0", 23 | "zone.js": "~0.13.0" 24 | }, 25 | "devDependencies": { 26 | "@angular-devkit/build-angular": "^16.0.2", 27 | "@angular/cli": "~16.0.2", 28 | "@angular/compiler-cli": "^16.0.0", 29 | "@types/jasmine": "~4.3.0", 30 | "angular-cli-ghpages": "^1.0.6", 31 | "autoprefixer": "^10.4.14", 32 | "jasmine-core": "~4.6.0", 33 | "karma": "~6.4.0", 34 | "karma-chrome-launcher": "~3.2.0", 35 | "karma-coverage": "~2.2.0", 36 | "karma-jasmine": "~5.1.0", 37 | "karma-jasmine-html-reporter": "~2.0.0", 38 | "postcss": "^8.4.23", 39 | "tailwindcss": "^3.3.2", 40 | "typescript": "~5.0.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /website/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | const routes: Routes = []; 5 | 6 | @NgModule({ 7 | imports: [RouterModule.forRoot(routes)], 8 | exports: [RouterModule] 9 | }) 10 | export class AppRoutingModule { } 11 | -------------------------------------------------------------------------------- /website/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 3 | font-size: 14px; 4 | color: #333; 5 | box-sizing: border-box; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | h1, 11 | h2, 12 | h3, 13 | h4, 14 | h5, 15 | h6 { 16 | margin: 8px 0; 17 | } 18 | 19 | p { 20 | margin: 0; 21 | } -------------------------------------------------------------------------------- /website/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |

7 | Full-text cache framework written in Go. 8 |

9 |
10 |
11 |

The speed you need. Execute 12 | lightning fast full-text searches with just a few lines of code. Easily access 13 | your data with caching. Both Socket and API implementations are already 14 | provided. Start now with Go. 15 |

16 |
17 | 18 |
19 | 20 |
21 | 22 | 23 |
24 |

25 | Hermes Go 26 |

27 |
28 |
29 |

Speed made easy. Import Hermes 30 | directly into your Go project without any worry of a network connection. 31 |

32 |
33 |
34 | 35 |
36 | 37 | 38 |
39 |

40 | Hermes Cloud 41 |

42 |
43 |
44 |

Connect with confidence. Install the Hermes 45 | Cloud CLI and immediately begin developing in other languages. Hermes Cloud takes advantage of 46 | sockets to deliver powerful performance. Start now with Python.

47 |
48 |
49 | 50 |
51 |
52 | 56 |
57 | 58 | 59 |
60 |

61 | Find us on Github! 62 |

63 |
64 |
65 |

Transparency leads to success. Worried about 66 | bugs or malicious intent? Then check out our github, everything is all open-sourced! Leave a 67 | star to support the project and share it with your friends! Click the laptop or here to visit.

69 |
70 | 71 | 72 | 73 |
74 | 75 | 76 | -------------------------------------------------------------------------------- /website/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(() => TestBed.configureTestingModule({ 7 | imports: [RouterTestingModule], 8 | declarations: [AppComponent] 9 | })); 10 | 11 | it('should create the app', () => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.componentInstance; 14 | expect(app).toBeTruthy(); 15 | }); 16 | 17 | it(`should have as title 'hermescloud'`, () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app.title).toEqual('hermescloud'); 21 | }); 22 | 23 | it('should render title', () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | fixture.detectChanges(); 26 | const compiled = fixture.nativeElement as HTMLElement; 27 | expect(compiled.querySelector('.content span')?.textContent).toContain('hermescloud app is running!'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /website/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'hermescloud'; 10 | } 11 | -------------------------------------------------------------------------------- /website/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { TerminalComponent } from './terminal/terminal.component'; 7 | import { NavbarComponent } from './navbar/navbar.component'; 8 | import { NavbarButtonComponent } from './navbar-button/navbar-button.component'; 9 | import { CodeExampleComponent } from './code-example/code-example.component'; 10 | import { LaptopComponent } from './laptop/laptop.component'; 11 | 12 | @NgModule({ 13 | declarations: [ 14 | AppComponent, 15 | TerminalComponent, 16 | NavbarComponent, 17 | NavbarButtonComponent, 18 | CodeExampleComponent, 19 | LaptopComponent, 20 | ], 21 | imports: [ 22 | BrowserModule, 23 | AppRoutingModule 24 | ], 25 | providers: [], 26 | bootstrap: [AppComponent] 27 | }) 28 | export class AppModule { } 29 | -------------------------------------------------------------------------------- /website/src/app/code-example/code-example.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/website/src/app/code-example/code-example.component.css -------------------------------------------------------------------------------- /website/src/app/code-example/code-example.component.html: -------------------------------------------------------------------------------- 1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |         func main() {{'{'}}
11 |             // Initialize the cache
12 |             cache := hermes.InitCache()
13 |             cache.FTInit(10, -1, 3)
14 |             
15 |             // Set a value in the cache with the key "user_id"
16 |             cache.Set("user_id", map[string]any {{'{'}} 
17 |                 "name":       cache.WithFT("tristan"),
18 |                 "age":        17,
19 |             {{'}'}})
20 | 
21 |             // Search for the word "tristan"
22 |             result, err := cache.Search(hermes.SearchParams{{'{'}} 
23 |                 Query:  "tristan",
24 |                 Limit:  100,
25 |                 Strict: false,
26 |             {{'}'}})
27 |             fmt.Println(result, err)
28 |         {{'}'}}
29 |     
30 |
31 |
-------------------------------------------------------------------------------- /website/src/app/code-example/code-example.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CodeExampleComponent } from './code-example.component'; 4 | 5 | describe('CodeExampleComponent', () => { 6 | let component: CodeExampleComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [CodeExampleComponent] 12 | }); 13 | fixture = TestBed.createComponent(CodeExampleComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /website/src/app/code-example/code-example.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-code-example', 5 | templateUrl: './code-example.component.html', 6 | styleUrls: ['./code-example.component.css'] 7 | }) 8 | export class CodeExampleComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /website/src/app/laptop/laptop.component.html: -------------------------------------------------------------------------------- 1 | 3 |
4 |
5 | Screen 6 |
7 |
8 |
9 |
10 |
11 |
12 |
-------------------------------------------------------------------------------- /website/src/app/laptop/laptop.component.scss: -------------------------------------------------------------------------------- 1 | .laptop { 2 | position: relative; 3 | max-width: 40rem; 4 | 5 | .laptop_screen { 6 | position: relative; 7 | z-index: 1; 8 | padding: 3%; 9 | border-radius: 2rem; 10 | background: #ecf1f7; 11 | background-image: linear-gradient(to bottom, #333, #111); 12 | box-shadow: 0 .1rem 0 #cfcfcf; 13 | border: 2px solid #ccc; 14 | 15 | img { 16 | display: block; 17 | max-width: 100%; 18 | height: auto; 19 | aspect-ratio: attr(width) / attr(height); 20 | background: #000; 21 | } 22 | } 23 | 24 | .laptop_bottom { 25 | position: relative; 26 | z-index: 1; 27 | margin-right: -7%; 28 | margin-left: -7%; 29 | height: .7rem; 30 | background: #e9eff5; 31 | background-image: linear-gradient(to right, #d2dde9 0%, #f9fcff 15%, #e5ebf2 40%, #e5ebf2 60%, #f9fcff 85%, #d2dde9 100%); 32 | 33 | &::before { 34 | display: block; 35 | margin: 0 auto; 36 | width: 20%; 37 | height: .7rem; 38 | border-radius: 0 0 .2rem .2rem; 39 | background: #f6f9fc; 40 | background-image: linear-gradient(to right, #c3cfdb 0%, #f6f9fc 10%, #f6f9fc 90%, #c3cfdb 100%); 41 | content: " "; 42 | } 43 | } 44 | 45 | .laptop_under { 46 | position: absolute; 47 | top: 100%; 48 | left: 25%; 49 | display: block; 50 | width: 50%; 51 | height: 1.5rem; 52 | background: #e2e8f0; 53 | background-image: linear-gradient(to bottom, #e2e8f0, #bec7d1); 54 | 55 | &::after, 56 | &::before { 57 | position: absolute; 58 | top: 0%; 59 | right: 100%; 60 | bottom: 0; 61 | display: block; 62 | width: 50%; 63 | border-bottom-left-radius: 100%; 64 | background: inherit; 65 | content: " "; 66 | } 67 | 68 | &::after { 69 | right: auto; 70 | left: 100%; 71 | border-bottom-right-radius: 100%; 72 | border-bottom-left-radius: 0; 73 | } 74 | } 75 | 76 | .laptop_shadow { 77 | position: absolute; 78 | right: -10%; 79 | bottom: -2.5rem; 80 | left: -10%; 81 | z-index: 0; 82 | height: 2rem; 83 | background: radial-gradient(ellipse closest-side, #000, transparent); 84 | opacity: 0.5; 85 | } 86 | } -------------------------------------------------------------------------------- /website/src/app/laptop/laptop.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LaptopComponent } from './laptop.component'; 4 | 5 | describe('LaptopComponent', () => { 6 | let component: LaptopComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [LaptopComponent] 12 | }); 13 | fixture = TestBed.createComponent(LaptopComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /website/src/app/laptop/laptop.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-laptop', 5 | templateUrl: './laptop.component.html', 6 | styleUrls: ['./laptop.component.scss'] 7 | }) 8 | export class LaptopComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /website/src/app/navbar-button/navbar-button.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/website/src/app/navbar-button/navbar-button.component.css -------------------------------------------------------------------------------- /website/src/app/navbar-button/navbar-button.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{text}} 3 | -------------------------------------------------------------------------------- /website/src/app/navbar-button/navbar-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NavbarButtonComponent } from './navbar-button.component'; 4 | 5 | describe('NavbarButtonComponent', () => { 6 | let component: NavbarButtonComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [NavbarButtonComponent] 12 | }); 13 | fixture = TestBed.createComponent(NavbarButtonComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /website/src/app/navbar-button/navbar-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-navbar-button', 5 | templateUrl: './navbar-button.component.html', 6 | styleUrls: ['./navbar-button.component.css'] 7 | }) 8 | export class NavbarButtonComponent { 9 | @Input() 10 | text: any; 11 | @Input() 12 | href: any; 13 | } 14 | -------------------------------------------------------------------------------- /website/src/app/navbar/navbar.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/website/src/app/navbar/navbar.component.css -------------------------------------------------------------------------------- /website/src/app/navbar/navbar.component.html: -------------------------------------------------------------------------------- 1 |
2 | logo 3 |

Hermes

4 | 5 |
6 | 7 | 8 | 14 |
-------------------------------------------------------------------------------- /website/src/app/navbar/navbar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NavbarComponent } from './navbar.component'; 4 | 5 | describe('NavbarComponent', () => { 6 | let component: NavbarComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [NavbarComponent] 12 | }); 13 | fixture = TestBed.createComponent(NavbarComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /website/src/app/navbar/navbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-navbar', 5 | templateUrl: './navbar.component.html', 6 | styleUrls: ['./navbar.component.css'] 7 | }) 8 | export class NavbarComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /website/src/app/terminal/terminal.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/website/src/app/terminal/terminal.component.css -------------------------------------------------------------------------------- /website/src/app/terminal/terminal.component.html: -------------------------------------------------------------------------------- 1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | hermes-user:~$ 11 |

12 | {{text}} 13 |
14 |

15 |
16 |
17 |
-------------------------------------------------------------------------------- /website/src/app/terminal/terminal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TerminalComponent } from './terminal.component'; 4 | 5 | describe('TerminalComponent', () => { 6 | let component: TerminalComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [TerminalComponent] 12 | }); 13 | fixture = TestBed.createComponent(TerminalComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /website/src/app/terminal/terminal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-terminal', 5 | templateUrl: './terminal.component.html', 6 | styleUrls: ['./terminal.component.css'] 7 | }) 8 | export class TerminalComponent { 9 | @Input() 10 | text: any; 11 | } 12 | -------------------------------------------------------------------------------- /website/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/website/src/assets/.gitkeep -------------------------------------------------------------------------------- /website/src/assets/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/website/src/assets/images/github.png -------------------------------------------------------------------------------- /website/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/website/src/assets/images/logo.png -------------------------------------------------------------------------------- /website/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realTristan/hermes/ad2f3c2255628af7372056976456e7395e9a7cb3/website/src/favicon.ico -------------------------------------------------------------------------------- /website/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hermes 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | 5 | 6 | platformBrowserDynamic().bootstrapModule(AppModule) 7 | .catch(err => console.error(err)); 8 | -------------------------------------------------------------------------------- /website/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | /* Scroll bar */ 7 | :root::-webkit-scrollbar { width: 1rem; } 8 | :root::-webkit-scrollbar-track { background: transparent; } 9 | :root::-webkit-scrollbar-thumb { background: #0f172a; } 10 | :root::-webkit-scrollbar-thumb:hover { background: #1e293b; } 11 | 12 | body, html { 13 | overflow-x: hidden; 14 | } -------------------------------------------------------------------------------- /website/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./src/**/*.{html,ts}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | screens: { 9 | 'xs': '514px', 10 | 'sm': '640px', 11 | 'md': '768px', 12 | 'lg': '1024px', 13 | 'xl': '1366px', 14 | '2xl': '1536px', 15 | } 16 | }, 17 | plugins: [], 18 | } -------------------------------------------------------------------------------- /website/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "ES2022", 20 | "module": "ES2022", 21 | "useDefineForClassFields": false, 22 | "lib": [ 23 | "ES2022", 24 | "dom" 25 | ] 26 | }, 27 | "angularCompilerOptions": { 28 | "enableI18nLegacyMessageIdFormat": false, 29 | "strictInjectionParameters": true, 30 | "strictInputAccessModifiers": true, 31 | "strictTemplates": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /website/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /withft.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | // WFT is a struct that represents a value to be set in the cache and in the full-text cache. 4 | type WFT struct { 5 | value string 6 | } 7 | 8 | // WithFT is a function that creates a new WFT struct with the specified value. 9 | // 10 | // Parameters: 11 | // - value: A string representing the value to set in the cache and in the full-text cache. 12 | // 13 | // Returns: 14 | // - A WFT struct with the specified value or the initial string 15 | func (cache *Cache) WithFT(value string) *WFT { 16 | return &WFT{value} 17 | } 18 | 19 | func (wft *WFT) Set(value string) { 20 | wft.value = value 21 | } 22 | 23 | func (wft *WFT) Value() string { 24 | return wft.value 25 | } 26 | 27 | func WFTGetValue(value any) string { 28 | if wft, ok := value.(*WFT); ok { 29 | return wft.value 30 | } else if v := WFTGetValueFromMap(value); len(v) > 0 { 31 | return v 32 | } 33 | return "" 34 | } 35 | 36 | // WFTGetValueFromMap is a function that gets the full-text value from a map. 37 | // 38 | // Parameters: 39 | // - value: any representing the value to get the full-text value from. 40 | // 41 | // Returns: 42 | // - A string representing the full-text value, or an empty string if the value is not a map or does not contain the correct keys. 43 | func WFTGetValueFromMap(value any) string { 44 | if _, ok := value.(map[string]any); !ok { 45 | return "" 46 | } 47 | 48 | // Verify that the map has the correct length 49 | var v map[string]any = value.(map[string]any) 50 | if len(v) != 2 { 51 | return "" 52 | } 53 | 54 | // Verify that the map has the correct keys 55 | if ft, ok := v["$hermes.full_text"]; ok { 56 | if ft, ok := ft.(bool); ok && ft { 57 | if v, ok := v["$hermes.value"]; ok { 58 | if v, ok := v.(string); ok { 59 | return v 60 | } 61 | } 62 | } 63 | } 64 | return "" 65 | } 66 | --------------------------------------------------------------------------------