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-lint.yaml b/.github/workflows/ci-go-lint.yaml
new file mode 100644
index 0000000..be45df1
--- /dev/null
+++ b/.github/workflows/ci-go-lint.yaml
@@ -0,0 +1,35 @@
+name: ci-go-lint
+
+on:
+ pull_request:
+ types:
+ - opened
+ - synchronize
+ - reopened
+ - ready_for_review
+ paths:
+ - "**/*.go"
+ - "**/go.mod"
+ - .github/workflows/*
+
+permissions:
+ contents: read
+ actions: write
+
+jobs:
+ ci-go-lint:
+ runs-on: warp-ubuntu-latest-arm64-2x
+
+ 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: golangci-lint
+ uses: golangci/golangci-lint-action@v6.1.1
+ with:
+ args: --timeout=10m
diff --git a/.github/workflows/ci-go-tests.yaml b/.github/workflows/ci-go-tests.yaml
index 6fbcc81..69052ed 100644
--- a/.github/workflows/ci-go-tests.yaml
+++ b/.github/workflows/ci-go-tests.yaml
@@ -8,8 +8,9 @@ on:
- reopened
- ready_for_review
paths:
- - '**/*.go'
- - '**/go.mod'
+ - "**/*.go"
+ - "**/go.mod"
+ - .github/workflows/*
permissions:
contents: read
@@ -17,7 +18,7 @@ permissions:
jobs:
ci-go-tests:
- runs-on: ubuntu-24.04
+ runs-on: warp-ubuntu-latest-arm64-4x
steps:
- uses: actions/checkout@v4
@@ -25,13 +26,8 @@ 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/.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/.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/trunk.yaml b/.trunk/trunk.yaml
new file mode 100644
index 0000000..9442ef2
--- /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.5
+ - checkov@3.2.346
+ - git-diff-check
+ - gofmt@1.20.4
+ - golangci-lint@1.62.2
+ - markdownlint@0.43.0
+ - osv-scanner@1.9.2
+ - prettier@3.4.2
+ - renovate@39.90.2
+ - trufflehog@3.88.0
+ - 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..e92097c
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,14 @@
+# Changelog
+
+## UNRELEASED
+
+- feat: add readfrom json tag to support reverse edges
+ [#49](https://github.com/hypermodeinc/modusDB/pull/49)
+
+- chore: Refactoring package management #51 [#51](https://github.com/hypermodeinc/modusDB/pull/51)
+
+## 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..6251714 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,98 @@
-ModusDB
-======
+
+
+[![modus](https://github.com/user-attachments/assets/1a6020bd-d041-4dd0-b4a9-ce01dc015b65)](https://github.com/hypermodeinc/modusdb)
+
+[![GitHub License](https://img.shields.io/github/license/hypermodeinc/modusdb)](https://github.com/hypermodeinc/modusdb?tab=Apache-2.0-1-ov-file#readme)
+[![chat](https://img.shields.io/discord/1267579648657850441)](https://discord.gg/NJQ4bJpffF)
+[![GitHub Repo stars](https://img.shields.io/github/stars/hypermodeinc/modusdb)](https://github.com/hypermodeinc/modusdb/stargazers)
+[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/hypermodeinc/modusdb)](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() {
+ db, err := New(NewDefaultConfig("/tmp/modusdb"))
+ if err != nil {
+ panic(err)
+ }
+ defer db.Close()
+
+ gid, user, err := modusdb.Upsert(db, User{
+ Id: "123",
+ Name: "A",
+ Age: 10,
+ })
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(user)
+
+ _, queriedUser, err := modusdb.Get[User](db, gid)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(queriedUser)
+
+ _, _, err = modusdb.Delete[User](db, 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 hello@hypermode.com.
+
+## Acknowledgements
+
+ModusDB builds heavily upon packages from the open source projects of
+[Dgraph](https://github.com/dgraph-io/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..b880474
--- /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/dgraph-io/dgraph/v24/dql"
+ "github.com/dgraph-io/dgraph/v24/schema"
+ "github.com/hypermodeinc/modusdb/api/apiutils"
+ "github.com/hypermodeinc/modusdb/api/structreflect"
+)
+
+func Create[T any](db *DB, object T, ns ...uint64) (uint64, T, error) {
+ db.mutex.Lock()
+ defer db.mutex.Unlock()
+ if len(ns) > 1 {
+ return 0, object, fmt.Errorf("only one namespace is allowed")
+ }
+ ctx, n, err := getDefaultNamespace(db, ns...)
+ if err != nil {
+ return 0, object, err
+ }
+
+ gid, err := db.z.nextUID()
+ if err != nil {
+ return 0, object, err
+ }
+
+ dms := make([]*dql.Mutation, 0)
+ sch := &schema.ParsedSchema{}
+ err = generateSetDqlMutationsAndSchema[T](ctx, n, object, gid, &dms, sch)
+ if err != nil {
+ return 0, object, err
+ }
+
+ err = n.alterSchemaWithParsed(ctx, sch)
+ if err != nil {
+ return 0, object, err
+ }
+
+ err = applyDqlMutations(ctx, db, dms)
+ if err != nil {
+ return 0, object, err
+ }
+
+ return getByGid[T](ctx, n, gid)
+}
+
+func Upsert[T any](db *DB, object T, ns ...uint64) (uint64, T, bool, error) {
+
+ var wasFound bool
+ db.mutex.Lock()
+ defer db.mutex.Unlock()
+ if len(ns) > 1 {
+ return 0, object, false, fmt.Errorf("only one namespace is allowed")
+ }
+
+ ctx, n, err := getDefaultNamespace(db, ns...)
+ 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, n, object, gid, &dms, sch)
+ if err != nil {
+ return 0, object, false, err
+ }
+
+ err = n.alterSchemaWithParsed(ctx, sch)
+ if err != nil {
+ return 0, object, false, err
+ }
+
+ if gid != 0 || cf != nil {
+ gid, err = getExistingObject[T](ctx, n, gid, cf, object)
+ if err != nil && err != apiutils.ErrNoObjFound {
+ return 0, object, false, err
+ }
+ wasFound = err == nil
+ }
+
+ if gid == 0 {
+ gid, err = db.z.nextUID()
+ if err != nil {
+ return 0, object, false, err
+ }
+ }
+
+ dms = make([]*dql.Mutation, 0)
+ err = generateSetDqlMutationsAndSchema[T](ctx, n, object, gid, &dms, sch)
+ if err != nil {
+ return 0, object, false, err
+ }
+
+ err = applyDqlMutations(ctx, db, dms)
+ if err != nil {
+ return 0, object, false, err
+ }
+
+ gid, object, err = getByGid[T](ctx, n, gid)
+ if err != nil {
+ return 0, object, false, err
+ }
+
+ return gid, object, wasFound, nil
+}
+
+func Get[T any, R UniqueField](db *DB, uniqueField R, ns ...uint64) (uint64, T, error) {
+ db.mutex.Lock()
+ defer db.mutex.Unlock()
+ var obj T
+ if len(ns) > 1 {
+ return 0, obj, fmt.Errorf("only one namespace is allowed")
+ }
+ ctx, n, err := getDefaultNamespace(db, ns...)
+ if err != nil {
+ return 0, obj, err
+ }
+ if uid, ok := any(uniqueField).(uint64); ok {
+ return getByGid[T](ctx, n, uid)
+ }
+
+ if cf, ok := any(uniqueField).(ConstrainedField); ok {
+ return getByConstrainedField[T](ctx, n, cf)
+ }
+
+ return 0, obj, fmt.Errorf("invalid unique field type")
+}
+
+func Query[T any](db *DB, queryParams QueryParams, ns ...uint64) ([]uint64, []T, error) {
+ db.mutex.Lock()
+ defer db.mutex.Unlock()
+ if len(ns) > 1 {
+ return nil, nil, fmt.Errorf("only one namespace is allowed")
+ }
+ ctx, n, err := getDefaultNamespace(db, ns...)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return executeQuery[T](ctx, n, queryParams, true)
+}
+
+func Delete[T any, R UniqueField](db *DB, uniqueField R, ns ...uint64) (uint64, T, error) {
+ db.mutex.Lock()
+ defer db.mutex.Unlock()
+ var zeroObj T
+ if len(ns) > 1 {
+ return 0, zeroObj, fmt.Errorf("only one namespace is allowed")
+ }
+ ctx, n, err := getDefaultNamespace(db, ns...)
+ if err != nil {
+ return 0, zeroObj, err
+ }
+ if uid, ok := any(uniqueField).(uint64); ok {
+ uid, obj, err := getByGid[T](ctx, n, uid)
+ if err != nil {
+ return 0, zeroObj, err
+ }
+
+ dms := generateDeleteDqlMutations(n, uid)
+
+ err = applyDqlMutations(ctx, db, 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, n, cf)
+ if err != nil {
+ return 0, zeroObj, err
+ }
+
+ dms := generateDeleteDqlMutations(n, uid)
+
+ err = applyDqlMutations(ctx, db, 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..84d2696
--- /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/dgraph-io/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..84a24c6
--- /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/dgraph-io/dgraph/v24/protos/pb"
+ "github.com/dgraph-io/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..3a5e149
--- /dev/null
+++ b/api/mutations/mutations.go
@@ -0,0 +1,81 @@
+/*
+ * 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/dgraph-io/dgraph/v24/protos/pb"
+ "github.com/dgraph-io/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,
+ jsonToReverseEdgeTags map[string]string) error {
+ if jsonToReverseEdgeTags[jsonName] == "" {
+ 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)
+ }
+
+ reverseEdge := jsonToReverseEdgeTags[jsonName]
+ typeName := strings.Split(reverseEdge, ".")[0]
+ u := &pb.SchemaUpdate{
+ Predicate: apiutils.AddNamespace(nsId, reverseEdge),
+ 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..f99234b
--- /dev/null
+++ b/api/querygen/dql_query.go
@@ -0,0 +1,182 @@
+/*
+ * 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 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
+ }
+ `
+
+ 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..6e8426c
--- /dev/null
+++ b/api_mutation_gen.go
@@ -0,0 +1,125 @@
+/*
+ * 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"
+
+ "github.com/dgraph-io/dgo/v240/protos/api"
+ "github.com/dgraph-io/dgraph/v24/dql"
+ "github.com/dgraph-io/dgraph/v24/protos/pb"
+ "github.com/dgraph-io/dgraph/v24/schema"
+ "github.com/dgraph-io/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] != "" {
+ if err := mutations.HandleReverseEdge(jsonName, reflectValueType, n.ID(), sch,
+ tagMaps.JsonToReverseEdge); 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..f205d8a
--- /dev/null
+++ b/api_mutation_helpers.go
@@ -0,0 +1,128 @@
+package modusdb
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+
+ "github.com/dgraph-io/dgraph/v24/dql"
+ "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/hypermodeinc/modusdb/api/apiutils"
+ "github.com/hypermodeinc/modusdb/api/structreflect"
+)
+
+func processStructValue(ctx context.Context, value any, n *Namespace) (any, error) {
+ if reflect.TypeOf(value).Kind() == reflect.Struct {
+ value = reflect.ValueOf(value).Interface()
+ newGid, err := getUidOrMutate(ctx, n.db, n, value)
+ if err != nil {
+ return nil, err
+ }
+ return newGid, nil
+ }
+ return value, nil
+}
+
+func processPointerValue(ctx context.Context, value any, n *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, n)
+ }
+ }
+ return value, nil
+}
+
+func getUidOrMutate[T any](ctx context.Context, db *DB, n *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, n, object, gid, &dms, sch)
+ if err != nil {
+ return 0, err
+ }
+
+ err = n.alterSchemaWithParsed(ctx, sch)
+ if err != nil {
+ return 0, err
+ }
+ if gid != 0 || cf != nil {
+ gid, err = getExistingObject(ctx, n, gid, cf, object)
+ if err != nil && err != apiutils.ErrNoObjFound {
+ return 0, err
+ }
+ if err == nil {
+ return gid, nil
+ }
+ }
+
+ gid, err = db.z.nextUID()
+ if err != nil {
+ return 0, err
+ }
+
+ dms = make([]*dql.Mutation, 0)
+ err = generateSetDqlMutationsAndSchema(ctx, n, object, gid, &dms, sch)
+ if err != nil {
+ return 0, err
+ }
+
+ err = applyDqlMutations(ctx, db, dms)
+ if err != nil {
+ return 0, err
+ }
+
+ return gid, nil
+}
+
+func applyDqlMutations(ctx context.Context, db *DB, dms []*dql.Mutation) error {
+ edges, err := query.ToDirectedEdges(dms, nil)
+ if err != nil {
+ return err
+ }
+
+ if !db.isOpen {
+ return ErrClosedDB
+ }
+
+ startTs, err := db.z.nextTs()
+ if err != nil {
+ return err
+ }
+ commitTs, err := db.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..4129870
--- /dev/null
+++ b/api_query_execution.go
@@ -0,0 +1,201 @@
+/*
+ * 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, n *Namespace, gid uint64) (uint64, T, error) {
+ return executeGet[T](ctx, n, gid)
+}
+
+func getByGidWithObject[T any](ctx context.Context, n *Namespace, gid uint64, obj T) (uint64, T, error) {
+ return executeGetWithObject[T](ctx, n, obj, false, gid)
+}
+
+func getByConstrainedField[T any](ctx context.Context, n *Namespace, cf ConstrainedField) (uint64, T, error) {
+ return executeGet[T](ctx, n, cf)
+}
+
+func getByConstrainedFieldWithObject[T any](ctx context.Context, n *Namespace,
+ cf ConstrainedField, obj T) (uint64, T, error) {
+
+ return executeGetWithObject[T](ctx, n, obj, false, cf)
+}
+
+func executeGet[T any, R UniqueField](ctx context.Context, n *Namespace, args ...R) (uint64, T, error) {
+ var obj T
+ if len(args) != 1 {
+ return 0, obj, fmt.Errorf("expected 1 argument, got %d", len(args))
+ }
+
+ return executeGetWithObject(ctx, n, obj, true, args...)
+}
+
+func executeGetWithObject[T any, R UniqueField](ctx context.Context, n *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 := n.queryWithLock(ctx, 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, n *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 := n.queryWithLock(ctx, 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, n *Namespace, gid uint64, cf *ConstrainedField,
+ object T) (uint64, error) {
+ var err error
+ if gid != 0 {
+ gid, _, err = getByGidWithObject[T](ctx, n, gid, object)
+ } else if cf != nil {
+ gid, _, err = getByConstrainedFieldWithObject[T](ctx, n, *cf, object)
+ }
+ if err != nil {
+ return 0, err
+ }
+ return gid, nil
+}
diff --git a/api_test.go b/api_test.go
new file mode 100644
index 0000000..3326d86
--- /dev/null
+++ b/api_test.go
@@ -0,0 +1,899 @@
+/*
+ * 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_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) {
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ gid, user, err := modusdb.Create(db, 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](db, 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](db, 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](db, gid)
+ require.NoError(t, err)
+
+ _, queriedUser3, err := modusdb.Get[User](db, 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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ ClerkId: "123",
+ }
+
+ gid, user, err := modusdb.Create(db, user, db1.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 := db1.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()))
+
+ // TODO schema{} should work
+ schemaQuery := `schema(pred: [User.name, User.age, User.clerk_id])
+ {
+ type
+ index
+ tokenizer
+ }`
+ resp, err = db1.Query(ctx, schemaQuery)
+ require.NoError(t, err)
+
+ require.JSONEq(t,
+ `{"schema":
+ [
+ {"predicate":"User.age","type":"int"},
+ {"predicate":"User.clerk_id","type":"string","index":true,"tokenizer":["exact"]},
+ {"predicate":"User.name","type":"string"}
+ ]
+ }`,
+ string(resp.GetJson()))
+}
+
+func TestCreateApiWithNonStruct(t *testing.T) {
+ ctx := context.Background()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ }
+
+ _, _, err = modusdb.Create[*User](db, &user, db1.ID())
+ require.Error(t, err)
+ require.Equal(t, "expected struct, got ptr", err.Error())
+}
+
+func TestGetApi(t *testing.T) {
+ ctx := context.Background()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ ClerkId: "123",
+ }
+
+ gid, _, err := modusdb.Create(db, user, db1.ID())
+ require.NoError(t, err)
+
+ gid, queriedUser, err := modusdb.Get[User](db, gid, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ ClerkId: "123",
+ }
+
+ _, _, err = modusdb.Create(db, user, db1.ID())
+ require.NoError(t, err)
+
+ gid, queriedUser, err := modusdb.Get[User](db, modusdb.ConstrainedField{
+ Key: "clerk_id",
+ Value: "123",
+ }, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ ClerkId: "123",
+ }
+
+ gid, _, err := modusdb.Create(db, user, db1.ID())
+ require.NoError(t, err)
+
+ _, _, err = modusdb.Delete[User](db, gid, db1.ID())
+ require.NoError(t, err)
+
+ _, queriedUser, err := modusdb.Get[User](db, gid, db1.ID())
+ require.Error(t, err)
+ require.Equal(t, "no object found", err.Error())
+ require.Equal(t, queriedUser, User{})
+
+ _, queriedUser, err = modusdb.Get[User](db, modusdb.ConstrainedField{
+ Key: "clerk_id",
+ Value: "123",
+ }, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.DropData(ctx))
+
+ user := User{
+ Name: "B",
+ Age: 20,
+ ClerkId: "123",
+ }
+
+ gid, user, _, err := modusdb.Upsert(db, user, db1.ID())
+ require.NoError(t, err)
+ require.Equal(t, user.Gid, gid)
+
+ user.Age = 21
+ gid, _, _, err = modusdb.Upsert(db, user, db1.ID())
+ require.NoError(t, err)
+ require.Equal(t, user.Gid, gid)
+
+ _, queriedUser, err := modusdb.Get[User](db, gid, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.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(db, user, db1.ID())
+ require.NoError(t, err)
+ }
+
+ gids, queriedUsers, err := modusdb.Query[User](db, modusdb.QueryParams{}, db1.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](db, 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),
+ },
+ },
+ }, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.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(db, user, db1.ID())
+ require.NoError(t, err)
+ }
+
+ gids, queriedUsers, err := modusdb.Query[User](db, modusdb.QueryParams{
+ Filter: &modusdb.Filter{
+ Field: "age",
+ String: modusdb.StringPredicate{
+ GreaterOrEqual: fmt.Sprintf("%d", 20),
+ },
+ },
+ Pagination: &modusdb.Pagination{
+ Limit: 3,
+ Offset: 1,
+ },
+ }, db1.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](db, modusdb.QueryParams{
+ Pagination: &modusdb.Pagination{
+ Limit: 3,
+ Offset: 1,
+ },
+ Sorting: &modusdb.Sorting{
+ OrderAscField: "age",
+ },
+ }, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.DropData(ctx))
+
+ projGid, project, err := modusdb.Create(db, Project{
+ Name: "P",
+ ClerkId: "456",
+ Branches: []Branch{
+ {Name: "B", ClerkId: "123"},
+ {Name: "B2", ClerkId: "456"},
+ },
+ }, db1.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(db, branch1, db1.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(db, branch2, db1.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](db, projGid, db1.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](db, modusdb.QueryParams{}, db1.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](db, projGid, db1.ID())
+ require.NoError(t, err)
+
+ queryBranchesGids, queriedBranches, err = modusdb.Query[Branch](db, modusdb.QueryParams{}, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.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(db, project, db1.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(db, branch, db1.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](db, modusdb.QueryParams{}, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.DropData(ctx))
+
+ branch := Branch{
+ Name: "B",
+ ClerkId: "123",
+ Proj: Project{
+ Name: "P",
+ ClerkId: "456",
+ },
+ }
+
+ gid, branch, err := modusdb.Create(db, branch, db1.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 := db1.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](db, gid, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.DropData(ctx))
+
+ projGid, project, err := modusdb.Create(db, Project{
+ Name: "P",
+ ClerkId: "456",
+ }, db1.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(db, branch, db1.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 := db1.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](db, gid, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.DropData(ctx))
+
+ projGid, project, err := modusdb.Create(db, Project{
+ Name: "P",
+ ClerkId: "456",
+ }, db1.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(db, branch, db1.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 := db1.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](db, gid, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.DropData(ctx))
+
+ branch := BadBranch{
+ Name: "B",
+ ClerkId: "123",
+ Proj: BadProject{
+ Name: "P",
+ ClerkId: "456",
+ },
+ }
+
+ _, _, err = modusdb.Create(db, branch, db1.ID())
+ require.Error(t, err)
+ require.Equal(t, fmt.Sprintf(apiutils.NoUniqueConstr, "BadProject"), err.Error())
+
+ proj := BadProject{
+ Name: "P",
+ ClerkId: "456",
+ }
+
+ _, _, err = modusdb.Create(db, proj, db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.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(db, doc, db1.ID())
+ require.NoError(t, err)
+ }
+
+ const query = `
+ {
+ documents(func: similar_to(Document.textVec, 5, "[0.1,0.1,0.1]")) {
+ Document.text
+ }
+ }`
+
+ resp, err := db1.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 = db1.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()
+ db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
+ require.NoError(t, err)
+ defer db.Close()
+
+ db1, err := db.CreateNamespace()
+ require.NoError(t, err)
+
+ require.NoError(t, db1.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(db, doc, db1.ID())
+ require.NoError(t, err)
+ }
+
+ gids, docs, err := modusdb.Query[Document](db, modusdb.QueryParams{
+ Filter: &modusdb.Filter{
+ Field: "textVec",
+ Vector: modusdb.VectorPredicate{
+ SimilarTo: []float32{0.1, 0.1, 0.1},
+ TopK: 5,
+ },
+ },
+ }, db1.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/api_types.go b/api_types.go
new file mode 100644
index 0000000..8102771
--- /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/dgraph-io/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 {
+ namespace uint64
+}
+
+func WithNamespace(namespace uint64) ModusDbOption {
+ return func(o *modusDbOptions) {
+ o.namespace = namespace
+ }
+}
+
+func getDefaultNamespace(db *DB, ns ...uint64) (context.Context, *Namespace, error) {
+ dbOpts := &modusDbOptions{
+ namespace: db.defaultNamespace.ID(),
+ }
+ for _, ns := range ns {
+ WithNamespace(ns)(dbOpts)
+ }
+
+ n, err := db.getNamespaceWithLock(dbOpts.namespace)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ctx := context.Background()
+ ctx = x.AttachNamespace(ctx, n.ID())
+
+ return ctx, n, 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..76d3d1b 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 {
diff --git a/db.go b/db.go
index 6b212bf..d092ecd 100644
--- a/db.go
+++ b/db.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 (
@@ -38,7 +47,7 @@ type DB struct {
z *zero
// points to default / 0 / galaxy namespace
- gxy *Namespace
+ defaultNamespace *Namespace
}
// New returns a new modusDB instance.
@@ -77,7 +86,7 @@ func New(conf Config) (*DB, error) {
x.UpdateHealthStatus(true)
- db.gxy = &Namespace{id: 0, db: db}
+ db.defaultNamespace = &Namespace{id: 0, db: db}
return db, nil
}
@@ -112,6 +121,10 @@ func (db *DB) GetNamespace(nsID uint64) (*Namespace, error) {
db.mutex.RLock()
defer db.mutex.RUnlock()
+ return db.getNamespaceWithLock(nsID)
+}
+
+func (db *DB) getNamespaceWithLock(nsID uint64) (*Namespace, error) {
if !db.isOpen {
return nil, ErrClosedDB
}
@@ -150,27 +163,27 @@ func (db *DB) DropAll(ctx context.Context) error {
}
func (db *DB) DropData(ctx context.Context) error {
- return db.gxy.DropData(ctx)
+ return db.defaultNamespace.DropData(ctx)
}
func (db *DB) AlterSchema(ctx context.Context, sch string) error {
- return db.gxy.AlterSchema(ctx, sch)
+ return db.defaultNamespace.AlterSchema(ctx, sch)
}
func (db *DB) Query(ctx context.Context, q string) (*api.Response, error) {
- return db.gxy.Query(ctx, q)
+ return db.defaultNamespace.Query(ctx, q)
}
func (db *DB) Mutate(ctx context.Context, ms []*api.Mutation) (map[string]uint64, error) {
- return db.gxy.Mutate(ctx, ms)
+ return db.defaultNamespace.Mutate(ctx, ms)
}
func (db *DB) Load(ctx context.Context, schemaPath, dataPath string) error {
- return db.gxy.Load(ctx, schemaPath, dataPath)
+ return db.defaultNamespace.Load(ctx, schemaPath, dataPath)
}
func (db *DB) LoadData(inCtx context.Context, dataDir string) error {
- return db.gxy.LoadData(inCtx, dataDir)
+ return db.defaultNamespace.LoadData(inCtx, dataDir)
}
// Close closes the modusDB instance.
diff --git a/db_test.go b/db_test.go
index bbcc9ae..5de5d68 100644
--- a/db_test.go
+++ b/db_test.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_test
import (
diff --git a/go.mod b/go.mod
index 7253865..877c2e5 100644
--- a/go.mod
+++ b/go.mod
@@ -6,24 +6,25 @@ 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/dgraph/v24 v24.0.3-0.20241213192353-fd411c5b915b
+ github.com/dgraph-io/ristretto/v2 v2.0.1
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.35.2
)
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/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
@@ -35,23 +36,23 @@ require (
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
- github.com/chewxy/math32 v1.11.0 // indirect
+ github.com/chewxy/math32 v1.10.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
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.30.0 // 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 +62,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,11 +82,12 @@ 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
@@ -93,28 +95,29 @@ require (
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/philhofer/fwd v1.1.2 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // 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/cobra v1.8.1 // indirect
- github.com/spf13/jwalterweatherman v1.1.0 // indirect
+ github.com/spf13/jwalterweatherman v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1 // 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 +126,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/crypto v0.30.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/net v0.32.0 // indirect
+ golang.org/x/sys v0.28.0 // indirect
+ golang.org/x/term v0.27.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/grpc v1.68.1 // 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..d91f346 100644
--- a/go.sum
+++ b/go.sum
@@ -41,22 +41,20 @@ 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/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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
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=
@@ -107,17 +105,11 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
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/chewxy/math32 v1.10.1 h1:LFpeY0SLJXeaiej/eIp2L40VYfscTvKh/FSEZ68uMkU=
+github.com/chewxy/math32 v1.10.1/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs=
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=
@@ -134,34 +126,33 @@ 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/dgraph/v24 v24.0.3-0.20241213192353-fd411c5b915b h1:y5i9HO4AS+i1RKRGXrHm1x62zsLKDkGKB417nUgvzCk=
+github.com/dgraph-io/dgraph/v24 v24.0.3-0.20241213192353-fd411c5b915b/go.mod h1:AFALH9qEz1P9ZmOeB6/jhvcKhomttcXRL8r6XptHFYg=
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.0.1 h1:7W0LfEP+USCmtrUjJsk+Jv2jbhJmb72N4yRI7GrLdMI=
+github.com/dgraph-io/ristretto/v2 v2.0.1/go.mod h1:K7caLeufSdxm+ITp1n/73U+VbFVAHrexfLbz4n14hpo=
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.4.0+incompatible h1:I9z7sQ5qyzO0BfAb9IMOawRkAGxhYsidKiTMcm0DU+A=
+github.com/docker/docker v27.4.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,9 +173,8 @@ 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=
@@ -193,8 +183,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo=
+github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
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=
@@ -213,9 +203,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,9 +212,6 @@ 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=
@@ -303,9 +289,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=
@@ -342,13 +327,14 @@ github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFO
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-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
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=
@@ -358,9 +344,8 @@ github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
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=
@@ -371,7 +356,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
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=
@@ -387,7 +371,6 @@ github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP
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=
@@ -408,9 +391,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
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 +409,10 @@ 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=
@@ -443,24 +425,25 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
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/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
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/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=
@@ -488,14 +471,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/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/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.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
@@ -534,8 +516,8 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9
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=
@@ -545,9 +527,8 @@ 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=
@@ -559,11 +540,13 @@ 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/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=
@@ -586,9 +569,8 @@ 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/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 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
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=
@@ -617,14 +599,13 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
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/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/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
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=
@@ -682,8 +663,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.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
+golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
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=
@@ -721,6 +702,7 @@ 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=
@@ -764,10 +746,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.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
+golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
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,8 +770,9 @@ 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=
@@ -834,19 +818,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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.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.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
+golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
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,9 +838,10 @@ 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=
@@ -914,13 +899,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.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
+golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
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=
@@ -991,8 +980,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.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
+google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
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 +996,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.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
+google.golang.org/protobuf v1.35.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=
diff --git a/live.go b/live.go
index 3e89e05..d786fab 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 (
diff --git a/live_benchmark_test.go b/live_benchmark_test.go
new file mode 100644
index 0000000..62aee86
--- /dev/null
+++ b/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 modusdb_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
+
+ db, err := modusdb.New(modusdb.NewDefaultConfig(b.TempDir()))
+ require.NoError(b, err)
+ defer db.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, db.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
+ db, err := modusdb.New(modusdb.NewDefaultConfig(b.TempDir()))
+ require.NoError(b, err)
+ defer db.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, db.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 := db.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/live_test.go
index b1e173b..4b7cf88 100644
--- a/live_test.go
+++ b/live_test.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_test
import (
@@ -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,7 +45,9 @@ func TestLiveLoaderSmall(t *testing.T) {
<12534504120601169429> <15617393957106514527> .
<14514306440537019930> <15617393957106514527> .
`
- )
+)
+
+func TestLiveLoaderSmall(t *testing.T) {
db, err := modusdb.New(modusdb.NewDefaultConfig(t.TempDir()))
require.NoError(t, err)
@@ -49,8 +56,8 @@ func TestLiveLoaderSmall(t *testing.T) {
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, os.WriteFile(schemaFile, []byte(DbSchema), 0600))
+ require.NoError(t, os.WriteFile(dataFile, []byte(SmallData), 0600))
require.NoError(t, db.Load(context.Background(), schemaFile, dataFile))
const query = `{
diff --git a/namespace.go b/namespace.go
index d9446df..710a1e0 100644
--- a/namespace.go
+++ b/namespace.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 (
@@ -61,6 +70,10 @@ func (n *Namespace) AlterSchema(ctx context.Context, sch string) error {
if err != nil {
return fmt.Errorf("error parsing schema: %w", err)
}
+ return n.alterSchemaWithParsed(ctx, sc)
+}
+
+func (n *Namespace) alterSchemaWithParsed(ctx context.Context, sc *schema.ParsedSchema) error {
for _, pred := range sc.Preds {
worker.InitTablet(pred.Predicate)
}
@@ -87,6 +100,8 @@ func (n *Namespace) Mutate(ctx context.Context, ms []*api.Mutation) (map[string]
return nil, nil
}
+ n.db.mutex.Lock()
+ defer n.db.mutex.Unlock()
dms := make([]*dql.Mutation, 0, len(ms))
for _, mu := range ms {
dm, err := edgraph.ParseMutationObject(mu, false)
@@ -113,15 +128,18 @@ func (n *Namespace) Mutate(ctx context.Context, ms []*api.Mutation) (map[string]
curId++
}
}
+
+ return n.mutateWithDqlMutation(ctx, dms, newUids)
+}
+
+func (n *Namespace) mutateWithDqlMutation(ctx context.Context, dms []*dql.Mutation,
+ newUids map[string]uint64) (map[string]uint64, error) {
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
}
@@ -164,6 +182,10 @@ func (n *Namespace) Query(ctx context.Context, query string) (*api.Response, err
n.db.mutex.RLock()
defer n.db.mutex.RUnlock()
+ return n.queryWithLock(ctx, query)
+}
+
+func (n *Namespace) queryWithLock(ctx context.Context, query string) (*api.Response, error) {
if !n.db.isOpen {
return nil, ErrClosedDB
}
diff --git a/namespace_test.go b/namespace_test.go
index 655e216..3f808bc 100644
--- a/namespace_test.go
+++ b/namespace_test.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_test
import (
diff --git a/vector_test.go b/vector_test.go
index 135d438..e62057d 100644
--- a/vector_test.go
+++ b/vector_test.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_test
import (
diff --git a/zero.go b/zero.go
index 4aeb362..130e030 100644
--- a/zero.go
+++ b/zero.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 (
@@ -8,6 +17,7 @@ import (
"github.com/dgraph-io/dgraph/v24/protos/pb"
"github.com/dgraph-io/dgraph/v24/worker"
"github.com/dgraph-io/dgraph/v24/x"
+ "google.golang.org/protobuf/proto"
)
const (
@@ -24,7 +34,7 @@ const (
zeroStateKey = "0-dgraph.modusdb.zero"
)
-func (db *DB) LeaseUIDs(numUIDs uint64) (pb.AssignedIds, error) {
+func (db *DB) LeaseUIDs(numUIDs uint64) (*pb.AssignedIds, error) {
num := &pb.Num{Val: numUIDs, Type: pb.Num_UID}
return db.z.nextUIDs(num)
}
@@ -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
}
}
@@ -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.lastNS}
+ data, err := proto.Marshal(zeroState)
if err != nil {
return fmt.Errorf("error marshalling zero state: %w", err)
}