Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions .devcontainer/devcontainer.json

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
types: [ opened, reopened ]

env:
JAVA_VERSION: '23'
JAVA_VERSION: '25'
JAVA_DISTRIBUTION: 'temurin'

permissions:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
.vscode
.gradle
build
/plugin/run
55 changes: 13 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,53 +16,24 @@ dependencies {
}
```

Add _depend_ inside `plugin.yml`:
Add _depend on_ inside `plugin.yml`:

```yaml
depend:
- Octopus
```

## Development
### Config

Full development setup available as [Development Container](https://containers.dev/).
Please use it for being able to tell "It works on my machine".
Make sure this is inside of the `/plugins/octopus/config.yml`

**Docker is required to be installed on your machine!**

### Create ~/dev.env

The development container is using a local env file on your
host machine for reading e.g. GitHub Tokens, Usernames, Email.
So please make sure it exists with your credentials in `~/dev.env`:

```text
GITHUB_EMAIL=your-mail@your-domain.com
GITHUB_USERNAME=YOUR_GITHUB_USERNAME
GITHUB_TOKEN=ghp_***
```

The `GITHUB_TOKEN` must've set following permission:

- `repo`
- `read:packages`
- `read:user`
- `user:email`

### IntelliJ IDEA

- Open IntelliJ (Welcome screen)
- Navigate to `Remote Development` - `Dev Containers`
- Press `New Dev Container`
- Select `From VCS Project`
- Select and connect with `Docker`
- Select `IntelliJ IDEA`
- Enter `Git Repository`: `https://github.com/o7studios/octopus-plugin`
- Select `Detection for devcontainer.json file` `Automatic`
- Press `Build Container and Continue`

### Development Container Issues

If you encounter an issue with setting up a development container, please
try to rebuild it first before opening a GitHub Issue. It's not uncommon
that some issues may fix themselves after a fresh container rebuild.
```yml
# Configuration of Octopus-Service
octopus:
# Host of Octopus-gRPC Server
host: "127.0.0.1"
# Port of Octopus-gRPC Server
port: 50051
# Replace to Octopus-API token
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config snippet uses the phrase “Replace to Octopus-API token”, which is ambiguous/grammatically incorrect. Consider rewording (e.g., “Set this to your Octopus API token”) for clearer setup instructions.

Suggested change
# Replace to Octopus-API token
# Set this to your Octopus API token

Copilot uses AI. Check for mistakes.
token: "development"
```
5 changes: 3 additions & 2 deletions api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ repositories {
}

dependencies {
api("studio.o7:octopus-sdk:0.3.3")
compileOnly("io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT")
api("studio.o7:octopus-sdk:0.5.9")
api("studio.o7:gentle:0.0.2")
compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
}

information {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package studio.o7.octopus.plugin.api.listener;
package studio.o7.octopus.plugin.api;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import studio.o7.octopus.sdk.gen.api.v1.Object;
import studio.o7.octopus.sdk.v1.Object;

import java.util.UUID;

@AllArgsConstructor
@Getter
public abstract class Listener {
public abstract class EventHandler {
/**
* ID for identifying this listener.
* (Made for internal purposes)
Expand All @@ -26,11 +26,6 @@ public abstract class Listener {
*/
protected final String keyPattern;

/**
* Priority of this listener (e.g. determines event order; lower is later)
*/
protected final int priority;

/**
* @param obj The affected object.
*/
Expand Down
112 changes: 75 additions & 37 deletions api/src/main/java/studio/o7/octopus/plugin/api/Octopus.java
Original file line number Diff line number Diff line change
@@ -1,67 +1,105 @@
package studio.o7.octopus.plugin.api;

import org.jspecify.annotations.NullMarked;
import gentle.Error;
import gentle.Result;
import studio.o7.octopus.plugin.Unsafe;
import studio.o7.octopus.plugin.api.listener.Listener;
import studio.o7.octopus.sdk.gen.api.v1.Entry;
import studio.o7.octopus.sdk.gen.api.v1.Object;
import studio.o7.octopus.sdk.v1.Entry;
import studio.o7.octopus.sdk.v1.QueryResponse;

import javax.annotation.Nullable;
import java.time.Instant;
import java.util.Collection;
import java.util.UUID;

@NullMarked
public interface Octopus {

static Octopus get() {
static Octopus instance() {
return Unsafe.getInstance().get();
}

/**
* Retrieves existing entries from the database matching a
* key pattern.
* Gets a unique entry of the given key
*
* @param key exact key pattern that will match between entries until one is found
* @return Returns the first {@link studio.o7.octopus.sdk.v1.Entry} that matches the key
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc says this returns the first Entry, but the method signature returns Result<Object, Error> (an Object, not an Entry). Please update the comment (or the return type) so the API contract is accurate.

Suggested change
* @return Returns the first {@link studio.o7.octopus.sdk.v1.Entry} that matches the key
* @return Returns the first {@link studio.o7.octopus.sdk.v1.Object} that matches the key

Copilot uses AI. Check for mistakes.
*/
default Collection<Entry> get(String keyPattern) {
return get(keyPattern, false);
}

/**
* Retrieves existing entries from the database matching a
* key pattern. Can optionally include expired objects.
*/
default Collection<Entry> get(String keyPattern, boolean includeExpired) {
return get(keyPattern, includeExpired, null, null);
}
Result<studio.o7.octopus.sdk.v1.Object, Error> get(String key);

/**
* Retrieves existing entries from the database matching a
* key pattern. Can optionally include expired objects and
* filter by revision creation time.
* Query multiple Entries
*
* @param queryParameter Query parameter to build the query request
* @return Returns a collection of matches for this query request
*/
Collection<Entry> get(String keyPattern, boolean includeExpired, @Nullable Instant createdRangeStart, @Nullable Instant createdRangeEnd);


void registerListener(Listener listener);

default void unregisterListener(Listener listener) {
unregisterListener(listener.getListenerUniqueId());
}

void unregisterListener(UUID listenerUniqueId);
Result<QueryResponse, Error> query(QueryParameter queryParameter);

/**
* <h1>Call</h1>
* <p>
* Stores an object on a key with new revision in the database
* and returns the stored version, including the new revision
* and ID.
*
* <h2>Expired</h2>
* If an entry is expired. For example a permission, set the expired_at field
* if it's not set and the entry will be flagged as expired
*
* <h2>Deletion</h2>
* If an entry should be deleted, just set the deleted_at field and it will be
* flagged as deleted
*
* @param obj Object that should be saved inside the database
* @return returns the created {@link studio.o7.octopus.sdk.v1.Entry}
*/
Entry call(Object obj);
Result<Entry, Error> call(studio.o7.octopus.sdk.v1.Object obj);

/**
* <h1>Call</h1>
* <p>
* Stores an object on a key with new revision in the database
* and just forgets it. All listeners will be called
* as usual without blocking this method.
*
* <h2>Expired</h2>
* If an entry is expired. For example a permission, set the expired_at field
* if it's not set and the entry will be flagged as expired
*
* <h2>Deletion</h2>
* If an entry should be deleted, just set the deleted_at field and it will be
* flagged as deleted
*
* @param obj Object that should be saved inside the database
*/
void callAndForget(Object obj);
void write(studio.o7.octopus.sdk.v1.Object obj);

/**
* <p>
* A registration of a handler will subscribe to its given key pattern
* and receive all updates on the given key pattern. The handlers `onCall`
* method will be invoked on the incoming {@link studio.o7.octopus.sdk.v1.EventCall}
* </p>
*
* <p>
* When subscribing, be reminded that the key pattern really matches the requested
* EventCalls, using symbols such as `*` and `<` will subscribe on multiple keys
* There's no safeguard to prevent subscribing to the same topic. So please make
* shure you're not handling a topic twice!
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: “shure” → “sure”.

Suggested change
* shure you're not handling a topic twice!
* sure you're not handling a topic twice!

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +83
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs say key patterns use * and <, but the matching implementation and other docs use > as the multi-token wildcard. Update this to > (or the correct symbol) to avoid misleading API consumers.

Suggested change
* EventCalls, using symbols such as `*` and `<` will subscribe on multiple keys
* There's no safeguard to prevent subscribing to the same topic. So please make
* shure you're not handling a topic twice!
* EventCalls, using symbols such as `*` and `>` will subscribe on multiple keys
* There's no safeguard to prevent subscribing to the same topic. So please make
* sure you're not handling a topic twice!

Copilot uses AI. Check for mistakes.
* </p>
*
* @param eventHandler Handler that will be invoked on matching incoming event
*/
void registerHandler(EventHandler eventHandler);

/**
* Unregister a handler
*
* @param eventHandler Handler to unregister
*/
default void unregisterHandler(EventHandler eventHandler) {
unregisterHandler(eventHandler.getListenerUniqueId());
}

/**
* Unregister a handler
*
* @param listenerUniqueId ID of the handler to unregister
*/
void unregisterHandler(UUID listenerUniqueId);
}
31 changes: 31 additions & 0 deletions api/src/main/java/studio/o7/octopus/plugin/api/OctopusError.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package studio.o7.octopus.plugin.api;

import gentle.Error;
import lombok.NonNull;

public enum OctopusError implements Error {

GET_REQUEST_FAILED(0, "While trying to get an entry, a gRPC-Error occurred"),
QUERY_REQUEST_FAILED(1, "While trying to query an entry, a gRPC-Error occurred"),
CALL_REQUEST_FAILED(1, "While trying to call an object, a gRPC-Error occurred"),
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CALL_REQUEST_FAILED and QUERY_REQUEST_FAILED both use error code 1. If codes are intended to be unique/stable for consumers, give each enum constant a distinct code (and keep them stable across releases).

Suggested change
CALL_REQUEST_FAILED(1, "While trying to call an object, a gRPC-Error occurred"),
CALL_REQUEST_FAILED(2, "While trying to call an object, a gRPC-Error occurred"),

Copilot uses AI. Check for mistakes.

;

private final int code;
private final String message;

OctopusError(int code, String message) {
this.code = code;
this.message = message;
}

@Override
public int code() {
return this.code;
}

@Override
public @NonNull String message() {
return this.message;
}
}
20 changes: 20 additions & 0 deletions api/src/main/java/studio/o7/octopus/plugin/api/QueryParameter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package studio.o7.octopus.plugin.api;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class QueryParameter {

private String keyPattern;
private String dataFilter;

private boolean includeExpired;

private int page;
private int pageSize;

private com.google.protobuf.Timestamp createdAtStart;
private com.google.protobuf.Timestamp createdAtEnd;
Comment on lines +1 to +18
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting in this new file is inconsistent with the rest of the codebase: add a blank line after the package declaration and import Timestamp instead of using a fully-qualified type in fields, to keep readability consistent (e.g., compare to OctopusError.java).

Copilot uses AI. Check for mistakes.

}

This file was deleted.

This file was deleted.

Loading
Loading