diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..cb93fb5
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,5 @@
+# CODEOWNERS info: https://help.github.com/en/articles/about-code-owners
+# Owners are automatically requested for review for PRs that changes code
+# that they own.
+
+* @hypermodeinc/database
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..e3f9ccf
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,28 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ""
+labels: bug
+assignees: ""
+---
+
+**Describe the bug** A clear and concise description of what the bug is.
+
+**To Reproduce** Steps to reproduce the behavior:
+
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior** A clear and concise description of what you expected to happen.
+
+**Screenshots** If applicable, add screenshots to help explain your problem.
+
+**Environment**
+
+- OS: [e.g. macOS, Windows, Ubuntu]
+- Language [e.g. Go]
+- Version [e.g. v0.xx]
+
+**Additional context** Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..8b81b4b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Modus Community Support
+ url: https://discord.hypermode.com
+ about: Please ask and answer questions here
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..3a3c141
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,17 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ""
+labels: ""
+assignees: ""
+---
+
+**Is your feature request related to a problem? Please describe.** A clear and concise description
+of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like** A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered** A clear and concise description of any alternative
+solutions or features you've considered.
+
+**Additional context** Add any other context or screenshots about the feature request here.
diff --git a/.github/actionlint.yml b/.github/actionlint.yml
new file mode 100644
index 0000000..5484e83
--- /dev/null
+++ b/.github/actionlint.yml
@@ -0,0 +1,5 @@
+self-hosted-runner:
+ # Labels of self-hosted runner in array of string
+ labels:
+ - warp-ubuntu-latest-arm64-2x
+ - warp-ubuntu-latest-arm64-4x
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..76974d6
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,26 @@
+**Description**
+
+Please explain the changes you made here.
+
+**Checklist**
+
+- [ ] Code compiles correctly and linting passes locally
+- [ ] For all _code_ changes, an entry added to the `CHANGELOG.md` file describing and linking to
+ this PR
+- [ ] Tests added for new functionality, or regression tests for bug fixes added as applicable
+- [ ] For public APIs, new features, etc., PR on [docs repo](https://github.com/hypermodeinc/docs)
+ staged and linked here
+
+**Instructions**
+
+- The PR title should follow the [Conventional Commits](https://www.conventionalcommits.org/)
+ syntax, leading with `fix:`, `feat:`, `chore:`, `ci:`, etc.
+- The description should briefly explain what the PR is about. In the case of a bugfix, describe or
+ link to the bug.
+- In the checklist section, check the boxes in that are applicable, using `[x]` syntax.
+ - If not applicable, remove the entire line. Only leave the box unchecked if you intend to come
+ back and check the box later.
+- Delete the `Instructions` line and everything below it, to indicate you have read and are
+ following these instructions. ๐
+
+Thank you for your contribution to modusDB!
diff --git a/.github/renovate.json b/.github/renovate.json
index 77df0c5..6bd88fb 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -1,6 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "extends": [
- "github>hypermodeinc/renovate-config"
- ]
+ "extends": ["github>hypermodeinc/renovate-config"]
}
diff --git a/.github/workflows/ci-go-tests.yaml b/.github/workflows/ci-go-lint.yaml
similarity index 69%
rename from .github/workflows/ci-go-tests.yaml
rename to .github/workflows/ci-go-lint.yaml
index 6fbcc81..be45df1 100644
--- a/.github/workflows/ci-go-tests.yaml
+++ b/.github/workflows/ci-go-lint.yaml
@@ -1,4 +1,4 @@
-name: ci-go-tests
+name: ci-go-lint
on:
pull_request:
@@ -8,16 +8,17 @@ on:
- reopened
- ready_for_review
paths:
- - '**/*.go'
- - '**/go.mod'
+ - "**/*.go"
+ - "**/go.mod"
+ - .github/workflows/*
permissions:
contents: read
actions: write
jobs:
- ci-go-tests:
- runs-on: ubuntu-24.04
+ ci-go-lint:
+ runs-on: warp-ubuntu-latest-arm64-2x
steps:
- uses: actions/checkout@v4
@@ -25,13 +26,10 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
- go-version-file: 'go.mod'
+ go-version-file: go.mod
cache-dependency-path: go.sum
- name: golangci-lint
uses: golangci/golangci-lint-action@v6.1.1
with:
args: --timeout=10m
-
- - name: Run Unit Tests
- run: go test -race -v ./...
diff --git a/.github/workflows/ci-go-load-tests.yaml b/.github/workflows/ci-go-load-tests.yaml
new file mode 100644
index 0000000..d8feb80
--- /dev/null
+++ b/.github/workflows/ci-go-load-tests.yaml
@@ -0,0 +1,33 @@
+name: ci-go-load-tests
+
+on:
+ pull_request:
+ types:
+ - opened
+ - synchronize
+ - reopened
+ - ready_for_review
+ paths:
+ - "**/*.go"
+ - "**/go.mod"
+ - .github/workflows/*
+
+permissions:
+ contents: read
+ actions: write
+
+jobs:
+ ci-go-tests:
+ runs-on: warp-ubuntu-latest-arm64-4x
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version-file: go.mod
+ cache-dependency-path: go.sum
+
+ - name: Run Unit Tests
+ run: go test -race -v ./load_test/...
diff --git a/.github/workflows/ci-go-unit-tests.yaml b/.github/workflows/ci-go-unit-tests.yaml
new file mode 100644
index 0000000..7493c8c
--- /dev/null
+++ b/.github/workflows/ci-go-unit-tests.yaml
@@ -0,0 +1,41 @@
+name: ci-go-unit-tests
+
+on:
+ pull_request:
+ types:
+ - opened
+ - synchronize
+ - reopened
+ - ready_for_review
+ paths:
+ - "**/*.go"
+ - "**/go.mod"
+ - .github/workflows/*
+
+permissions:
+ contents: read
+ actions: write
+
+jobs:
+ ci-go-tests:
+ name: Test ${{ matrix.os }}
+ runs-on:
+ "${{ matrix.os == 'linux' && 'warp-ubuntu-latest-x64-4x' || matrix.os == 'macos' &&
+ 'warp-macos-15-arm64-6x'}}"
+
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [linux, macos]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version-file: go.mod
+ cache-dependency-path: go.sum
+
+ - name: Run Unit Tests
+ run: go test -race -v ./unit_test/...
diff --git a/.gitignore b/.gitignore
index b3d8b55..a63304e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,5 @@ go.work.sum
# env file
.env
+
+cpu_profile.prof
diff --git a/.trunk/.gitignore b/.trunk/.gitignore
new file mode 100644
index 0000000..15966d0
--- /dev/null
+++ b/.trunk/.gitignore
@@ -0,0 +1,9 @@
+*out
+*logs
+*actions
+*notifications
+*tools
+plugins
+user_trunk.yaml
+user.yaml
+tmp
diff --git a/.golangci.yaml b/.trunk/configs/.golangci.yaml
similarity index 100%
rename from .golangci.yaml
rename to .trunk/configs/.golangci.yaml
diff --git a/.trunk/configs/.markdownlint.json b/.trunk/configs/.markdownlint.json
new file mode 100644
index 0000000..449148d
--- /dev/null
+++ b/.trunk/configs/.markdownlint.json
@@ -0,0 +1,8 @@
+{
+ "line-length": { "line_length": 150, "tables": false },
+ "no-inline-html": false,
+ "no-bare-urls": false,
+ "no-space-in-emphasis": false,
+ "no-emphasis-as-heading": false,
+ "first-line-heading": false
+}
diff --git a/.trunk/configs/.markdownlint.yaml b/.trunk/configs/.markdownlint.yaml
new file mode 100644
index 0000000..b40ee9d
--- /dev/null
+++ b/.trunk/configs/.markdownlint.yaml
@@ -0,0 +1,2 @@
+# Prettier friendly markdownlint config (all formatting rules disabled)
+extends: markdownlint/style/prettier
diff --git a/.trunk/configs/.prettierrc b/.trunk/configs/.prettierrc
new file mode 100644
index 0000000..577642c
--- /dev/null
+++ b/.trunk/configs/.prettierrc
@@ -0,0 +1,5 @@
+{
+ "semi": false,
+ "proseWrap": "always",
+ "printWidth": 100
+}
diff --git a/.trunk/configs/.yamllint.yaml b/.trunk/configs/.yamllint.yaml
new file mode 100644
index 0000000..184e251
--- /dev/null
+++ b/.trunk/configs/.yamllint.yaml
@@ -0,0 +1,7 @@
+rules:
+ quoted-strings:
+ required: only-when-needed
+ extra-allowed: ["{|}"]
+ key-duplicates: {}
+ octal-values:
+ forbid-implicit-octal: true
diff --git a/.trunk/configs/ruff.toml b/.trunk/configs/ruff.toml
new file mode 100644
index 0000000..f5a235c
--- /dev/null
+++ b/.trunk/configs/ruff.toml
@@ -0,0 +1,5 @@
+# Generic, formatter-friendly config.
+select = ["B", "D3", "E", "F"]
+
+# Never enforce `E501` (line length violations). This should be handled by formatters.
+ignore = ["E501"]
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
new file mode 100644
index 0000000..ae1945f
--- /dev/null
+++ b/.trunk/trunk.yaml
@@ -0,0 +1,43 @@
+# This file controls the behavior of Trunk: https://docs.trunk.io/cli
+# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml
+
+version: 0.1
+
+cli:
+ version: 1.22.8
+
+# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins)
+plugins:
+ sources:
+ - id: trunk
+ ref: v1.6.6
+ uri: https://github.com/trunk-io/plugins
+
+# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes)
+runtimes:
+ enabled:
+ - go@1.23.3
+ - node@18.20.5
+ - python@3.10.8
+
+# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration)
+lint:
+ enabled:
+ - actionlint@1.7.6
+ - checkov@3.2.352
+ - git-diff-check
+ - gofmt@1.20.4
+ - golangci-lint@1.63.4
+ - markdownlint@0.43.0
+ - osv-scanner@1.9.2
+ - prettier@3.4.2
+ - renovate@39.106.0
+ - trufflehog@3.88.2
+ - yamllint@1.35.1
+
+actions:
+ enabled:
+ - trunk-announce
+ - trunk-check-pre-push
+ - trunk-fmt-pre-commit
+ - trunk-upgrade-available
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..29d4338
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["trunk.io"]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..93ff3ac
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "trunk.io",
+ "editor.trimAutoWhitespace": true,
+ "trunk.autoInit": false
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..993afa0
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,22 @@
+# Changelog
+
+## UNRELEASED
+
+- feat: add readfrom json tag to support reverse edges
+ [#49](https://github.com/hypermodeinc/modusDB/pull/49)
+
+- chore: Refactoring package management [#51](https://github.com/hypermodeinc/modusDB/pull/51)
+
+- fix: alter schema on reverse edge after querying schema
+ [#55](https://github.com/hypermodeinc/modusDB/pull/55)
+
+- feat: update interface to engine and namespace
+ [#57](https://github.com/hypermodeinc/modusDB/pull/57)
+
+- chore: Update dgraph dependency [#62](https://github.com/hypermodeinc/modusDB/pull/62)
+
+## 2025-01-02 - Version 0.1.0
+
+Baseline for the changelog.
+
+See git commit history for changes for this version and prior.
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..614af54
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,113 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our community a
+harassment-free experience for everyone, regardless of age, body size, visible or invisible
+disability, ethnicity, sex characteristics, gender identity and expression, level of experience,
+education, socio-economic status, nationality, personal appearance, race, religion, or sexual
+identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and
+healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our community include:
+
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the
+ experience
+- Focusing on what is best not just for us as individuals, but for the overall community
+
+Examples of unacceptable behavior include:
+
+- The use of sexualized language or imagery, and sexual attention or advances of any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email address, without their
+ explicit permission
+- Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior
+and will take appropriate and fair corrective action in response to any behavior that they deem
+inappropriate, threatening, offensive, or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject comments, commits,
+code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and
+will communicate reasons for moderation decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when an individual is
+officially representing the community in public spaces. Examples of representing our community
+include using an official e-mail address, posting via an official social media account, or acting as
+an appointed representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community
+leaders responsible for enforcement at hello@hypermode.com. All complaints will be reviewed and
+investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the reporter of any
+incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining the consequences for
+any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or
+unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing clarity around the
+nature of the violation and an explanation of why the behavior was inappropriate. A public apology
+may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No interaction with the people
+involved, including unsolicited interaction with those enforcing the Code of Conduct, for a
+specified period of time. This includes avoiding interactions in community spaces as well as
+external channels like social media. Violating these terms may lead to a temporary or permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including sustained inappropriate
+behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public communication with the
+community for a specified period of time. No public or private interaction with the people involved,
+including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this
+period. Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community standards, including
+sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement
+of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..b119d3c
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,73 @@
+# Contributing to modusDB
+
+We're really glad you're here and would love for you to contribute to modusDB! There are a variety
+of ways to make modusDB better, including bug fixes, features, docs, and blog posts (among others).
+Every bit helps ๐
+
+Please help us keep the community safe while working on the project by upholding our
+[Code of Conduct](/CODE_OF_CONDUCT.md) at all times.
+
+Before jumping to a pull request, ensure you've looked at
+[PRs](https://github.com/hypermodeinc/modusdb/pulls) and
+[issues](https://github.com/hypermodeinc/modusdb/issues) (open and closed) for existing work related
+to your idea.
+
+If in doubt or contemplating a larger change, join the
+[Hypermode Discord](https://discord.hypermode.com) and start a discussion in the
+[#modus](https://discord.com/channels/1267579648657850441/1292948253796466730) channel.
+
+## Codebase
+
+The development language of modusDB is Go.
+
+### Development environment
+
+The fastest path to setting up a development environment for modusDB is through VS Code. The repo
+includes a set of configs to set VS Code up automatically.
+
+### Clone the Modus repository
+
+To contribute code, start by forking the Modus repository. In the top-right of the
+[repo](https://github.com/hypermodeinc/modusdb), click **Fork**. Follow the instructions to create a
+fork of the repo in your GitHub workspace.
+
+### Building and running tests
+
+Wherever possible, we use the built-in language capabilities. For example, unit tests can be run
+with:
+
+```bash
+go test ./...
+```
+
+### Opening a pull request
+
+When you're ready, open a pull request against the `main` branch in the modusDB repo. Include a
+clear, detailed description of the changes you've made. Be sure to add and update tests and docs as
+needed.
+
+We do our best to respond to PRs within a few days. If you've not heard back, feel free to ping on
+Discord.
+
+## Other ways to help
+
+Pull requests are awesome, but there are many ways to help.
+
+### Documentation improvements
+
+Modus docs are maintained in a [separate repository](https://github.com/hypermodeinc/docs). Relevant
+updates and issues should be opened in that repo.
+
+### Blogging and presenting your work
+
+Share what you're building with Modus in your preferred medium. We'd love to help amplify your work
+and/or provide feedback, so get in touch if you'd like some help!
+
+### Join the community
+
+There are lots of people building with modusDB who are excited to connect!
+
+- Chat on [Discord](https://discord.hypermode.com)
+- Join the conversation on [X](https://x.com/hypermodeinc)
+- Read the latest posts on the [Blog](https://hypermode.com/blog)
+- Connect with us on [LinkedIn](https://linkedin.com/company/hypermode)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
index 2283ef2..fb31e13 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,98 @@
-ModusDB
-======
+
+
+[](https://github.com/hypermodeinc/modusdb)
+
+[](https://github.com/hypermodeinc/modusdb?tab=Apache-2.0-1-ov-file#readme)
+[](https://discord.gg/NJQ4bJpffF)
+[](https://github.com/hypermodeinc/modusdb/stargazers)
+[](https://github.com/hypermodeinc/modusdb/commits/main/)
+
+
+
+
+ Docs
+ ยท
+ Discord
+
+
+**ModusDB is a high-performance, transactional database system.** It's designed to be type-first,
+schema-agnostic, and portable. ModusDB provides object-oriented APIs that makes it simple to build
+new apps, paired with support for advanced use cases through the Dgraph Query Language (DQL). A
+dynamic schema allows for natural relations to be expressed in your data with performance that
+scales with your use case.
+
+ModusDB is available as a Go package for running in-process, providing low-latency reads, writes,
+and vector searches. Weโve made trade-offs to prioritize speed and simplicity.
+
+The [modus framework](https://github.com/hypermodeinc/modus) is optimized for apps that require
+sub-second response times. ModusDB augments polyglot functions with simple to use data and vector
+storage. When paired together, you can build a complete AI semantic search or retrieval-augmented
+generation (RAG) feature with a single framework.
+
+## Quickstart
+
+```go
+package main
+
+import (
+ "github.com/hypermodeinc/modusdb"
+)
+
+type User struct {
+ Gid uint64 `json:"gid,omitempty"`
+ Id string `json:"id,omitempty" db:"constraint=unique"`
+ Name string `json:"name,omitempty"`
+ Age int `json:"age,omitempty"`
+}
+
+func main() {
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig("/local/modusdb"))
+ if err != nil {
+ panic(err)
+ }
+ defer engine.Close()
+
+ gid, user, err := modusdb.Upsert(ns, User{
+ Id: "123",
+ Name: "A",
+ Age: 10,
+ })
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(user)
+
+ _, queriedUser, err := modusdb.Get[User](ns, gid)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(queriedUser)
+
+ _, _, err = modusdb.Delete[User](ns, gid)
+ if err != nil {
+ panic(err)
+ }
+}
+```
+
+## Open Source
+
+The modus framework, including modusDB, is developed by [Hypermode](https://hypermode.com/) as an
+open-source project, integral but independent from Hypermode.
+
+We welcome external contributions. See the [CONTRIBUTING.md](./CONTRIBUTING.md) file if you would
+like to get involved.
+
+Modus and its components are Copyright 2025 Hypermode Inc., and licensed under the terms of the
+Apache License, Version 2.0. See the [LICENSE](./LICENSE) file for a complete copy of the license.
+If you have any questions about modus licensing, or need an alternate license or other arrangement,
+please contact us at .
+
+## Acknowledgements
+
+ModusDB builds heavily upon packages from the open source projects of
+[Dgraph](https://github.com/hypermodeinc/dgraph) (graph query processing and transaction
+management), [Badger](https://github.com/dgraph-io/badger) (data storage), and
+[Ristretto](https://github.com/dgraph-io/ristretto) (cache). We expect the architecture and
+implementations of modusDB and Dgraph to expand in differentiation over time as the projects
+optimize for different core use cases, while maintaining Dgraph Query Language (DQL) compatibility.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..65d28b6
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,10 @@
+# Reporting Security Concerns
+
+We take the security of Modus very seriously. If you believe you have found a security vulnerability
+in Modus, we encourage you to let us know right away.
+
+We will investigate all legitimate reports and do our best to quickly fix the problem. Please report
+any issues or vulnerabilities via Github Security Advisories instead of posting a public issue in
+GitHub. You can also send security communications to security@hypermode.com.
+
+Please include the version identifier and details on how the vulnerability can be exploited.
diff --git a/api.go b/api.go
new file mode 100644
index 0000000..ae6dcbb
--- /dev/null
+++ b/api.go
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package modusdb
+
+import (
+ "fmt"
+
+ "github.com/hypermodeinc/dgraph/v24/dql"
+ "github.com/hypermodeinc/dgraph/v24/schema"
+ "github.com/hypermodeinc/modusdb/api/apiutils"
+ "github.com/hypermodeinc/modusdb/api/structreflect"
+)
+
+func Create[T any](engine *Engine, object T, nsId ...uint64) (uint64, T, error) {
+ engine.mutex.Lock()
+ defer engine.mutex.Unlock()
+ if len(nsId) > 1 {
+ return 0, object, fmt.Errorf("only one namespace is allowed")
+ }
+ ctx, ns, err := getDefaultNamespace(engine, nsId...)
+ if err != nil {
+ return 0, object, err
+ }
+
+ gid, err := engine.z.nextUID()
+ if err != nil {
+ return 0, object, err
+ }
+
+ dms := make([]*dql.Mutation, 0)
+ sch := &schema.ParsedSchema{}
+ err = generateSetDqlMutationsAndSchema[T](ctx, ns, object, gid, &dms, sch)
+ if err != nil {
+ return 0, object, err
+ }
+
+ err = engine.alterSchemaWithParsed(ctx, sch)
+ if err != nil {
+ return 0, object, err
+ }
+
+ err = applyDqlMutations(ctx, engine, dms)
+ if err != nil {
+ return 0, object, err
+ }
+
+ return getByGid[T](ctx, ns, gid)
+}
+
+func Upsert[T any](engine *Engine, object T, nsId ...uint64) (uint64, T, bool, error) {
+
+ var wasFound bool
+ engine.mutex.Lock()
+ defer engine.mutex.Unlock()
+ if len(nsId) > 1 {
+ return 0, object, false, fmt.Errorf("only one namespace is allowed")
+ }
+
+ ctx, ns, err := getDefaultNamespace(engine, nsId...)
+ if err != nil {
+ return 0, object, false, err
+ }
+
+ gid, cfKeyValue, err := structreflect.GetUniqueConstraint[T](object)
+ if err != nil {
+ return 0, object, false, err
+ }
+ var cf *ConstrainedField
+ if cfKeyValue != nil {
+ cf = &ConstrainedField{
+ Key: cfKeyValue.Key(),
+ Value: cfKeyValue.Value(),
+ }
+ }
+
+ dms := make([]*dql.Mutation, 0)
+ sch := &schema.ParsedSchema{}
+ err = generateSetDqlMutationsAndSchema[T](ctx, ns, object, gid, &dms, sch)
+ if err != nil {
+ return 0, object, false, err
+ }
+
+ err = ns.engine.alterSchemaWithParsed(ctx, sch)
+ if err != nil {
+ return 0, object, false, err
+ }
+
+ if gid != 0 || cf != nil {
+ gid, err = getExistingObject[T](ctx, ns, gid, cf, object)
+ if err != nil && err != apiutils.ErrNoObjFound {
+ return 0, object, false, err
+ }
+ wasFound = err == nil
+ }
+
+ if gid == 0 {
+ gid, err = engine.z.nextUID()
+ if err != nil {
+ return 0, object, false, err
+ }
+ }
+
+ dms = make([]*dql.Mutation, 0)
+ err = generateSetDqlMutationsAndSchema[T](ctx, ns, object, gid, &dms, sch)
+ if err != nil {
+ return 0, object, false, err
+ }
+
+ err = applyDqlMutations(ctx, engine, dms)
+ if err != nil {
+ return 0, object, false, err
+ }
+
+ gid, object, err = getByGid[T](ctx, ns, gid)
+ if err != nil {
+ return 0, object, false, err
+ }
+
+ return gid, object, wasFound, nil
+}
+
+func Get[T any, R UniqueField](engine *Engine, uniqueField R, nsId ...uint64) (uint64, T, error) {
+ engine.mutex.Lock()
+ defer engine.mutex.Unlock()
+ var obj T
+ if len(nsId) > 1 {
+ return 0, obj, fmt.Errorf("only one namespace is allowed")
+ }
+ ctx, ns, err := getDefaultNamespace(engine, nsId...)
+ if err != nil {
+ return 0, obj, err
+ }
+ if uid, ok := any(uniqueField).(uint64); ok {
+ return getByGid[T](ctx, ns, uid)
+ }
+
+ if cf, ok := any(uniqueField).(ConstrainedField); ok {
+ return getByConstrainedField[T](ctx, ns, cf)
+ }
+
+ return 0, obj, fmt.Errorf("invalid unique field type")
+}
+
+func Query[T any](engine *Engine, queryParams QueryParams, nsId ...uint64) ([]uint64, []T, error) {
+ engine.mutex.Lock()
+ defer engine.mutex.Unlock()
+ if len(nsId) > 1 {
+ return nil, nil, fmt.Errorf("only one namespace is allowed")
+ }
+ ctx, ns, err := getDefaultNamespace(engine, nsId...)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return executeQuery[T](ctx, ns, queryParams, true)
+}
+
+func Delete[T any, R UniqueField](engine *Engine, uniqueField R, nsId ...uint64) (uint64, T, error) {
+ engine.mutex.Lock()
+ defer engine.mutex.Unlock()
+ var zeroObj T
+ if len(nsId) > 1 {
+ return 0, zeroObj, fmt.Errorf("only one namespace is allowed")
+ }
+ ctx, ns, err := getDefaultNamespace(engine, nsId...)
+ if err != nil {
+ return 0, zeroObj, err
+ }
+ if uid, ok := any(uniqueField).(uint64); ok {
+ uid, obj, err := getByGid[T](ctx, ns, uid)
+ if err != nil {
+ return 0, zeroObj, err
+ }
+
+ dms := generateDeleteDqlMutations(ns, uid)
+
+ err = applyDqlMutations(ctx, engine, dms)
+ if err != nil {
+ return 0, zeroObj, err
+ }
+
+ return uid, obj, nil
+ }
+
+ if cf, ok := any(uniqueField).(ConstrainedField); ok {
+ uid, obj, err := getByConstrainedField[T](ctx, ns, cf)
+ if err != nil {
+ return 0, zeroObj, err
+ }
+
+ dms := generateDeleteDqlMutations(ns, uid)
+
+ err = applyDqlMutations(ctx, engine, dms)
+ if err != nil {
+ return 0, zeroObj, err
+ }
+
+ return uid, obj, nil
+ }
+
+ return 0, zeroObj, fmt.Errorf("invalid unique field type")
+}
diff --git a/api/apiutils/apiutils.go b/api/apiutils/apiutils.go
new file mode 100644
index 0000000..4769792
--- /dev/null
+++ b/api/apiutils/apiutils.go
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package apiutils
+
+import (
+ "fmt"
+
+ "github.com/hypermodeinc/dgraph/v24/x"
+)
+
+var (
+ ErrNoObjFound = fmt.Errorf("no object found")
+ NoUniqueConstr = "unique constraint not defined for any field on type %s"
+)
+
+func GetPredicateName(typeName, fieldName string) string {
+ return fmt.Sprint(typeName, ".", fieldName)
+}
+
+func AddNamespace(ns uint64, pred string) string {
+ return x.NamespaceAttr(ns, pred)
+}
diff --git a/api/dgraphtypes/dgraphtypes.go b/api/dgraphtypes/dgraphtypes.go
new file mode 100644
index 0000000..6906623
--- /dev/null
+++ b/api/dgraphtypes/dgraphtypes.go
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dgraphtypes
+
+import (
+ "encoding/binary"
+ "fmt"
+ "time"
+
+ "github.com/dgraph-io/dgo/v240/protos/api"
+ "github.com/hypermodeinc/dgraph/v24/protos/pb"
+ "github.com/hypermodeinc/dgraph/v24/types"
+ "github.com/hypermodeinc/modusdb/api/structreflect"
+ "github.com/twpayne/go-geom"
+ "github.com/twpayne/go-geom/encoding/wkb"
+)
+
+func addIndex(u *pb.SchemaUpdate, index string, uniqueConstraintExists bool) bool {
+ u.Directive = pb.SchemaUpdate_INDEX
+ switch index {
+ case "exact":
+ u.Tokenizer = []string{"exact"}
+ case "term":
+ u.Tokenizer = []string{"term"}
+ case "hash":
+ u.Tokenizer = []string{"hash"}
+ case "unique":
+ u.Tokenizer = []string{"exact"}
+ u.Unique = true
+ u.Upsert = true
+ uniqueConstraintExists = true
+ case "fulltext":
+ u.Tokenizer = []string{"fulltext"}
+ case "trigram":
+ u.Tokenizer = []string{"trigram"}
+ case "vector":
+ u.IndexSpecs = []*pb.VectorIndexSpec{
+ {
+ Name: "hnsw",
+ Options: []*pb.OptionPair{
+ {
+ Key: "metric",
+ Value: "cosine",
+ },
+ },
+ },
+ }
+ default:
+ return uniqueConstraintExists
+ }
+ return uniqueConstraintExists
+}
+
+func ValueToPosting_ValType(v any) (pb.Posting_ValType, error) {
+ switch v.(type) {
+ case string:
+ return pb.Posting_STRING, nil
+ case int, int8, int16, int32, int64, uint, uint8, uint16, uint32:
+ return pb.Posting_INT, nil
+ case uint64:
+ return pb.Posting_UID, nil
+ case bool:
+ return pb.Posting_BOOL, nil
+ case float32, float64:
+ return pb.Posting_FLOAT, nil
+ case []byte:
+ return pb.Posting_BINARY, nil
+ case time.Time:
+ return pb.Posting_DATETIME, nil
+ case geom.Point:
+ return pb.Posting_GEO, nil
+ case []float32, []float64:
+ return pb.Posting_VFLOAT, nil
+ default:
+ return pb.Posting_DEFAULT, fmt.Errorf("unsupported type %T", v)
+ }
+}
+
+func ValueToApiVal(v any) (*api.Value, error) {
+ switch val := v.(type) {
+ case string:
+ return &api.Value{Val: &api.Value_StrVal{StrVal: val}}, nil
+ case int:
+ return &api.Value{Val: &api.Value_IntVal{IntVal: int64(val)}}, nil
+ case int8:
+ return &api.Value{Val: &api.Value_IntVal{IntVal: int64(val)}}, nil
+ case int16:
+ return &api.Value{Val: &api.Value_IntVal{IntVal: int64(val)}}, nil
+ case int32:
+ return &api.Value{Val: &api.Value_IntVal{IntVal: int64(val)}}, nil
+ case int64:
+ return &api.Value{Val: &api.Value_IntVal{IntVal: val}}, nil
+ case uint8:
+ return &api.Value{Val: &api.Value_IntVal{IntVal: int64(val)}}, nil
+ case uint16:
+ return &api.Value{Val: &api.Value_IntVal{IntVal: int64(val)}}, nil
+ case uint32:
+ return &api.Value{Val: &api.Value_IntVal{IntVal: int64(val)}}, nil
+ case uint64:
+ return &api.Value{Val: &api.Value_UidVal{UidVal: val}}, nil
+ case bool:
+ return &api.Value{Val: &api.Value_BoolVal{BoolVal: val}}, nil
+ case float32:
+ return &api.Value{Val: &api.Value_DoubleVal{DoubleVal: float64(val)}}, nil
+ case float64:
+ return &api.Value{Val: &api.Value_DoubleVal{DoubleVal: val}}, nil
+ case []float32:
+ return &api.Value{Val: &api.Value_Vfloat32Val{
+ Vfloat32Val: types.FloatArrayAsBytes(val)}}, nil
+ case []float64:
+ float32Slice := make([]float32, len(val))
+ for i, v := range val {
+ float32Slice[i] = float32(v)
+ }
+ return &api.Value{Val: &api.Value_Vfloat32Val{
+ Vfloat32Val: types.FloatArrayAsBytes(float32Slice)}}, nil
+ case []byte:
+ return &api.Value{Val: &api.Value_BytesVal{BytesVal: val}}, nil
+ case time.Time:
+ bytes, err := val.MarshalBinary()
+ if err != nil {
+ return nil, err
+ }
+ return &api.Value{Val: &api.Value_DateVal{DateVal: bytes}}, nil
+ case geom.Point:
+ bytes, err := wkb.Marshal(&val, binary.LittleEndian)
+ if err != nil {
+ return nil, err
+ }
+ return &api.Value{Val: &api.Value_GeoVal{GeoVal: bytes}}, nil
+ case uint:
+ return &api.Value{Val: &api.Value_DefaultVal{DefaultVal: fmt.Sprint(v)}}, nil
+ default:
+ return nil, fmt.Errorf("unsupported type %T", v)
+ }
+}
+
+func HandleConstraints(u *pb.SchemaUpdate, jsonToDbTags map[string]*structreflect.DbTag, jsonName string,
+ valType pb.Posting_ValType, uniqueConstraintFound bool) (bool, error) {
+ if jsonToDbTags[jsonName] == nil {
+ return uniqueConstraintFound, nil
+ }
+
+ constraint := jsonToDbTags[jsonName].Constraint
+ if constraint == "vector" && valType != pb.Posting_VFLOAT {
+ return false, fmt.Errorf("vector index can only be applied to []float values")
+ }
+
+ return addIndex(u, constraint, uniqueConstraintFound), nil
+}
diff --git a/api/mutations/mutations.go b/api/mutations/mutations.go
new file mode 100644
index 0000000..0da794c
--- /dev/null
+++ b/api/mutations/mutations.go
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package mutations
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/dgraph-io/dgo/v240/protos/api"
+ "github.com/hypermodeinc/dgraph/v24/protos/pb"
+ "github.com/hypermodeinc/dgraph/v24/schema"
+ "github.com/hypermodeinc/modusdb/api/apiutils"
+ "github.com/hypermodeinc/modusdb/api/dgraphtypes"
+)
+
+func HandleReverseEdge(jsonName string, value reflect.Type, nsId uint64, sch *schema.ParsedSchema,
+ reverseEdgeStr string) error {
+ if reverseEdgeStr == "" {
+ return nil
+ }
+
+ if value.Kind() != reflect.Slice || value.Elem().Kind() != reflect.Struct {
+ return fmt.Errorf("reverse edge %s should be a slice of structs", jsonName)
+ }
+
+ typeName := strings.Split(reverseEdgeStr, ".")[0]
+ u := &pb.SchemaUpdate{
+ Predicate: apiutils.AddNamespace(nsId, reverseEdgeStr),
+ ValueType: pb.Posting_UID,
+ Directive: pb.SchemaUpdate_REVERSE,
+ }
+
+ sch.Preds = append(sch.Preds, u)
+ sch.Types = append(sch.Types, &pb.TypeUpdate{
+ TypeName: apiutils.AddNamespace(nsId, typeName),
+ Fields: []*pb.SchemaUpdate{u},
+ })
+ return nil
+}
+
+func CreateNQuadAndSchema(value any, gid uint64, jsonName string, t reflect.Type,
+ nsId uint64) (*api.NQuad, *pb.SchemaUpdate, error) {
+ valType, err := dgraphtypes.ValueToPosting_ValType(value)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ val, err := dgraphtypes.ValueToApiVal(value)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ nquad := &api.NQuad{
+ Namespace: nsId,
+ Subject: fmt.Sprint(gid),
+ Predicate: apiutils.GetPredicateName(t.Name(), jsonName),
+ }
+
+ u := &pb.SchemaUpdate{
+ Predicate: apiutils.AddNamespace(nsId, apiutils.GetPredicateName(t.Name(), jsonName)),
+ ValueType: valType,
+ }
+
+ if valType == pb.Posting_UID {
+ nquad.ObjectId = fmt.Sprint(value)
+ u.Directive = pb.SchemaUpdate_REVERSE
+ } else {
+ nquad.ObjectValue = val
+ }
+
+ return nquad, u, nil
+}
diff --git a/api/querygen/dql_query.go b/api/querygen/dql_query.go
new file mode 100644
index 0000000..0a5a797
--- /dev/null
+++ b/api/querygen/dql_query.go
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package querygen
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+type SchemaField struct {
+ Name string `json:"name"`
+}
+
+type SchemaType struct {
+ Name string `json:"name,omitempty"`
+ Fields []SchemaField `json:"fields,omitempty"`
+}
+
+type SchemaResponse struct {
+ Types []SchemaType `json:"types,omitempty"`
+}
+
+type QueryFunc func() string
+
+const (
+ ObjQuery = `
+ {
+ obj(func: %s) {
+ gid: uid
+ expand(_all_) {
+ gid: uid
+ expand(_all_)
+ dgraph.type
+ }
+ dgraph.type
+ %s
+ }
+ }
+ `
+
+ ObjsQuery = `
+ {
+ objs(func: type("%s")%s) @filter(%s) {
+ gid: uid
+ expand(_all_) {
+ gid: uid
+ expand(_all_)
+ dgraph.type
+ }
+ dgraph.type
+ %s
+ }
+ }
+ `
+
+ ReverseEdgeQuery = `
+ %s: ~%s {
+ gid: uid
+ expand(_all_)
+ dgraph.type
+ }
+ `
+
+ SchemaQuery = `
+ schema{}
+ `
+
+ FuncUid = `uid(%d)`
+ FuncEq = `eq(%s, %s)`
+ FuncSimilarTo = `similar_to(%s, %d, "[%s]")`
+ FuncAllOfTerms = `allofterms(%s, "%s")`
+ FuncAnyOfTerms = `anyofterms(%s, "%s")`
+ FuncAllOfText = `alloftext(%s, "%s")`
+ FuncAnyOfText = `anyoftext(%s, "%s")`
+ FuncRegExp = `regexp(%s, /%s/)`
+ FuncLe = `le(%s, %s)`
+ FuncGe = `ge(%s, %s)`
+ FuncGt = `gt(%s, %s)`
+ FuncLt = `lt(%s, %s)`
+)
+
+func BuildUidQuery(gid uint64) QueryFunc {
+ return func() string {
+ return fmt.Sprintf(FuncUid, gid)
+ }
+}
+
+func BuildEqQuery(key string, value any) QueryFunc {
+ return func() string {
+ return fmt.Sprintf(FuncEq, key, value)
+ }
+}
+
+func BuildSimilarToQuery(indexAttr string, topK int64, vec []float32) QueryFunc {
+ vecStrArr := make([]string, len(vec))
+ for i := range vec {
+ vecStrArr[i] = strconv.FormatFloat(float64(vec[i]), 'f', -1, 32)
+ }
+ vecStr := strings.Join(vecStrArr, ",")
+ return func() string {
+ return fmt.Sprintf(FuncSimilarTo, indexAttr, topK, vecStr)
+ }
+}
+
+func BuildAllOfTermsQuery(attr string, terms string) QueryFunc {
+ return func() string {
+ return fmt.Sprintf(FuncAllOfTerms, attr, terms)
+ }
+}
+
+func BuildAnyOfTermsQuery(attr string, terms string) QueryFunc {
+ return func() string {
+ return fmt.Sprintf(FuncAnyOfTerms, attr, terms)
+ }
+}
+
+func BuildAllOfTextQuery(attr, text string) QueryFunc {
+ return func() string {
+ return fmt.Sprintf(FuncAllOfText, attr, text)
+ }
+}
+
+func BuildAnyOfTextQuery(attr, text string) QueryFunc {
+ return func() string {
+ return fmt.Sprintf(FuncAnyOfText, attr, text)
+ }
+}
+
+func BuildRegExpQuery(attr, pattern string) QueryFunc {
+ return func() string {
+ return fmt.Sprintf(FuncRegExp, attr, pattern)
+ }
+}
+
+func BuildLeQuery(attr, value string) QueryFunc {
+ return func() string {
+ return fmt.Sprintf(FuncLe, attr, value)
+ }
+}
+
+func BuildGeQuery(attr, value string) QueryFunc {
+ return func() string {
+ return fmt.Sprintf(FuncGe, attr, value)
+ }
+}
+
+func BuildGtQuery(attr, value string) QueryFunc {
+ return func() string {
+ return fmt.Sprintf(FuncGt, attr, value)
+ }
+}
+
+func BuildLtQuery(attr, value string) QueryFunc {
+ return func() string {
+ return fmt.Sprintf(FuncLt, attr, value)
+ }
+}
+
+func And(qfs ...QueryFunc) QueryFunc {
+ return func() string {
+ qs := make([]string, len(qfs))
+ for i, qf := range qfs {
+ qs[i] = qf()
+ }
+ return strings.Join(qs, " AND ")
+ }
+}
+
+func Or(qfs ...QueryFunc) QueryFunc {
+ return func() string {
+ qs := make([]string, len(qfs))
+ for i, qf := range qfs {
+ qs[i] = qf()
+ }
+ return strings.Join(qs, " OR ")
+ }
+}
+
+func Not(qf QueryFunc) QueryFunc {
+ return func() string {
+ return "NOT " + qf()
+ }
+}
+
+func FormatObjQuery(qf QueryFunc, extraFields string) string {
+ return fmt.Sprintf(ObjQuery, qf(), extraFields)
+}
+
+func FormatObjsQuery(typeName string, qf QueryFunc, paginationAndSorting string, extraFields string) string {
+ return fmt.Sprintf(ObjsQuery, typeName, paginationAndSorting, qf(), extraFields)
+}
diff --git a/api/structreflect/keyval.go b/api/structreflect/keyval.go
new file mode 100644
index 0000000..bfa5bdb
--- /dev/null
+++ b/api/structreflect/keyval.go
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package structreflect
+
+type keyValue struct {
+ key string
+ value any
+}
+
+func (kv *keyValue) Key() string {
+ return kv.key
+}
+
+func (kv *keyValue) Value() any {
+ return kv.value
+}
diff --git a/api/structreflect/structreflect.go b/api/structreflect/structreflect.go
new file mode 100644
index 0000000..fa3b5fd
--- /dev/null
+++ b/api/structreflect/structreflect.go
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package structreflect
+
+import (
+ "fmt"
+ "reflect"
+ "strconv"
+
+ "github.com/hypermodeinc/modusdb/api/apiutils"
+)
+
+func GetFieldTags(t reflect.Type) (*TagMaps, error) {
+ tags := &TagMaps{
+ FieldToJson: make(map[string]string),
+ JsonToDb: make(map[string]*DbTag),
+ JsonToReverseEdge: make(map[string]string),
+ }
+
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+
+ jsonName, err := parseJsonTag(field)
+ if err != nil {
+ return nil, err
+ }
+ tags.FieldToJson[field.Name] = jsonName
+
+ if reverseEdge, err := parseReverseEdgeTag(field); err != nil {
+ return nil, err
+ } else if reverseEdge != "" {
+ tags.JsonToReverseEdge[jsonName] = reverseEdge
+ }
+
+ if dbTag := parseDbTag(field); dbTag != nil {
+ tags.JsonToDb[jsonName] = dbTag
+ }
+ }
+
+ return tags, nil
+}
+
+func CreateDynamicStruct(t reflect.Type, fieldToJson map[string]string, depth int) reflect.Type {
+ fields := make([]reflect.StructField, 0, len(fieldToJson))
+ for fieldName, jsonName := range fieldToJson {
+ field, _ := t.FieldByName(fieldName)
+ if fieldName != "Gid" {
+ if field.Type.Kind() == reflect.Struct {
+ if depth <= 1 {
+ tagMaps, _ := GetFieldTags(field.Type)
+ nestedType := CreateDynamicStruct(field.Type, tagMaps.FieldToJson, depth+1)
+ fields = append(fields, reflect.StructField{
+ Name: field.Name,
+ Type: nestedType,
+ Tag: reflect.StructTag(fmt.Sprintf(`json:"%s.%s"`, t.Name(), jsonName)),
+ })
+ }
+ } else if field.Type.Kind() == reflect.Ptr &&
+ field.Type.Elem().Kind() == reflect.Struct {
+ tagMaps, _ := GetFieldTags(field.Type.Elem())
+ nestedType := CreateDynamicStruct(field.Type.Elem(), tagMaps.FieldToJson, depth+1)
+ fields = append(fields, reflect.StructField{
+ Name: field.Name,
+ Type: reflect.PointerTo(nestedType),
+ Tag: reflect.StructTag(fmt.Sprintf(`json:"%s.%s"`, t.Name(), jsonName)),
+ })
+ } else if field.Type.Kind() == reflect.Slice &&
+ field.Type.Elem().Kind() == reflect.Struct {
+ tagMaps, _ := GetFieldTags(field.Type.Elem())
+ nestedType := CreateDynamicStruct(field.Type.Elem(), tagMaps.FieldToJson, depth+1)
+ fields = append(fields, reflect.StructField{
+ Name: field.Name,
+ Type: reflect.SliceOf(nestedType),
+ Tag: reflect.StructTag(fmt.Sprintf(`json:"%s.%s"`, t.Name(), jsonName)),
+ })
+ } else {
+ fields = append(fields, reflect.StructField{
+ Name: field.Name,
+ Type: field.Type,
+ Tag: reflect.StructTag(fmt.Sprintf(`json:"%s.%s"`, t.Name(), jsonName)),
+ })
+ }
+
+ }
+ }
+ fields = append(fields, reflect.StructField{
+ Name: "Gid",
+ Type: reflect.TypeOf(""),
+ Tag: reflect.StructTag(`json:"gid"`),
+ }, reflect.StructField{
+ Name: "DgraphType",
+ Type: reflect.TypeOf([]string{}),
+ Tag: reflect.StructTag(`json:"dgraph.type"`),
+ })
+ return reflect.StructOf(fields)
+}
+
+func MapDynamicToFinal(dynamic any, final any, isNested bool) (uint64, error) {
+ vFinal := reflect.ValueOf(final).Elem()
+ vDynamic := reflect.ValueOf(dynamic).Elem()
+
+ gid := uint64(0)
+
+ for i := 0; i < vDynamic.NumField(); i++ {
+
+ dynamicField := vDynamic.Type().Field(i)
+ dynamicFieldType := dynamicField.Type
+ dynamicValue := vDynamic.Field(i)
+
+ var finalField reflect.Value
+ if dynamicField.Name == "Gid" {
+ finalField = vFinal.FieldByName("Gid")
+ gidStr := dynamicValue.String()
+ gid, _ = strconv.ParseUint(gidStr, 0, 64)
+ } else if dynamicField.Name == "DgraphType" {
+ fieldArrInterface := dynamicValue.Interface()
+ fieldArr, ok := fieldArrInterface.([]string)
+ if ok {
+ if len(fieldArr) == 0 {
+ if !isNested {
+ return 0, apiutils.ErrNoObjFound
+ } else {
+ continue
+ }
+ }
+ } else {
+ return 0, fmt.Errorf("DgraphType field should be an array of strings")
+ }
+ } else {
+ finalField = vFinal.FieldByName(dynamicField.Name)
+ }
+ if dynamicFieldType.Kind() == reflect.Struct {
+ _, err := MapDynamicToFinal(dynamicValue.Addr().Interface(), finalField.Addr().Interface(), true)
+ if err != nil {
+ return 0, err
+ }
+ } else if dynamicFieldType.Kind() == reflect.Ptr &&
+ dynamicFieldType.Elem().Kind() == reflect.Struct {
+ // if field is a pointer, find if the underlying is a struct
+ _, err := MapDynamicToFinal(dynamicValue.Interface(), finalField.Interface(), true)
+ if err != nil {
+ return 0, err
+ }
+ } else if dynamicFieldType.Kind() == reflect.Slice &&
+ dynamicFieldType.Elem().Kind() == reflect.Struct {
+ for j := 0; j < dynamicValue.Len(); j++ {
+ sliceElem := dynamicValue.Index(j).Addr().Interface()
+ finalSliceElem := reflect.New(finalField.Type().Elem()).Elem()
+ _, err := MapDynamicToFinal(sliceElem, finalSliceElem.Addr().Interface(), true)
+ if err != nil {
+ return 0, err
+ }
+ finalField.Set(reflect.Append(finalField, finalSliceElem))
+ }
+ } else {
+ if finalField.IsValid() && finalField.CanSet() {
+ // if field name is gid, convert it to uint64
+ if dynamicField.Name == "Gid" {
+ finalField.SetUint(gid)
+ } else {
+ finalField.Set(dynamicValue)
+ }
+ }
+ }
+ }
+ return gid, nil
+}
+
+func ConvertDynamicToTyped[T any](obj any, t reflect.Type) (uint64, T, error) {
+ var result T
+ finalObject := reflect.New(t).Interface()
+ gid, err := MapDynamicToFinal(obj, finalObject, false)
+ if err != nil {
+ return 0, result, err
+ }
+
+ if typedPtr, ok := finalObject.(*T); ok {
+ return gid, *typedPtr, nil
+ } else if dirType, ok := finalObject.(T); ok {
+ return gid, dirType, nil
+ }
+ return 0, result, fmt.Errorf("failed to convert type %T to %T", finalObject, obj)
+}
+
+func GetUniqueConstraint[T any](object T) (uint64, *keyValue, error) {
+ t := reflect.TypeOf(object)
+ tagMaps, err := GetFieldTags(t)
+ if err != nil {
+ return 0, nil, err
+ }
+ jsonTagToValue := GetJsonTagToValues(object, tagMaps.FieldToJson)
+
+ for jsonName, value := range jsonTagToValue {
+ if jsonName == "gid" {
+ gid, ok := value.(uint64)
+ if !ok {
+ continue
+ }
+ if gid != 0 {
+ return gid, nil, nil
+ }
+ }
+ if tagMaps.JsonToDb[jsonName] != nil && IsValidUniqueIndex(tagMaps.JsonToDb[jsonName].Constraint) {
+ // check if value is zero or nil
+ if value == reflect.Zero(reflect.TypeOf(value)).Interface() || value == nil {
+ continue
+ }
+ return 0, &keyValue{key: jsonName, value: value}, nil
+ }
+ }
+
+ return 0, nil, fmt.Errorf(apiutils.NoUniqueConstr, t.Name())
+}
+
+func IsValidUniqueIndex(name string) bool {
+ return name == "unique"
+}
diff --git a/api/structreflect/tagparser.go b/api/structreflect/tagparser.go
new file mode 100644
index 0000000..da9224f
--- /dev/null
+++ b/api/structreflect/tagparser.go
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package structreflect
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/hypermodeinc/modusdb/api/apiutils"
+)
+
+func parseJsonTag(field reflect.StructField) (string, error) {
+ jsonTag := field.Tag.Get("json")
+ if jsonTag == "" {
+ return "", fmt.Errorf("field %s has no json tag", field.Name)
+ }
+ return strings.Split(jsonTag, ",")[0], nil
+}
+
+func parseDbTag(field reflect.StructField) *DbTag {
+ dbConstraintsTag := field.Tag.Get("db")
+ if dbConstraintsTag == "" {
+ return nil
+ }
+
+ dbTag := &DbTag{}
+ dbTagsSplit := strings.Split(dbConstraintsTag, ",")
+ for _, tag := range dbTagsSplit {
+ split := strings.Split(tag, "=")
+ if split[0] == "constraint" {
+ dbTag.Constraint = split[1]
+ }
+ }
+ return dbTag
+}
+
+func parseReverseEdgeTag(field reflect.StructField) (string, error) {
+ reverseEdgeTag := field.Tag.Get("readFrom")
+ if reverseEdgeTag == "" {
+ return "", nil
+ }
+
+ typeAndField := strings.Split(reverseEdgeTag, ",")
+ if len(typeAndField) != 2 {
+ return "", fmt.Errorf(`field %s has invalid readFrom tag, expected format is type=,field=`, field.Name)
+ }
+
+ t := strings.Split(typeAndField[0], "=")[1]
+ f := strings.Split(typeAndField[1], "=")[1]
+ return apiutils.GetPredicateName(t, f), nil
+}
diff --git a/api/structreflect/tags.go b/api/structreflect/tags.go
new file mode 100644
index 0000000..23f7ac6
--- /dev/null
+++ b/api/structreflect/tags.go
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package structreflect
+
+type DbTag struct {
+ Constraint string
+}
+
+type TagMaps struct {
+ FieldToJson map[string]string
+ JsonToDb map[string]*DbTag
+ JsonToReverseEdge map[string]string
+}
diff --git a/api/structreflect/value_extractor.go b/api/structreflect/value_extractor.go
new file mode 100644
index 0000000..191ea00
--- /dev/null
+++ b/api/structreflect/value_extractor.go
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package structreflect
+
+import (
+ "reflect"
+)
+
+func GetJsonTagToValues(object any, fieldToJsonTags map[string]string) map[string]any {
+ values := make(map[string]any)
+ v := reflect.ValueOf(object)
+ for v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ }
+
+ for fieldName, jsonName := range fieldToJsonTags {
+ fieldValue := v.FieldByName(fieldName)
+ values[jsonName] = fieldValue.Interface()
+ }
+ return values
+}
diff --git a/api_mutation_gen.go b/api_mutation_gen.go
new file mode 100644
index 0000000..d2d1d4f
--- /dev/null
+++ b/api_mutation_gen.go
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package modusdb
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/dgraph-io/dgo/v240/protos/api"
+ "github.com/hypermodeinc/dgraph/v24/dql"
+ "github.com/hypermodeinc/dgraph/v24/protos/pb"
+ "github.com/hypermodeinc/dgraph/v24/schema"
+ "github.com/hypermodeinc/dgraph/v24/x"
+ "github.com/hypermodeinc/modusdb/api/apiutils"
+ "github.com/hypermodeinc/modusdb/api/dgraphtypes"
+ "github.com/hypermodeinc/modusdb/api/mutations"
+ "github.com/hypermodeinc/modusdb/api/structreflect"
+)
+
+func generateSetDqlMutationsAndSchema[T any](ctx context.Context, n *Namespace, object T,
+ gid uint64, dms *[]*dql.Mutation, sch *schema.ParsedSchema) error {
+ t := reflect.TypeOf(object)
+ if t.Kind() != reflect.Struct {
+ return fmt.Errorf("expected struct, got %s", t.Kind())
+ }
+
+ tagMaps, err := structreflect.GetFieldTags(t)
+ if err != nil {
+ return err
+ }
+ jsonTagToValue := structreflect.GetJsonTagToValues(object, tagMaps.FieldToJson)
+
+ nquads := make([]*api.NQuad, 0)
+ uniqueConstraintFound := false
+ for jsonName, value := range jsonTagToValue {
+
+ reflectValueType := reflect.TypeOf(value)
+ var nquad *api.NQuad
+
+ if tagMaps.JsonToReverseEdge[jsonName] != "" {
+ reverseEdgeStr := tagMaps.JsonToReverseEdge[jsonName]
+ typeName := strings.Split(reverseEdgeStr, ".")[0]
+ currSchema, err := getSchema(ctx, n)
+ if err != nil {
+ return err
+ }
+
+ typeFound := false
+ predicateFound := false
+ for _, t := range currSchema.Types {
+ if t.Name == typeName {
+ typeFound = true
+ for _, f := range t.Fields {
+ if f.Name == reverseEdgeStr {
+ predicateFound = true
+ break
+ }
+ }
+ break
+ }
+ }
+
+ if !(typeFound && predicateFound) {
+ if err := mutations.HandleReverseEdge(jsonName, reflectValueType, n.ID(), sch,
+ reverseEdgeStr); err != nil {
+ return err
+ }
+ }
+ continue
+ }
+ if jsonName == "gid" {
+ uniqueConstraintFound = true
+ continue
+ }
+
+ value, err = processStructValue(ctx, value, n)
+ if err != nil {
+ return err
+ }
+
+ value, err = processPointerValue(ctx, value, n)
+ if err != nil {
+ return err
+ }
+
+ nquad, u, err := mutations.CreateNQuadAndSchema(value, gid, jsonName, t, n.ID())
+ if err != nil {
+ return err
+ }
+
+ uniqueConstraintFound, err = dgraphtypes.HandleConstraints(u, tagMaps.JsonToDb,
+ jsonName, u.ValueType, uniqueConstraintFound)
+ if err != nil {
+ return err
+ }
+
+ sch.Preds = append(sch.Preds, u)
+ nquads = append(nquads, nquad)
+ }
+ if !uniqueConstraintFound {
+ return fmt.Errorf(apiutils.NoUniqueConstr, t.Name())
+ }
+
+ sch.Types = append(sch.Types, &pb.TypeUpdate{
+ TypeName: apiutils.AddNamespace(n.ID(), t.Name()),
+ Fields: sch.Preds,
+ })
+
+ val, err := dgraphtypes.ValueToApiVal(t.Name())
+ if err != nil {
+ return err
+ }
+ typeNquad := &api.NQuad{
+ Namespace: n.ID(),
+ Subject: fmt.Sprint(gid),
+ Predicate: "dgraph.type",
+ ObjectValue: val,
+ }
+ nquads = append(nquads, typeNquad)
+
+ *dms = append(*dms, &dql.Mutation{
+ Set: nquads,
+ })
+
+ return nil
+}
+
+func generateDeleteDqlMutations(n *Namespace, gid uint64) []*dql.Mutation {
+ return []*dql.Mutation{{
+ Del: []*api.NQuad{
+ {
+ Namespace: n.ID(),
+ Subject: fmt.Sprint(gid),
+ Predicate: x.Star,
+ ObjectValue: &api.Value{
+ Val: &api.Value_DefaultVal{DefaultVal: x.Star},
+ },
+ },
+ },
+ }}
+}
diff --git a/api_mutation_helpers.go b/api_mutation_helpers.go
new file mode 100644
index 0000000..e060dc2
--- /dev/null
+++ b/api_mutation_helpers.go
@@ -0,0 +1,128 @@
+package modusdb
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+
+ "github.com/hypermodeinc/dgraph/v24/dql"
+ "github.com/hypermodeinc/dgraph/v24/protos/pb"
+ "github.com/hypermodeinc/dgraph/v24/query"
+ "github.com/hypermodeinc/dgraph/v24/schema"
+ "github.com/hypermodeinc/dgraph/v24/worker"
+ "github.com/hypermodeinc/modusdb/api/apiutils"
+ "github.com/hypermodeinc/modusdb/api/structreflect"
+)
+
+func processStructValue(ctx context.Context, value any, ns *Namespace) (any, error) {
+ if reflect.TypeOf(value).Kind() == reflect.Struct {
+ value = reflect.ValueOf(value).Interface()
+ newGid, err := getUidOrMutate(ctx, ns.engine, ns, value)
+ if err != nil {
+ return nil, err
+ }
+ return newGid, nil
+ }
+ return value, nil
+}
+
+func processPointerValue(ctx context.Context, value any, ns *Namespace) (any, error) {
+ reflectValueType := reflect.TypeOf(value)
+ if reflectValueType.Kind() == reflect.Pointer {
+ reflectValueType = reflectValueType.Elem()
+ if reflectValueType.Kind() == reflect.Struct {
+ value = reflect.ValueOf(value).Elem().Interface()
+ return processStructValue(ctx, value, ns)
+ }
+ }
+ return value, nil
+}
+
+func getUidOrMutate[T any](ctx context.Context, engine *Engine, ns *Namespace, object T) (uint64, error) {
+ gid, cfKeyValue, err := structreflect.GetUniqueConstraint[T](object)
+ if err != nil {
+ return 0, err
+ }
+ var cf *ConstrainedField
+ if cfKeyValue != nil {
+ cf = &ConstrainedField{Key: cfKeyValue.Key(), Value: cfKeyValue.Value()}
+ }
+
+ dms := make([]*dql.Mutation, 0)
+ sch := &schema.ParsedSchema{}
+ err = generateSetDqlMutationsAndSchema(ctx, ns, object, gid, &dms, sch)
+ if err != nil {
+ return 0, err
+ }
+
+ err = engine.alterSchemaWithParsed(ctx, sch)
+ if err != nil {
+ return 0, err
+ }
+ if gid != 0 || cf != nil {
+ gid, err = getExistingObject(ctx, ns, gid, cf, object)
+ if err != nil && err != apiutils.ErrNoObjFound {
+ return 0, err
+ }
+ if err == nil {
+ return gid, nil
+ }
+ }
+
+ gid, err = engine.z.nextUID()
+ if err != nil {
+ return 0, err
+ }
+
+ dms = make([]*dql.Mutation, 0)
+ err = generateSetDqlMutationsAndSchema(ctx, ns, object, gid, &dms, sch)
+ if err != nil {
+ return 0, err
+ }
+
+ err = applyDqlMutations(ctx, engine, dms)
+ if err != nil {
+ return 0, err
+ }
+
+ return gid, nil
+}
+
+func applyDqlMutations(ctx context.Context, engine *Engine, dms []*dql.Mutation) error {
+ edges, err := query.ToDirectedEdges(dms, nil)
+ if err != nil {
+ return err
+ }
+
+ if !engine.isOpen.Load() {
+ return ErrClosedEngine
+ }
+
+ startTs, err := engine.z.nextTs()
+ if err != nil {
+ return err
+ }
+ commitTs, err := engine.z.nextTs()
+ if err != nil {
+ return err
+ }
+
+ m := &pb.Mutations{
+ GroupId: 1,
+ StartTs: startTs,
+ Edges: edges,
+ }
+ m.Edges, err = query.ExpandEdges(ctx, m)
+ if err != nil {
+ return fmt.Errorf("error expanding edges: %w", err)
+ }
+
+ p := &pb.Proposal{Mutations: m, StartTs: startTs}
+ if err := worker.ApplyMutations(ctx, p); err != nil {
+ return err
+ }
+
+ return worker.ApplyCommited(ctx, &pb.OracleDelta{
+ Txns: []*pb.TxnStatus{{StartTs: startTs, CommitTs: commitTs}},
+ })
+}
diff --git a/api_query_execution.go b/api_query_execution.go
new file mode 100644
index 0000000..4e90527
--- /dev/null
+++ b/api_query_execution.go
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package modusdb
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "reflect"
+
+ "github.com/hypermodeinc/modusdb/api/apiutils"
+ "github.com/hypermodeinc/modusdb/api/querygen"
+ "github.com/hypermodeinc/modusdb/api/structreflect"
+)
+
+func getByGid[T any](ctx context.Context, ns *Namespace, gid uint64) (uint64, T, error) {
+ return executeGet[T](ctx, ns, gid)
+}
+
+func getByGidWithObject[T any](ctx context.Context, ns *Namespace, gid uint64, obj T) (uint64, T, error) {
+ return executeGetWithObject[T](ctx, ns, obj, false, gid)
+}
+
+func getByConstrainedField[T any](ctx context.Context, ns *Namespace, cf ConstrainedField) (uint64, T, error) {
+ return executeGet[T](ctx, ns, cf)
+}
+
+func getByConstrainedFieldWithObject[T any](ctx context.Context, ns *Namespace,
+ cf ConstrainedField, obj T) (uint64, T, error) {
+
+ return executeGetWithObject[T](ctx, ns, obj, false, cf)
+}
+
+func executeGet[T any, R UniqueField](ctx context.Context, ns *Namespace, args ...R) (uint64, T, error) {
+ var obj T
+ if len(args) != 1 {
+ return 0, obj, fmt.Errorf("expected 1 argument, got %ds", len(args))
+ }
+
+ return executeGetWithObject(ctx, ns, obj, true, args...)
+}
+
+func executeGetWithObject[T any, R UniqueField](ctx context.Context, ns *Namespace,
+ obj T, withReverse bool, args ...R) (uint64, T, error) {
+ t := reflect.TypeOf(obj)
+
+ tagMaps, err := structreflect.GetFieldTags(t)
+ if err != nil {
+ return 0, obj, err
+ }
+ readFromQuery := ""
+ if withReverse {
+ for jsonTag, reverseEdgeTag := range tagMaps.JsonToReverseEdge {
+ readFromQuery += fmt.Sprintf(querygen.ReverseEdgeQuery,
+ apiutils.GetPredicateName(t.Name(), jsonTag), reverseEdgeTag)
+ }
+ }
+
+ var cf ConstrainedField
+ var query string
+ gid, ok := any(args[0]).(uint64)
+ if ok {
+ query = querygen.FormatObjQuery(querygen.BuildUidQuery(gid), readFromQuery)
+ } else if cf, ok = any(args[0]).(ConstrainedField); ok {
+ query = querygen.FormatObjQuery(querygen.BuildEqQuery(apiutils.GetPredicateName(t.Name(),
+ cf.Key), cf.Value), readFromQuery)
+ } else {
+ return 0, obj, fmt.Errorf("invalid unique field type")
+ }
+
+ if tagMaps.JsonToDb[cf.Key] != nil && tagMaps.JsonToDb[cf.Key].Constraint == "" {
+ return 0, obj, fmt.Errorf("constraint not defined for field %s", cf.Key)
+ }
+
+ resp, err := ns.engine.queryWithLock(ctx, ns, query)
+ if err != nil {
+ return 0, obj, err
+ }
+
+ dynamicType := structreflect.CreateDynamicStruct(t, tagMaps.FieldToJson, 1)
+
+ dynamicInstance := reflect.New(dynamicType).Interface()
+
+ var result struct {
+ Obj []any `json:"obj"`
+ }
+
+ result.Obj = append(result.Obj, dynamicInstance)
+
+ // Unmarshal the JSON response into the dynamic struct
+ if err := json.Unmarshal(resp.Json, &result); err != nil {
+ return 0, obj, err
+ }
+
+ // Check if we have at least one object in the response
+ if len(result.Obj) == 0 {
+ return 0, obj, apiutils.ErrNoObjFound
+ }
+
+ return structreflect.ConvertDynamicToTyped[T](result.Obj[0], t)
+}
+
+func executeQuery[T any](ctx context.Context, ns *Namespace, queryParams QueryParams,
+ withReverse bool) ([]uint64, []T, error) {
+ var obj T
+ t := reflect.TypeOf(obj)
+ tagMaps, err := structreflect.GetFieldTags(t)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var filterQueryFunc querygen.QueryFunc = func() string {
+ return ""
+ }
+ var paginationAndSorting string
+ if queryParams.Filter != nil {
+ filterQueryFunc = filtersToQueryFunc(t.Name(), *queryParams.Filter)
+ }
+ if queryParams.Pagination != nil || queryParams.Sorting != nil {
+ var pagination, sorting string
+ if queryParams.Pagination != nil {
+ pagination = paginationToQueryString(*queryParams.Pagination)
+ }
+ if queryParams.Sorting != nil {
+ sorting = sortingToQueryString(t.Name(), *queryParams.Sorting)
+ }
+ paginationAndSorting = fmt.Sprintf("%s %s", pagination, sorting)
+ }
+
+ readFromQuery := ""
+ if withReverse {
+ for jsonTag, reverseEdgeTag := range tagMaps.JsonToReverseEdge {
+ readFromQuery += fmt.Sprintf(querygen.ReverseEdgeQuery, apiutils.GetPredicateName(t.Name(), jsonTag), reverseEdgeTag)
+ }
+ }
+
+ query := querygen.FormatObjsQuery(t.Name(), filterQueryFunc, paginationAndSorting, readFromQuery)
+
+ resp, err := ns.engine.queryWithLock(ctx, ns, query)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ dynamicType := structreflect.CreateDynamicStruct(t, tagMaps.FieldToJson, 1)
+
+ var result struct {
+ Objs []any `json:"objs"`
+ }
+
+ var tempMap map[string][]any
+ if err := json.Unmarshal(resp.Json, &tempMap); err != nil {
+ return nil, nil, err
+ }
+
+ // Determine the number of elements
+ numElements := len(tempMap["objs"])
+
+ // Append the interface the correct number of times
+ for i := 0; i < numElements; i++ {
+ result.Objs = append(result.Objs, reflect.New(dynamicType).Interface())
+ }
+
+ // Unmarshal the JSON response into the dynamic struct
+ if err := json.Unmarshal(resp.Json, &result); err != nil {
+ return nil, nil, err
+ }
+
+ gids := make([]uint64, len(result.Objs))
+ objs := make([]T, len(result.Objs))
+ for i, obj := range result.Objs {
+ gid, typedObj, err := structreflect.ConvertDynamicToTyped[T](obj, t)
+ if err != nil {
+ return nil, nil, err
+ }
+ gids[i] = gid
+ objs[i] = typedObj
+ }
+
+ return gids, objs, nil
+}
+
+func getExistingObject[T any](ctx context.Context, ns *Namespace, gid uint64, cf *ConstrainedField,
+ object T) (uint64, error) {
+ var err error
+ if gid != 0 {
+ gid, _, err = getByGidWithObject[T](ctx, ns, gid, object)
+ } else if cf != nil {
+ gid, _, err = getByConstrainedFieldWithObject[T](ctx, ns, *cf, object)
+ }
+ if err != nil {
+ return 0, err
+ }
+ return gid, nil
+}
+
+func getSchema(ctx context.Context, ns *Namespace) (*querygen.SchemaResponse, error) {
+ resp, err := ns.engine.queryWithLock(ctx, ns, querygen.SchemaQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ var schema querygen.SchemaResponse
+ if err := json.Unmarshal(resp.Json, &schema); err != nil {
+ return nil, err
+ }
+ return &schema, nil
+}
diff --git a/api_types.go b/api_types.go
new file mode 100644
index 0000000..18ce99a
--- /dev/null
+++ b/api_types.go
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package modusdb
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/hypermodeinc/dgraph/v24/x"
+ "github.com/hypermodeinc/modusdb/api/apiutils"
+ "github.com/hypermodeinc/modusdb/api/querygen"
+)
+
+type UniqueField interface {
+ uint64 | ConstrainedField
+}
+type ConstrainedField struct {
+ Key string
+ Value any
+}
+
+type QueryParams struct {
+ Filter *Filter
+ Pagination *Pagination
+ Sorting *Sorting
+}
+
+type Filter struct {
+ Field string
+ String StringPredicate
+ Vector VectorPredicate
+ And *Filter
+ Or *Filter
+ Not *Filter
+}
+
+type Pagination struct {
+ Limit int64
+ Offset int64
+ After string
+}
+
+type Sorting struct {
+ OrderAscField string
+ OrderDescField string
+ OrderDescFirst bool
+}
+
+type StringPredicate struct {
+ Equals string
+ LessThan string
+ LessOrEqual string
+ GreaterThan string
+ GreaterOrEqual string
+ AllOfTerms []string
+ AnyOfTerms []string
+ AllOfText []string
+ AnyOfText []string
+ RegExp string
+}
+
+type VectorPredicate struct {
+ SimilarTo []float32
+ TopK int64
+}
+
+type ModusDbOption func(*modusDbOptions)
+
+type modusDbOptions struct {
+ ns uint64
+}
+
+func WithNamespace(ns uint64) ModusDbOption {
+ return func(o *modusDbOptions) {
+ o.ns = ns
+ }
+}
+
+func getDefaultNamespace(engine *Engine, nsId ...uint64) (context.Context, *Namespace, error) {
+ dbOpts := &modusDbOptions{
+ ns: engine.db0.ID(),
+ }
+ for _, ns := range nsId {
+ WithNamespace(ns)(dbOpts)
+ }
+
+ d, err := engine.getNamespaceWithLock(dbOpts.ns)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ctx := context.Background()
+ ctx = x.AttachNamespace(ctx, d.ID())
+
+ return ctx, d, nil
+}
+
+func filterToQueryFunc(typeName string, f Filter) querygen.QueryFunc {
+ // Handle logical operators first
+ if f.And != nil {
+ return querygen.And(filterToQueryFunc(typeName, *f.And))
+ }
+ if f.Or != nil {
+ return querygen.Or(filterToQueryFunc(typeName, *f.Or))
+ }
+ if f.Not != nil {
+ return querygen.Not(filterToQueryFunc(typeName, *f.Not))
+ }
+
+ // Handle field predicates
+ if f.String.Equals != "" {
+ return querygen.BuildEqQuery(apiutils.GetPredicateName(typeName, f.Field), f.String.Equals)
+ }
+ if len(f.String.AllOfTerms) != 0 {
+ return querygen.BuildAllOfTermsQuery(apiutils.GetPredicateName(typeName,
+ f.Field), strings.Join(f.String.AllOfTerms, " "))
+ }
+ if len(f.String.AnyOfTerms) != 0 {
+ return querygen.BuildAnyOfTermsQuery(apiutils.GetPredicateName(typeName,
+ f.Field), strings.Join(f.String.AnyOfTerms, " "))
+ }
+ if len(f.String.AllOfText) != 0 {
+ return querygen.BuildAllOfTextQuery(apiutils.GetPredicateName(typeName,
+ f.Field), strings.Join(f.String.AllOfText, " "))
+ }
+ if len(f.String.AnyOfText) != 0 {
+ return querygen.BuildAnyOfTextQuery(apiutils.GetPredicateName(typeName,
+ f.Field), strings.Join(f.String.AnyOfText, " "))
+ }
+ if f.String.RegExp != "" {
+ return querygen.BuildRegExpQuery(apiutils.GetPredicateName(typeName,
+ f.Field), f.String.RegExp)
+ }
+ if f.String.LessThan != "" {
+ return querygen.BuildLtQuery(apiutils.GetPredicateName(typeName,
+ f.Field), f.String.LessThan)
+ }
+ if f.String.LessOrEqual != "" {
+ return querygen.BuildLeQuery(apiutils.GetPredicateName(typeName,
+ f.Field), f.String.LessOrEqual)
+ }
+ if f.String.GreaterThan != "" {
+ return querygen.BuildGtQuery(apiutils.GetPredicateName(typeName,
+ f.Field), f.String.GreaterThan)
+ }
+ if f.String.GreaterOrEqual != "" {
+ return querygen.BuildGeQuery(apiutils.GetPredicateName(typeName,
+ f.Field), f.String.GreaterOrEqual)
+ }
+ if f.Vector.SimilarTo != nil {
+ return querygen.BuildSimilarToQuery(apiutils.GetPredicateName(typeName,
+ f.Field), f.Vector.TopK, f.Vector.SimilarTo)
+ }
+
+ // Return empty query if no conditions match
+ return func() string { return "" }
+}
+
+// Helper function to combine multiple filters
+func filtersToQueryFunc(typeName string, filter Filter) querygen.QueryFunc {
+ return filterToQueryFunc(typeName, filter)
+}
+
+func paginationToQueryString(p Pagination) string {
+ paginationStr := ""
+ if p.Limit > 0 {
+ paginationStr += ", " + fmt.Sprintf("first: %d", p.Limit)
+ }
+ if p.Offset > 0 {
+ paginationStr += ", " + fmt.Sprintf("offset: %d", p.Offset)
+ } else if p.After != "" {
+ paginationStr += ", " + fmt.Sprintf("after: %s", p.After)
+ }
+ if paginationStr == "" {
+ return ""
+ }
+ return paginationStr
+}
+
+func sortingToQueryString(typeName string, s Sorting) string {
+ if s.OrderAscField == "" && s.OrderDescField == "" {
+ return ""
+ }
+
+ var parts []string
+ first, second := s.OrderDescField, s.OrderAscField
+ firstOp, secondOp := "orderdesc", "orderasc"
+
+ if !s.OrderDescFirst {
+ first, second = s.OrderAscField, s.OrderDescField
+ firstOp, secondOp = "orderasc", "orderdesc"
+ }
+
+ if first != "" {
+ parts = append(parts, fmt.Sprintf("%s: %s", firstOp, apiutils.GetPredicateName(typeName, first)))
+ }
+ if second != "" {
+ parts = append(parts, fmt.Sprintf("%s: %s", secondOp, apiutils.GetPredicateName(typeName, second)))
+ }
+
+ return ", " + strings.Join(parts, ", ")
+}
diff --git a/config.go b/config.go
index e33a0ad..0eac913 100644
--- a/config.go
+++ b/config.go
@@ -1,3 +1,12 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
package modusdb
type Config struct {
@@ -11,8 +20,8 @@ func NewDefaultConfig(dir string) Config {
return Config{dataDir: dir, limitNormalizeNode: 10000}
}
-func (cc Config) WithLimitNormalizeNode(n int) Config {
- cc.limitNormalizeNode = n
+func (cc Config) WithLimitNormalizeNode(d int) Config {
+ cc.limitNormalizeNode = d
return cc
}
diff --git a/db.go b/db.go
deleted file mode 100644
index 6b212bf..0000000
--- a/db.go
+++ /dev/null
@@ -1,216 +0,0 @@
-package modusdb
-
-import (
- "context"
- "errors"
- "fmt"
- "path"
- "sync"
- "sync/atomic"
-
- "github.com/dgraph-io/badger/v4"
- "github.com/dgraph-io/dgo/v240/protos/api"
- "github.com/dgraph-io/dgraph/v24/edgraph"
- "github.com/dgraph-io/dgraph/v24/posting"
- "github.com/dgraph-io/dgraph/v24/protos/pb"
- "github.com/dgraph-io/dgraph/v24/schema"
- "github.com/dgraph-io/dgraph/v24/worker"
- "github.com/dgraph-io/dgraph/v24/x"
- "github.com/dgraph-io/ristretto/v2/z"
-)
-
-var (
- // This ensures that we only have one instance of modusDB in this process.
- singleton atomic.Bool
-
- ErrSingletonOnly = errors.New("only one modusDB instance is supported")
- ErrEmptyDataDir = errors.New("data directory is required")
- ErrClosedDB = errors.New("modusDB instance is closed")
- ErrNonExistentNamespace = errors.New("namespace does not exist")
-)
-
-// DB is an instance of modusDB.
-// For now, we only support one instance of modusDB per process.
-type DB struct {
- mutex sync.RWMutex
- isOpen bool
-
- z *zero
-
- // points to default / 0 / galaxy namespace
- gxy *Namespace
-}
-
-// New returns a new modusDB instance.
-func New(conf Config) (*DB, error) {
- // Ensure that we do not create another instance of modusDB in the same process
- if !singleton.CompareAndSwap(false, true) {
- return nil, ErrSingletonOnly
- }
-
- if err := conf.validate(); err != nil {
- return nil, err
- }
-
- // setup data directories
- worker.Config.PostingDir = path.Join(conf.dataDir, "p")
- worker.Config.WALDir = path.Join(conf.dataDir, "w")
- x.WorkerConfig.TmpDir = path.Join(conf.dataDir, "t")
-
- // TODO: optimize these and more options
- x.WorkerConfig.Badger = badger.DefaultOptions("").FromSuperFlag(worker.BadgerDefaults)
- x.Config.MaxRetries = 10
- x.Config.Limit = z.NewSuperFlag("max-pending-queries=100000")
- x.Config.LimitNormalizeNode = conf.limitNormalizeNode
-
- // initialize each package
- edgraph.Init()
- worker.State.InitStorage()
- worker.InitForLite(worker.State.Pstore)
- schema.Init(worker.State.Pstore)
- posting.Init(worker.State.Pstore, 0) // TODO: set cache size
-
- db := &DB{isOpen: true}
- if err := db.reset(); err != nil {
- return nil, fmt.Errorf("error resetting db: %w", err)
- }
-
- x.UpdateHealthStatus(true)
-
- db.gxy = &Namespace{id: 0, db: db}
- return db, nil
-}
-
-func (db *DB) CreateNamespace() (*Namespace, error) {
- db.mutex.RLock()
- defer db.mutex.RUnlock()
-
- if !db.isOpen {
- return nil, ErrClosedDB
- }
-
- startTs, err := db.z.nextTs()
- if err != nil {
- return nil, err
- }
- nsID, err := db.z.nextNS()
- if err != nil {
- return nil, err
- }
-
- if err := worker.ApplyInitialSchema(nsID, startTs); err != nil {
- return nil, fmt.Errorf("error applying initial schema: %w", err)
- }
- for _, pred := range schema.State().Predicates() {
- worker.InitTablet(pred)
- }
-
- return &Namespace{id: nsID, db: db}, nil
-}
-
-func (db *DB) GetNamespace(nsID uint64) (*Namespace, error) {
- db.mutex.RLock()
- defer db.mutex.RUnlock()
-
- if !db.isOpen {
- return nil, ErrClosedDB
- }
-
- if nsID > db.z.lastNS {
- return nil, ErrNonExistentNamespace
- }
-
- // TODO: when delete namespace is implemented, check if the namespace exists
-
- return &Namespace{id: nsID, db: db}, nil
-}
-
-// DropAll drops all the data and schema in the modusDB instance.
-func (db *DB) DropAll(ctx context.Context) error {
- db.mutex.Lock()
- defer db.mutex.Unlock()
-
- if !db.isOpen {
- return ErrClosedDB
- }
-
- p := &pb.Proposal{Mutations: &pb.Mutations{
- GroupId: 1,
- DropOp: pb.Mutations_ALL,
- }}
- if err := worker.ApplyMutations(ctx, p); err != nil {
- return fmt.Errorf("error applying mutation: %w", err)
- }
- if err := db.reset(); err != nil {
- return fmt.Errorf("error resetting db: %w", err)
- }
-
- // TODO: insert drop record
- return nil
-}
-
-func (db *DB) DropData(ctx context.Context) error {
- return db.gxy.DropData(ctx)
-}
-
-func (db *DB) AlterSchema(ctx context.Context, sch string) error {
- return db.gxy.AlterSchema(ctx, sch)
-}
-
-func (db *DB) Query(ctx context.Context, q string) (*api.Response, error) {
- return db.gxy.Query(ctx, q)
-}
-
-func (db *DB) Mutate(ctx context.Context, ms []*api.Mutation) (map[string]uint64, error) {
- return db.gxy.Mutate(ctx, ms)
-}
-
-func (db *DB) Load(ctx context.Context, schemaPath, dataPath string) error {
- return db.gxy.Load(ctx, schemaPath, dataPath)
-}
-
-func (db *DB) LoadData(inCtx context.Context, dataDir string) error {
- return db.gxy.LoadData(inCtx, dataDir)
-}
-
-// Close closes the modusDB instance.
-func (db *DB) Close() {
- db.mutex.Lock()
- defer db.mutex.Unlock()
-
- if !db.isOpen {
- return
- }
-
- if !singleton.CompareAndSwap(true, false) {
- panic("modusDB instance was not properly opened")
- }
-
- db.isOpen = false
- x.UpdateHealthStatus(false)
- posting.Cleanup()
- worker.State.Dispose()
-}
-
-func (db *DB) reset() error {
- z, restart, err := newZero()
- if err != nil {
- return fmt.Errorf("error initializing zero: %w", err)
- }
-
- if !restart {
- if err := worker.ApplyInitialSchema(0, 1); err != nil {
- return fmt.Errorf("error applying initial schema: %w", err)
- }
- }
-
- if err := schema.LoadFromDb(context.Background()); err != nil {
- return fmt.Errorf("error loading schema: %w", err)
- }
- for _, pred := range schema.State().Predicates() {
- worker.InitTablet(pred)
- }
-
- db.z = z
- return nil
-}
diff --git a/engine.go b/engine.go
new file mode 100644
index 0000000..a026bcf
--- /dev/null
+++ b/engine.go
@@ -0,0 +1,384 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package modusdb
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "path"
+ "strconv"
+ "sync"
+ "sync/atomic"
+
+ "github.com/dgraph-io/badger/v4"
+ "github.com/dgraph-io/dgo/v240/protos/api"
+ "github.com/dgraph-io/ristretto/v2/z"
+ "github.com/hypermodeinc/dgraph/v24/dql"
+ "github.com/hypermodeinc/dgraph/v24/edgraph"
+ "github.com/hypermodeinc/dgraph/v24/posting"
+ "github.com/hypermodeinc/dgraph/v24/protos/pb"
+ "github.com/hypermodeinc/dgraph/v24/query"
+ "github.com/hypermodeinc/dgraph/v24/schema"
+ "github.com/hypermodeinc/dgraph/v24/worker"
+ "github.com/hypermodeinc/dgraph/v24/x"
+)
+
+var (
+ // This ensures that we only have one instance of modusDB in this process.
+ singleton atomic.Bool
+
+ ErrSingletonOnly = errors.New("only one modusDB engine is supported")
+ ErrEmptyDataDir = errors.New("data directory is required")
+ ErrClosedEngine = errors.New("modusDB engine is closed")
+ ErrNonExistentDB = errors.New("namespace does not exist")
+)
+
+// Engine is an instance of modusDB.
+// For now, we only support one instance of modusDB per process.
+type Engine struct {
+ mutex sync.RWMutex
+ isOpen atomic.Bool
+
+ z *zero
+
+ // points to default / 0 / galaxy namespace
+ db0 *Namespace
+}
+
+// NewEngine returns a new modusDB instance.
+func NewEngine(conf Config) (*Engine, error) {
+ // Ensure that we do not create another instance of modusDB in the same process
+ if !singleton.CompareAndSwap(false, true) {
+ return nil, ErrSingletonOnly
+ }
+
+ if err := conf.validate(); err != nil {
+ return nil, err
+ }
+
+ // setup data directories
+ worker.Config.PostingDir = path.Join(conf.dataDir, "p")
+ worker.Config.WALDir = path.Join(conf.dataDir, "w")
+ x.WorkerConfig.TmpDir = path.Join(conf.dataDir, "t")
+
+ // TODO: optimize these and more options
+ x.WorkerConfig.Badger = badger.DefaultOptions("").FromSuperFlag(worker.BadgerDefaults)
+ x.Config.MaxRetries = 10
+ x.Config.Limit = z.NewSuperFlag("max-pending-queries=100000")
+ x.Config.LimitNormalizeNode = conf.limitNormalizeNode
+
+ // initialize each package
+ edgraph.Init()
+ worker.State.InitStorage()
+ worker.InitForLite(worker.State.Pstore)
+ schema.Init(worker.State.Pstore)
+ posting.Init(worker.State.Pstore, 0, false) // TODO: set cache size
+
+ engine := &Engine{}
+ engine.isOpen.Store(true)
+ if err := engine.reset(); err != nil {
+ return nil, fmt.Errorf("error resetting db: %w", err)
+ }
+
+ x.UpdateHealthStatus(true)
+
+ engine.db0 = &Namespace{id: 0, engine: engine}
+ return engine, nil
+}
+
+func (engine *Engine) CreateNamespace() (*Namespace, error) {
+ engine.mutex.RLock()
+ defer engine.mutex.RUnlock()
+
+ if !engine.isOpen.Load() {
+ return nil, ErrClosedEngine
+ }
+
+ startTs, err := engine.z.nextTs()
+ if err != nil {
+ return nil, err
+ }
+ nsID, err := engine.z.nextNamespace()
+ if err != nil {
+ return nil, err
+ }
+
+ if err := worker.ApplyInitialSchema(nsID, startTs); err != nil {
+ return nil, fmt.Errorf("error applying initial schema: %w", err)
+ }
+ for _, pred := range schema.State().Predicates() {
+ worker.InitTablet(pred)
+ }
+
+ return &Namespace{id: nsID, engine: engine}, nil
+}
+
+func (engine *Engine) GetNamespace(nsID uint64) (*Namespace, error) {
+ engine.mutex.RLock()
+ defer engine.mutex.RUnlock()
+
+ return engine.getNamespaceWithLock(nsID)
+}
+
+func (engine *Engine) getNamespaceWithLock(nsID uint64) (*Namespace, error) {
+ if !engine.isOpen.Load() {
+ return nil, ErrClosedEngine
+ }
+
+ if nsID > engine.z.lastNamespace {
+ return nil, ErrNonExistentDB
+ }
+
+ // TODO: when delete namespace is implemented, check if the namespace exists
+
+ return &Namespace{id: nsID, engine: engine}, nil
+}
+
+func (engine *Engine) GetDefaultNamespace() *Namespace {
+ return engine.db0
+}
+
+// DropAll drops all the data and schema in the modusDB instance.
+func (engine *Engine) DropAll(ctx context.Context) error {
+ engine.mutex.Lock()
+ defer engine.mutex.Unlock()
+
+ if !engine.isOpen.Load() {
+ return ErrClosedEngine
+ }
+
+ p := &pb.Proposal{Mutations: &pb.Mutations{
+ GroupId: 1,
+ DropOp: pb.Mutations_ALL,
+ }}
+ if err := worker.ApplyMutations(ctx, p); err != nil {
+ return fmt.Errorf("error applying mutation: %w", err)
+ }
+ if err := engine.reset(); err != nil {
+ return fmt.Errorf("error resetting db: %w", err)
+ }
+
+ // TODO: insert drop record
+ return nil
+}
+
+func (engine *Engine) dropData(ctx context.Context, ns *Namespace) error {
+ engine.mutex.Lock()
+ defer engine.mutex.Unlock()
+
+ if !engine.isOpen.Load() {
+ return ErrClosedEngine
+ }
+
+ p := &pb.Proposal{Mutations: &pb.Mutations{
+ GroupId: 1,
+ DropOp: pb.Mutations_DATA,
+ DropValue: strconv.FormatUint(ns.ID(), 10),
+ }}
+
+ if err := worker.ApplyMutations(ctx, p); err != nil {
+ return fmt.Errorf("error applying mutation: %w", err)
+ }
+
+ // TODO: insert drop record
+ // TODO: should we reset back the timestamp as well?
+ return nil
+}
+
+func (engine *Engine) alterSchema(ctx context.Context, ns *Namespace, sch string) error {
+ engine.mutex.Lock()
+ defer engine.mutex.Unlock()
+
+ if !engine.isOpen.Load() {
+ return ErrClosedEngine
+ }
+
+ sc, err := schema.ParseWithNamespace(sch, ns.ID())
+ if err != nil {
+ return fmt.Errorf("error parsing schema: %w", err)
+ }
+ return engine.alterSchemaWithParsed(ctx, sc)
+}
+
+func (engine *Engine) alterSchemaWithParsed(ctx context.Context, sc *schema.ParsedSchema) error {
+ for _, pred := range sc.Preds {
+ worker.InitTablet(pred.Predicate)
+ }
+
+ startTs, err := engine.z.nextTs()
+ if err != nil {
+ return err
+ }
+
+ p := &pb.Proposal{Mutations: &pb.Mutations{
+ GroupId: 1,
+ StartTs: startTs,
+ Schema: sc.Preds,
+ Types: sc.Types,
+ }}
+ if err := worker.ApplyMutations(ctx, p); err != nil {
+ return fmt.Errorf("error applying mutation: %w", err)
+ }
+ return nil
+}
+
+func (engine *Engine) query(ctx context.Context, ns *Namespace, q string) (*api.Response, error) {
+ engine.mutex.RLock()
+ defer engine.mutex.RUnlock()
+
+ return engine.queryWithLock(ctx, ns, q)
+}
+
+func (engine *Engine) queryWithLock(ctx context.Context, ns *Namespace, q string) (*api.Response, error) {
+ if !engine.isOpen.Load() {
+ return nil, ErrClosedEngine
+ }
+
+ ctx = x.AttachNamespace(ctx, ns.ID())
+ return (&edgraph.Server{}).QueryNoAuth(ctx, &api.Request{
+ ReadOnly: true,
+ Query: q,
+ StartTs: engine.z.readTs(),
+ })
+}
+
+func (engine *Engine) mutate(ctx context.Context, ns *Namespace, ms []*api.Mutation) (map[string]uint64, error) {
+ if len(ms) == 0 {
+ return nil, nil
+ }
+
+ engine.mutex.Lock()
+ defer engine.mutex.Unlock()
+ dms := make([]*dql.Mutation, 0, len(ms))
+ for _, mu := range ms {
+ dm, err := edgraph.ParseMutationObject(mu, false)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing mutation: %w", err)
+ }
+ dms = append(dms, dm)
+ }
+ newUids, err := query.ExtractBlankUIDs(ctx, dms)
+ if err != nil {
+ return nil, err
+ }
+ if len(newUids) > 0 {
+ num := &pb.Num{Val: uint64(len(newUids)), Type: pb.Num_UID}
+ res, err := engine.z.nextUIDs(num)
+ if err != nil {
+ return nil, err
+ }
+
+ curId := res.StartId
+ for k := range newUids {
+ x.AssertTruef(curId != 0 && curId <= res.EndId, "not enough uids generated")
+ newUids[k] = curId
+ curId++
+ }
+ }
+
+ return engine.mutateWithDqlMutation(ctx, ns, dms, newUids)
+}
+
+func (engine *Engine) mutateWithDqlMutation(ctx context.Context, ns *Namespace, dms []*dql.Mutation,
+ newUids map[string]uint64) (map[string]uint64, error) {
+ edges, err := query.ToDirectedEdges(dms, newUids)
+ if err != nil {
+ return nil, fmt.Errorf("error converting to directed edges: %w", err)
+ }
+ ctx = x.AttachNamespace(ctx, ns.ID())
+
+ if !engine.isOpen.Load() {
+ return nil, ErrClosedEngine
+ }
+
+ startTs, err := engine.z.nextTs()
+ if err != nil {
+ return nil, err
+ }
+ commitTs, err := engine.z.nextTs()
+ if err != nil {
+ return nil, err
+ }
+
+ m := &pb.Mutations{
+ GroupId: 1,
+ StartTs: startTs,
+ Edges: edges,
+ }
+
+ m.Edges, err = query.ExpandEdges(ctx, m)
+ if err != nil {
+ return nil, fmt.Errorf("error expanding edges: %w", err)
+ }
+
+ for _, edge := range m.Edges {
+ worker.InitTablet(edge.Attr)
+ }
+
+ p := &pb.Proposal{Mutations: m, StartTs: startTs}
+ if err := worker.ApplyMutations(ctx, p); err != nil {
+ return nil, err
+ }
+
+ return newUids, worker.ApplyCommited(ctx, &pb.OracleDelta{
+ Txns: []*pb.TxnStatus{{StartTs: startTs, CommitTs: commitTs}},
+ })
+}
+
+func (engine *Engine) Load(ctx context.Context, schemaPath, dataPath string) error {
+ return engine.db0.Load(ctx, schemaPath, dataPath)
+}
+
+func (engine *Engine) LoadData(inCtx context.Context, dataDir string) error {
+ return engine.db0.LoadData(inCtx, dataDir)
+}
+
+// Close closes the modusDB instance.
+func (engine *Engine) Close() {
+ engine.mutex.Lock()
+ defer engine.mutex.Unlock()
+
+ if !engine.isOpen.Load() {
+ return
+ }
+
+ if !singleton.CompareAndSwap(true, false) {
+ panic("modusDB instance was not properly opened")
+ }
+
+ engine.isOpen.Store(false)
+ x.UpdateHealthStatus(false)
+ posting.Cleanup()
+ worker.State.Dispose()
+}
+
+func (ns *Engine) reset() error {
+ z, restart, err := newZero()
+ if err != nil {
+ return fmt.Errorf("error initializing zero: %w", err)
+ }
+
+ if !restart {
+ if err := worker.ApplyInitialSchema(0, 1); err != nil {
+ return fmt.Errorf("error applying initial schema: %w", err)
+ }
+ }
+
+ if err := schema.LoadFromDb(context.Background()); err != nil {
+ return fmt.Errorf("error loading schema: %w", err)
+ }
+ for _, pred := range schema.State().Predicates() {
+ worker.InitTablet(pred)
+ }
+
+ ns.z = z
+ return nil
+}
diff --git a/go.mod b/go.mod
index 7253865..445539d 100644
--- a/go.mod
+++ b/go.mod
@@ -6,27 +6,36 @@ toolchain go1.23.3
require (
github.com/cavaliergopher/grab/v3 v3.0.1
- github.com/dgraph-io/badger/v4 v4.4.0
- github.com/dgraph-io/dgo/v240 v240.0.1
- github.com/dgraph-io/dgraph/v24 v24.0.3-0.20241202011806-64256ce6cac9
- github.com/dgraph-io/ristretto/v2 v2.0.0
+ github.com/dgraph-io/badger/v4 v4.5.0
+ github.com/dgraph-io/dgo/v240 v240.1.0
+ github.com/dgraph-io/ristretto/v2 v2.1.0
+ github.com/hypermodeinc/dgraph/v24 v24.0.3-0.20250123224129-a0d027dcffe0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.10.0
- golang.org/x/sync v0.9.0
+ github.com/twpayne/go-geom v1.5.7
+ golang.org/x/sync v0.10.0
+ google.golang.org/protobuf v1.36.2
+)
+
+require (
+ github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+ github.com/sagikazarmark/locafero v0.4.0 // indirect
+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+ github.com/sourcegraph/conc v0.3.0 // indirect
)
require (
contrib.go.opencensus.io/exporter/jaeger v0.2.1 // indirect
contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect
- github.com/DataDog/datadog-go v4.8.3+incompatible // indirect
+ github.com/DataDog/datadog-go v3.5.0+incompatible // indirect
github.com/DataDog/opencensus-go-exporter-datadog v0.0.0-20220622145613-731d59e8b567 // indirect
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
- github.com/IBM/sarama v1.43.3 // indirect
- github.com/Microsoft/go-winio v0.6.2 // indirect
- github.com/agnivade/levenshtein v1.1.1 // indirect
+ github.com/IBM/sarama v1.45.0 // indirect
+ github.com/agnivade/levenshtein v1.0.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.17.0 // indirect
- github.com/blevesearch/bleve/v2 v2.4.3 // indirect
+ // trunk-ignore(osv-scanner/GHSA-9w9f-6mg8-jp7w)
+ github.com/blevesearch/bleve/v2 v2.4.4 // indirect
github.com/blevesearch/bleve_index_api v1.1.12 // indirect
github.com/blevesearch/geo v0.1.20 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
@@ -40,18 +49,18 @@ require (
github.com/dgraph-io/gqlgen v0.13.2 // indirect
github.com/dgraph-io/gqlparser/v2 v2.2.2 // indirect
github.com/dgraph-io/simdjson-go v0.3.0 // indirect
- github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
+ github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect
github.com/dgryski/go-groupvarint v0.0.0-20230630160417-2bfb7969fb3c // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/eapache/go-resiliency v1.7.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
github.com/eapache/queue v1.1.0 // indirect
- github.com/felixge/fgprof v0.9.5 // indirect
+ github.com/felixge/fgprof v0.9.3 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
- github.com/getsentry/sentry-go v0.29.1 // indirect
+ github.com/getsentry/sentry-go v0.31.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-kit/log v0.2.1 // indirect
- github.com/go-logfmt/logfmt v0.6.0 // indirect
+ github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
@@ -61,17 +70,17 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/codesearch v1.2.0 // indirect
github.com/google/flatbuffers v24.3.25+incompatible // indirect
- github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect
+ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
- github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect
+ github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
- github.com/hashicorp/go-sockaddr v1.0.6 // indirect
+ github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
- github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/vault/api v1.15.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
@@ -81,40 +90,40 @@ require (
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
+ github.com/klauspost/cpuid v1.2.3 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/magiconair/properties v1.8.7 // indirect
- github.com/minio/md5-simd v1.1.2 // indirect
+ github.com/minio/md5-simd v1.1.0 // indirect
github.com/minio/minio-go/v6 v6.0.57 // indirect
- github.com/minio/sha256-simd v1.0.1 // indirect
+ github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/panicwrap v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
- github.com/pelletier/go-toml v1.9.5 // indirect
- github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 // indirect
- github.com/pierrec/lz4/v4 v4.1.21 // indirect
+ github.com/philhofer/fwd v1.1.2 // indirect
+ github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
- github.com/prometheus/common v0.59.1 // indirect
+ github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
- github.com/prometheus/statsd_exporter v0.27.1 // indirect
+ github.com/prometheus/statsd_exporter v0.22.7 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
+ github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
+ github.com/sergi/go-diff v1.2.0 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect
github.com/spf13/afero v1.11.0 // indirect
- github.com/spf13/cast v1.3.1 // indirect
+ github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/cobra v1.8.1 // indirect
- github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
- github.com/spf13/viper v1.7.1 // indirect
+ github.com/spf13/viper v1.19.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
- github.com/tinylib/msgp v1.2.1 // indirect
- github.com/twpayne/go-geom v1.5.7 // indirect
- github.com/uber/jaeger-client-go v2.28.0+incompatible // indirect
+ github.com/tinylib/msgp v1.1.8 // indirect
+ github.com/uber/jaeger-client-go v2.25.0+incompatible // indirect
github.com/viterin/partial v1.1.0 // indirect
github.com/viterin/vek v0.4.2 // indirect
github.com/xdg/scram v1.0.5 // indirect
@@ -123,18 +132,18 @@ require (
go.opencensus.io v0.24.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
- golang.org/x/crypto v0.29.0 // indirect
- golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
- golang.org/x/net v0.31.0 // indirect
- golang.org/x/sys v0.27.0 // indirect
- golang.org/x/term v0.26.0 // indirect
- golang.org/x/text v0.20.0 // indirect
+ golang.org/x/crypto v0.32.0 // indirect
+ golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
+ golang.org/x/net v0.34.0 // indirect
+ golang.org/x/sys v0.29.0 // indirect
+ golang.org/x/term v0.28.0 // indirect
+ golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.6.0 // indirect
+ gonum.org/v1/gonum v0.12.0 // indirect
google.golang.org/api v0.196.0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
- google.golang.org/grpc v1.68.0 // indirect
- google.golang.org/protobuf v1.34.2 // indirect
- gopkg.in/DataDog/dd-trace-go.v1 v1.67.1 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
+ google.golang.org/grpc v1.69.2 // indirect
+ gopkg.in/DataDog/dd-trace-go.v1 v1.22.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index 46296ee..d614a10 100644
--- a/go.sum
+++ b/go.sum
@@ -21,7 +21,6 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -41,22 +40,19 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
+github.com/DataDog/datadog-go v3.5.0+incompatible h1:AShr9cqkF+taHjyQgcBcQUt/ZNK+iPq4ROaZwSX5c/U=
github.com/DataDog/datadog-go v3.5.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
-github.com/DataDog/datadog-go v4.8.3+incompatible h1:fNGaYSuObuQb5nzeTQqowRAd9bpDIRRV4/gUtIBjh8Q=
-github.com/DataDog/datadog-go v4.8.3+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/opencensus-go-exporter-datadog v0.0.0-20220622145613-731d59e8b567 h1:Z7zdcyzme2egv0lC43X1Q/+DxHjZflQCnJXX0mDp7+I=
github.com/DataDog/opencensus-go-exporter-datadog v0.0.0-20220622145613-731d59e8b567/go.mod h1:/VV3EFO/hTNQZHAqaj+CPGy2+ioFrP4EX3iRwozubhQ=
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
-github.com/IBM/sarama v1.43.3 h1:Yj6L2IaNvb2mRBop39N7mmJAHBVY3dTPncr3qGVkxPA=
-github.com/IBM/sarama v1.43.3/go.mod h1:FVIRaLrhK3Cla/9FfRF5X9Zua2KpS3SYIXxhac1H+FQ=
-github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
-github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/IBM/sarama v1.45.0 h1:IzeBevTn809IJ/dhNKhP5mpxEXTmELuezO2tgHD9G5E=
+github.com/IBM/sarama v1.45.0/go.mod h1:EEay63m8EZkeumco9TDXf2JT3uDnZsZqFgV46n4yZdY=
+github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
+github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
+github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
-github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
-github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY=
github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
@@ -72,8 +68,6 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -82,9 +76,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bits-and-blooms/bitset v1.17.0 h1:1X2TS7aHz1ELcC0yU1y2stUs/0ig5oMU6STFZGrhvHI=
github.com/bits-and-blooms/bitset v1.17.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
-github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
-github.com/blevesearch/bleve/v2 v2.4.3 h1:XDYj+1prgX84L2Cf+V3ojrOPqXxy0qxyd2uLMmeuD+4=
-github.com/blevesearch/bleve/v2 v2.4.3/go.mod h1:hEPDPrbYw3vyrm5VOa36GyS4bHWuIf4Fflp7460QQXY=
+github.com/blevesearch/bleve/v2 v2.4.4 h1:RwwLGjUm54SwyyykbrZs4vc1qjzYic4ZnAnY9TwNl60=
+github.com/blevesearch/bleve/v2 v2.4.4/go.mod h1:fa2Eo6DP7JR+dMFpQe+WiZXINKSunh7WBtlDGbolKXk=
github.com/blevesearch/bleve_index_api v1.1.12 h1:P4bw9/G/5rulOF7SJ9l4FsDoo7UFJ+5kexNy1RXfegY=
github.com/blevesearch/bleve_index_api v1.1.12/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8=
github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM=
@@ -102,31 +95,19 @@ github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYpt
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chewxy/math32 v1.11.0 h1:8sek2JWqeaKkVnHa7bPVqCEOUPbARo4SGxs6toKyAOo=
github.com/chewxy/math32 v1.11.0/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs=
-github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
-github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
-github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA=
github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -134,34 +115,29 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgraph-io/badger/v4 v4.4.0 h1:rA48XiDynZLyMdlaJl67p9+lqfqwxlgKtCpYLAio7Zk=
-github.com/dgraph-io/badger/v4 v4.4.0/go.mod h1:sONMmPPfbnj9FPwS/etCqky/ULth6CQJuAZSuWCmixE=
-github.com/dgraph-io/dgo/v240 v240.0.1 h1:R0d9Cao3MOghrC9RVXshw6v8Jr/IjKgU2mK9sR9nclc=
-github.com/dgraph-io/dgo/v240 v240.0.1/go.mod h1:urpjhWGdYVSVQAwd000iu4wHyHPpuHpwJ7aILsuGF5A=
-github.com/dgraph-io/dgraph/v24 v24.0.3-0.20241202011806-64256ce6cac9 h1:6ink3iffWaAqHCOX8j35oC8+K2oiDFLwsyNSd40YmBQ=
-github.com/dgraph-io/dgraph/v24 v24.0.3-0.20241202011806-64256ce6cac9/go.mod h1:2e/yPl+J7eEl9eaeeYSGMwuiQAxVh9x2Gm1SsaVvM3o=
+github.com/dgraph-io/badger/v4 v4.5.0 h1:TeJE3I1pIWLBjYhIYCA1+uxrjWEoJXImFBMEBVSm16g=
+github.com/dgraph-io/badger/v4 v4.5.0/go.mod h1:ysgYmIeG8dS/E8kwxT7xHyc7MkmwNYLRoYnFbr7387A=
+github.com/dgraph-io/dgo/v240 v240.1.0 h1:xd8z9kEXDWOAblaLJ2HLg2tXD6ngMQwq3ehLUS7GKNg=
+github.com/dgraph-io/dgo/v240 v240.1.0/go.mod h1:r8WASETKfodzKqThSAhhTNIzcEMychArKKlZXQufWuA=
github.com/dgraph-io/gqlgen v0.13.2 h1:TNhndk+eHKj5qE7BenKKSYdSIdOGhLqxR1rCiMso9KM=
github.com/dgraph-io/gqlgen v0.13.2/go.mod h1:iCOrOv9lngN7KAo+jMgvUPVDlYHdf7qDwsTkQby2Sis=
github.com/dgraph-io/gqlparser/v2 v2.1.1/go.mod h1:MYS4jppjyx8b9tuUtjV7jU1UFZK6P9fvO8TsIsQtRKU=
github.com/dgraph-io/gqlparser/v2 v2.2.2 h1:CnxXOKL4EPguKqcGV/z4u4VoW5izUkOTIsNM6xF+0f4=
github.com/dgraph-io/gqlparser/v2 v2.2.2/go.mod h1:MYS4jppjyx8b9tuUtjV7jU1UFZK6P9fvO8TsIsQtRKU=
-github.com/dgraph-io/ristretto/v2 v2.0.0 h1:l0yiSOtlJvc0otkqyMaDNysg8E9/F/TYZwMbxscNOAQ=
-github.com/dgraph-io/ristretto/v2 v2.0.0/go.mod h1:FVFokF2dRqXyPyeMnK1YDy8Fc6aTe0IKgbcd03CYeEk=
+github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I=
+github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4=
github.com/dgraph-io/simdjson-go v0.3.0 h1:h71LO7vR4LHMPUhuoGN8bqGm1VNfGOlAG8BI6iDUKw0=
github.com/dgraph-io/simdjson-go v0.3.0/go.mod h1:Otpysdjaxj9OGaJusn4pgQV7OFh2bELuHANq0I78uvY=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
-github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-groupvarint v0.0.0-20230630160417-2bfb7969fb3c h1:cHaw4wmusVzAZLEPWOCCGCfu6UvFXx9UboCHQCnjvxY=
github.com/dgryski/go-groupvarint v0.0.0-20230630160417-2bfb7969fb3c/go.mod h1:MlkUQveSLEDbIgq2r1e++tSf0zfzU9mQpa9Qkczl+9Y=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
-github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
-github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
-github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
-github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
-github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
+github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
+github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U=
+github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -182,20 +158,19 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
-github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
-github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/getsentry/sentry-go v0.29.1 h1:DyZuChN8Hz3ARxGVV8ePaNXh1dQ7d76AiB117xcREwA=
-github.com/getsentry/sentry-go v0.29.1/go.mod h1:x3AtIzN01d6SiWkderzaH28Tm0lgkafpJ5Bm3li39O0=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/getsentry/sentry-go v0.31.1 h1:ELVc0h7gwyhnXHDouXkhqTFSO5oslsRDk0++eyE0KJ4=
+github.com/getsentry/sentry-go v0.31.1/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
@@ -213,9 +188,8 @@ github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBj
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
-github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
-github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@@ -223,12 +197,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
-github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
-github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
-github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -240,7 +210,6 @@ github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1Ix
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.3 h1:oDTdz9f5VGVVNGu/Q7UXKWYsD0873HXLHdJUNBsSEKM=
github.com/golang/glog v1.2.3/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -303,9 +272,8 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
-github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
-github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ=
-github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
+github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -319,59 +287,42 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
-github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
-github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
-github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc=
-github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0=
+github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ=
+github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
+github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
-github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I=
-github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
-github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM=
-github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
-github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
-github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA=
github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
+github.com/hypermodeinc/dgraph/v24 v24.0.3-0.20250123224129-a0d027dcffe0 h1:yOWDWYXrF8cfjXbB3fqabYDKcMqJWqgPKAraauinRe4=
+github.com/hypermodeinc/dgraph/v24 v24.0.3-0.20250123224129-a0d027dcffe0/go.mod h1:cxYPGOzHMDPQbv5uf9i2CI/dWKrKcyVmRLX7j/P3DxM=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
-github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
@@ -386,8 +337,6 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -401,16 +350,16 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
+github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
-github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.3/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -425,12 +374,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
-github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
-github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
-github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -442,25 +388,19 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
-github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
-github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v6 v6.0.57 h1:ixPkbKkyD7IhnluRgQpGSpHdpvNVaW6OD5R9IAO/9Tw=
github.com/minio/minio-go/v6 v6.0.57/go.mod h1:5+R/nM9Pwrh0vqF+HbYYDQ84wdUFPyXHkrdT4AIkifM=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
-github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
-github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
+github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
+github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
-github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
-github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/panicwrap v1.0.0 h1:67zIyVakCIvcs69A0FGfZjBdPleaonSgGlXRSRlb6fE=
@@ -479,7 +419,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
@@ -488,16 +427,13 @@ github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKw
github.com/opentracing/basictracer-go v1.1.0/go.mod h1:V2HZueSJEp879yv285Aap1BS69fQMD+MNP1mRs6mBQc=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
-github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
-github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
-github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 h1:jYi87L8j62qkXzaYHAQAhEapgukhenIMZRBKTNRLHJ4=
-github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
-github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
-github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
+github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
+github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
+github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -511,7 +447,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
@@ -526,18 +461,15 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
-github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0=
-github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0=
+github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
@@ -545,25 +477,26 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0=
github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI=
-github.com/prometheus/statsd_exporter v0.27.1 h1:tcRJOmwlA83HPfWzosAgr2+zEN5XDFv+M2mn/uYkn5Y=
-github.com/prometheus/statsd_exporter v0.27.1/go.mod h1:vA6ryDfsN7py/3JApEst6nLTJboq66XsNcJGNmC88NQ=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
+github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
+github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
@@ -573,27 +506,20 @@ github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
-github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
+github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
-github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
-github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
+github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -610,21 +536,20 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
-github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
-github.com/tinylib/msgp v1.2.1 h1:6ypy2qcCznxpP4hpORzhtXyTqrBs7cfM9MCCWY8zsmU=
-github.com/tinylib/msgp v1.2.1/go.mod h1:2vIGs3lcUo8izAATNobrCHevYZC/LMsJtw4JPiYPHro=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
+github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/twpayne/go-geom v1.5.7 h1:7fdceDUr03/MP7rAKOaTV6x9njMiQdxB/D0PDzMTCDc=
github.com/twpayne/go-geom v1.5.7/go.mod h1:y4fTAQtLedXW8eG2Yo4tYrIGN1yIwwKkmA+K3iSHKBA=
+github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.28.0+incompatible h1:G4QSBfvPKvg5ZM2j9MrJFdfI5iSljY/WnJqOGFao6HI=
-github.com/uber/jaeger-client-go v2.28.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
@@ -636,13 +561,11 @@ github.com/xdg/scram v1.0.5 h1:TuS0RFmt5Is5qm9Tm2SoD89OPqe4IRiFtyFY4iwWXsw=
github.com/xdg/scram v1.0.5/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.3 h1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4=
github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd/client/pkg/v3 v3.5.17 h1:XxnDXAWq2pnxqx76ljWwiQ9jylbpC4rvkAeRVOUKKVw=
go.etcd.io/etcd/client/pkg/v3 v3.5.17/go.mod h1:4DqK1TKacp/86nJk4FLQqo6Mn2vvQFBmruW3pP14H/w=
go.etcd.io/etcd/raft/v3 v3.5.17 h1:wHPW/b1oFBw/+HjDAQ9vfr17OIInejTIsmwMZpK1dNo=
@@ -657,23 +580,23 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
-go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
-go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
-go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
-go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
-go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
-go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
+go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
+go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
+go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
+go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
+go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
+go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
+go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
+go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
+go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -682,8 +605,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
-golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
+golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
+golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -697,8 +620,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
-golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
+golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
+golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -721,14 +644,12 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -764,10 +685,11 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
-golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -787,13 +709,12 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
-golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -834,19 +755,19 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
-golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
-golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
+golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
+golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -854,15 +775,15 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
-golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -884,7 +805,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -914,13 +834,17 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
+golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
+golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
-gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
+gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o=
+gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -976,8 +900,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -991,8 +915,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
-google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
+google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
+google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1007,11 +931,10 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
-google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
+google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
+google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+gopkg.in/DataDog/dd-trace-go.v1 v1.22.0 h1:gpWsqqkwUldNZXGJqT69NU9MdEDhLboK1C4nMgR0MWw=
gopkg.in/DataDog/dd-trace-go.v1 v1.22.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
-gopkg.in/DataDog/dd-trace-go.v1 v1.67.1 h1:frgcpZ18wmpj+/TwyDJM8057M65aOdgaxLiZ8pb1PFU=
-gopkg.in/DataDog/dd-trace-go.v1 v1.67.1/go.mod h1:6DdiJPKOeJfZyd/IUGCAd5elY8qPGkztK6wbYYsMjag=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1021,11 +944,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/live.go b/live.go
index 3e89e05..f6ba0dc 100644
--- a/live.go
+++ b/live.go
@@ -1,3 +1,12 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
package modusdb
import (
@@ -10,9 +19,9 @@ import (
"time"
"github.com/dgraph-io/dgo/v240/protos/api"
- "github.com/dgraph-io/dgraph/v24/chunker"
- "github.com/dgraph-io/dgraph/v24/filestore"
- "github.com/dgraph-io/dgraph/v24/x"
+ "github.com/hypermodeinc/dgraph/v24/chunker"
+ "github.com/hypermodeinc/dgraph/v24/filestore"
+ "github.com/hypermodeinc/dgraph/v24/x"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
@@ -237,7 +246,7 @@ func (l *liveLoader) uid(ns uint64, val string) (string, error) {
return uid, nil
}
- asUID, err := l.n.db.LeaseUIDs(1)
+ asUID, err := l.n.engine.LeaseUIDs(1)
if err != nil {
return "", fmt.Errorf("error allocating UID: %w", err)
}
diff --git a/load_test/live_benchmark_test.go b/load_test/live_benchmark_test.go
new file mode 100644
index 0000000..daa0536
--- /dev/null
+++ b/load_test/live_benchmark_test.go
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package load_test
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "runtime"
+ "runtime/pprof"
+ "testing"
+
+ "github.com/hypermodeinc/modusdb"
+ "github.com/stretchr/testify/require"
+)
+
+func BenchmarkDatabaseOperations(b *testing.B) {
+ setupProfiler := func(b *testing.B) *os.File {
+ f, err := os.Create("cpu_profile.prof")
+ if err != nil {
+ b.Fatal("could not create CPU profile: ", err)
+ }
+ if err := pprof.StartCPUProfile(f); err != nil {
+ b.Fatal("could not start CPU profiling: ", err)
+ }
+ return f
+ }
+
+ reportMemStats := func(b *testing.B, initialAlloc uint64) {
+ var ms runtime.MemStats
+ runtime.ReadMemStats(&ms)
+ b.ReportMetric(float64(ms.Alloc-initialAlloc)/float64(b.N), "bytes/op")
+ b.ReportMetric(float64(ms.NumGC), "total-gc-cycles")
+ }
+
+ b.Run("DropAndLoad", func(b *testing.B) {
+ f := setupProfiler(b)
+ defer f.Close()
+ defer pprof.StopCPUProfile()
+
+ var ms runtime.MemStats
+ runtime.ReadMemStats(&ms)
+ initialAlloc := ms.Alloc
+
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(b.TempDir()))
+ require.NoError(b, err)
+ defer engine.Close()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ dataFolder := b.TempDir()
+ schemaFile := filepath.Join(dataFolder, "data.schema")
+ dataFile := filepath.Join(dataFolder, "data.rdf")
+ require.NoError(b, os.WriteFile(schemaFile, []byte(DbSchema), 0600))
+ require.NoError(b, os.WriteFile(dataFile, []byte(SmallData), 0600))
+ require.NoError(b, engine.Load(context.Background(), schemaFile, dataFile))
+ }
+ reportMemStats(b, initialAlloc)
+ })
+
+ b.Run("Query", func(b *testing.B) {
+ f := setupProfiler(b)
+ defer f.Close()
+ defer pprof.StopCPUProfile()
+
+ var ms runtime.MemStats
+ runtime.ReadMemStats(&ms)
+ initialAlloc := ms.Alloc
+
+ // Setup database with data once
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(b.TempDir()))
+ require.NoError(b, err)
+ defer engine.Close()
+
+ dataFolder := b.TempDir()
+ schemaFile := filepath.Join(dataFolder, "data.schema")
+ dataFile := filepath.Join(dataFolder, "data.rdf")
+ require.NoError(b, os.WriteFile(schemaFile, []byte(DbSchema), 0600))
+ require.NoError(b, os.WriteFile(dataFile, []byte(SmallData), 0600))
+ require.NoError(b, engine.Load(context.Background(), schemaFile, dataFile))
+
+ const query = `{
+ caro(func: allofterms(name@en, "Marc Caro")) {
+ name@en
+ director.film {
+ name@en
+ }
+ }
+ }`
+ const expected = `{
+ "caro": [
+ {
+ "name@en": "Marc Caro",
+ "director.film": [
+ {
+ "name@en": "Delicatessen"
+ },
+ {
+ "name@en": "The City of Lost Children"
+ }
+ ]
+ }
+ ]
+ }`
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ resp, err := engine.GetDefaultNamespace().Query(context.Background(), query)
+ require.NoError(b, err)
+ require.JSONEq(b, expected, string(resp.Json))
+ }
+ reportMemStats(b, initialAlloc)
+ })
+}
diff --git a/live_test.go b/load_test/live_test.go
similarity index 67%
rename from live_test.go
rename to load_test/live_test.go
index b1e173b..10cfde5 100644
--- a/live_test.go
+++ b/load_test/live_test.go
@@ -1,4 +1,13 @@
-package modusdb_test
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package load_test
import (
"context"
@@ -8,8 +17,8 @@ import (
"time"
"github.com/cavaliergopher/grab/v3"
- "github.com/dgraph-io/dgraph/v24/dgraphapi"
- "github.com/dgraph-io/dgraph/v24/systest/1million/common"
+ "github.com/hypermodeinc/dgraph/v24/dgraphapi"
+ "github.com/hypermodeinc/dgraph/v24/systest/1million/common"
"github.com/stretchr/testify/require"
"github.com/hypermodeinc/modusdb"
@@ -19,15 +28,11 @@ const (
baseURL = "https://github.com/dgraph-io/benchmarks/blob/master/data"
oneMillionSchema = baseURL + "/1million.schema?raw=true"
oneMillionRDF = baseURL + "/1million.rdf.gz?raw=true"
-)
-
-func TestLiveLoaderSmall(t *testing.T) {
- const (
- dbSchema = `
+ DbSchema = `
director.film : [uid] @reverse @count .
name : string @index(hash, term, trigram, fulltext) @lang .
`
- data = `
+ SmallData = `
<12534504120601169429> "Marc Caro"@en .
<2698880893682087932> "Delicatessen"@en .
<2698880893682087932> "Delicatessen"@de .
@@ -40,18 +45,20 @@ func TestLiveLoaderSmall(t *testing.T) {
<12534504120601169429> <15617393957106514527> .
<14514306440537019930> <15617393957106514527> .
`
- )
+)
+
+func TestLiveLoaderSmall(t *testing.T) {
- db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
require.NoError(t, err)
- defer db.Close()
+ defer engine.Close()
dataFolder := t.TempDir()
schemaFile := filepath.Join(dataFolder, "data.schema")
dataFile := filepath.Join(dataFolder, "data.rdf")
- require.NoError(t, os.WriteFile(schemaFile, []byte(dbSchema), 0600))
- require.NoError(t, os.WriteFile(dataFile, []byte(data), 0600))
- require.NoError(t, db.Load(context.Background(), schemaFile, dataFile))
+ require.NoError(t, os.WriteFile(schemaFile, []byte(DbSchema), 0600))
+ require.NoError(t, os.WriteFile(dataFile, []byte(SmallData), 0600))
+ require.NoError(t, engine.Load(context.Background(), schemaFile, dataFile))
const query = `{
caro(func: allofterms(name@en, "Marc Caro")) {
@@ -77,15 +84,15 @@ func TestLiveLoaderSmall(t *testing.T) {
]
}`
- resp, err := db.Query(context.Background(), query)
+ resp, err := engine.GetDefaultNamespace().Query(context.Background(), query)
require.NoError(t, err)
require.JSONEq(t, expected, string(resp.Json))
}
func TestLiveLoader1Million(t *testing.T) {
- db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
require.NoError(t, err)
- defer db.Close()
+ defer engine.Close()
baseDir := t.TempDir()
schResp, err := grab.Get(baseDir, oneMillionSchema)
@@ -93,12 +100,12 @@ func TestLiveLoader1Million(t *testing.T) {
dataResp, err := grab.Get(baseDir, oneMillionRDF)
require.NoError(t, err)
- require.NoError(t, db.DropAll(context.Background()))
- require.NoError(t, db.Load(context.Background(), schResp.Filename, dataResp.Filename))
+ require.NoError(t, engine.DropAll(context.Background()))
+ require.NoError(t, engine.Load(context.Background(), schResp.Filename, dataResp.Filename))
for _, tt := range common.OneMillionTCs {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
- resp, err := db.Query(ctx, tt.Query)
+ resp, err := engine.GetDefaultNamespace().Query(ctx, tt.Query)
cancel()
if ctx.Err() == context.DeadlineExceeded {
diff --git a/namespace.go b/namespace.go
index d9446df..b40bbf8 100644
--- a/namespace.go
+++ b/namespace.go
@@ -1,177 +1,44 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
package modusdb
import (
"context"
- "fmt"
- "strconv"
"github.com/dgraph-io/dgo/v240/protos/api"
- "github.com/dgraph-io/dgraph/v24/dql"
- "github.com/dgraph-io/dgraph/v24/edgraph"
- "github.com/dgraph-io/dgraph/v24/protos/pb"
- "github.com/dgraph-io/dgraph/v24/query"
- "github.com/dgraph-io/dgraph/v24/schema"
- "github.com/dgraph-io/dgraph/v24/worker"
- "github.com/dgraph-io/dgraph/v24/x"
)
// Namespace is one of the namespaces in modusDB.
type Namespace struct {
- id uint64
- db *DB
+ id uint64
+ engine *Engine
}
-func (n *Namespace) ID() uint64 {
- return n.id
+func (ns *Namespace) ID() uint64 {
+ return ns.id
}
// DropData drops all the data in the modusDB instance.
-func (n *Namespace) DropData(ctx context.Context) error {
- n.db.mutex.Lock()
- defer n.db.mutex.Unlock()
-
- if !n.db.isOpen {
- return ErrClosedDB
- }
-
- p := &pb.Proposal{Mutations: &pb.Mutations{
- GroupId: 1,
- DropOp: pb.Mutations_DATA,
- DropValue: strconv.FormatUint(n.ID(), 10),
- }}
-
- if err := worker.ApplyMutations(ctx, p); err != nil {
- return fmt.Errorf("error applying mutation: %w", err)
- }
-
- // TODO: insert drop record
- // TODO: should we reset back the timestamp as well?
- return nil
+func (ns *Namespace) DropData(ctx context.Context) error {
+ return ns.engine.dropData(ctx, ns)
}
-func (n *Namespace) AlterSchema(ctx context.Context, sch string) error {
- n.db.mutex.Lock()
- defer n.db.mutex.Unlock()
-
- if !n.db.isOpen {
- return ErrClosedDB
- }
-
- sc, err := schema.ParseWithNamespace(sch, n.ID())
- if err != nil {
- return fmt.Errorf("error parsing schema: %w", err)
- }
- for _, pred := range sc.Preds {
- worker.InitTablet(pred.Predicate)
- }
-
- startTs, err := n.db.z.nextTs()
- if err != nil {
- return err
- }
-
- p := &pb.Proposal{Mutations: &pb.Mutations{
- GroupId: 1,
- StartTs: startTs,
- Schema: sc.Preds,
- Types: sc.Types,
- }}
- if err := worker.ApplyMutations(ctx, p); err != nil {
- return fmt.Errorf("error applying mutation: %w", err)
- }
- return nil
+func (ns *Namespace) AlterSchema(ctx context.Context, sch string) error {
+ return ns.engine.alterSchema(ctx, ns, sch)
}
-func (n *Namespace) Mutate(ctx context.Context, ms []*api.Mutation) (map[string]uint64, error) {
- if len(ms) == 0 {
- return nil, nil
- }
-
- dms := make([]*dql.Mutation, 0, len(ms))
- for _, mu := range ms {
- dm, err := edgraph.ParseMutationObject(mu, false)
- if err != nil {
- return nil, fmt.Errorf("error parsing mutation: %w", err)
- }
- dms = append(dms, dm)
- }
- newUids, err := query.ExtractBlankUIDs(ctx, dms)
- if err != nil {
- return nil, err
- }
- if len(newUids) > 0 {
- num := &pb.Num{Val: uint64(len(newUids)), Type: pb.Num_UID}
- res, err := n.db.z.nextUIDs(num)
- if err != nil {
- return nil, err
- }
-
- curId := res.StartId
- for k := range newUids {
- x.AssertTruef(curId != 0 && curId <= res.EndId, "not enough uids generated")
- newUids[k] = curId
- curId++
- }
- }
- edges, err := query.ToDirectedEdges(dms, newUids)
- if err != nil {
- return nil, err
- }
- ctx = x.AttachNamespace(ctx, n.ID())
-
- n.db.mutex.Lock()
- defer n.db.mutex.Unlock()
-
- if !n.db.isOpen {
- return nil, ErrClosedDB
- }
-
- startTs, err := n.db.z.nextTs()
- if err != nil {
- return nil, err
- }
- commitTs, err := n.db.z.nextTs()
- if err != nil {
- return nil, err
- }
-
- m := &pb.Mutations{
- GroupId: 1,
- StartTs: startTs,
- Edges: edges,
- }
- m.Edges, err = query.ExpandEdges(ctx, m)
- if err != nil {
- return nil, fmt.Errorf("error expanding edges: %w", err)
- }
-
- for _, edge := range m.Edges {
- worker.InitTablet(edge.Attr)
- }
-
- p := &pb.Proposal{Mutations: m, StartTs: startTs}
- if err := worker.ApplyMutations(ctx, p); err != nil {
- return nil, err
- }
-
- return newUids, worker.ApplyCommited(ctx, &pb.OracleDelta{
- Txns: []*pb.TxnStatus{{StartTs: startTs, CommitTs: commitTs}},
- })
+func (ns *Namespace) Mutate(ctx context.Context, ms []*api.Mutation) (map[string]uint64, error) {
+ return ns.engine.mutate(ctx, ns, ms)
}
// Query performs query or mutation or upsert on the given modusDB instance.
-func (n *Namespace) Query(ctx context.Context, query string) (*api.Response, error) {
- n.db.mutex.RLock()
- defer n.db.mutex.RUnlock()
-
- if !n.db.isOpen {
- return nil, ErrClosedDB
- }
-
- ctx = x.AttachNamespace(ctx, n.ID())
- return (&edgraph.Server{}).QueryNoAuth(ctx, &api.Request{
- ReadOnly: true,
- Query: query,
- StartTs: n.db.z.readTs(),
- })
+func (ns *Namespace) Query(ctx context.Context, query string) (*api.Response, error) {
+ return ns.engine.query(ctx, ns, query)
}
diff --git a/unit_test/api_test.go b/unit_test/api_test.go
new file mode 100644
index 0000000..34861b8
--- /dev/null
+++ b/unit_test/api_test.go
@@ -0,0 +1,879 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package unit_test
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/hypermodeinc/modusdb"
+ "github.com/hypermodeinc/modusdb/api/apiutils"
+)
+
+type User struct {
+ Gid uint64 `json:"gid,omitempty"`
+ Name string `json:"name,omitempty"`
+ Age int `json:"age,omitempty"`
+ ClerkId string `json:"clerk_id,omitempty" db:"constraint=unique"`
+}
+
+func TestFirstTimeUser(t *testing.T) {
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ gid, user, err := modusdb.Create(engine, User{
+ Name: "A",
+ Age: 10,
+ ClerkId: "123",
+ })
+
+ require.NoError(t, err)
+ require.Equal(t, user.Gid, gid)
+ require.Equal(t, "A", user.Name)
+ require.Equal(t, 10, user.Age)
+ require.Equal(t, "123", user.ClerkId)
+
+ gid, queriedUser, err := modusdb.Get[User](engine, gid)
+
+ require.NoError(t, err)
+ require.Equal(t, queriedUser.Gid, gid)
+ require.Equal(t, 10, queriedUser.Age)
+ require.Equal(t, "A", queriedUser.Name)
+ require.Equal(t, "123", queriedUser.ClerkId)
+
+ gid, queriedUser2, err := modusdb.Get[User](engine, modusdb.ConstrainedField{
+ Key: "clerk_id",
+ Value: "123",
+ })
+
+ require.NoError(t, err)
+ require.Equal(t, queriedUser.Gid, gid)
+ require.Equal(t, 10, queriedUser2.Age)
+ require.Equal(t, "A", queriedUser2.Name)
+ require.Equal(t, "123", queriedUser2.ClerkId)
+
+ _, _, err = modusdb.Delete[User](engine, gid)
+ require.NoError(t, err)
+
+ _, queriedUser3, err := modusdb.Get[User](engine, gid)
+ require.Error(t, err)
+ require.Equal(t, "no object found", err.Error())
+ require.Equal(t, queriedUser3, User{})
+
+}
+
+func TestCreateApi(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ ClerkId: "123",
+ }
+
+ gid, user, err := modusdb.Create(engine, user, ns1.ID())
+ require.NoError(t, err)
+
+ require.Equal(t, "B", user.Name)
+ require.Equal(t, user.Gid, gid)
+
+ query := `{
+ me(func: has(User.name)) {
+ uid
+ User.name
+ User.age
+ User.clerk_id
+ }
+ }`
+ resp, err := ns1.Query(ctx, query)
+ require.NoError(t, err)
+ require.JSONEq(t, `{"me":[{"uid":"0x2","User.name":"B","User.age":20,"User.clerk_id":"123"}]}`,
+ string(resp.GetJson()))
+}
+
+func TestCreateApiWithNonStruct(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ }
+
+ _, _, err = modusdb.Create[*User](engine, &user, ns1.ID())
+ require.Error(t, err)
+ require.Equal(t, "expected struct, got ptr", err.Error())
+}
+
+func TestGetApi(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ ClerkId: "123",
+ }
+
+ gid, _, err := modusdb.Create(engine, user, ns1.ID())
+ require.NoError(t, err)
+
+ gid, queriedUser, err := modusdb.Get[User](engine, gid, ns1.ID())
+
+ require.NoError(t, err)
+ require.Equal(t, queriedUser.Gid, gid)
+ require.Equal(t, 20, queriedUser.Age)
+ require.Equal(t, "B", queriedUser.Name)
+ require.Equal(t, "123", queriedUser.ClerkId)
+}
+
+func TestGetApiWithConstrainedField(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ ClerkId: "123",
+ }
+
+ _, _, err = modusdb.Create(engine, user, ns1.ID())
+ require.NoError(t, err)
+
+ gid, queriedUser, err := modusdb.Get[User](engine, modusdb.ConstrainedField{
+ Key: "clerk_id",
+ Value: "123",
+ }, ns1.ID())
+
+ require.NoError(t, err)
+ require.Equal(t, queriedUser.Gid, gid)
+ require.Equal(t, 20, queriedUser.Age)
+ require.Equal(t, "B", queriedUser.Name)
+ require.Equal(t, "123", queriedUser.ClerkId)
+}
+
+func TestDeleteApi(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ ClerkId: "123",
+ }
+
+ gid, _, err := modusdb.Create(engine, user, ns1.ID())
+ require.NoError(t, err)
+
+ _, _, err = modusdb.Delete[User](engine, gid, ns1.ID())
+ require.NoError(t, err)
+
+ _, queriedUser, err := modusdb.Get[User](engine, gid, ns1.ID())
+ require.Error(t, err)
+ require.Equal(t, "no object found", err.Error())
+ require.Equal(t, queriedUser, User{})
+
+ _, queriedUser, err = modusdb.Get[User](engine, modusdb.ConstrainedField{
+ Key: "clerk_id",
+ Value: "123",
+ }, ns1.ID())
+ require.Error(t, err)
+ require.Equal(t, "no object found", err.Error())
+ require.Equal(t, queriedUser, User{})
+}
+
+func TestUpsertApi(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ ClerkId: "123",
+ }
+
+ gid, user, _, err := modusdb.Upsert(engine, user, ns1.ID())
+ require.NoError(t, err)
+ require.Equal(t, user.Gid, gid)
+
+ user.Age = 21
+ gid, _, _, err = modusdb.Upsert(engine, user, ns1.ID())
+ require.NoError(t, err)
+ require.Equal(t, user.Gid, gid)
+
+ _, queriedUser, err := modusdb.Get[User](engine, gid, ns1.ID())
+ require.NoError(t, err)
+ require.Equal(t, user.Gid, queriedUser.Gid)
+ require.Equal(t, 21, queriedUser.Age)
+ require.Equal(t, "B", queriedUser.Name)
+ require.Equal(t, "123", queriedUser.ClerkId)
+}
+
+func TestQueryApi(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ users := []User{
+ {Name: "A", Age: 10, ClerkId: "123"},
+ {Name: "B", Age: 20, ClerkId: "123"},
+ {Name: "C", Age: 30, ClerkId: "123"},
+ {Name: "D", Age: 40, ClerkId: "123"},
+ {Name: "E", Age: 50, ClerkId: "123"},
+ }
+
+ for _, user := range users {
+ _, _, err = modusdb.Create(engine, user, ns1.ID())
+ require.NoError(t, err)
+ }
+
+ gids, queriedUsers, err := modusdb.Query[User](engine, modusdb.QueryParams{}, ns1.ID())
+ require.NoError(t, err)
+ require.Len(t, queriedUsers, 5)
+ require.Len(t, gids, 5)
+ require.Equal(t, "A", queriedUsers[0].Name)
+ require.Equal(t, "B", queriedUsers[1].Name)
+ require.Equal(t, "C", queriedUsers[2].Name)
+ require.Equal(t, "D", queriedUsers[3].Name)
+ require.Equal(t, "E", queriedUsers[4].Name)
+
+ gids, queriedUsers, err = modusdb.Query[User](engine, modusdb.QueryParams{
+ Filter: &modusdb.Filter{
+ Field: "age",
+ String: modusdb.StringPredicate{
+ // The reason its a string even for int is bc i cant tell if
+ // user wants to compare with 0 the number or didn't provide a value
+ // TODO: fix this
+ GreaterOrEqual: fmt.Sprintf("%d", 20),
+ },
+ },
+ }, ns1.ID())
+
+ require.NoError(t, err)
+ require.Len(t, queriedUsers, 4)
+ require.Len(t, gids, 4)
+ require.Equal(t, "B", queriedUsers[0].Name)
+ require.Equal(t, "C", queriedUsers[1].Name)
+ require.Equal(t, "D", queriedUsers[2].Name)
+ require.Equal(t, "E", queriedUsers[3].Name)
+}
+
+func TestQueryApiWithPaginiationAndSorting(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ users := []User{
+ {Name: "A", Age: 10, ClerkId: "123"},
+ {Name: "B", Age: 20, ClerkId: "123"},
+ {Name: "C", Age: 30, ClerkId: "123"},
+ {Name: "D", Age: 40, ClerkId: "123"},
+ {Name: "E", Age: 50, ClerkId: "123"},
+ }
+
+ for _, user := range users {
+ _, _, err = modusdb.Create(engine, user, ns1.ID())
+ require.NoError(t, err)
+ }
+
+ gids, queriedUsers, err := modusdb.Query[User](engine, modusdb.QueryParams{
+ Filter: &modusdb.Filter{
+ Field: "age",
+ String: modusdb.StringPredicate{
+ GreaterOrEqual: fmt.Sprintf("%d", 20),
+ },
+ },
+ Pagination: &modusdb.Pagination{
+ Limit: 3,
+ Offset: 1,
+ },
+ }, ns1.ID())
+
+ require.NoError(t, err)
+ require.Len(t, queriedUsers, 3)
+ require.Len(t, gids, 3)
+ require.Equal(t, "C", queriedUsers[0].Name)
+ require.Equal(t, "D", queriedUsers[1].Name)
+ require.Equal(t, "E", queriedUsers[2].Name)
+
+ gids, queriedUsers, err = modusdb.Query[User](engine, modusdb.QueryParams{
+ Pagination: &modusdb.Pagination{
+ Limit: 3,
+ Offset: 1,
+ },
+ Sorting: &modusdb.Sorting{
+ OrderAscField: "age",
+ },
+ }, ns1.ID())
+
+ require.NoError(t, err)
+ require.Len(t, queriedUsers, 3)
+ require.Len(t, gids, 3)
+ require.Equal(t, "B", queriedUsers[0].Name)
+ require.Equal(t, "C", queriedUsers[1].Name)
+ require.Equal(t, "D", queriedUsers[2].Name)
+}
+
+type Project struct {
+ Gid uint64 `json:"gid,omitempty"`
+ Name string `json:"name,omitempty"`
+ ClerkId string `json:"clerk_id,omitempty" db:"constraint=unique"`
+ Branches []Branch `json:"branches,omitempty" readFrom:"type=Branch,field=proj"`
+}
+
+type Branch struct {
+ Gid uint64 `json:"gid,omitempty"`
+ Name string `json:"name,omitempty"`
+ ClerkId string `json:"clerk_id,omitempty" db:"constraint=unique"`
+ Proj Project `json:"proj,omitempty"`
+}
+
+func TestReverseEdgeGet(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ projGid, project, err := modusdb.Create(engine, Project{
+ Name: "P",
+ ClerkId: "456",
+ Branches: []Branch{
+ {Name: "B", ClerkId: "123"},
+ {Name: "B2", ClerkId: "456"},
+ },
+ }, ns1.ID())
+ require.NoError(t, err)
+
+ require.Equal(t, "P", project.Name)
+ require.Equal(t, project.Gid, projGid)
+
+ // modifying a read-only field will be a no-op
+ require.Len(t, project.Branches, 0)
+
+ branch1 := Branch{
+ Name: "B",
+ ClerkId: "123",
+ Proj: Project{
+ Gid: projGid,
+ },
+ }
+
+ branch1Gid, branch1, err := modusdb.Create(engine, branch1, ns1.ID())
+ require.NoError(t, err)
+
+ require.Equal(t, "B", branch1.Name)
+ require.Equal(t, branch1.Gid, branch1Gid)
+ require.Equal(t, projGid, branch1.Proj.Gid)
+ require.Equal(t, "P", branch1.Proj.Name)
+
+ branch2 := Branch{
+ Name: "B2",
+ ClerkId: "456",
+ Proj: Project{
+ Gid: projGid,
+ },
+ }
+
+ branch2Gid, branch2, err := modusdb.Create(engine, branch2, ns1.ID())
+ require.NoError(t, err)
+ require.Equal(t, "B2", branch2.Name)
+ require.Equal(t, branch2.Gid, branch2Gid)
+ require.Equal(t, projGid, branch2.Proj.Gid)
+
+ getProjGid, queriedProject, err := modusdb.Get[Project](engine, projGid, ns1.ID())
+ require.NoError(t, err)
+ require.Equal(t, projGid, getProjGid)
+ require.Equal(t, "P", queriedProject.Name)
+ require.Len(t, queriedProject.Branches, 2)
+ require.Equal(t, "B", queriedProject.Branches[0].Name)
+ require.Equal(t, "B2", queriedProject.Branches[1].Name)
+
+ queryBranchesGids, queriedBranches, err := modusdb.Query[Branch](engine, modusdb.QueryParams{}, ns1.ID())
+ require.NoError(t, err)
+ require.Len(t, queriedBranches, 2)
+ require.Len(t, queryBranchesGids, 2)
+ require.Equal(t, "B", queriedBranches[0].Name)
+ require.Equal(t, "B2", queriedBranches[1].Name)
+
+ // max depth is 2, so we should not see the branches within project
+ require.Len(t, queriedBranches[0].Proj.Branches, 0)
+
+ _, _, err = modusdb.Delete[Project](engine, projGid, ns1.ID())
+ require.NoError(t, err)
+
+ queryBranchesGids, queriedBranches, err = modusdb.Query[Branch](engine, modusdb.QueryParams{}, ns1.ID())
+ require.NoError(t, err)
+ require.Len(t, queriedBranches, 2)
+ require.Len(t, queryBranchesGids, 2)
+ require.Equal(t, "B", queriedBranches[0].Name)
+ require.Equal(t, "B2", queriedBranches[1].Name)
+}
+
+func TestReverseEdgeQuery(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ projects := []Project{
+ {Name: "P1", ClerkId: "456"},
+ {Name: "P2", ClerkId: "789"},
+ }
+
+ branchCounter := 1
+ clerkCounter := 100
+
+ for _, project := range projects {
+ projGid, project, err := modusdb.Create(engine, project, ns1.ID())
+ require.NoError(t, err)
+ require.Equal(t, project.Name, project.Name)
+ require.Equal(t, project.Gid, projGid)
+
+ branches := []Branch{
+ {Name: fmt.Sprintf("B%d", branchCounter), ClerkId: fmt.Sprintf("%d", clerkCounter), Proj: Project{Gid: projGid}},
+ {Name: fmt.Sprintf("B%d", branchCounter+1), ClerkId: fmt.Sprintf("%d", clerkCounter+1), Proj: Project{Gid: projGid}},
+ }
+ branchCounter += 2
+ clerkCounter += 2
+
+ for _, branch := range branches {
+ branchGid, branch, err := modusdb.Create(engine, branch, ns1.ID())
+ require.NoError(t, err)
+ require.Equal(t, branch.Name, branch.Name)
+ require.Equal(t, branch.Gid, branchGid)
+ require.Equal(t, projGid, branch.Proj.Gid)
+ }
+ }
+
+ queriedProjectsGids, queriedProjects, err := modusdb.Query[Project](engine, modusdb.QueryParams{}, ns1.ID())
+ require.NoError(t, err)
+ require.Len(t, queriedProjects, 2)
+ require.Len(t, queriedProjectsGids, 2)
+ require.Equal(t, "P1", queriedProjects[0].Name)
+ require.Equal(t, "P2", queriedProjects[1].Name)
+ require.Len(t, queriedProjects[0].Branches, 2)
+ require.Len(t, queriedProjects[1].Branches, 2)
+ require.Equal(t, "B1", queriedProjects[0].Branches[0].Name)
+ require.Equal(t, "B2", queriedProjects[0].Branches[1].Name)
+ require.Equal(t, "B3", queriedProjects[1].Branches[0].Name)
+ require.Equal(t, "B4", queriedProjects[1].Branches[1].Name)
+}
+
+func TestNestedObjectMutation(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ branch := Branch{
+ Name: "B",
+ ClerkId: "123",
+ Proj: Project{
+ Name: "P",
+ ClerkId: "456",
+ },
+ }
+
+ gid, branch, err := modusdb.Create(engine, branch, ns1.ID())
+ require.NoError(t, err)
+
+ require.Equal(t, "B", branch.Name)
+ require.Equal(t, branch.Gid, gid)
+ require.NotEqual(t, uint64(0), branch.Proj.Gid)
+ require.Equal(t, "P", branch.Proj.Name)
+
+ query := `{
+ me(func: has(Branch.name)) {
+ uid
+ Branch.name
+ Branch.clerk_id
+ Branch.proj {
+ uid
+ Project.name
+ Project.clerk_id
+ }
+ }
+ }`
+ resp, err := ns1.Query(ctx, query)
+ require.NoError(t, err)
+ require.JSONEq(t,
+ `{"me":[{"uid":"0x2","Branch.name":"B","Branch.clerk_id":"123","Branch.proj":
+ {"uid":"0x3","Project.name":"P","Project.clerk_id":"456"}}]}`,
+ string(resp.GetJson()))
+
+ gid, queriedBranch, err := modusdb.Get[Branch](engine, gid, ns1.ID())
+ require.NoError(t, err)
+ require.Equal(t, queriedBranch.Gid, gid)
+ require.Equal(t, "B", queriedBranch.Name)
+
+}
+
+func TestLinkingObjectsByConstrainedFields(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ projGid, project, err := modusdb.Create(engine, Project{
+ Name: "P",
+ ClerkId: "456",
+ }, ns1.ID())
+ require.NoError(t, err)
+
+ require.Equal(t, "P", project.Name)
+ require.Equal(t, project.Gid, projGid)
+
+ branch := Branch{
+ Name: "B",
+ ClerkId: "123",
+ Proj: Project{
+ Name: "P",
+ ClerkId: "456",
+ },
+ }
+
+ gid, branch, err := modusdb.Create(engine, branch, ns1.ID())
+ require.NoError(t, err)
+
+ require.Equal(t, "B", branch.Name)
+ require.Equal(t, branch.Gid, gid)
+ require.Equal(t, projGid, branch.Proj.Gid)
+ require.Equal(t, "P", branch.Proj.Name)
+
+ query := `{
+ me(func: has(Branch.name)) {
+ uid
+ Branch.name
+ Branch.clerk_id
+ Branch.proj {
+ uid
+ Project.name
+ Project.clerk_id
+ }
+ }
+ }`
+ resp, err := ns1.Query(ctx, query)
+ require.NoError(t, err)
+ require.JSONEq(t,
+ `{"me":[{"uid":"0x3","Branch.name":"B","Branch.clerk_id":"123","Branch.proj":
+ {"uid":"0x2","Project.name":"P","Project.clerk_id":"456"}}]}`,
+ string(resp.GetJson()))
+
+ gid, queriedBranch, err := modusdb.Get[Branch](engine, gid, ns1.ID())
+ require.NoError(t, err)
+ require.Equal(t, queriedBranch.Gid, gid)
+ require.Equal(t, "B", queriedBranch.Name)
+
+}
+
+func TestLinkingObjectsByGid(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ projGid, project, err := modusdb.Create(engine, Project{
+ Name: "P",
+ ClerkId: "456",
+ }, ns1.ID())
+ require.NoError(t, err)
+
+ require.Equal(t, "P", project.Name)
+ require.Equal(t, project.Gid, projGid)
+
+ branch := Branch{
+ Name: "B",
+ ClerkId: "123",
+ Proj: Project{
+ Gid: projGid,
+ },
+ }
+
+ gid, branch, err := modusdb.Create(engine, branch, ns1.ID())
+ require.NoError(t, err)
+
+ require.Equal(t, "B", branch.Name)
+ require.Equal(t, branch.Gid, gid)
+ require.Equal(t, projGid, branch.Proj.Gid)
+ require.Equal(t, "P", branch.Proj.Name)
+
+ query := `{
+ me(func: has(Branch.name)) {
+ uid
+ Branch.name
+ Branch.clerk_id
+ Branch.proj {
+ uid
+ Project.name
+ Project.clerk_id
+ }
+ }
+ }`
+ resp, err := ns1.Query(ctx, query)
+ require.NoError(t, err)
+ require.JSONEq(t,
+ `{"me":[{"uid":"0x3","Branch.name":"B","Branch.clerk_id":"123",
+ "Branch.proj":{"uid":"0x2","Project.name":"P","Project.clerk_id":"456"}}]}`,
+ string(resp.GetJson()))
+
+ gid, queriedBranch, err := modusdb.Get[Branch](engine, gid, ns1.ID())
+ require.NoError(t, err)
+ require.Equal(t, queriedBranch.Gid, gid)
+ require.Equal(t, "B", queriedBranch.Name)
+
+}
+
+type BadProject struct {
+ Name string `json:"name,omitempty"`
+ ClerkId string `json:"clerk_id,omitempty"`
+}
+
+type BadBranch struct {
+ Gid uint64 `json:"gid,omitempty"`
+ Name string `json:"name,omitempty"`
+ ClerkId string `json:"clerk_id,omitempty" db:"constraint=unique"`
+ Proj BadProject `json:"proj,omitempty"`
+}
+
+func TestNestedObjectMutationWithBadType(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ branch := BadBranch{
+ Name: "B",
+ ClerkId: "123",
+ Proj: BadProject{
+ Name: "P",
+ ClerkId: "456",
+ },
+ }
+
+ _, _, err = modusdb.Create(engine, branch, ns1.ID())
+ require.Error(t, err)
+ require.Equal(t, fmt.Sprintf(apiutils.NoUniqueConstr, "BadProject"), err.Error())
+
+ proj := BadProject{
+ Name: "P",
+ ClerkId: "456",
+ }
+
+ _, _, err = modusdb.Create(engine, proj, ns1.ID())
+ require.Error(t, err)
+ require.Equal(t, fmt.Sprintf(apiutils.NoUniqueConstr, "BadProject"), err.Error())
+
+}
+
+type Document struct {
+ Gid uint64 `json:"gid,omitempty"`
+ Text string `json:"text,omitempty"`
+ TextVec []float32 `json:"textVec,omitempty" db:"constraint=vector"`
+}
+
+func TestVectorIndexSearchTyped(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ documents := []Document{
+ {Text: "apple", TextVec: []float32{0.1, 0.1, 0.0}},
+ {Text: "banana", TextVec: []float32{0.0, 1.0, 0.0}},
+ {Text: "carrot", TextVec: []float32{0.0, 0.0, 1.0}},
+ {Text: "dog", TextVec: []float32{1.0, 1.0, 0.0}},
+ {Text: "elephant", TextVec: []float32{0.0, 1.0, 1.0}},
+ {Text: "fox", TextVec: []float32{1.0, 0.0, 1.0}},
+ {Text: "gorilla", TextVec: []float32{1.0, 1.0, 1.0}},
+ }
+
+ for _, doc := range documents {
+ _, _, err = modusdb.Create(engine, doc, ns1.ID())
+ require.NoError(t, err)
+ }
+
+ const query = `
+ {
+ documents(func: similar_to(Document.textVec, 5, "[0.1,0.1,0.1]")) {
+ Document.text
+ }
+ }`
+
+ resp, err := ns1.Query(ctx, query)
+ require.NoError(t, err)
+ require.JSONEq(t, `{
+ "documents":[
+ {"Document.text":"apple"},
+ {"Document.text":"dog"},
+ {"Document.text":"elephant"},
+ {"Document.text":"fox"},
+ {"Document.text":"gorilla"}
+ ]
+ }`, string(resp.GetJson()))
+
+ const query2 = `
+ {
+ documents(func: type("Document")) @filter(similar_to(Document.textVec, 5, "[0.1,0.1,0.1]")) {
+ Document.text
+ }
+ }`
+
+ resp, err = ns1.Query(ctx, query2)
+ require.NoError(t, err)
+ require.JSONEq(t, `{
+ "documents":[
+ {"Document.text":"apple"},
+ {"Document.text":"dog"},
+ {"Document.text":"elephant"},
+ {"Document.text":"fox"},
+ {"Document.text":"gorilla"}
+ ]
+ }`, string(resp.GetJson()))
+}
+
+func TestVectorIndexSearchWithQuery(t *testing.T) {
+ ctx := context.Background()
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer engine.Close()
+
+ ns1, err := engine.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, ns1.DropData(ctx))
+
+ documents := []Document{
+ {Text: "apple", TextVec: []float32{0.1, 0.1, 0.0}},
+ {Text: "banana", TextVec: []float32{0.0, 1.0, 0.0}},
+ {Text: "carrot", TextVec: []float32{0.0, 0.0, 1.0}},
+ {Text: "dog", TextVec: []float32{1.0, 1.0, 0.0}},
+ {Text: "elephant", TextVec: []float32{0.0, 1.0, 1.0}},
+ {Text: "fox", TextVec: []float32{1.0, 0.0, 1.0}},
+ {Text: "gorilla", TextVec: []float32{1.0, 1.0, 1.0}},
+ }
+
+ for _, doc := range documents {
+ _, _, err = modusdb.Create(engine, doc, ns1.ID())
+ require.NoError(t, err)
+ }
+
+ gids, docs, err := modusdb.Query[Document](engine, modusdb.QueryParams{
+ Filter: &modusdb.Filter{
+ Field: "textVec",
+ Vector: modusdb.VectorPredicate{
+ SimilarTo: []float32{0.1, 0.1, 0.1},
+ TopK: 5,
+ },
+ },
+ }, ns1.ID())
+
+ require.NoError(t, err)
+ require.Len(t, docs, 5)
+ require.Len(t, gids, 5)
+ require.Equal(t, "apple", docs[0].Text)
+ require.Equal(t, "dog", docs[1].Text)
+ require.Equal(t, "elephant", docs[2].Text)
+ require.Equal(t, "fox", docs[3].Text)
+ require.Equal(t, "gorilla", docs[4].Text)
+}
diff --git a/db_test.go b/unit_test/engine_test.go
similarity index 52%
rename from db_test.go
rename to unit_test/engine_test.go
index bbcc9ae..0996bd4 100644
--- a/db_test.go
+++ b/unit_test/engine_test.go
@@ -1,4 +1,13 @@
-package modusdb_test
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package unit_test
import (
"bytes"
@@ -16,14 +25,14 @@ import (
func TestRestart(t *testing.T) {
dataDir := t.TempDir()
- db, err := modusdb.New(modusdb.NewDefaultConfig(dataDir))
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(dataDir))
require.NoError(t, err)
- defer func() { db.Close() }()
+ defer func() { engine.Close() }()
- require.NoError(t, db.DropAll(context.Background()))
- require.NoError(t, db.AlterSchema(context.Background(), "name: string @index(term) ."))
+ require.NoError(t, engine.DropAll(context.Background()))
+ require.NoError(t, engine.GetDefaultNamespace().AlterSchema(context.Background(), "name: string @index(term) ."))
- _, err = db.Mutate(context.Background(), []*api.Mutation{
+ _, err = engine.GetDefaultNamespace().Mutate(context.Background(), []*api.Mutation{
{
Set: []*api.NQuad{
{
@@ -42,27 +51,27 @@ func TestRestart(t *testing.T) {
name
}
}`
- qresp, err := db.Query(context.Background(), query)
+ qresp, err := engine.GetDefaultNamespace().Query(context.Background(), query)
require.NoError(t, err)
require.JSONEq(t, `{"me":[{"name":"A"}]}`, string(qresp.GetJson()))
- db.Close()
- db, err = modusdb.New(modusdb.NewDefaultConfig(dataDir))
+ engine.Close()
+ engine, err = modusdb.NewEngine(modusdb.NewDefaultConfig(dataDir))
require.NoError(t, err)
- qresp, err = db.Query(context.Background(), query)
+ qresp, err = engine.GetDefaultNamespace().Query(context.Background(), query)
require.NoError(t, err)
require.JSONEq(t, `{"me":[{"name":"A"}]}`, string(qresp.GetJson()))
- require.NoError(t, db.DropAll(context.Background()))
+ require.NoError(t, engine.DropAll(context.Background()))
}
func TestSchemaQuery(t *testing.T) {
- db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
require.NoError(t, err)
- defer db.Close()
+ defer engine.Close()
- require.NoError(t, db.DropAll(context.Background()))
- require.NoError(t, db.AlterSchema(context.Background(), `
+ require.NoError(t, engine.DropAll(context.Background()))
+ require.NoError(t, engine.GetDefaultNamespace().AlterSchema(context.Background(), `
name: string @index(exact) .
age: int .
married: bool .
@@ -70,7 +79,7 @@ func TestSchemaQuery(t *testing.T) {
dob: datetime .
`))
- resp, err := db.Query(context.Background(), `schema(pred: [name, age]) {type}`)
+ resp, err := engine.GetDefaultNamespace().Query(context.Background(), `schema(pred: [name, age]) {type}`)
require.NoError(t, err)
require.JSONEq(t,
@@ -86,15 +95,15 @@ func TestBasicVector(t *testing.T) {
}
vectBytes := buf.Bytes()
- db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
require.NoError(t, err)
- defer db.Close()
+ defer engine.Close()
- require.NoError(t, db.DropAll(context.Background()))
- require.NoError(t, db.AlterSchema(context.Background(),
+ require.NoError(t, engine.DropAll(context.Background()))
+ require.NoError(t, engine.GetDefaultNamespace().AlterSchema(context.Background(),
`project_description_v: float32vector @index(hnsw(exponent: "5", metric: "euclidean")) .`))
- uids, err := db.Mutate(context.Background(), []*api.Mutation{{
+ uids, err := engine.GetDefaultNamespace().Mutate(context.Background(), []*api.Mutation{{
Set: []*api.NQuad{{
Subject: "_:vector",
Predicate: "project_description_v",
@@ -110,7 +119,7 @@ func TestBasicVector(t *testing.T) {
t.Fatalf("Expected non-zero uid")
}
- resp, err := db.Query(context.Background(), fmt.Sprintf(`query {
+ resp, err := engine.GetDefaultNamespace().Query(context.Background(), fmt.Sprintf(`query {
q (func: uid(%v)) {
project_description_v
}
diff --git a/namespace_test.go b/unit_test/namespace_test.go
similarity index 58%
rename from namespace_test.go
rename to unit_test/namespace_test.go
index 655e216..cf01753 100644
--- a/namespace_test.go
+++ b/unit_test/namespace_test.go
@@ -1,4 +1,13 @@
-package modusdb_test
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package unit_test
import (
"context"
@@ -10,18 +19,18 @@ import (
"github.com/hypermodeinc/modusdb"
)
-func TestNonGalaxyNamespace(t *testing.T) {
- db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+func TestNonGalaxyDB(t *testing.T) {
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
require.NoError(t, err)
- defer db.Close()
+ defer engine.Close()
- db1, err := db.CreateNamespace()
+ ns1, err := engine.CreateNamespace()
require.NoError(t, err)
- require.NoError(t, db1.DropData(context.Background()))
- require.NoError(t, db1.AlterSchema(context.Background(), "name: string @index(exact) ."))
+ require.NoError(t, ns1.DropData(context.Background()))
+ require.NoError(t, ns1.AlterSchema(context.Background(), "name: string @index(exact) ."))
- _, err = db1.Mutate(context.Background(), []*api.Mutation{
+ _, err = ns1.Mutate(context.Background(), []*api.Mutation{
{
Set: []*api.NQuad{
{
@@ -39,24 +48,24 @@ func TestNonGalaxyNamespace(t *testing.T) {
name
}
}`
- resp, err := db1.Query(context.Background(), query)
+ resp, err := ns1.Query(context.Background(), query)
require.NoError(t, err)
require.JSONEq(t, `{"me":[{"name":"A"}]}`, string(resp.GetJson()))
}
func TestDropData(t *testing.T) {
- db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
require.NoError(t, err)
- defer db.Close()
+ defer engine.Close()
- db1, err := db.CreateNamespace()
+ ns1, err := engine.CreateNamespace()
require.NoError(t, err)
- require.NoError(t, db1.DropData(context.Background()))
- require.NoError(t, db1.AlterSchema(context.Background(), "name: string @index(exact) ."))
+ require.NoError(t, ns1.DropData(context.Background()))
+ require.NoError(t, ns1.AlterSchema(context.Background(), "name: string @index(exact) ."))
- _, err = db1.Mutate(context.Background(), []*api.Mutation{
+ _, err = ns1.Mutate(context.Background(), []*api.Mutation{
{
Set: []*api.NQuad{
{
@@ -74,30 +83,30 @@ func TestDropData(t *testing.T) {
name
}
}`
- resp, err := db1.Query(context.Background(), query)
+ resp, err := ns1.Query(context.Background(), query)
require.NoError(t, err)
require.JSONEq(t, `{"me":[{"name":"A"}]}`, string(resp.GetJson()))
- require.NoError(t, db1.DropData(context.Background()))
+ require.NoError(t, ns1.DropData(context.Background()))
- resp, err = db1.Query(context.Background(), query)
+ resp, err = ns1.Query(context.Background(), query)
require.NoError(t, err)
require.JSONEq(t, `{"me":[]}`, string(resp.GetJson()))
}
-func TestMultipleNamespaces(t *testing.T) {
- db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+func TestMultipleDBs(t *testing.T) {
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
require.NoError(t, err)
- defer db.Close()
+ defer engine.Close()
- db0, err := db.GetNamespace(0)
+ db0, err := engine.GetNamespace(0)
require.NoError(t, err)
- db1, err := db.CreateNamespace()
+ ns1, err := engine.CreateNamespace()
require.NoError(t, err)
- require.NoError(t, db.DropAll(context.Background()))
+ require.NoError(t, engine.DropAll(context.Background()))
require.NoError(t, db0.AlterSchema(context.Background(), "name: string @index(exact) ."))
- require.NoError(t, db1.AlterSchema(context.Background(), "name: string @index(exact) ."))
+ require.NoError(t, ns1.AlterSchema(context.Background(), "name: string @index(exact) ."))
_, err = db0.Mutate(context.Background(), []*api.Mutation{
{
@@ -112,7 +121,7 @@ func TestMultipleNamespaces(t *testing.T) {
})
require.NoError(t, err)
- _, err = db1.Mutate(context.Background(), []*api.Mutation{
+ _, err = ns1.Mutate(context.Background(), []*api.Mutation{
{
Set: []*api.NQuad{
{
@@ -134,29 +143,29 @@ func TestMultipleNamespaces(t *testing.T) {
require.NoError(t, err)
require.JSONEq(t, `{"me":[{"name":"A"}]}`, string(resp.GetJson()))
- resp, err = db1.Query(context.Background(), query)
+ resp, err = ns1.Query(context.Background(), query)
require.NoError(t, err)
require.JSONEq(t, `{"me":[{"name":"B"}]}`, string(resp.GetJson()))
- require.NoError(t, db1.DropData(context.Background()))
- resp, err = db1.Query(context.Background(), query)
+ require.NoError(t, ns1.DropData(context.Background()))
+ resp, err = ns1.Query(context.Background(), query)
require.NoError(t, err)
require.JSONEq(t, `{"me":[]}`, string(resp.GetJson()))
}
-func TestQueryWrongNamespace(t *testing.T) {
- db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+func TestQueryWrongDB(t *testing.T) {
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
require.NoError(t, err)
- defer db.Close()
+ defer engine.Close()
- db0, err := db.GetNamespace(0)
+ db0, err := engine.GetNamespace(0)
require.NoError(t, err)
- db1, err := db.CreateNamespace()
+ ns1, err := engine.CreateNamespace()
require.NoError(t, err)
- require.NoError(t, db.DropAll(context.Background()))
+ require.NoError(t, engine.DropAll(context.Background()))
require.NoError(t, db0.AlterSchema(context.Background(), "name: string @index(exact) ."))
- require.NoError(t, db1.AlterSchema(context.Background(), "name: string @index(exact) ."))
+ require.NoError(t, ns1.AlterSchema(context.Background(), "name: string @index(exact) ."))
_, err = db0.Mutate(context.Background(), []*api.Mutation{
{
@@ -178,24 +187,24 @@ func TestQueryWrongNamespace(t *testing.T) {
}
}`
- resp, err := db1.Query(context.Background(), query)
+ resp, err := ns1.Query(context.Background(), query)
require.NoError(t, err)
require.JSONEq(t, `{"me":[]}`, string(resp.GetJson()))
}
-func TestTwoNamespaces(t *testing.T) {
- db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+func TestTwoDBs(t *testing.T) {
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
require.NoError(t, err)
- defer db.Close()
+ defer engine.Close()
- db0, err := db.GetNamespace(0)
+ db0, err := engine.GetNamespace(0)
require.NoError(t, err)
- db1, err := db.CreateNamespace()
+ ns1, err := engine.CreateNamespace()
require.NoError(t, err)
- require.NoError(t, db.DropAll(context.Background()))
+ require.NoError(t, engine.DropAll(context.Background()))
require.NoError(t, db0.AlterSchema(context.Background(), "foo: string @index(exact) ."))
- require.NoError(t, db1.AlterSchema(context.Background(), "bar: string @index(exact) ."))
+ require.NoError(t, ns1.AlterSchema(context.Background(), "bar: string @index(exact) ."))
_, err = db0.Mutate(context.Background(), []*api.Mutation{
{
@@ -210,7 +219,7 @@ func TestTwoNamespaces(t *testing.T) {
})
require.NoError(t, err)
- _, err = db1.Mutate(context.Background(), []*api.Mutation{
+ _, err = ns1.Mutate(context.Background(), []*api.Mutation{
{
Set: []*api.NQuad{
{
@@ -237,23 +246,23 @@ func TestTwoNamespaces(t *testing.T) {
bar
}
}`
- resp, err = db1.Query(context.Background(), query)
+ resp, err = ns1.Query(context.Background(), query)
require.NoError(t, err)
require.JSONEq(t, `{"me":[{"bar":"B"}]}`, string(resp.GetJson()))
}
-func TestNamespaceDBRestart(t *testing.T) {
+func TestDBDBRestart(t *testing.T) {
dataDir := t.TempDir()
- db, err := modusdb.New(modusdb.NewDefaultConfig(dataDir))
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(dataDir))
require.NoError(t, err)
- defer func() { db.Close() }()
+ defer func() { engine.Close() }()
- db1, err := db.CreateNamespace()
+ ns1, err := engine.CreateNamespace()
require.NoError(t, err)
- ns1 := db1.ID()
+ ns1Id := ns1.ID()
- require.NoError(t, db1.AlterSchema(context.Background(), "bar: string @index(exact) ."))
- _, err = db1.Mutate(context.Background(), []*api.Mutation{
+ require.NoError(t, ns1.AlterSchema(context.Background(), "bar: string @index(exact) ."))
+ _, err = ns1.Mutate(context.Background(), []*api.Mutation{
{
Set: []*api.NQuad{
{
@@ -266,15 +275,15 @@ func TestNamespaceDBRestart(t *testing.T) {
})
require.NoError(t, err)
- db.Close()
- db, err = modusdb.New(modusdb.NewDefaultConfig(dataDir))
+ engine.Close()
+ engine, err = modusdb.NewEngine(modusdb.NewDefaultConfig(dataDir))
require.NoError(t, err)
- db2, err := db.CreateNamespace()
+ db2, err := engine.CreateNamespace()
require.NoError(t, err)
- require.Greater(t, db2.ID(), ns1)
+ require.Greater(t, db2.ID(), ns1Id)
- db1, err = db.GetNamespace(ns1)
+ ns1, err = engine.GetNamespace(ns1Id)
require.NoError(t, err)
query := `{
@@ -282,7 +291,7 @@ func TestNamespaceDBRestart(t *testing.T) {
bar
}
}`
- resp, err := db1.Query(context.Background(), query)
+ resp, err := ns1.Query(context.Background(), query)
require.NoError(t, err)
require.JSONEq(t, `{"me":[{"bar":"B"}]}`, string(resp.GetJson()))
}
diff --git a/vector_test.go b/unit_test/vector_test.go
similarity index 61%
rename from vector_test.go
rename to unit_test/vector_test.go
index 135d438..4b7d930 100644
--- a/vector_test.go
+++ b/unit_test/vector_test.go
@@ -1,4 +1,13 @@
-package modusdb_test
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICENSE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package unit_test
import (
"context"
@@ -8,7 +17,7 @@ import (
"testing"
"github.com/dgraph-io/dgo/v240/protos/api"
- "github.com/dgraph-io/dgraph/v24/dgraphapi"
+ "github.com/hypermodeinc/dgraph/v24/dgraphapi"
"github.com/stretchr/testify/require"
"github.com/hypermodeinc/modusdb"
@@ -20,20 +29,20 @@ const (
)
func TestVectorDelete(t *testing.T) {
- db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ engine, err := modusdb.NewEngine(modusdb.NewDefaultConfig(t.TempDir()))
require.NoError(t, err)
- defer db.Close()
+ defer engine.Close()
- require.NoError(t, db.DropAll(context.Background()))
- require.NoError(t, db.AlterSchema(context.Background(),
+ require.NoError(t, engine.DropAll(context.Background()))
+ require.NoError(t, engine.GetDefaultNamespace().AlterSchema(context.Background(),
fmt.Sprintf(vectorSchemaWithIndex, "vtest", "4", "euclidean")))
// insert random vectors
- assignIDs, err := db.LeaseUIDs(numVectors + 1)
+ assignIDs, err := engine.LeaseUIDs(numVectors + 1)
require.NoError(t, err)
//nolint:gosec
rdf, vectors := dgraphapi.GenerateRandomVectors(int(assignIDs.StartId)-10, int(assignIDs.EndId)-10, 10, "vtest")
- _, err = db.Mutate(context.Background(), []*api.Mutation{{SetNquads: []byte(rdf)}})
+ _, err = engine.GetDefaultNamespace().Mutate(context.Background(), []*api.Mutation{{SetNquads: []byte(rdf)}})
require.NoError(t, err)
// check the count of the vectors inserted
@@ -42,7 +51,7 @@ func TestVectorDelete(t *testing.T) {
count(uid)
}
}`
- resp, err := db.Query(context.Background(), q1)
+ resp, err := engine.GetDefaultNamespace().Query(context.Background(), q1)
require.NoError(t, err)
require.JSONEq(t, fmt.Sprintf(`{"vector":[{"count":%d}]}`, numVectors), string(resp.Json))
@@ -55,11 +64,11 @@ func TestVectorDelete(t *testing.T) {
}
}`
- require.Equal(t, vectors, queryVectors(t, db, vectorQuery))
+ require.Equal(t, vectors, queryVectors(t, engine, vectorQuery))
triples := strings.Split(rdf, "\n")
deleteTriple := func(idx int) string {
- _, err := db.Mutate(context.Background(), []*api.Mutation{{
+ _, err := engine.GetDefaultNamespace().Mutate(context.Background(), []*api.Mutation{{
DelNquads: []byte(triples[idx]),
}})
require.NoError(t, err)
@@ -71,7 +80,7 @@ func TestVectorDelete(t *testing.T) {
}
}`, uid[1:len(uid)-1])
- res, err := db.Query(context.Background(), q2)
+ res, err := engine.GetDefaultNamespace().Query(context.Background(), q2)
require.NoError(t, err)
require.JSONEq(t, `{"vector":[]}`, string(res.Json))
return triples[idx]
@@ -87,17 +96,17 @@ func TestVectorDelete(t *testing.T) {
for i := 0; i < len(triples)-2; i++ {
triple := deleteTriple(i)
vectorQuery := fmt.Sprintf(q3, strings.Split(triple, `"`)[1])
- respVectors := queryVectors(t, db, vectorQuery)
+ respVectors := queryVectors(t, engine, vectorQuery)
require.Len(t, respVectors, 1)
require.Contains(t, vectors, respVectors[0])
}
triple := deleteTriple(len(triples) - 2)
- _ = queryVectors(t, db, fmt.Sprintf(q3, strings.Split(triple, `"`)[1]))
+ _ = queryVectors(t, engine, fmt.Sprintf(q3, strings.Split(triple, `"`)[1]))
}
-func queryVectors(t *testing.T, db *modusdb.DB, query string) [][]float32 {
- resp, err := db.Query(context.Background(), query)
+func queryVectors(t *testing.T, engine *modusdb.Engine, query string) [][]float32 {
+ resp, err := engine.GetDefaultNamespace().Query(context.Background(), query)
require.NoError(t, err)
var data struct {
diff --git a/zero.go b/zero.go
index 4aeb362..4468ac8 100644
--- a/zero.go
+++ b/zero.go
@@ -1,13 +1,23 @@
+/*
+ * Copyright 2025 Hypermode Inc.
+ * Licensed under the terms of the Apache License, Version 2.0
+ * See the LICEDBE file that accompanied this code for further details.
+ *
+ * SPDX-FileCopyrightText: 2025 Hypermode Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
package modusdb
import (
"fmt"
"github.com/dgraph-io/badger/v4"
- "github.com/dgraph-io/dgraph/v24/posting"
- "github.com/dgraph-io/dgraph/v24/protos/pb"
- "github.com/dgraph-io/dgraph/v24/worker"
- "github.com/dgraph-io/dgraph/v24/x"
+ "github.com/hypermodeinc/dgraph/v24/posting"
+ "github.com/hypermodeinc/dgraph/v24/protos/pb"
+ "github.com/hypermodeinc/dgraph/v24/worker"
+ "github.com/hypermodeinc/dgraph/v24/x"
+ "google.golang.org/protobuf/proto"
)
const (
@@ -24,9 +34,9 @@ const (
zeroStateKey = "0-dgraph.modusdb.zero"
)
-func (db *DB) LeaseUIDs(numUIDs uint64) (pb.AssignedIds, error) {
+func (ns *Engine) LeaseUIDs(numUIDs uint64) (*pb.AssignedIds, error) {
num := &pb.Num{Val: numUIDs, Type: pb.Num_UID}
- return db.z.nextUIDs(num)
+ return ns.z.nextUIDs(num)
}
type zero struct {
@@ -36,7 +46,7 @@ type zero struct {
minLeasedTs uint64
maxLeasedTs uint64
- lastNS uint64
+ lastNamespace uint64
}
func newZero() (*zero, bool, error) {
@@ -52,13 +62,13 @@ func newZero() (*zero, bool, error) {
z.maxLeasedUID = initialUID
z.minLeasedTs = initialTs
z.maxLeasedTs = initialTs
- z.lastNS = 0
+ z.lastNamespace = 0
} else {
z.minLeasedUID = zs.MaxUID
z.maxLeasedUID = zs.MaxUID
z.minLeasedTs = zs.MaxTxnTs
z.maxLeasedTs = zs.MaxTxnTs
- z.lastNS = zs.MaxNsID
+ z.lastNamespace = zs.MaxNsID
}
posting.Oracle().ProcessDelta(&pb.OracleDelta{MaxAssigned: z.minLeasedTs - 1})
worker.SetMaxUID(z.minLeasedUID - 1)
@@ -90,24 +100,32 @@ func (z *zero) readTs() uint64 {
return z.minLeasedTs - 1
}
-func (z *zero) nextUIDs(num *pb.Num) (pb.AssignedIds, error) {
- var resp pb.AssignedIds
+func (z *zero) nextUID() (uint64, error) {
+ uids, err := z.nextUIDs(&pb.Num{Val: 1, Type: pb.Num_UID})
+ if err != nil {
+ return 0, err
+ }
+ return uids.StartId, nil
+}
+
+func (z *zero) nextUIDs(num *pb.Num) (*pb.AssignedIds, error) {
+ var resp *pb.AssignedIds
if num.Bump {
if z.minLeasedUID >= num.Val {
- resp = pb.AssignedIds{StartId: z.minLeasedUID, EndId: z.minLeasedUID}
+ resp = &pb.AssignedIds{StartId: z.minLeasedUID, EndId: z.minLeasedUID}
z.minLeasedUID += 1
} else {
- resp = pb.AssignedIds{StartId: z.minLeasedUID, EndId: num.Val}
+ resp = &pb.AssignedIds{StartId: z.minLeasedUID, EndId: num.Val}
z.minLeasedUID = num.Val + 1
}
} else {
- resp = pb.AssignedIds{StartId: z.minLeasedUID, EndId: z.minLeasedUID + num.Val - 1}
+ resp = &pb.AssignedIds{StartId: z.minLeasedUID, EndId: z.minLeasedUID + num.Val - 1}
z.minLeasedUID += num.Val
}
for z.minLeasedUID >= z.maxLeasedUID {
if err := z.leaseUIDs(); err != nil {
- return pb.AssignedIds{}, err
+ return nil, err
}
}
@@ -115,12 +133,12 @@ func (z *zero) nextUIDs(num *pb.Num) (pb.AssignedIds, error) {
return resp, nil
}
-func (z *zero) nextNS() (uint64, error) {
- z.lastNS++
+func (z *zero) nextNamespace() (uint64, error) {
+ z.lastNamespace++
if err := z.writeZeroState(); err != nil {
return 0, fmt.Errorf("error leasing namespace ID: %w", err)
}
- return z.lastNS, nil
+ return z.lastNamespace, nil
}
func readZeroState() (*pb.MembershipState, error) {
@@ -135,20 +153,20 @@ func readZeroState() (*pb.MembershipState, error) {
return nil, fmt.Errorf("error getting zero state: %v", err)
}
- var zeroState pb.MembershipState
+ zeroState := &pb.MembershipState{}
err = item.Value(func(val []byte) error {
- return zeroState.Unmarshal(val)
+ return proto.Unmarshal(val, zeroState)
})
if err != nil {
return nil, fmt.Errorf("error unmarshalling zero state: %v", err)
}
- return &zeroState, nil
+ return zeroState, nil
}
func (z *zero) writeZeroState() error {
- zeroState := pb.MembershipState{MaxUID: z.maxLeasedUID, MaxTxnTs: z.maxLeasedTs, MaxNsID: z.lastNS}
- data, err := zeroState.Marshal()
+ zeroState := &pb.MembershipState{MaxUID: z.maxLeasedUID, MaxTxnTs: z.maxLeasedTs, MaxNsID: z.lastNamespace}
+ data, err := proto.Marshal(zeroState)
if err != nil {
return fmt.Errorf("error marshalling zero state: %w", err)
}