feat: add tinystruct-patterns skill and bootstrapping guidance (#1336)

* feat: add tinystruct-patterns skill and bootstrapping guidance

* docs(skills): restructuralize tinystruct-patterns to standard skill format

- Reorganize SKILL.md and all reference documents into "When to Use", "How It Works", and "Examples" sections to conform to project standards.
- Refine data-handling.md example to return Builder objects directly, leveraging framework auto-serialization.
- Simplify @Action examples in routing.md for better readability.
- Clarify framework mechanics including CLI bootstrapping via ApplicationManager.init(), event-driven architecture, and regex-based routing.

* Update skills/tinystruct-patterns/references/testing.md

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update skills/tinystruct-patterns/SKILL.md

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update skills/tinystruct-patterns/references/routing.md

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Update skills/tinystruct-patterns/references/testing.md

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Update skills/tinystruct-patterns/references/testing.md

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
James M. ZHOU
2026-05-11 13:19:44 +08:00
committed by GitHub
parent e196f8a4cb
commit f01929c31a
6 changed files with 433 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
---
name: tinystruct-patterns
description: Use when developing application modules or microservices with the tinystruct Java framework. Covers routing, context management, JSON handling with Builder, and CLI/HTTP dual-mode patterns.
origin: ECC
---
# tinystruct Development Patterns
Architecture and implementation patterns for building modules with the **tinystruct** Java framework a lightweight system where CLI and HTTP are equal citizens.
## When to Use
- Creating new `Application` modules by extending `AbstractApplication`.
- Defining routes and command-line actions using `@Action`.
- Handling per-request state via `Context`.
- Performing JSON serialization using the native `Builder` component.
- Configuring database connections or system settings in `application.properties`.
- Generating or re-generating the standard `bin/dispatcher` entry point via `ApplicationManager.init()`.
- Debugging routing conflicts (Actions) or CLI argument parsing.
## How It Works
The tinystruct framework treats any method annotated with `@Action` as a routable endpoint for both terminal and web environments. Applications are created by extending `AbstractApplication`, which provides core lifecycle hooks like `init()` and access to the request `Context`.
Routing is handled by the `ActionRegistry`, which automatically maps path segments to method arguments and injects dependencies. For data-only services, the native `Builder` component should be used for JSON serialization to maintain a zero-dependency footprint. The framework also includes a utility in `ApplicationManager` to bootstrap the project's execution environment by generating the `bin/dispatcher` script.
## Examples
### Basic Application (MyService)
```java
public class MyService extends AbstractApplication {
@Override
public void init() {
this.setTemplateRequired(false); // Disable .view lookup for data/API apps
}
@Override public String version() { return "1.0.0"; }
@Action("greet")
public String greet() {
return "Hello from tinystruct!";
}
}
```
### Parameterized Routing (getUser)
```java
// Handles /api/user/123 (Web) or "bin/dispatcher api/user/123" (CLI)
@Action("api/user/(\\d+)")
public String getUser(int userId) {
return "User ID: " + userId;
}
```
### HTTP Mode Disambiguation (login)
```java
@Action(value = "login", mode = Mode.HTTP_POST)
public boolean doLogin() {
// Process login logic
return true;
}
```
### Native JSON Data Handling (getData)
```java
@Action("api/data")
public Builder getData() throws ApplicationException {
Builder builder = new Builder();
builder.put("status", "success");
Builder nested = new Builder();
nested.put("id", 1);
nested.put("name", "James");
builder.put("data", nested);
return builder;
}
```
## Configuration
Settings are managed in `src/main/resources/application.properties`.
```properties
# Database
driver=org.h2.Driver
database.url=jdbc:h2:~/mydb
# App specific
my.service.endpoint=https://api.example.com
```
## Testing Patterns
Use JUnit 5 to test actions by verifying they are registered in the `ActionRegistry`.
```java
@Test
void testActionRegistration() {
Application app = new MyService();
app.init();
ActionRegistry registry = ActionRegistry.getInstance();
assertNotNull(registry.get("greet"));
}
```
## Red Flags & Anti-patterns
| Symptom | Correct Pattern |
|---|---|
| Importing `com.google.gson` or `com.fasterxml.jackson` | Use `org.tinystruct.data.component.Builder`. |
| `FileNotFoundException` for `.view` files | Call `setTemplateRequired(false)` in `init()` for API-only apps. |
| Annotating `private` methods with `@Action` | Actions must be `public` to be registered by the framework. |
| Hardcoding `main(String[] args)` in apps | Use `bin/dispatcher` as the entry point for all modules. |
| Manual `ActionRegistry` registration | Prefer the `@Action` annotation for automatic discovery. |
## Technical Reference
Detailed guides are available in the `references/` directory:
- [Architecture & Config](references/architecture.md) — Abstractions, Package Map, Properties
- [Routing & @Action](references/routing.md) — Annotation details, Modes, Parameters
- [Data Handling](references/data-handling.md) — Using the native `Builder` for JSON
- [System & Usage](references/system-usage.md) — Context, Sessions, Events, CLI usage
- [Testing Patterns](references/testing.md) — JUnit 5 integration and ActionRegistry testing
## Reference Source Files (Internal)
- `src/main/java/org/tinystruct/AbstractApplication.java` — Core base class
- `src/main/java/org/tinystruct/system/annotation/Action.java` — Annotation & Modes
- `src/main/java/org/tinystruct/application/ActionRegistry.java` — Routing Engine
- `src/main/java/org/tinystruct/data/component/Builder.java` — JSON/Data Serializer

View File

@@ -0,0 +1,77 @@
# tinystruct Architecture and Configuration
## When to Use
Choose **tinystruct** when you need a lightweight, high-performance Java framework that treats CLI and HTTP as equal citizens. It is ideal for building microservices, command-line utilities, and data-driven applications where a small footprint and zero-dependency JSON handling are required. Use it when you want to write logic once and expose it via both a terminal and a web server without modification.
## How It Works
### Core Architecture
The framework operates on a singleton `ActionRegistry` that maps URL patterns (or command strings) to `Action` objects. When a request arrives, the system resolves the path and invokes the corresponding method handle.
#### Key Abstractions
| Class/Interface | Role |
|---|---|
| `AbstractApplication` | Base class for all tinystruct applications. Extend this. |
| `@Action` annotation | Maps a method to a URI path (web) or command name (CLI). The single routing primitive. |
| `ActionRegistry` | Singleton that maps URL patterns to `Action` objects via regex. Never instantiate directly. |
| `Action` | Wraps a `MethodHandle` + regex pattern + priority + `Mode` for dispatch. |
| `Context` | Per-request state store. Access via `getContext()`. Holds CLI args and HTTP request/response. |
| `Dispatcher` | CLI entry point (`bin/dispatcher`). Reads `--import` to load applications. |
| `HttpServer` | Built-in Netty-based HTTP server. Start with `bin/dispatcher start --import org.tinystruct.system.HttpServer`. |
### Package Map
```
org.tinystruct/
├── AbstractApplication.java ← extend this
├── Application.java ← interface
├── ApplicationException.java ← checked exception
├── ApplicationRuntimeException.java ← unchecked exception
├── application/
│ ├── Action.java ← runtime action wrapper
│ ├── ActionRegistry.java ← singleton route registry
│ └── Context.java ← request context
├── system/
│ ├── annotation/Action.java ← @Action annotation + Mode enum
│ ├── Dispatcher.java ← CLI dispatcher
│ ├── HttpServer.java ← built-in HTTP server
│ ├── EventDispatcher.java ← event bus
│ └── Settings.java ← reads application.properties
├── data/component/Builder.java ← JSON serialization (use instead of Gson/Jackson)
└── http/ ← Request, Response, Constants
```
### Template Behavior and Dispatch Flow
By default, the framework assumes a view template is required. If `templateRequired` is `true`, `toString()` looks for a `.view` file in `src/main/resources/themes/<ClassName>.view`. Use `getContext()` to manage state and `setVariable("name", value)` to pass data to templates, which use `[%name%]` for interpolation.
## Examples
### Minimal Application Initialization
```java
@Override
public void init() {
this.setTemplateRequired(false); // Skip .view template lookup for data-only apps
}
```
### Action Definition and CLI Invocation
```java
@Action("hello")
public String hello() {
return "Hello, tinystruct!";
}
```
**Execution via Dispatcher:**
```bash
bin/dispatcher hello
```
### Configuration Access
Located at `src/main/resources/application.properties`:
```java
String port = this.getConfiguration("server.port");
```

View File

@@ -0,0 +1,35 @@
# tinystruct Data Handling (JSON)
## When to Use
Prefer `org.tinystruct.data.component.Builder` in scenarios where you need a lightweight, high-performance JSON solution with **zero external dependencies**. It is specifically designed to keep your tinystruct applications lean and fast, making it the ideal choice for microservices and CLI tools where including heavy libraries like Jackson or Gson would be overkill.
## How It Works
The `Builder` class provides a simple key-value interface for both creating and reading JSON structures. It integrates directly with `AbstractApplication` result handling; when an action method returns a `Builder` object, the framework automatically serializes it to the response stream. This prevents the need for manual string conversion and ensures consistent data formatting across your application modules.
## Examples
### Serialization
```java
import org.tinystruct.data.component.Builder;
// Create and populate
Builder response = new Builder();
response.put("status", "success");
response.put("count", 42);
response.put("data", someList);
return response; // {"status":"success","count":42,...}
```
### Parsing
```java
import org.tinystruct.data.component.Builder;
// Parse a JSON string
Builder parsed = new Builder();
parsed.parse(jsonString);
String status = parsed.get("status").toString();
```

View File

@@ -0,0 +1,57 @@
# tinystruct @Action Routing Reference
## When to Use
Use the `@Action` annotation in your applications to define routes for both CLI commands and HTTP endpoints. It is appropriate whenever you need to map logic to a specific path, handle parameterized requests (e.g., retrieving a resource by ID), or restrict execution to specific HTTP methods (GET, POST, etc.) while maintaining a consistent command structure across environments.
## How It Works
The `ActionRegistry` parses `@Action` annotations to build a routing table. For parameterized methods, the framework automatically maps Java parameter types (int, String, etc.) to corresponding regex segments to generate an internal matching pattern. For instance, `getUser(int id)` generates a regex targeting digits, while `search(String query)` targets generic path segments.
When a request is dispatched, the `ActionRegistry` automatically injects dependencies like `Request` and `Response` into the action method if they are specified as parameters, drawing them directly from the current request's `Context`. Execution is further filtered by the `Mode` value, allowing a single path to invoke different logic depending on whether the trigger was a terminal command or a specific type of HTTP request.
### Mode Values
| Mode | When it triggers |
|---|---|
| `DEFAULT` | Both CLI and HTTP (GET, POST, etc.) |
| `CLI` | CLI dispatcher only |
| `HTTP_GET` | HTTP GET only |
| `HTTP_POST` | HTTP POST only |
| `HTTP_PUT` | HTTP PUT only |
| `HTTP_DELETE` | HTTP DELETE only |
| `HTTP_PATCH` | HTTP PATCH only |
## Examples
### Basic Action Declaration
```java
@Action(
value = "path/subpath", // required: URI segment or CLI command
description = "What it does", // shown in --help output
mode = Mode.HTTP_POST, // default: Mode.DEFAULT (both CLI + HTTP)
options = {}, // CLI option flags
example = "curl -X POST http://localhost:8080/path/subpath/42"
)
public String myAction(int id) { ... }
```
### Parameterized Paths (Regex Generation)
```java
@Action("user/{id}")
public String getUser(int id) { ... }
// → pattern: ^/?user/(-?\d+)$
@Action("search")
public String search(String query) { ... }
// → pattern: ^/?search/([^/]+)$
```
### Request and Response Injection
```java
@Action(value = "upload", mode = Mode.HTTP_POST)
public String upload(Request<?, ?> req, Response<?, ?> res) throws ApplicationException {
// req.getParameter("file"), res.setHeader(...), etc.
return "ok";
}
```

View File

@@ -0,0 +1,74 @@
# tinystruct System and Usage Reference
## When to Use
Use the system and usage patterns described here when you need to handle stateful interactions across CLI and HTTP modes, manage user sessions in web applications, or implement loosely coupled communication between application modules using an event-driven architecture.
## How It Works
The framework's `Context` serves as the primary data store for request-specific state. In CLI mode, flags passed as `--key value` are automatically parsed and stored in the `Context` with the `--` prefix, allowing action methods to retrieve command parameters easily. For web applications, the system provides standard session management via the `Request` object, enabling the storage of user data across multiple HTTP requests.
The internal `EventDispatcher` facilitates an asynchronous event bus. By defining custom `Event` classes and registering handlers (typically within an application's `init()` method), you can trigger background tasks—such as sending emails or logging audit trails—without blocking the main execution path.
## Examples
### Context and CLI Arguments
```java
@Action("echo")
public String echo() {
// CLI: bin/dispatcher echo --words "Hello World"
Object words = getContext().getAttribute("--words");
if (words != null) return words.toString();
return "No words provided";
}
```
### Session Management (Web Mode)
```java
@Action(value = "login", mode = Mode.HTTP_POST)
public String login(Request request) {
request.getSession().setAttribute("userId", "42");
return "Logged in";
}
@Action("profile")
public String profile(Request request) {
Object userId = request.getSession().getAttribute("userId");
if (userId == null) return "Not logged in";
return "User: " + userId;
}
```
### Event System
```java
// 1. Define an event
public class OrderCreatedEvent implements org.tinystruct.system.Event<Order> {
private final Order order;
public OrderCreatedEvent(Order order) { this.order = order; }
@Override public String getName() { return "order_created"; }
@Override public Order getPayload() { return order; }
}
// 2. Register a handler
EventDispatcher.getInstance().registerHandler(OrderCreatedEvent.class, event -> {
CompletableFuture.runAsync(() -> sendConfirmationEmail(event.getPayload()));
});
// 3. Dispatch
EventDispatcher.getInstance().dispatch(new OrderCreatedEvent(newOrder));
```
### Running the Application
```bash
# CLI mode
bin/dispatcher hello
bin/dispatcher echo --words "Hello" --import com.example.HelloApp
# HTTP server (listens on :8080 by default)
bin/dispatcher start --import org.tinystruct.system.HttpServer
# Database utilities
bin/dispatcher generate --table users
bin/dispatcher sql-query "SELECT * FROM users"
```

View File

@@ -0,0 +1,59 @@
# tinystruct Testing Patterns
## When to Use
Use the testing patterns described here when writing units tests for your tinystruct applications with **JUnit 5**. These patterns are essential for verifying that your `@Action` methods return the correct results and that your routing logic is properly registered within the singleton `ActionRegistry`.
## How It Works
Testing tinystruct applications requires a specific setup to ensure framework-level features like annotation processing and configuration management are active. By creating a new instance of your application and passing it a `Settings` object in the `setUp()` method, you trigger the `init()` lifecycle. This ensures all `@Action` methods are discovered and registered.
Because the `ActionRegistry` is a singleton, it is critical to maintain isolation between tests by properly initializing your application state before each test execution, preventing side effects from leaking across the test suite.
## Examples
### Unit Testing an Application
```java
import org.junit.jupiter.api.*;
import org.tinystruct.application.ActionRegistry;
import org.tinystruct.system.Settings;
class MyAppTest {
private MyApp app;
@BeforeEach
void setUp() {
app = new MyApp();
Settings config = new Settings();
app.setConfiguration(config);
app.init(); // triggers @Action annotation processing
}
void testHello() throws Exception {
// Direct invocation via the application object
Object result = app.invoke("hello");
Assertions.assertEquals("Hello, tinystruct!", result);
}
@Test
void testGreet() throws Exception {
// Invocation with arguments
Object result = app.invoke("greet", new Object[]{"James"});
Assertions.assertEquals("Hello, James!", result);
}
}
```
### Testing via ActionRegistry
If you need to test the routing logic itself, use the `ActionRegistry` singleton to verify path matching:
```java
@Test
void testRouting() {
ActionRegistry registry = ActionRegistry.getInstance();
// Verify a path matches an action
Action action = registry.getAction("greet/James");
Assertions.assertNotNull(action);
}
```
Reference: `src/test/java/org/tinystruct/application/ActionRegistryTest.java`