From 1838173262aa68b9cdd7f0871aa46dbe6828c7da Mon Sep 17 00:00:00 2001 From: "@James" Date: Wed, 13 May 2026 01:23:58 +0800 Subject: [PATCH 01/12] feat: update tinystruct-patterns skill with comprehensive expert knowledge --- skills/tinystruct-patterns/SKILL.md | 161 +++++++++++++----- .../references/architecture.md | 23 ++- .../references/data-handling.md | 45 +++-- .../references/database.md | 97 +++++++++++ .../tinystruct-patterns/references/routing.md | 35 ++-- .../references/system-usage.md | 101 ++++++----- .../tinystruct-patterns/references/testing.md | 47 +++-- 7 files changed, 378 insertions(+), 131 deletions(-) create mode 100644 skills/tinystruct-patterns/references/database.md diff --git a/skills/tinystruct-patterns/SKILL.md b/skills/tinystruct-patterns/SKILL.md index e2d474af4..17739cabd 100644 --- a/skills/tinystruct-patterns/SKILL.md +++ b/skills/tinystruct-patterns/SKILL.md @@ -1,28 +1,36 @@ --- 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. +description: Expert guidance for developing with the tinystruct Java framework. Use when working on the tinystruct codebase or any project built on tinystruct — including creating Application classes, @Action-mapped routes, unit tests, ActionRegistry, HTTP/CLI dual-mode handling, the built-in HTTP server, the event system, JSON with Builder/Builders, database persistence with AbstractData, POJO generation, Server-Sent Events (SSE), file uploads, and outbound HTTP networking. 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. +Architecture and implementation patterns for building modules with the **tinystruct** Java framework – a lightweight, high-performance framework that treats CLI and HTTP as equal citizens, requiring no `main()` method and minimal configuration. + +## Core Principle + +**CLI and HTTP are equal citizens.** Every method annotated with `@Action` should ideally be runnable from both a terminal and a web browser without modification. This "dual-mode" capability is the core design philosophy of tinystruct. ## 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. +- Performing JSON serialization using the native `Builder` and `Builders` components. +- Working with database persistence via `AbstractData` POJOs. +- Generating POJOs from database tables using the `generate` command. +- Implementing Server-Sent Events (SSE) for real-time push. +- Handling file uploads via multipart data. +- Making outbound HTTP requests with `URLRequest` and `HTTPHandler`. - 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. +Routing is handled by the `ActionRegistry`, which automatically maps path segments to method arguments and injects dependencies. For data-only services, the native `Builder` and `Builders` components should be used for JSON serialization to maintain a zero-dependency footprint. The database layer uses `AbstractData` POJOs paired with XML mapping files for CRUD operations without external ORM libraries. ## Examples @@ -40,38 +48,76 @@ public class MyService extends AbstractApplication { 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; + // Path parameter: GET /?q=greet/James OR bin/dispatcher greet/James + @Action("greet") + public String greet(String name) { + return "Hello, " + name + "!"; + } } ``` ### HTTP Mode Disambiguation (login) ```java @Action(value = "login", mode = Mode.HTTP_POST) -public boolean doLogin() { - // Process login logic - return true; +public String doLogin(Request request) throws ApplicationException { + request.getSession().setAttribute("userId", "42"); + return "Logged in"; } ``` -### Native JSON Data Handling (getData) +### Native JSON Data Handling (Builder + Builders) ```java +import org.tinystruct.data.component.Builder; +import org.tinystruct.data.component.Builders; + @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; +public String getData() throws ApplicationException { + Builders dataList = new Builders(); + Builder item = new Builder(); + item.put("id", 1); + item.put("name", "James"); + dataList.add(item); + + Builder response = new Builder(); + response.put("status", "success"); + response.put("data", dataList); + return response.toString(); // {"status":"success","data":[{"id":1,"name":"James"}]} +} +``` + +### SSE (Server-Sent Events) +```java +import org.tinystruct.http.SSEPushManager; + +@Action("sse/connect") +public String connect() { + return "{\"type\":\"connect\",\"message\":\"Connected to SSE\"}"; +} + +// Push to a specific client +String sessionId = getContext().getId(); +Builder msg = new Builder(); +msg.put("text", "Hello, user!"); +SSEPushManager.getInstance().push(sessionId, msg); + +// Broadcast to all +SSEPushManager.getInstance().broadcast(broadcastMsg); +``` + +### File Upload +```java +import org.tinystruct.data.FileEntity; + +@Action(value = "upload", mode = Mode.HTTP_POST) +public String upload(Request request) throws ApplicationException { + List files = request.getAttachments(); + if (files != null) { + for (FileEntity file : files) { + System.out.println("Uploaded: " + file.getFilename()); + } + } + return "Upload OK"; } ``` @@ -83,35 +129,48 @@ Settings are managed in `src/main/resources/application.properties`. # Database driver=org.h2.Driver database.url=jdbc:h2:~/mydb +database.user=sa +database.password= -# App specific -my.service.endpoint=https://api.example.com +# Server +default.home.page=hello +server.port=8080 + +# Locale +default.language=en_US + +# Session (Redis for clustered environments) +# default.session.repository=org.tinystruct.http.RedisSessionRepository +# redis.host=127.0.0.1 +# redis.port=6379 ``` -## Testing Patterns - -Use JUnit 5 to test actions by verifying they are registered in the `ActionRegistry`. - +Access config values in your application: ```java -@Test -void testActionRegistration() { - Application app = new MyService(); - app.init(); - - ActionRegistry registry = ActionRegistry.getInstance(); - assertNotNull(registry.get("greet")); -} +String port = this.getConfiguration("server.port"); ``` ## 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. | +| Importing `com.google.gson` or `com.fasterxml.jackson` | Use `org.tinystruct.data.component.Builder` / `Builders`. | +| Using `List` for JSON arrays | Use `Builders` to avoid generic type erasure issues. | +| `ApplicationRuntimeException: template not found` | 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. | +| Action not found at runtime | Ensure class is imported via `--import` or listed in `application.properties`. | +| CLI arg not visible | Pass with `--key value`; access via `getContext().getAttribute("--key")`. | +| Two methods same path, wrong one fires | Set explicit `mode` (e.g., `HTTP_GET` vs `HTTP_POST`) to disambiguate. | + +## Best Practices + +1. **Granular Applications**: Break logic into smaller, focused applications rather than one monolithic class. +2. **Setup in `init()`**: Leverage `init()` for setup (config, DB) rather than the constructor. Do NOT call `setAction()` — use `@Action` annotation. +3. **Mode Awareness**: Use the `Mode` parameter in `@Action` to restrict sensitive operations to `CLI` only or specific HTTP methods. +4. **Context over Params**: For optional CLI flags, use `getContext().getAttribute("--flag")` rather than adding parameters to the method signature. +5. **Asynchronous Events**: For heavy tasks triggered by events, use `CompletableFuture.runAsync()` inside the event handler. ## Technical Reference @@ -119,13 +178,23 @@ 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 +- [Data Handling](references/data-handling.md) — Builder, Builders, JSON serialization & parsing +- [Database Persistence](references/database.md) — AbstractData POJOs, CRUD, mapping XML, POJO generation +- [System & Usage](references/system-usage.md) — Context, Sessions, SSE, File Uploads, Events, Networking +- [Testing Patterns](references/testing.md) — JUnit 5 unit and HTTP integration testing ## Reference Source Files (Internal) -- `src/main/java/org/tinystruct/AbstractApplication.java` — Core base class +- `src/main/java/org/tinystruct/AbstractApplication.java` — Core base class with lifecycle hooks - `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 +- `src/main/java/org/tinystruct/data/component/Builder.java` — JSON object serializer +- `src/main/java/org/tinystruct/data/component/Builders.java` — JSON array serializer +- `src/main/java/org/tinystruct/data/component/AbstractData.java` — Base POJO class with CRUD +- `src/main/java/org/tinystruct/data/Mapping.java` — Mapping XML parser +- `src/main/java/org/tinystruct/data/tools/MySQLGenerator.java` — POJO generator reference +- `src/main/java/org/tinystruct/data/component/FieldType.java` — SQL-to-Java type mappings +- `src/main/java/org/tinystruct/data/component/Condition.java` — Fluent SQL query builder +- `src/main/java/org/tinystruct/http/SSEPushManager.java` — SSE connection management +- `src/test/java/org/tinystruct/application/ActionRegistryTest.java` — Registry test examples +- `src/test/java/org/tinystruct/system/HttpServerHttpModeTest.java` — HTTP integration test patterns diff --git a/skills/tinystruct-patterns/references/architecture.md b/skills/tinystruct-patterns/references/architecture.md index bcbbb13bb..3d7ea5e82 100644 --- a/skills/tinystruct-patterns/references/architecture.md +++ b/skills/tinystruct-patterns/references/architecture.md @@ -2,7 +2,7 @@ ## 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. +Choose **tinystruct** when you need a lightweight, high-performance Java framework that treats CLI and HTTP as equal citizens. Ideal for microservices, CLI utilities, and data-driven applications with a small footprint and zero-dependency JSON handling. ## How It Works @@ -20,7 +20,7 @@ The framework operates on a singleton `ActionRegistry` that maps URL patterns (o | `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`. | +| `HttpServer` | Built-in HTTP server. Start with `bin/dispatcher start --import org.tinystruct.system.HttpServer`. | ### Package Map @@ -40,13 +40,23 @@ org.tinystruct/ │ ├── 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 +├── data/ +│ ├── component/Builder.java ← JSON object (use instead of Gson/Jackson) +│ ├── component/Builders.java ← JSON array +│ ├── component/AbstractData.java ← base POJO for DB persistence +│ ├── component/Condition.java ← fluent SQL query builder +│ ├── component/FieldType.java ← SQL-to-Java type mappings +│ ├── Mapping.java ← reads .map.xml metadata +│ ├── DatabaseOperator.java ← low-level JDBC wrapper +│ └── FileEntity.java ← file upload representation +├── http/ ← Request, Response, Constants +│ └── SSEPushManager.java ← Server-Sent Events management +└── net/ ← URLRequest, HTTPHandler (outbound HTTP) ``` ### 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/.view`. Use `getContext()` to manage state and `setVariable("name", value)` to pass data to templates, which use `[%name%]` for interpolation. +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/.view`. Use `setVariable("name", value)` to pass data to templates, which use `{%name%}` for interpolation. ## Examples @@ -55,6 +65,7 @@ By default, the framework assumes a view template is required. If `templateRequi @Override public void init() { this.setTemplateRequired(false); // Skip .view template lookup for data-only apps + // Do NOT call setAction() here — use @Action annotation instead } ``` @@ -68,6 +79,8 @@ public String hello() { **Execution via Dispatcher:** ```bash bin/dispatcher hello +bin/dispatcher greet/James +bin/dispatcher echo --words "Hello" --import com.example.HelloApp ``` ### Configuration Access diff --git a/skills/tinystruct-patterns/references/data-handling.md b/skills/tinystruct-patterns/references/data-handling.md index 3f008c34a..125443ac8 100644 --- a/skills/tinystruct-patterns/references/data-handling.md +++ b/skills/tinystruct-patterns/references/data-handling.md @@ -2,34 +2,59 @@ ## 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. +Prefer `org.tinystruct.data.component.Builder` and `Builders` for lightweight, zero-dependency JSON. Use `Builder` for JSON objects (`{}`), `Builders` for JSON arrays (`[]`). **Always use `Builders` instead of `List`** to avoid generic type erasure issues. ## 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. +`Builder` provides a key-value interface for creating and reading JSON objects. `Builders` provides an indexed list for JSON arrays. Both integrate directly with `AbstractApplication` result handling. + +### Why Builder/Builders? +- **Zero External Dependencies** — lean and fast +- **Native Integration** — works with framework result handling +- **Type Safety** — `Builders` serializes properly to `[]`; `List` can cause casting issues ## Examples -### Serialization +### Serialize a Single Object ```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,...} +return response.toString(); // {"status":"success","count":42} ``` -### Parsing +### Serialize a List using Builders ```java import org.tinystruct.data.component.Builder; +import org.tinystruct.data.component.Builders; -// Parse a JSON string +Builders dataList = new Builders(); +for (MyModel item : myCollection) { + Builder b = new Builder(); + b.put("id", item.getId()); + b.put("name", item.getName()); + dataList.add(b); +} +Builder response = new Builder(); +response.put("data", dataList); +return response.toString(); // {"data":[{"id":1,"name":"X"}]} +``` + +### Parse a JSON Object +```java Builder parsed = new Builder(); parsed.parse(jsonString); - String status = parsed.get("status").toString(); ``` + +### Parse a JSON Array +```java +Builders parsedArray = new Builders(); +parsedArray.parse(jsonArrayString); +for (int i = 0; i < parsedArray.size(); i++) { + Builder item = parsedArray.get(i); + System.out.println(item.get("name")); +} +``` diff --git a/skills/tinystruct-patterns/references/database.md b/skills/tinystruct-patterns/references/database.md new file mode 100644 index 000000000..947bbaa92 --- /dev/null +++ b/skills/tinystruct-patterns/references/database.md @@ -0,0 +1,97 @@ +# tinystruct Database Persistence + +## When to Use + +Use the built-in ORM-like data layer for database operations. It provides a lightweight alternative to JPA/Hibernate using POJOs extending `AbstractData` and XML mapping files. + +## Architecture + +Each table is represented by: +1. **Java POJO**: Extends `AbstractData`, provides getters/setters and `setData(Row)`. +2. **Mapping XML**: `ClassName.map.xml` in resources, binding Java fields to DB columns. + +### Key Base Class: `AbstractData` +Provides CRUD methods: +- `append()` / `appendAndGetId()` +- `update()` +- `delete()` +- `findAll()` / `findOneById()` / `findOneByKey(key, value)` +- `findWith(where, params)` +- `find(SQL, params)` + +## POJO Generation (CLI) + +Introspect a live database table to produce the POJO and mapping file. + +### Configuration +`application.properties`: +```properties +driver=com.mysql.cj.jdbc.Driver +database.url=jdbc:mysql://localhost:3306/mydb +database.user=root +database.password=secret +``` + +### Command +```bash +# Interactive mode +bin/dispatcher generate + +# Specify table +bin/dispatcher generate --tables users +``` + +## Examples + +### CRUD Operations +```java +// CREATE +User user = new User(); +user.setUsername("james"); +user.append(); + +// READ +User user = new User(); +user.setId(42); +user.findOneById(); + +// UPDATE +user.setEmail("new@example.com"); +user.update(); + +// DELETE +user.delete(); +``` + +### Querying with Conditions +```java +User user = new User(); +Table results = user.findWith("username LIKE ?", new Object[]{"%jam%"}); + +// Fluent Condition Builder +Condition condition = new Condition(); +condition.setRequestFields("id,username"); +Table results = user.find( + condition.select("`users`").and("email LIKE ?").orderBy("id DESC"), + new Object[]{"%@example.com"} +); +``` + +### Mapping XML Structure +`User.map.xml`: +```xml + + + + + + + +``` + +## Important Rules + +1. **File Placement**: The mapping XML **must** mirror the POJO's package path under `src/main/resources/`. +2. **Naming**: Table names are singularized for class names (`users` → `User`). Underscored columns become camelCase fields (`created_at` → `createdAt`). +3. **Setters**: Use `setFieldAsXxx` methods (e.g., `setFieldAsString`) in setters to sync state with the internal field map. +4. **Id Field**: The primary key field in Java is always named `Id` (inherited from `AbstractData`). diff --git a/skills/tinystruct-patterns/references/routing.md b/skills/tinystruct-patterns/references/routing.md index ddb2c9e85..256d916eb 100644 --- a/skills/tinystruct-patterns/references/routing.md +++ b/skills/tinystruct-patterns/references/routing.md @@ -2,13 +2,17 @@ ## 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. +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, or restrict execution to specific HTTP methods 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. +The `ActionRegistry` parses `@Action` annotations to build a routing table. For parameterized methods, the framework automatically maps Java parameter types to corresponding regex 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. +### Regex Generation Rules +- `getUser(int id)` → pattern: `^/?user/(-?\d+)$` +- `search(String query)` → pattern: `^/?search/([^/]+)$` + +Supported parameter types: `String`, `int/Integer`, `long/Long`, `float/Float`, `double/Double`, `boolean/Boolean`, `char/Character`, `short/Short`, `byte/Byte`, `Date` (parsed as `yyyy-MM-dd HH:mm:ss`). ### Mode Values @@ -22,6 +26,8 @@ When a request is dispatched, the `ActionRegistry` automatically injects depende | `HTTP_DELETE` | HTTP DELETE only | | `HTTP_PATCH` | HTTP PATCH only | +> **Note:** You can map HTTP method names to `Mode` using `Action.Mode.fromName(String methodName)`. Unknown or null values return `Mode.DEFAULT`. + ## Examples ### Basic Action Declaration @@ -29,29 +35,30 @@ When a request is dispatched, the `ActionRegistry` automatically injects depende @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" + mode = Mode.DEFAULT, // default: Mode.DEFAULT + example = "bin/dispatcher path/subpath/42" ) public String myAction(int id) { ... } ``` -### Parameterized Paths (Regex Generation) +### Parameterized Paths ```java @Action("user/{id}") public String getUser(int id) { ... } -// → pattern: ^/?user/(-?\d+)$ - -@Action("search") -public String search(String query) { ... } -// → pattern: ^/?search/([^/]+)$ +// → CLI: bin/dispatcher user/42 +// → HTTP: /?q=user/42 ``` -### Request and Response Injection +### Dependency Injection +`ActionRegistry` automatically injects `Request` and/or `Response` from `Context` if they are parameters: + ```java @Action(value = "upload", mode = Mode.HTTP_POST) public String upload(Request req, Response res) throws ApplicationException { - // req.getParameter("file"), res.setHeader(...), etc. + // Access raw request/response if needed return "ok"; } ``` + +### Path Matching Priority +If two methods share the same path, the framework uses the first match in the `ActionRegistry`. Use explicit `Mode` values to disambiguate (e.g., separating a GET for a form and a POST for submission). diff --git a/skills/tinystruct-patterns/references/system-usage.md b/skills/tinystruct-patterns/references/system-usage.md index 54c6402c6..7ff12c134 100644 --- a/skills/tinystruct-patterns/references/system-usage.md +++ b/skills/tinystruct-patterns/references/system-usage.md @@ -2,13 +2,26 @@ ## 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. +Use these patterns to handle request state, manage web sessions, implement Server-Sent Events (SSE), handle file uploads, or perform outbound HTTP networking. ## 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. +### Context and CLI Arguments +`Context` is the primary data store for request-specific state. CLI flags passed as `--key value` are stored in `Context` as `"--key"`. -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. +### Session Management +Pluggable architecture. Default is `MemorySessionRepository`. Configure Redis in `application.properties`: +```properties +default.session.repository=org.tinystruct.http.RedisSessionRepository +redis.host=127.0.0.1 +redis.port=6379 +``` + +### Server-Sent Events (SSE) +Built-in support for real-time push. The `HttpServer` automatically handles the SSE lifecycle when it detects the `Accept: text/event-stream` header. Connections are tracked by session ID in `SSEPushManager`. + +### Outbound Networking +Use `URLRequest` and `HTTPHandler` for making HTTP requests to external services. ## Examples @@ -23,52 +36,62 @@ public String echo() { } ``` -### Session Management (Web Mode) +### Session Management ```java @Action(value = "login", mode = Mode.HTTP_POST) -public String login(Request request) { +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; +### Server-Sent Events (SSE) +```java +@Action("sse/connect") +public String connect() { + return "{\"type\":\"connect\",\"message\":\"Connected\"}"; +} + +// In another method or event handler: +String sessionId = getContext().getId(); +SSEPushManager.getInstance().push(sessionId, new Builder().put("msg", "hello")); +``` + +### File Uploads +```java +import org.tinystruct.data.FileEntity; + +@Action(value = "upload", mode = Mode.HTTP_POST) +public String upload(Request request) throws ApplicationException { + List files = request.getAttachments(); + if (files != null) { + for (FileEntity file : files) { + // file.getFilename(), file.getContent() + } + } + return "Uploaded"; +} +``` + +### Outbound HTTP +```java +import org.tinystruct.net.URLRequest; +import org.tinystruct.net.handlers.HTTPHandler; + +URLRequest request = new URLRequest(new URL("https://api.example.com")); +request.setMethod("POST").setBody("{\"data\":\"val\"}"); + +HTTPHandler handler = new HTTPHandler(); +var response = handler.handleRequest(request); +if (response.getStatusCode() == 200) { + String body = response.getBody(); } ``` ### Event System +Register handlers in `init()` for asynchronous task execution. ```java -// 1. Define an event -public class OrderCreatedEvent implements org.tinystruct.system.Event { - 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())); +EventDispatcher.getInstance().registerHandler(MyEvent.class, event -> { + CompletableFuture.runAsync(() -> doHeavyWork(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" ``` diff --git a/skills/tinystruct-patterns/references/testing.md b/skills/tinystruct-patterns/references/testing.md index 10152665a..e17ad6293 100644 --- a/skills/tinystruct-patterns/references/testing.md +++ b/skills/tinystruct-patterns/references/testing.md @@ -2,58 +2,71 @@ ## 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`. +Use these patterns when writing unit tests for your applications with **JUnit 5**. Essential for verifying action logic, routing registration, and HTTP mode behavior. ## 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. +### Unit Testing Applications +ActionRegistry is a singleton. To test an application: +1. Instantiate the application. +2. Provide a `Settings` object (triggers `init()` and annotation processing). +3. Use `app.invoke(path, args)` to test logic directly. -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. +### HTTP Integration Testing +For tests involving the built-in HTTP server: +1. Start `HttpServer` in a background thread. +2. Use `ApplicationManager.call("start", context, Action.Mode.CLI)` to boot. +3. Wait for the port to be open using a `Socket`. +4. Use `URLRequest` and `HTTPHandler` to perform actual requests. ## Examples -### Unit Testing an Application +### Unit Test ```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 + app.setConfiguration(new Settings()); + // init() is called automatically by setConfiguration } + + @Test void testHello() throws Exception { - // Direct invocation via the application object Object result = app.invoke("hello"); - Assertions.assertEquals("Hello, tinystruct!", result); + Assertions.assertEquals("Hello!", 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: - +### ActionRegistry Match Testing ```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` + +### HTTP Integration Pattern +Reference: `src/test/java/org/tinystruct/system/HttpServerHttpModeTest.java` + +```java +// Pattern: +// 1. Start server in thread +// 2. Poll for port availability +// 3. Send HTTP request via HTTPHandler +// 4. Assert response body/status +``` From ccb541ec97b4dd9577e03766b2a482b808de30ac Mon Sep 17 00:00:00 2001 From: "James M. ZHOU" Date: Fri, 15 May 2026 00:19:27 +0800 Subject: [PATCH 02/12] Update skills/tinystruct-patterns/SKILL.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- skills/tinystruct-patterns/SKILL.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skills/tinystruct-patterns/SKILL.md b/skills/tinystruct-patterns/SKILL.md index 17739cabd..84e5cb3df 100644 --- a/skills/tinystruct-patterns/SKILL.md +++ b/skills/tinystruct-patterns/SKILL.md @@ -102,7 +102,8 @@ msg.put("text", "Hello, user!"); SSEPushManager.getInstance().push(sessionId, msg); // Broadcast to all -SSEPushManager.getInstance().broadcast(broadcastMsg); +// Broadcast to all +SSEPushManager.getInstance().broadcast(msg); ``` ### File Upload From 5089e9261f98a001a70f4ff988a9aef114c05955 Mon Sep 17 00:00:00 2001 From: "James M. ZHOU" Date: Fri, 15 May 2026 00:21:52 +0800 Subject: [PATCH 03/12] Update skills/tinystruct-patterns/SKILL.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- skills/tinystruct-patterns/SKILL.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/skills/tinystruct-patterns/SKILL.md b/skills/tinystruct-patterns/SKILL.md index 84e5cb3df..0a8aeff0c 100644 --- a/skills/tinystruct-patterns/SKILL.md +++ b/skills/tinystruct-patterns/SKILL.md @@ -12,7 +12,9 @@ Architecture and implementation patterns for building modules with the **tinystr **CLI and HTTP are equal citizens.** Every method annotated with `@Action` should ideally be runnable from both a terminal and a web browser without modification. This "dual-mode" capability is the core design philosophy of tinystruct. -## When to Use +## When to Activate + +### When to Use - Creating new `Application` modules by extending `AbstractApplication`. - Defining routes and command-line actions using `@Action`. From f822d09ba738adffc82a8eef807b55d6dc2bd08a Mon Sep 17 00:00:00 2001 From: "James M. ZHOU" Date: Fri, 15 May 2026 00:24:24 +0800 Subject: [PATCH 04/12] Update skills/tinystruct-patterns/references/database.md Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- skills/tinystruct-patterns/references/database.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/tinystruct-patterns/references/database.md b/skills/tinystruct-patterns/references/database.md index 947bbaa92..67be8cf82 100644 --- a/skills/tinystruct-patterns/references/database.md +++ b/skills/tinystruct-patterns/references/database.md @@ -71,7 +71,7 @@ Table results = user.findWith("username LIKE ?", new Object[]{"%jam%"}); // Fluent Condition Builder Condition condition = new Condition(); condition.setRequestFields("id,username"); -Table results = user.find( +Table filtered = user.find( condition.select("`users`").and("email LIKE ?").orderBy("id DESC"), new Object[]{"%@example.com"} ); From 388b654febd276f5628200befd11b7e1745e5646 Mon Sep 17 00:00:00 2001 From: "@James" Date: Fri, 15 May 2026 00:33:27 +0800 Subject: [PATCH 05/12] Update testing.md --- skills/tinystruct-patterns/references/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/tinystruct-patterns/references/testing.md b/skills/tinystruct-patterns/references/testing.md index e17ad6293..f3c64e5b6 100644 --- a/skills/tinystruct-patterns/references/testing.md +++ b/skills/tinystruct-patterns/references/testing.md @@ -33,7 +33,7 @@ class MyAppTest { void setUp() { app = new MyApp(); app.setConfiguration(new Settings()); - // init() is called automatically by setConfiguration + app.init(); // triggers @Action annotation processing and registers all actions } @Test From fb2c895bc33dd4b997780adbe47d0849807fbc2b Mon Sep 17 00:00:00 2001 From: "@James" Date: Fri, 15 May 2026 00:44:07 +0800 Subject: [PATCH 06/12] Update database.md --- skills/tinystruct-patterns/references/database.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/skills/tinystruct-patterns/references/database.md b/skills/tinystruct-patterns/references/database.md index 67be8cf82..ef2273210 100644 --- a/skills/tinystruct-patterns/references/database.md +++ b/skills/tinystruct-patterns/references/database.md @@ -4,13 +4,15 @@ Use the built-in ORM-like data layer for database operations. It provides a lightweight alternative to JPA/Hibernate using POJOs extending `AbstractData` and XML mapping files. -## Architecture +## How It Works + +### Architecture Each table is represented by: 1. **Java POJO**: Extends `AbstractData`, provides getters/setters and `setData(Row)`. 2. **Mapping XML**: `ClassName.map.xml` in resources, binding Java fields to DB columns. -### Key Base Class: `AbstractData` +#### Key Base Class: `AbstractData` Provides CRUD methods: - `append()` / `appendAndGetId()` - `update()` @@ -19,11 +21,11 @@ Provides CRUD methods: - `findWith(where, params)` - `find(SQL, params)` -## POJO Generation (CLI) +### POJO Generation (CLI) Introspect a live database table to produce the POJO and mapping file. -### Configuration +#### Configuration `application.properties`: ```properties driver=com.mysql.cj.jdbc.Driver @@ -32,7 +34,7 @@ database.user=root database.password=secret ``` -### Command +#### Command ```bash # Interactive mode bin/dispatcher generate From fb1f81e0d7c29b1f9f3102efc1c91b53bd6d00fa Mon Sep 17 00:00:00 2001 From: "@James" Date: Thu, 24 Sep 2026 21:10:42 +0800 Subject: [PATCH 07/12] docs: sync tinystruct-patterns skill with latest tinystruct framework (1.7.34) Sync skills/tinystruct-patterns against tinystruct's own canonical copy of this skill (.agents/skills/tinystruct-patterns/), which the framework repo keeps up to date with each release: - Document the bin/dispatcher / bin/dispatcher.cmd launcher generation workflow (never hand-write or copy it; the framework generates it via ApplicationManager.init()). - Add native MCP server/tool integration (org.tinystruct.mcp.*), including the prompt-injection warning for tool return values and overloaded-tool schema merging. - Add annotation-based DB mapping (@Table/@Id/@Column) alongside XML mapping, the --mapping annotation generator flag, and table auto-creation via database.autocreate. - Add the programmatic logging/diagnostics subsystem (ANSI console colors, StackWalker-based caller tracing, per-package level overrides). - Update the architecture package map accordingly. Also update the Japanese translation (docs/ja-JP/skills/tinystruct-patterns) to match, which had drifted to an older, condensed version of this skill. Co-Authored-By: Claude Sonnet 5 --- .../ja-JP/skills/tinystruct-patterns/SKILL.md | 363 ++++++++++++++++-- skills/tinystruct-patterns/SKILL.md | 218 ++++++++++- .../references/architecture.md | 4 +- .../references/database.md | 73 +++- 4 files changed, 605 insertions(+), 53 deletions(-) diff --git a/docs/ja-JP/skills/tinystruct-patterns/SKILL.md b/docs/ja-JP/skills/tinystruct-patterns/SKILL.md index b204125bb..cd20f927f 100644 --- a/docs/ja-JP/skills/tinystruct-patterns/SKILL.md +++ b/docs/ja-JP/skills/tinystruct-patterns/SKILL.md @@ -1,28 +1,111 @@ --- name: tinystruct-patterns -description: tinystructフレームワークでアプリケーションモジュールまたはマイクロサービスを開発する際に使用。ルーティング、コンテキスト管理、BuilderによるJSON処理、CLI/HTTPデュアルモードのパターンをカバー。 +description: tinystruct Java フレームワークで開発する際の専門ガイダンス。tinystruct コードベース、または tinystruct 上に構築されたあらゆるプロジェクトで作業する際に使用します — プロジェクトに存在しない場合の bin/dispatcher・bin/dispatcher.cmd 起動スクリプトの生成、Application クラスの作成、@Action によるルート定義、ユニットテスト、ActionRegistry、HTTP/CLI デュアルモード対応、組み込み HTTP サーバー、イベントシステム、Builder/Builders による JSON 処理、AbstractData によるデータベース永続化、POJO 生成、Server-Sent Events (SSE)、ファイルアップロード、アウトバウンド HTTP 通信を含みます。 origin: ECC --- # tinystruct 開発パターン -**tinystruct** Java フレームワークを使用してモジュールをビルドするためのアーキテクチャと実装パターン。CLIとHTTPが等しく扱われる軽量なシステムです。 +**tinystruct** Java フレームワークを使用してモジュールをビルドするためのアーキテクチャと実装パターン。CLI と HTTP を等しく扱う軽量・高性能なフレームワークで、`main()` メソッドを必要とせず、最小限の設定で動作します。 + +## コア原則 + +**CLI と HTTP は等しい市民(equal citizens)です。** `@Action` でアノテーションされたすべてのメソッドは、理想的には変更なしでターミナルとWebブラウザの両方から実行可能であるべきです。この「デュアルモード」対応能力が tinystruct のコア設計思想です。 + +## 主要な開発ツール:`bin/dispatcher` + +**`bin/dispatcher` は tinystruct アプリケーションを開発・実行・テスト・デバッグするためのデフォルトかつ最優先のツールです — IDE の実行構成、手書きの `main()`、curl、ブラウザよりも先にこれを使ってください。** すべての `@Action` は設計上デュアルモードであるため、`bin/dispatcher` を使うことでサーバーやブラウザを必要とせず、ターミナルから直接ルーティング・引数バインディング・ビジネスロジックを、最速のフィードバックループで検証できます。 + +```bash +# @Action を直接実行 - 新しいアクションが動作するか確認する最速の方法 +bin/dispatcher greet/James +bin/dispatcher echo --words "Praise the Lord" + +# Web向けの対応部分を検証する必要があるときに HTTP サーバーを起動 +bin/dispatcher start --import org.tinystruct.system.HttpServer + +# 今回の実行のために追加の Application/MCP クラスをインポート +# (クラスごとに --import を1つ。カンマ区切りのリストは ClassNotFoundException になる) +bin/dispatcher start --import org.tinystruct.system.HttpServer --import com.example.MyService + +# 利用可能な内容を確認 +bin/dispatcher --help +bin/dispatcher --version +``` + +このワークフローをデフォルトにしてください:`@Action` を実装したら、`bin/dispatcher ` ですぐに実行して CLI モードで正しく動作することを確認し、Web向けのパス(例:`mode = Mode.HTTP_POST`、セッション、ファイルアップロードなど)を検証する必要があるときだけ HTTP サーバー(これも `bin/dispatcher start ...` で)を起動します。アプリのエントリポイントとして `main(String[] args)` を決してハードコードしないでください — `bin/dispatcher`(Windows では `bin/dispatcher.cmd`)がすべてのモジュールの唯一のエントリポイントです。 + +### `bin/dispatcher` と `bin/dispatcher.cmd` の生成 + +すべての tinystruct プロジェクトは `bin/` に起動スクリプトを必要とします。**フレームワークがこれを生成します — 手で書いたり、他のプロジェクトからコピーしたり、`main()` で代用したりしないでください。** `ApplicationManager.init()` は、起動スクリプトが存在しない場合、tinystruct jar 内のテンプレートからそのjarのバージョンを埋め込んだ、実行中のOS用のスクリプトを作業ディレクトリ配下の `bin/` に書き出します。改行コードと実行権限はすでに正しく設定されています。 + +**1. プロジェクトがビルド対象とする tinystruct のバージョンを確認する**:`pom.xml` の `` プロパティ、または `org.tinystruct:tinystruct` 依存関係です。jar は `~/.m2/repository/org/tinystruct/tinystruct//`(なければ `mvn dependency:resolve` を実行)か、プロジェクトの `lib/` に存在している必要があります。 + +**2. プロジェクトルートから、フレームワークを一度実行します:** + +```bash +java -cp ~/.m2/repository/org/tinystruct/tinystruct/1.7.34/tinystruct-1.7.34.jar \ + org.tinystruct.system.Dispatcher --version +``` + +Windows では `%USERPROFILE%\.m2\repository\org\tinystruct\tinystruct\1.7.34\tinystruct-1.7.34.jar` と同じクラスを使用します。これにより**実行したOS用のスクリプトだけ**が、まだ存在しない場合にのみ作成されます: + +| 実行環境 | 作成されるもの | +|---|---| +| Linux, macOS | `bin/dispatcher`(実行可能、LF) | +| Windows | `bin\dispatcher.cmd`(CRLF) | + +**3. 検証する。** プロジェクトルートから `bin/dispatcher --version`(Windows では `bin\dispatcher.cmd --version`)を実行します。`Dispatcher (cli) (built on tinystruct-)` と表示され、スクリプト内の `VERSION` は jar のバージョンと一致します。続けて `bin/dispatcher --help` を実行すると、コマンド一覧とインポートされたすべてのアクションが表示されます。作成したファイルをユーザーに見せてください。 + +**4. 両方のスクリプトを取得するには**、各OSでステップ2を実行するか、もう一方のOSでチームメイトやCIに実行させてその結果をコミットしてもらいます。どちらのOSでチェックアウトしても動作するよう、両方をコミットしてください。`-Dos.name=…` で別のOSを偽装しようとしないでください:Windows では `setPosixFilePermissions` の `UnsupportedOperationException` で失敗し、空の `bin/dispatcher` が残ります。チェックアウト時に改行コードが壊れないよう、以下の `.gitattributes` を推奨します: + +``` +bin/dispatcher text eol=lf +bin/dispatcher.cmd text eol=crlf +``` + +**tinystruct のアップグレード:** `pom.xml` のバージョンを変更し、古いスクリプトを削除して、新しい jar でステップ2を繰り返します。あるいは `bin/dispatcher update` が Maven Central で最新リリースを確認し、プロジェクトの依存関係をアップグレードしてスクリプトを強制的に再生成します。これにはネットワークが必要で、`pom.xml` を編集し、選択したバージョンではなく*最新*バージョンに移行します。 + +**これは暗黙的にも発生します。** `ApplicationManager.init()` に到達するコード(dispatcher 自体、またはユニットテスト内の `ApplicationManager.install(app, config)`)は、スクリプトが存在しない場合、*カレントディレクトリ*にそれを作成します。ユニットテストが `bin/dispatcher.cmd` を各モジュールのディレクトリに散らかしてしまう場合は、ビルドディレクトリから実行してください:surefire の設定で `${project.build.directory}` を設定します。 + +**スクリプトの動作内容**(ほとんどの問題を説明します): + +- 必ず**プロジェクトルートから**実行してください:シェルスクリプトはカレントディレクトリをルートとみなし、`.cmd` は `bin/` の親ディレクトリをルートとみなします。 +- クラスパスは `target/classes`、`lib/*.jar`、`WEB-INF/lib/*`、`WEB-INF/classes`、そして tinystruct の jar(`lib/` になければ `~/.m2`)で構成され、その後 `org.tinystruct.system.Dispatcher` を引数付きで実行します。 +- 初回実行時、`mvnw` / `mvnw.cmd` が存在しない場合は、プロジェクトルートに Maven Wrapper を展開します(Unix では `unzip`、Windows では PowerShell)。これが起こることをユーザーに伝えてください。ファイルはコミットしても、無視してもかまいません。 +- `-D…` や `-X…` 引数を JVM オプションとして扱うのはシェルスクリプトのみです。 + +**注意点:** + +- **Windows では `bin\dispatcher.cmd` を使ってください。** シェルスクリプトはクラスパスを `:` で結合するため、Git Bash/MSYS 上では `ClassNotFoundException: org.tinystruct.system.Dispatcher` で失敗します。WSL か `.cmd` を使用してください。 +- **クラスパスに含まれるのは tinystruct の jar のみです。** フレームワークはデフォルトで fat jar を提供しなくなったため、他のモジュール、JDBCドライバ、ライブラリ(jjwt、lettuce など)は `lib/` に置く必要があります:`mvn dependency:copy-dependencies -DoutputDirectory=lib`(`lib/` は git-ignore してください)。マルチモジュールビルドでは、起動スクリプトを `target/classes` と `lib/` を持つディレクトリに置き、兄弟モジュールの jar をその `lib/` にコピーしてください。兄弟モジュールがクラスパスに含まれないディレクトリから実行すると `NoClassDefFoundError` になります。 +- **アプリケーションは `--import` または設定でロードしてください。** クラスごとに `--import` を1つ(`--import a.A,b.B` は失敗します。`--import a.A --import b.B` と書いてください)、または `application.properties` に一度だけ列挙します:`default.import.applications=a.A;b.B`(`;` 区切り)。 +- **`.cmd` には `JAVA_HOME` の設定が必要です**。設定されていないとエラーで停止します。 +- **`bin/` は `target/classes` と `lib/` を持つディレクトリの直下に置いてください。** `.cmd` は `bin/` の親ディレクトリをルートとみなすため、`bin/` が誤った場所にネストしていると、間違ったクラスパスが `Dispatcher` の前に付与されます。 ## 使用するタイミング +### 使用する場面 + +- `bin/dispatcher` を通じて `@Action` を実行・テスト・デバッグするとき — これは HTTP や IDE の実行構成に頼る前の、tinystruct アプリを扱うデフォルトの方法です。 +- `bin/dispatcher` / `bin/dispatcher.cmd` が存在しないプロジェクトをセットアップするとき — フレームワークに生成させてください(「`bin/dispatcher` と `bin/dispatcher.cmd` の生成」を参照)。 - `AbstractApplication` を拡張して新しい `Application` モジュールを作成するとき。 - `@Action` を使用してルートとコマンドラインアクションを定義するとき。 - `Context` を通じてリクエストごとの状態を処理するとき。 -- ネイティブの `Builder` コンポーネントを使用してJSONシリアライゼーションを行うとき。 -- `application.properties` でデータベース接続またはシステム設定を構成するとき。 -- `ApplicationManager.init()` を通じて標準的な `bin/dispatcher` エントリポイントを生成または再生成するとき。 -- ルーティング競合(Action)またはCLI引数解析のデバッグを行うとき。 +- ネイティブの `Builder` と `Builders` コンポーネントを使用してJSONシリアライゼーションを行うとき。 +- `AbstractData` POJO を使用してデータベース永続化を行うとき。 +- `generate` コマンドを使用してデータベーステーブルから POJO を生成するとき(XML マッピングファイルまたはアノテーション `--mapping annotation`)。 +- リアルタイムプッシュのために Server-Sent Events (SSE) を実装するとき。 +- multipart データによるファイルアップロードを処理するとき。 +- `URLRequest` と `HTTPHandler` を使用してアウトバウンドHTTPリクエストを行うとき。 +- `application.properties` でデータベース接続やシステム設定を構成するとき。 +- ルーティングの競合(Action)や CLI 引数解析をデバッグするとき。 ## 動作の仕組み tinystruct フレームワークは、`@Action` でアノテーションされたメソッドをターミナルとWeb環境の両方でルーティング可能なエンドポイントとして扱います。アプリケーションは `AbstractApplication` を拡張することで作成され、`init()` などのコアライフサイクルフックとリクエスト `Context` へのアクセスが提供されます。 -ルーティングは `ActionRegistry` によって処理され、パスセグメントをメソッド引数に自動的にマッピングして依存関係を注入します。データのみのサービスでは、ゼロ依存のフットプリントを維持するために、JSONシリアライゼーションにネイティブの `Builder` コンポーネントを使用すべきです。フレームワークには `ApplicationManager` のユーティリティも含まれており、`bin/dispatcher` スクリプトを生成することでプロジェクトの実行環境をブートストラップします。 +ルーティングは `ActionRegistry` によって処理され、パスセグメントをメソッド引数に自動的にマッピングして依存関係を注入します。データのみのサービスでは、ゼロ依存のフットプリントを維持するために、JSONシリアライゼーションにネイティブの `Builder` と `Builders` コンポーネントを使用すべきです。データベース層は `@Table`/`@Column` アノテーションまたは XML マッピングファイルでテーブルにマッピングされた `AbstractData` POJO を使用し、外部 ORM ライブラリなしで CRUD 操作を行います。不足しているテーブルは `database.autocreate=true` で自動的に作成できます。 ## 例 @@ -40,58 +123,247 @@ public class MyService extends AbstractApplication { public String greet() { return "Hello from tinystruct!"; } -} -``` -### パラメータ付きルーティング(getUser) -```java -// Web: /api/user/123 または CLI: "bin/dispatcher api/user/123" を処理 -@Action("api/user/(\\d+)") -public String getUser(int userId) { - return "User ID: " + userId; + // パスパラメータ: GET /?q=greet/James または bin/dispatcher greet/James + @Action("greet") + public String greet(String name) { + return "Hello, " + name + "!"; + } } ``` ### HTTPモード分岐(login) ```java @Action(value = "login", mode = Mode.HTTP_POST) -public boolean doLogin() { - // ログイン処理 - return true; +public String doLogin(Request request) throws ApplicationException { + request.getSession().setAttribute("userId", "42"); + return "Logged in"; } ``` -### ネイティブJSONデータ処理(getData) +### ネイティブJSONデータ処理(Builder + Builders) ```java +import org.tinystruct.data.component.Builder; +import org.tinystruct.data.component.Builders; + @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; +public String getData() throws ApplicationException { + Builders dataList = new Builders(); + Builder item = new Builder(); + item.put("id", 1); + item.put("name", "James"); + dataList.add(item); + + Builder response = new Builder(); + response.put("status", "success"); + response.put("data", dataList); + return response.toString(); // {"status":"success","data":[{"id":1,"name":"James"}]} } ``` +### SSE(Server-Sent Events) +```java +import org.tinystruct.http.SSEPushManager; + +@Action("sse/connect") +public String connect() { + return "{\"type\":\"connect\",\"message\":\"Connected to SSE\"}"; +} + +// 特定のクライアントへプッシュ +String sessionId = getContext().getId(); +Builder msg = new Builder(); +msg.put("text", "Hello, user!"); +SSEPushManager.getInstance().push(sessionId, msg); + +// 全員へブロードキャスト +// 全員へブロードキャスト +SSEPushManager.getInstance().broadcast(msg); +``` + +### ファイルアップロード +```java +import org.tinystruct.data.FileEntity; + +@Action(value = "upload", mode = Mode.HTTP_POST) +public String upload(Request request) throws ApplicationException { + List files = request.getAttachments(); + if (files != null) { + for (FileEntity file : files) { + System.out.println("Uploaded: " + file.getFilename()); + } + } + return "Upload OK"; +} +``` + +### データベースメタデータ操作 +`DatabaseOperator` は、接続レベルのカタログ、スキーマ、メタデータへの直接アクセスを提供します: +```java +import org.tinystruct.data.DatabaseOperator; + +DatabaseOperator operator = new DatabaseOperator(); +try { + String catalog = operator.getCatalog(); + String schema = operator.getSchema(); + java.sql.DatabaseMetaData metaData = operator.getMetaData(); + System.out.println("Using catalog: " + catalog + ", schema: " + schema); +} finally { + operator.close(); +} +``` + +## MCP サーバーとツールの統合 + +tinystruct は SDK バージョン **`1.7.0`** から Model Context Protocol (MCP) のネイティブサポートを提供しています。 +MCP API(例:`org.tinystruct.mcp.MCPTool`、`org.tinystruct.mcp.MCPServer`、`org.tinystruct.mcp.MCPException`)はコアの依存関係に直接含まれています: +```xml + + org.tinystruct + tinystruct + 1.7.34 + +``` + +> **セキュリティ警告(プロンプトインジェクション):** +> ツールの戻り値は、そのままAIモデルのコンテキストウィンドウに渡されます。ツールの戻り値文字列に含める前に、呼び出し元から渡されたすべての引数を検証・サニタイズする**必要があります**。入力をサニタイズしないと、攻撃者がモデルの挙動を上書きする敵対的な指示(プロンプトインジェクション)を注入できてしまいます。常に長さ、文字種、null であるかどうかを検証してください。 + +**MCP ツールを作成するには:** +1. `org.tinystruct.mcp.MCPTool` を拡張する。 +2. `@Action` で操作にアノテーションを付け、`arguments` 配列内で `@Argument` を使ってパラメータを宣言する。 +3. パラメータは `@Argument` のキーに対応する明示的なメソッド引数として受け取る(ツールの引数に `getContext().getAttribute(...)` を使用**しないでください**)。 + +```java +import org.tinystruct.mcp.MCPTool; +import org.tinystruct.mcp.MCPException; +import org.tinystruct.system.annotation.Action; +import org.tinystruct.system.annotation.Argument; + +public class MyCustomTool extends MCPTool { + public MyCustomTool() { + super("custom", "A custom tool for demonstrating MCP"); + } + + @Action( + value = "custom/hello", + description = "Say hello to someone", + arguments = { + @Argument(key = "name", description = "The name to greet", type = "string", optional = false) + } + ) + public String hello(String name) throws MCPException { + // SECURITY: モデルに返す前にツール入力を検証・サニタイズし、 + // プロンプトインジェクションの脆弱性を防止する。 + if (name == null || name.length() > 50 || !name.matches("^[a-zA-Z0-9 ]+$")) { + throw new MCPException("Invalid name provided"); + } + return "Hello, " + name + "!"; + } +} +``` + +**MCP サーバーをデプロイするには:** +1. `org.tinystruct.mcp.MCPServer` を拡張する。 +2. `init()` をオーバーライドし、`this.registerTool()` を使ってツールを登録する。フレームワークが `@Action` メソッドを自動的にスキャンしてマッピングします。 + +```java +import org.tinystruct.mcp.MCPServer; + +public class MyMCPServer extends MCPServer { + @Override + public void init() { + super.init(); + this.registerTool(new MyCustomTool()); + } + + @Override + public String version() { + return "1.0.0"; + } +} +``` + +dispatcher 経由でサーバーを実行します: +```bash +bin/dispatcher start --import org.tinystruct.system.HttpServer --import com.example.MyMCPServer +``` + +### オーバーロードされたツールメソッド +tinystruct は `MCPTool` 内で同一ツール名に対するオーバーロードメソッド(同じツール名を共有しつつ異なるパラメータシグネチャを受け取る)をサポートしています。 +- **スキーママージ**:同じ名前を持つすべてのオーバーロードの入力スキーマは、動的に統合されたひとつの JSON スキーマにマージされます。プロパティは和集合として扱われ、必須プロパティは積集合として扱われます(*すべての*オーバーロードで必須とされているフィールドのみが必須として残ります)。 +- **実行ルーティング**:ツールを実行する際、サーバーは利用可能なオーバーロードメソッドを順に試行します。各メソッドのスキーマ検証が最初に行われ、提供された引数に対して最初に検証を通過したオーバーロードが実行されます。どのシグネチャも一致・成功しない場合、フレームワークは適切な例外をスローします。 + ## 設定 設定は `src/main/resources/application.properties` で管理されます。 -## テストパターン +```properties +# データベース +driver=org.h2.Driver +database.url=jdbc:h2:~/mydb +database.user=sa +database.password= +# 任意:初回利用時にクラスのマッピングから不足しているテーブルを作成する(デフォルトはオフ) +# database.autocreate=true -JUnit 5 を使用して、アクションが `ActionRegistry` に登録されていることを検証することでアクションをテストします。 +# サーバー +default.home.page=hello +server.port=8080 +default.server.open_browser=true + +# ロケール +default.language=en_US + +# セッション(クラスタ環境向けの Redis) +# default.session.repository=org.tinystruct.http.RedisSessionRepository +# redis.host=127.0.0.1 +# redis.port=6379 + +# プログラムによるロギング設定 +logging.enabled=true +logging.level=INFO +org.tinystruct.level=FINE +``` + +アプリケーション内で設定値にアクセスする: +```java +String port = this.getConfiguration("server.port"); +``` + +## ロギングと診断 + +フレームワークには `java.util.logging`(JUL)を包む形のプログラム的なラッパーが含まれており、標準で見やすいコンソール出力と高度な呼び出し元トレースを提供します。 + +- **ANSI コンソールカラー**:コンソール出力はログレベルに応じて色分けされます(SEVERE は赤、WARNING は黄、INFO は緑、CONFIG はシアン、FINE/デバッグログはグレー)。 +- **正確な呼び出し元トレース**:Java の `StackWalker` API を使用して実行時のコールスタックをトレースします。ログを発生させた呼び出し元の正確なクラス、メソッド、ファイル名、行番号を特定してログに記録します(内部のユーティリティ層やロギング層は経由しません)。 +- **パッケージ/ロガーレベルの上書き**:`application.properties` にパッケージ単位の上書きを直接設定できます(例:`org.tinystruct.level=FINE`)。 ## レッドフラグとアンチパターン | 症状 | 正しいパターン | |---|---| -| `com.google.gson` または `com.fasterxml.jackson` のインポート | `org.tinystruct.data.component.Builder` を使用する。 | -| `.view` ファイルの `FileNotFoundException` | APIのみのアプリでは `init()` 内で `setTemplateRequired(false)` を呼び出す。 | +| `com.google.gson` や `com.fasterxml.jackson` のインポート | `org.tinystruct.data.component.Builder` / `Builders` を使用する。 | +| JSON配列に `List` を使用する | ジェネリックの型消去の問題を避けるため `Builders` を使用する。 | +| `ApplicationRuntimeException: template not found` | API専用アプリでは `init()` 内で `setTemplateRequired(false)` を呼び出す。 | | `private` メソッドへの `@Action` アノテーション | アクションはフレームワークに登録されるために `public` である必要がある。 | -| アプリ内での `main(String[] args)` のハードコーディング | すべてのモジュールのエントリポイントとして `bin/dispatcher` を使用する。 | +| アプリ内で `main(String[] args)` をハードコードする、または curl/ブラウザ/IDE の実行構成だけでテストする | すべてのモジュールのエントリポイント兼デフォルトの開発/テストツールとして `bin/dispatcher` を使用する。 | +| `bin/dispatcher` が存在しない、または手書き・コピーされた起動スクリプトになっている | フレームワークに生成させる:プロジェクトルートから `org.tinystruct.system.Dispatcher --version` を一度実行する(現在のOS用のスクリプトが作成される)。 | +| Windows で `bin/dispatcher` が `ClassNotFoundException: org.tinystruct.system.Dispatcher` で失敗する | シェルスクリプトは Git Bash/MSYS 上では実行できない。`bin\dispatcher.cmd` または WSL を使用する。 | +| `--import a.A,b.B` で `ClassNotFoundException` が発生する | クラスごとに `--import` を1つ:`--import a.A --import b.B`。 | +| シェルスクリプトの起動ファイルが `\r: command not found` で失敗する | ファイルが CRLF 改行になっている。`bin/dispatcher` を LF に変換する。 | | 手動での `ActionRegistry` 登録 | 自動検出のために `@Action` アノテーションを優先する。 | +| 実行時にアクションが見つからない | クラスが `--import` でインポートされているか、`application.properties` に列挙されているか確認する。 | +| CLI引数が認識されない | `--key value` の形で渡し、`getContext().getAttribute("--key")` でアクセスする。 | +| 同じパスの2つのメソッドで、意図しない方が呼ばれる | 明示的な `mode`(例:`HTTP_GET` と `HTTP_POST`)を設定して区別する。 | + +## ベストプラクティス + +1. **`bin/dispatcher` を最優先に**:何よりもまず `bin/dispatcher` に対して開発・検証を行ってください — CLI からアクションを実行して挙動を確認し、その後 HTTP/モードに関する関心事を積み重ねます。これが最速のインナーループであり、フレームワークが実際にルーティングと引数バインディングを行う方法と確実に一致する唯一のツールです。 +2. **粒度の細かいアプリケーション**:巨大な単一クラスではなく、ロジックを小さく焦点を絞ったアプリケーションに分割する。 +3. **`init()` でのセットアップ**:コンストラクタではなく `init()` をセットアップ(設定、DB など)に活用する。`setAction()` を呼び出さず、`@Action` アノテーションを使用する。 +4. **モードへの意識**:`@Action` の `Mode` パラメータを使用して、機密性の高い操作を `CLI` のみ、または特定の HTTP メソッドに制限する。 +5. **パラメータより Context を優先**:任意の CLI フラグには、メソッドシグネチャにパラメータを追加するのではなく `getContext().getAttribute("--flag")` を使用する。 +6. **非同期イベント**:イベントによってトリガーされる重い処理には、イベントハンドラ内で `CompletableFuture.runAsync()` を使用する。 ## テクニカルリファレンス @@ -99,6 +371,27 @@ JUnit 5 を使用して、アクションが `ActionRegistry` に登録されて - [アーキテクチャと設定](references/architecture.md) — 抽象化、パッケージマップ、プロパティ - [ルーティングと@Action](references/routing.md) — アノテーションの詳細、モード、パラメータ -- [データ処理](references/data-handling.md) — JSONのためのネイティブ `Builder` の使用 -- [システムと使用方法](references/system-usage.md) — Context、セッション、イベント、CLI使用方法 -- [テストパターン](references/testing.md) — JUnit 5 統合と ActionRegistry テスト +- [データ処理](references/data-handling.md) — Builder、Builders、JSONのシリアライズ・パース +- [データベース永続化](references/database.md) — AbstractData POJO、CRUD、アノテーションとXMLマッピング、POJO生成、テーブルの自動作成 +- [システムと使用方法](references/system-usage.md) — Context、セッション、SSE、ファイルアップロード、イベント、ネットワーキング +- [テストパターン](references/testing.md) — JUnit 5 によるユニットテストとHTTP統合テスト + +## 参照元ソースファイル(内部用) + +- `src/main/java/org/tinystruct/AbstractApplication.java` — ライフサイクルフックを持つコアベースクラス +- `src/main/java/org/tinystruct/system/annotation/Action.java` — アノテーションとモード +- `src/main/java/org/tinystruct/application/ActionRegistry.java` — ルーティングエンジン +- `src/main/java/org/tinystruct/data/component/Builder.java` — JSONオブジェクトのシリアライザ +- `src/main/java/org/tinystruct/data/component/Builders.java` — JSON配列のシリアライザ +- `src/main/java/org/tinystruct/data/component/AbstractData.java` — CRUDを持つ基底POJOクラス +- `src/main/java/org/tinystruct/data/Mapping.java` — マッピングメタデータ(アノテーションまたはXML)、クラスごとにキャッシュ +- `src/main/java/org/tinystruct/data/annotation/Table.java` — `@Table`(`@Id` と `@Column` も同梱) +- `src/main/java/org/tinystruct/data/tools/TableCreator.java` — 不足しているテーブルを作成する(`database.autocreate`) +- `src/main/java/org/tinystruct/data/tools/MySQLGenerator.java` — POJOジェネレータのリファレンス(`MappingMode` でXMLかアノテーションかを選択) +- `src/main/java/org/tinystruct/data/component/FieldType.java` — SQLからJavaへの型マッピング +- `src/main/java/org/tinystruct/data/component/Condition.java` — 流暢なSQLクエリビルダー +- `src/main/java/org/tinystruct/http/SSEPushManager.java` — SSE接続管理 +- `src/main/java/org/tinystruct/system/logging/LogFormatter.java` — ANSIコンソールカラーとStackWalkerベースの呼び出し元トレースを備えたカスタムログフォーマッタ +- `src/main/java/org/tinystruct/system/logging/LoggerConfigurer.java` — アプリケーションプロパティからのプログラムによるロギング構成 +- `src/test/java/org/tinystruct/application/ActionRegistryTest.java` — レジストリのテスト例 +- `src/test/java/org/tinystruct/system/HttpServerHttpModeTest.java` — HTTP統合テストパターン diff --git a/skills/tinystruct-patterns/SKILL.md b/skills/tinystruct-patterns/SKILL.md index 0a8aeff0c..737b87e9f 100644 --- a/skills/tinystruct-patterns/SKILL.md +++ b/skills/tinystruct-patterns/SKILL.md @@ -1,6 +1,6 @@ --- name: tinystruct-patterns -description: Expert guidance for developing with the tinystruct Java framework. Use when working on the tinystruct codebase or any project built on tinystruct — including creating Application classes, @Action-mapped routes, unit tests, ActionRegistry, HTTP/CLI dual-mode handling, the built-in HTTP server, the event system, JSON with Builder/Builders, database persistence with AbstractData, POJO generation, Server-Sent Events (SSE), file uploads, and outbound HTTP networking. +description: Expert guidance for developing with the tinystruct Java framework. Use when working on the tinystruct codebase or any project built on tinystruct — including generating the bin/dispatcher and bin/dispatcher.cmd launcher scripts when a project lacks them, creating Application classes, @Action-mapped routes, unit tests, ActionRegistry, HTTP/CLI dual-mode handling, the built-in HTTP server, the event system, JSON with Builder/Builders, database persistence with AbstractData, POJO generation, Server-Sent Events (SSE), file uploads, and outbound HTTP networking. origin: ECC --- @@ -12,16 +12,89 @@ Architecture and implementation patterns for building modules with the **tinystr **CLI and HTTP are equal citizens.** Every method annotated with `@Action` should ideally be runnable from both a terminal and a web browser without modification. This "dual-mode" capability is the core design philosophy of tinystruct. +## Primary Development Tool: `bin/dispatcher` + +**`bin/dispatcher` is the default, highest-priority tool for developing, running, testing, and debugging a tinystruct application — reach for it before an IDE run configuration, a hand-written `main()`, curl, or a browser.** Because every `@Action` is dual-mode by design, `bin/dispatcher` lets you exercise routing, argument binding, and business logic directly from the terminal, with the fastest possible feedback loop and no server/browser required. + +```bash +# Run any @Action directly - the fastest way to verify a new action works +bin/dispatcher greet/James +bin/dispatcher echo --words "Praise the Lord" + +# Start the HTTP server when you need the web-facing counterpart +bin/dispatcher start --import org.tinystruct.system.HttpServer + +# Import additional Application/MCP classes for the current run +# (one --import per class; a comma-separated list fails with ClassNotFoundException) +bin/dispatcher start --import org.tinystruct.system.HttpServer --import com.example.MyService + +# Discover what's available +bin/dispatcher --help +bin/dispatcher --version +``` + +Default to this workflow: implement the `@Action`, run it immediately via `bin/dispatcher ` to confirm it behaves correctly in CLI mode, and only start the HTTP server (also via `bin/dispatcher start ...`) once you need to verify the web-facing path (e.g. `mode = Mode.HTTP_POST`, sessions, file uploads). Never hardcode a `main(String[] args)` as an app's entry point — `bin/dispatcher` (or `bin/dispatcher.cmd` on Windows) is the one entry point for every module. + +### Generating `bin/dispatcher` and `bin/dispatcher.cmd` + +Every tinystruct project needs its launcher in `bin/`. **The framework generates it — never write it by hand, and never copy it from another project or substitute a `main()`.** `ApplicationManager.init()` writes the launcher for the current operating system into `bin/` under the working directory whenever it is missing, from templates inside the tinystruct jar, with that jar's version filled in. Line endings and the executable bit are already right. + +**1. Find the tinystruct version** the project builds against: the `` property or the `org.tinystruct:tinystruct` dependency in `pom.xml`. The jar must be reachable, in `~/.m2/repository/org/tinystruct/tinystruct//` (run `mvn dependency:resolve` if it is not) or in the project's `lib/`. + +**2. From the project root, run the framework once:** + +```bash +java -cp ~/.m2/repository/org/tinystruct/tinystruct/1.7.34/tinystruct-1.7.34.jar \ + org.tinystruct.system.Dispatcher --version +``` + +On Windows use `%USERPROFILE%\.m2\repository\org\tinystruct\tinystruct\1.7.34\tinystruct-1.7.34.jar` and the same class. This creates **only the script for the OS you ran it on**, and only if it is not already there: + +| Run on | Creates | +|---|---| +| Linux, macOS | `bin/dispatcher` (executable, LF) | +| Windows | `bin\dispatcher.cmd` (CRLF) | + +**3. Verify.** From the project root run `bin/dispatcher --version` (Windows: `bin\dispatcher.cmd --version`). It prints `Dispatcher (cli) (built on tinystruct-)`, and the `VERSION` inside the script equals the jar's version. Then `bin/dispatcher --help` lists the commands and every imported action. Show the user the file you created. + +**4. Get both scripts by running step 2 on each OS**, or let a teammate or CI on the other OS run it and commit the result. Commit both, so a checkout on either OS works. Do not try to fake the other OS with `-Dos.name=…`: on Windows it fails (`UnsupportedOperationException` from `setPosixFilePermissions`) and leaves an empty `bin/dispatcher`. Recommended `.gitattributes`, so a checkout does not corrupt the line endings: + +``` +bin/dispatcher text eol=lf +bin/dispatcher.cmd text eol=crlf +``` + +**Upgrading tinystruct:** change the version in `pom.xml`, delete the old script, and repeat step 2 with the new jar. Alternatively `bin/dispatcher update` checks Maven Central for the latest release, upgrades the project's dependency and regenerates the script with force; it needs the network, edits the `pom.xml`, and goes to the *latest* version, not a chosen one. + +**It also happens implicitly.** Any code that reaches `ApplicationManager.init()` (the dispatcher itself, or `ApplicationManager.install(app, config)` in a unit test) creates the script in the *current directory* if it is missing. If unit tests scatter `bin/dispatcher.cmd` into module directories, run them from the build directory: in the surefire configuration set `${project.build.directory}`. + +**What the scripts do**, which explains most problems: + +- Run them **from the project root**: the shell script takes the current directory as the root, the `.cmd` takes the parent of `bin/`. +- The classpath is `target/classes`, `lib/*.jar`, `WEB-INF/lib/*`, `WEB-INF/classes` and the tinystruct jar (from `lib/`, else `~/.m2`), then they run `org.tinystruct.system.Dispatcher` with your arguments. +- On the first run, if `mvnw` / `mvnw.cmd` is missing, they extract the Maven Wrapper into the project root (`unzip` on Unix, PowerShell on Windows). Tell the user this happens; the files can be committed or ignored. +- Only the shell script treats `-D…` and `-X…` arguments as JVM options. + +**Gotchas:** + +- **On Windows use `bin\dispatcher.cmd`.** The shell script joins the classpath with `:`, so under Git Bash/MSYS it fails with `ClassNotFoundException: org.tinystruct.system.Dispatcher`. Use WSL, or the `.cmd`. +- **Only the tinystruct jar is on the classpath.** The framework no longer ships a fat jar by default, so your other modules, JDBC drivers and libraries (jjwt, lettuce, …) must be in `lib/`: `mvn dependency:copy-dependencies -DoutputDirectory=lib` (and git-ignore `lib/`). In a multi-module build, keep the launcher in the directory that holds `target/classes` and `lib/`, and copy the sibling modules' jars into that `lib/`; running it from a module whose siblings are not on its classpath fails with `NoClassDefFoundError`. +- **Load applications with `--import` or configuration.** One `--import` per class (`--import a.A,b.B` fails; write `--import a.A --import b.B`), or list them once in `application.properties`: `default.import.applications=a.A;b.B` (`;`-separated). +- **`JAVA_HOME` must be set** for the `.cmd`; it stops with an error otherwise. +- **`bin/` sits directly under the directory that holds `target/classes` and `lib/`.** The `.cmd` treats the parent of `bin/` as the root, so a `bin/` nested in the wrong directory puts the wrong classpath in front of `Dispatcher`. + ## When to Activate ### When to Use +- Running, testing, or debugging any `@Action` via `bin/dispatcher` — this is the default way to work with a tinystruct app, before reaching for HTTP or an IDE run configuration. +- Setting up a project that has no `bin/dispatcher` / `bin/dispatcher.cmd` — have the framework generate it (see "Generating `bin/dispatcher` and `bin/dispatcher.cmd`"). - 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` and `Builders` components. - Working with database persistence via `AbstractData` POJOs. -- Generating POJOs from database tables using the `generate` command. +- Generating POJOs from database tables using the `generate` command, with an XML mapping file or annotations (`--mapping annotation`). - Implementing Server-Sent Events (SSE) for real-time push. - Handling file uploads via multipart data. - Making outbound HTTP requests with `URLRequest` and `HTTPHandler`. @@ -32,7 +105,7 @@ Architecture and implementation patterns for building modules with the **tinystr 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` and `Builders` components should be used for JSON serialization to maintain a zero-dependency footprint. The database layer uses `AbstractData` POJOs paired with XML mapping files for CRUD operations without external ORM libraries. +Routing is handled by the `ActionRegistry`, which automatically maps path segments to method arguments and injects dependencies. For data-only services, the native `Builder` and `Builders` components should be used for JSON serialization to maintain a zero-dependency footprint. The database layer uses `AbstractData` POJOs mapped to tables with `@Table`/`@Column` annotations or XML mapping files for CRUD operations without external ORM libraries. Missing tables can be created automatically with `database.autocreate=true`. ## Examples @@ -124,6 +197,102 @@ public String upload(Request request) throws ApplicationException { } ``` +### Database Metadata Operations +`DatabaseOperator` provides direct access to connection-level catalog, schema, and metadata: +```java +import org.tinystruct.data.DatabaseOperator; + +DatabaseOperator operator = new DatabaseOperator(); +try { + String catalog = operator.getCatalog(); + String schema = operator.getSchema(); + java.sql.DatabaseMetaData metaData = operator.getMetaData(); + System.out.println("Using catalog: " + catalog + ", schema: " + schema); +} finally { + operator.close(); +} +``` + +## MCP Server and Tools Integration + +tinystruct provides native support for the Model Context Protocol (MCP) starting with SDK version **`1.7.0`**. +The MCP APIs (e.g., `org.tinystruct.mcp.MCPTool`, `org.tinystruct.mcp.MCPServer`, `org.tinystruct.mcp.MCPException`) are included directly in the core dependency: +```xml + + org.tinystruct + tinystruct + 1.7.34 + +``` + +> **SECURITY WARNING (Prompt Injection):** +> Tool return values are fed directly back into the AI model's context window. You **MUST** validate and sanitize all caller-supplied arguments before including them in the tool's return string. Failure to sanitize inputs can allow an attacker to inject adversarial instructions (Prompt Injection) that override the model's behavior. Always validate length, character sets, and nullity. + +**To create an MCP Tool:** +1. Extend `org.tinystruct.mcp.MCPTool`. +2. Annotate operations with `@Action` and declare parameters using `@Argument` within the `arguments` array. +3. Accept parameters as explicit method arguments matching the keys in `@Argument`. (Do **not** use `getContext().getAttribute(...)` for tool arguments). + +```java +import org.tinystruct.mcp.MCPTool; +import org.tinystruct.mcp.MCPException; +import org.tinystruct.system.annotation.Action; +import org.tinystruct.system.annotation.Argument; + +public class MyCustomTool extends MCPTool { + public MyCustomTool() { + super("custom", "A custom tool for demonstrating MCP"); + } + + @Action( + value = "custom/hello", + description = "Say hello to someone", + arguments = { + @Argument(key = "name", description = "The name to greet", type = "string", optional = false) + } + ) + public String hello(String name) throws MCPException { + // SECURITY: Validate/sanitize tool inputs before returning to the model + // to prevent prompt injection vulnerabilities. + if (name == null || name.length() > 50 || !name.matches("^[a-zA-Z0-9 ]+$")) { + throw new MCPException("Invalid name provided"); + } + return "Hello, " + name + "!"; + } +} +``` + +**To deploy an MCP Server:** +1. Extend `org.tinystruct.mcp.MCPServer`. +2. Override `init()` and register your tools using `this.registerTool()`. The framework automatically scans and maps the `@Action` methods. + +```java +import org.tinystruct.mcp.MCPServer; + +public class MyMCPServer extends MCPServer { + @Override + public void init() { + super.init(); + this.registerTool(new MyCustomTool()); + } + + @Override + public String version() { + return "1.0.0"; + } +} +``` + +Run the server via the dispatcher: +```bash +bin/dispatcher start --import org.tinystruct.system.HttpServer --import com.example.MyMCPServer +``` + +### Overloaded Tool Methods +tinystruct supports overloaded methods for the same tool name in `MCPTool` (sharing the same tool name but accepting different parameter signatures). +- **Schema Merging**: Input schemas of all overloads with the same name are dynamically merged into a unified JSON schema. Properties are unioned, and required properties are intersected (only fields required across *all* overloads remain marked mandatory). +- **Execution Routing**: When executing a tool, the server iterates sequentially through the available overloaded methods, attempting execution. Each method's schema validation is performed first, and the first overload that validates against the provided arguments successfully executes. If no signature matches or succeeds, the framework throws an appropriate exception. + ## Configuration Settings are managed in `src/main/resources/application.properties`. @@ -134,10 +303,13 @@ driver=org.h2.Driver database.url=jdbc:h2:~/mydb database.user=sa database.password= +# Optional: create missing tables from the class mapping on first use (off by default) +# database.autocreate=true # Server default.home.page=hello server.port=8080 +default.server.open_browser=true # Locale default.language=en_US @@ -146,6 +318,11 @@ default.language=en_US # default.session.repository=org.tinystruct.http.RedisSessionRepository # redis.host=127.0.0.1 # redis.port=6379 + +# Programmatic Logging Configuration +logging.enabled=true +logging.level=INFO +org.tinystruct.level=FINE ``` Access config values in your application: @@ -153,6 +330,14 @@ Access config values in your application: String port = this.getConfiguration("server.port"); ``` +## Logging and Diagnostics + +The framework includes a programmatic wrapper around `java.util.logging` (JUL) that provides beautiful console output and advanced caller tracing out of the box. + +- **ANSI Console Colors**: Console output is color-coded based on the log level (red for SEVERE, yellow for WARNING, green for INFO, cyan for CONFIG, and grey for FINE/debug logs). +- **Precise Caller Tracing**: Uses Java's `StackWalker` API to trace the call stack at runtime. It identifies and logs the exact class, method, filename, and line number of the caller that initiated the log (bypassing internal utility and logging layers). +- **Package/Logger Level Overrides**: Set package-specific overrides directly in `application.properties` (e.g., `org.tinystruct.level=FINE`). + ## Red Flags & Anti-patterns | Symptom | Correct Pattern | @@ -161,7 +346,11 @@ String port = this.getConfiguration("server.port"); | Using `List` for JSON arrays | Use `Builders` to avoid generic type erasure issues. | | `ApplicationRuntimeException: template not found` | 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. | +| Hardcoding `main(String[] args)` in apps, or testing only via curl/browser/IDE run configs | Use `bin/dispatcher` as the entry point and default dev/test tool for all modules. | +| `bin/dispatcher` is missing, or a hand-written or copied launcher script | Let the framework generate it: run `org.tinystruct.system.Dispatcher --version` once from the project root (creates the current OS's script). | +| `bin/dispatcher` fails with `ClassNotFoundException: org.tinystruct.system.Dispatcher` on Windows | The shell script cannot run under Git Bash/MSYS; use `bin\dispatcher.cmd` or WSL. | +| `ClassNotFoundException` for `--import a.A,b.B` | One `--import` per class: `--import a.A --import b.B`. | +| A shell-script launcher that fails with `\r: command not found` | The file has CRLF endings; convert `bin/dispatcher` to LF. | | Manual `ActionRegistry` registration | Prefer the `@Action` annotation for automatic discovery. | | Action not found at runtime | Ensure class is imported via `--import` or listed in `application.properties`. | | CLI arg not visible | Pass with `--key value`; access via `getContext().getAttribute("--key")`. | @@ -169,11 +358,12 @@ String port = this.getConfiguration("server.port"); ## Best Practices -1. **Granular Applications**: Break logic into smaller, focused applications rather than one monolithic class. -2. **Setup in `init()`**: Leverage `init()` for setup (config, DB) rather than the constructor. Do NOT call `setAction()` — use `@Action` annotation. -3. **Mode Awareness**: Use the `Mode` parameter in `@Action` to restrict sensitive operations to `CLI` only or specific HTTP methods. -4. **Context over Params**: For optional CLI flags, use `getContext().getAttribute("--flag")` rather than adding parameters to the method signature. -5. **Asynchronous Events**: For heavy tasks triggered by events, use `CompletableFuture.runAsync()` inside the event handler. +1. **`bin/dispatcher` first**: Develop and verify against `bin/dispatcher` before anything else — run the action from the CLI, confirm behavior, then layer on HTTP/mode concerns. It's the fastest inner-loop and the one tool guaranteed to match how the framework actually routes and binds arguments. +2. **Granular Applications**: Break logic into smaller, focused applications rather than one monolithic class. +3. **Setup in `init()`**: Leverage `init()` for setup (config, DB) rather than the constructor. Do NOT call `setAction()` — use `@Action` annotation. +4. **Mode Awareness**: Use the `Mode` parameter in `@Action` to restrict sensitive operations to `CLI` only or specific HTTP methods. +5. **Context over Params**: For optional CLI flags, use `getContext().getAttribute("--flag")` rather than adding parameters to the method signature. +6. **Asynchronous Events**: For heavy tasks triggered by events, use `CompletableFuture.runAsync()` inside the event handler. ## Technical Reference @@ -182,7 +372,7 @@ 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) — Builder, Builders, JSON serialization & parsing -- [Database Persistence](references/database.md) — AbstractData POJOs, CRUD, mapping XML, POJO generation +- [Database Persistence](references/database.md) — AbstractData POJOs, CRUD, annotation and XML mapping, POJO generation, table auto-creation - [System & Usage](references/system-usage.md) — Context, Sessions, SSE, File Uploads, Events, Networking - [Testing Patterns](references/testing.md) — JUnit 5 unit and HTTP integration testing @@ -194,10 +384,14 @@ Detailed guides are available in the `references/` directory: - `src/main/java/org/tinystruct/data/component/Builder.java` — JSON object serializer - `src/main/java/org/tinystruct/data/component/Builders.java` — JSON array serializer - `src/main/java/org/tinystruct/data/component/AbstractData.java` — Base POJO class with CRUD -- `src/main/java/org/tinystruct/data/Mapping.java` — Mapping XML parser -- `src/main/java/org/tinystruct/data/tools/MySQLGenerator.java` — POJO generator reference +- `src/main/java/org/tinystruct/data/Mapping.java` — Mapping metadata (annotations or XML), cached per class +- `src/main/java/org/tinystruct/data/annotation/Table.java` — `@Table`, with `@Id` and `@Column` alongside it +- `src/main/java/org/tinystruct/data/tools/TableCreator.java` — Creates missing tables (`database.autocreate`) +- `src/main/java/org/tinystruct/data/tools/MySQLGenerator.java` — POJO generator reference (`MappingMode` selects XML or annotations) - `src/main/java/org/tinystruct/data/component/FieldType.java` — SQL-to-Java type mappings - `src/main/java/org/tinystruct/data/component/Condition.java` — Fluent SQL query builder - `src/main/java/org/tinystruct/http/SSEPushManager.java` — SSE connection management +- `src/main/java/org/tinystruct/system/logging/LogFormatter.java` — Custom log formatter with ANSI console colors and StackWalker-based caller tracing +- `src/main/java/org/tinystruct/system/logging/LoggerConfigurer.java` — Programmatic logging configurator from application properties - `src/test/java/org/tinystruct/application/ActionRegistryTest.java` — Registry test examples - `src/test/java/org/tinystruct/system/HttpServerHttpModeTest.java` — HTTP integration test patterns diff --git a/skills/tinystruct-patterns/references/architecture.md b/skills/tinystruct-patterns/references/architecture.md index 3d7ea5e82..5fa7ef2b6 100644 --- a/skills/tinystruct-patterns/references/architecture.md +++ b/skills/tinystruct-patterns/references/architecture.md @@ -46,7 +46,9 @@ org.tinystruct/ │ ├── component/AbstractData.java ← base POJO for DB persistence │ ├── component/Condition.java ← fluent SQL query builder │ ├── component/FieldType.java ← SQL-to-Java type mappings -│ ├── Mapping.java ← reads .map.xml metadata +│ ├── annotation/ ← @Table, @Id, @Column mapping annotations +│ ├── Mapping.java ← reads @Table/@Column annotations or .map.xml metadata, cached per class +│ ├── tools/TableCreator.java ← creates missing tables (database.autocreate) │ ├── DatabaseOperator.java ← low-level JDBC wrapper │ └── FileEntity.java ← file upload representation ├── http/ ← Request, Response, Constants diff --git a/skills/tinystruct-patterns/references/database.md b/skills/tinystruct-patterns/references/database.md index ef2273210..46d7e762b 100644 --- a/skills/tinystruct-patterns/references/database.md +++ b/skills/tinystruct-patterns/references/database.md @@ -2,7 +2,7 @@ ## When to Use -Use the built-in ORM-like data layer for database operations. It provides a lightweight alternative to JPA/Hibernate using POJOs extending `AbstractData` and XML mapping files. +Use the built-in ORM-like data layer for database operations. It provides a lightweight alternative to JPA/Hibernate using POJOs extending `AbstractData`, mapped to tables with annotations or XML mapping files. ## How It Works @@ -10,7 +10,11 @@ Use the built-in ORM-like data layer for database operations. It provides a ligh Each table is represented by: 1. **Java POJO**: Extends `AbstractData`, provides getters/setters and `setData(Row)`. -2. **Mapping XML**: `ClassName.map.xml` in resources, binding Java fields to DB columns. +2. **Mapping**, either of: + - **Annotations**: `@Table` on the class and `@Column` on its fields (see [Annotation Mapping](#annotation-mapping)). + - **Mapping XML**: `ClassName.map.xml` in resources, binding Java fields to DB columns. + +The mapping is read once per class and cached, so creating an instance never re-reads or re-parses it, whichever form you use. If a class has `@Table`, it wins over a `.map.xml` of the same name. #### Key Base Class: `AbstractData` Provides CRUD methods: @@ -23,7 +27,7 @@ Provides CRUD methods: ### POJO Generation (CLI) -Introspect a live database table to produce the POJO and mapping file. +Introspect a live database table to produce the POJO and its mapping: a `.map.xml` file by default, or annotations on the POJO itself. #### Configuration `application.properties`: @@ -41,8 +45,13 @@ bin/dispatcher generate # Specify table bin/dispatcher generate --tables users + +# Annotate the POJO instead of writing a .map.xml file +bin/dispatcher generate --tables users --mapping annotation ``` +`--mapping` accepts `xml` (the default) or `annotation`. In annotation mode no `.map.xml` is written and no resources directory is created. An unknown value is rejected before any prompt. + ## Examples ### CRUD Operations @@ -84,16 +93,70 @@ Table filtered = user.find( ```xml - + ``` +### Annotation Mapping +The same `User` mapped with annotations (this is what `generate --mapping annotation` produces): +```java +import org.tinystruct.data.annotation.Column; +import org.tinystruct.data.annotation.Id; +import org.tinystruct.data.annotation.Table; + +@Table(name = "users", + id = @Id(name = "Id", column = "id", type = "int", length = 11, autoIncrement = true)) +public class User extends AbstractData implements Serializable { + @Column(name = "username", type = "varchar", length = 50) + private String username; + + @Column(name = "email", type = "varchar", length = 100) + private String email; + + // getters, setters (using setFieldAsXxx) and setData(Row) as usual +} +``` + +| Annotation | On | Attributes | +|---|---|---| +| `@Table` | class | `name` (required), `schema` (default none), `id` (default: no identifier) | +| `@Id` | inside `@Table(id = ...)` | `name` (default `Id`), `column`, `type`, `length`, `autoIncrement`, `autoGenerated`. An empty `column` means the table has no identifier. | +| `@Column` | field | `type` (required), `name` (the DB column; defaults to the Java field name), `length` | + +- The identifier is described inside `@Table` because it is inherited from `AbstractData`; there is no field of your own to annotate. +- `type` takes the same names as the XML `type` attribute (the `FieldType` names, matched case-insensitively): `INT`, `BIGINT`, `VARCHAR`, `TEXT`, `DATE`, `TIMESTAMP` and so on. +- Only fields with `@Column` are mapped. `@Table` is inherited, and the annotated fields of superclasses are mapped too. +- Mapping the same property twice, for example a subclass field that hides an annotated superclass field, fails with an error the first time the class is used. +- `autoGenerated = true` gives a non-integer id a fresh UUID for every new instance; `autoIncrement = true` leaves the value to the database. +- The `` element of a `.map.xml` file uses the same names, `autoIncrement` and `autoGenerated`. Files written with the original `increment` and `generate` attributes keep working; if both spellings are present, the new one wins. + +### Auto-Creating Tables +Add one line to `application.properties` to have missing tables created for you: +```properties +database.autocreate=true +``` + +- **Off by default.** Nothing touches your schema unless you turn this on, and the database user then needs the `CREATE` privilege. +- The first time a mapped class is instantiated, its table is created if it does not exist. This happens once per class per process, works for annotation and XML mappings alike, and adds nothing to later instances. +- It is a single statement that is safe when several instances start together: `CREATE TABLE IF NOT EXISTS` (SQL Server uses an `OBJECT_ID` guard instead). +- The `@Id` column becomes the primary key, with the database's own auto-increment or identity syntax when `autoIncrement = true`. +- Column types are rendered as the target database's native type, for example `TIMESTAMP` becomes `DATETIME` on MySQL and `DATETIME2` on SQL Server, `TEXT` becomes `VARCHAR(MAX)` on SQL Server, and `BLOB` becomes `BYTEA` on PostgreSQL. A `VARCHAR` without a length gets 255. +- A failure (for example missing privileges) is logged as a warning and never stops the object from being created; the first real statement then reports the database's own error. + +Limits to keep in mind: +- `DECIMAL` columns are created without precision or scale, because the mapping carries only a single `length`. +- A column type that is not one of the `FieldType` names is rejected. +- Only tables, columns and the primary key are created. There are no indexes, foreign keys, defaults or `NOT NULL` on other columns. +- An existing table is never altered or migrated, even if it differs from the mapping. +- Redis has no tables and is not supported. + ## Important Rules -1. **File Placement**: The mapping XML **must** mirror the POJO's package path under `src/main/resources/`. +1. **File Placement** (XML mapping only): The mapping XML **must** mirror the POJO's package path under `src/main/resources/`. 2. **Naming**: Table names are singularized for class names (`users` → `User`). Underscored columns become camelCase fields (`created_at` → `createdAt`). 3. **Setters**: Use `setFieldAsXxx` methods (e.g., `setFieldAsString`) in setters to sync state with the internal field map. 4. **Id Field**: The primary key field in Java is always named `Id` (inherited from `AbstractData`). +5. **One Mapping Source**: Use annotations or XML for a class, not both. If both exist the annotation wins and the XML is ignored. From ae47883fc655b7239a6b7b6d54e45fb439f0ab72 Mon Sep 17 00:00:00 2001 From: "@James" Date: Thu, 24 Sep 2026 21:48:12 +0800 Subject: [PATCH 08/12] Fix the issues --- docs/ja-JP/skills/tinystruct-patterns/SKILL.md | 9 +++++---- skills/tinystruct-patterns/SKILL.md | 7 ++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/ja-JP/skills/tinystruct-patterns/SKILL.md b/docs/ja-JP/skills/tinystruct-patterns/SKILL.md index cd20f927f..1b7cb6168 100644 --- a/docs/ja-JP/skills/tinystruct-patterns/SKILL.md +++ b/docs/ja-JP/skills/tinystruct-patterns/SKILL.md @@ -18,7 +18,8 @@ origin: ECC ```bash # @Action を直接実行 - 新しいアクションが動作するか確認する最速の方法 -bin/dispatcher greet/James +# (クラスをインポートする必要があります。--import または application.properties の default.import.applications を参照してください) +bin/dispatcher greet/James --import com.example.MyService bin/dispatcher echo --words "Praise the Lord" # Web向けの対応部分を検証する必要があるときに HTTP サーバーを起動 @@ -44,11 +45,11 @@ bin/dispatcher --version **2. プロジェクトルートから、フレームワークを一度実行します:** ```bash -java -cp ~/.m2/repository/org/tinystruct/tinystruct/1.7.34/tinystruct-1.7.34.jar \ +java -cp ~/.m2/repository/org/tinystruct/tinystruct//tinystruct-.jar \ org.tinystruct.system.Dispatcher --version ``` -Windows では `%USERPROFILE%\.m2\repository\org\tinystruct\tinystruct\1.7.34\tinystruct-1.7.34.jar` と同じクラスを使用します。これにより**実行したOS用のスクリプトだけ**が、まだ存在しない場合にのみ作成されます: +`` をステップ1で確認したバージョンに置き換えてください。Windows では `%USERPROFILE%\.m2\repository\org\tinystruct\tinystruct\\tinystruct-.jar` と同じクラスを使用します。これにより**実行したOS用のスクリプトだけ**が、まだ存在しない場合にのみ作成されます: | 実行環境 | 作成されるもの | |---|---| @@ -372,7 +373,7 @@ String port = this.getConfiguration("server.port"); - [アーキテクチャと設定](references/architecture.md) — 抽象化、パッケージマップ、プロパティ - [ルーティングと@Action](references/routing.md) — アノテーションの詳細、モード、パラメータ - [データ処理](references/data-handling.md) — Builder、Builders、JSONのシリアライズ・パース -- [データベース永続化](references/database.md) — AbstractData POJO、CRUD、アノテーションとXMLマッピング、POJO生成、テーブルの自動作成 +- [データベース永続化](../../../../skills/tinystruct-patterns/references/database.md) — AbstractData POJO、CRUD、アノテーションとXMLマッピング、POJO生成、テーブルの自動作成 - [システムと使用方法](references/system-usage.md) — Context、セッション、SSE、ファイルアップロード、イベント、ネットワーキング - [テストパターン](references/testing.md) — JUnit 5 によるユニットテストとHTTP統合テスト diff --git a/skills/tinystruct-patterns/SKILL.md b/skills/tinystruct-patterns/SKILL.md index 737b87e9f..52067f8f6 100644 --- a/skills/tinystruct-patterns/SKILL.md +++ b/skills/tinystruct-patterns/SKILL.md @@ -18,7 +18,8 @@ Architecture and implementation patterns for building modules with the **tinystr ```bash # Run any @Action directly - the fastest way to verify a new action works -bin/dispatcher greet/James +# (the class must be imported; see --import or default.import.applications in application.properties) +bin/dispatcher greet/James --import com.example.MyService bin/dispatcher echo --words "Praise the Lord" # Start the HTTP server when you need the web-facing counterpart @@ -44,11 +45,11 @@ Every tinystruct project needs its launcher in `bin/`. **The framework generates **2. From the project root, run the framework once:** ```bash -java -cp ~/.m2/repository/org/tinystruct/tinystruct/1.7.34/tinystruct-1.7.34.jar \ +java -cp ~/.m2/repository/org/tinystruct/tinystruct//tinystruct-.jar \ org.tinystruct.system.Dispatcher --version ``` -On Windows use `%USERPROFILE%\.m2\repository\org\tinystruct\tinystruct\1.7.34\tinystruct-1.7.34.jar` and the same class. This creates **only the script for the OS you ran it on**, and only if it is not already there: +Replace `` with the version found in step 1. On Windows use `%USERPROFILE%\.m2\repository\org\tinystruct\tinystruct\\tinystruct-.jar` and the same class. This creates **only the script for the OS you ran it on**, and only if it is not already there: | Run on | Creates | |---|---| From f83eaf6c65ed7907e0b7770f14347b231362f64a Mon Sep 17 00:00:00 2001 From: "@James" Date: Thu, 24 Sep 2026 21:55:00 +0800 Subject: [PATCH 09/12] fix: add Japanese database reference and clean up relative link --- .../ja-JP/skills/tinystruct-patterns/SKILL.md | 2 +- .../references/database.md | 162 ++++++++++++++++++ 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 docs/ja-JP/skills/tinystruct-patterns/references/database.md diff --git a/docs/ja-JP/skills/tinystruct-patterns/SKILL.md b/docs/ja-JP/skills/tinystruct-patterns/SKILL.md index 1b7cb6168..c3b576349 100644 --- a/docs/ja-JP/skills/tinystruct-patterns/SKILL.md +++ b/docs/ja-JP/skills/tinystruct-patterns/SKILL.md @@ -373,7 +373,7 @@ String port = this.getConfiguration("server.port"); - [アーキテクチャと設定](references/architecture.md) — 抽象化、パッケージマップ、プロパティ - [ルーティングと@Action](references/routing.md) — アノテーションの詳細、モード、パラメータ - [データ処理](references/data-handling.md) — Builder、Builders、JSONのシリアライズ・パース -- [データベース永続化](../../../../skills/tinystruct-patterns/references/database.md) — AbstractData POJO、CRUD、アノテーションとXMLマッピング、POJO生成、テーブルの自動作成 +- [データベース永続化](references/database.md) — AbstractData POJO、CRUD、アノテーションとXMLマッピング、POJO生成、テーブルの自動作成 - [システムと使用方法](references/system-usage.md) — Context、セッション、SSE、ファイルアップロード、イベント、ネットワーキング - [テストパターン](references/testing.md) — JUnit 5 によるユニットテストとHTTP統合テスト diff --git a/docs/ja-JP/skills/tinystruct-patterns/references/database.md b/docs/ja-JP/skills/tinystruct-patterns/references/database.md new file mode 100644 index 000000000..433b1a1f5 --- /dev/null +++ b/docs/ja-JP/skills/tinystruct-patterns/references/database.md @@ -0,0 +1,162 @@ +# tinystruct データベース永続化 + +## 使用する場面 + +データベース操作にはフレームワーク組み込みのORM的なデータ層を使用します。これは `AbstractData` を拡張したPOJOを、アノテーションまたはXMLマッピングファイルでテーブルにマッピングし、JPA/Hibernateの軽量な代替として機能します。 + +## 動作の仕組み + +### アーキテクチャ + +各テーブルは以下によって表されます: +1. **Java POJO**:`AbstractData` を拡張し、getter/setter と `setData(Row)` を提供する。 +2. **マッピング**、いずれか一方: + - **アノテーション**:クラスに `@Table`、フィールドに `@Column`([アノテーションマッピング](#アノテーションマッピング)を参照)。 + - **マッピングXML**:リソースに置かれた `ClassName.map.xml` で、Javaフィールドとデータベースカラムを結びつける。 + +マッピングはクラスごとに一度だけ読み込まれてキャッシュされるため、インスタンスの生成で再読み込みや再解析は行われません。クラスに `@Table` が存在する場合、同名の `.map.xml` より優先されます。 + +#### 主要な基底クラス:`AbstractData` +CRUDメソッドを提供します: +- `append()` / `appendAndGetId()` +- `update()` +- `delete()` +- `findAll()` / `findOneById()` / `findOneByKey(key, value)` +- `findWith(where, params)` +- `find(SQL, params)` + +### POJOの生成(CLI) + +ライブのデータベーステーブルを内省してPOJOとそのマッピングを生成します。デフォルトでは `.map.xml` ファイルが、`annotation` オプションを指定するとPOJO自体にアノテーションが付与されます。 + +#### 設定 +`application.properties`: +```properties +driver=com.mysql.cj.jdbc.Driver +database.url=jdbc:mysql://localhost:3306/mydb +database.user=root +database.password=secret +``` + +#### コマンド +```bash +# インタラクティブモード +bin/dispatcher generate + +# テーブルを指定 +bin/dispatcher generate --tables users + +# .map.xml ではなくPOJOにアノテーションを付与 +bin/dispatcher generate --tables users --mapping annotation +``` + +`--mapping` は `xml`(デフォルト)または `annotation` を受け付けます。アノテーションモードでは `.map.xml` は書き出されず、リソースディレクトリも作成されません。不明な値を指定するとプロンプトの前にエラーで拒否されます。 + +## 例 + +### CRUD操作 +```java +// CREATE +User user = new User(); +user.setUsername("james"); +user.append(); + +// READ +User user = new User(); +user.setId(42); +user.findOneById(); + +// UPDATE +user.setEmail("new@example.com"); +user.update(); + +// DELETE +user.delete(); +``` + +### 条件を使ったクエリ +```java +User user = new User(); +Table results = user.findWith("username LIKE ?", new Object[]{"%jam%"}); + +// 流暢なConditionビルダー +Condition condition = new Condition(); +condition.setRequestFields("id,username"); +Table filtered = user.find( + condition.select("`users`").and("email LIKE ?").orderBy("id DESC"), + new Object[]{"%@example.com"} +); +``` + +### マッピングXMLの構造 +`User.map.xml`: +```xml + + + + + + + +``` + +### アノテーションマッピング +アノテーションを使った同じ `User`(`generate --mapping annotation` が生成するもの): +```java +import org.tinystruct.data.annotation.Column; +import org.tinystruct.data.annotation.Id; +import org.tinystruct.data.annotation.Table; + +@Table(name = "users", + id = @Id(name = "Id", column = "id", type = "int", length = 11, autoIncrement = true)) +public class User extends AbstractData implements Serializable { + @Column(name = "username", type = "varchar", length = 50) + private String username; + + @Column(name = "email", type = "varchar", length = 100) + private String email; + + // getter、setter(setFieldAsXxx を使用)、および setData(Row) を通常通り実装 +} +``` + +| アノテーション | 対象 | 属性 | +|---|---|---| +| `@Table` | クラス | `name`(必須)、`schema`(デフォルト:なし)、`id`(デフォルト:識別子なし) | +| `@Id` | `@Table(id = ...)` 内 | `name`(デフォルト `Id`)、`column`、`type`、`length`、`autoIncrement`、`autoGenerated`。`column` が空の場合、テーブルに識別子がないことを意味する。 | +| `@Column` | フィールド | `type`(必須)、`name`(DBカラム名。デフォルトはJavaフィールド名)、`length` | + +- 識別子は `AbstractData` から継承されるため、独自のフィールドに `@Id` でアノテーションするのではなく `@Table` 内で記述する。 +- `type` はXMLの `type` 属性と同じ名前(`FieldType` の名前、大文字小文字を無視してマッチング)を取る:`INT`、`BIGINT`、`VARCHAR`、`TEXT`、`DATE`、`TIMESTAMP` など。 +- `@Column` が付与されたフィールドのみマッピングされる。`@Table` は継承され、スーパークラスのアノテーション付きフィールドもマッピングされる。 +- 同じプロパティを二重にマッピング(例:スーパークラスのアノテーション付きフィールドを隠すサブクラスフィールド)すると、そのクラスが最初に使われる際にエラーになる。 +- `autoGenerated = true` は整数でないIDに新規インスタンスごとにUUIDを付与し、`autoIncrement = true` はその値をデータベースに委ねる。 +- `.map.xml` の `` 要素も同じ名前 `autoIncrement`・``autoGenerated` を使う。旧来の `increment`・`generate` 属性で書かれたファイルも引き続き動作する。両方の綴りが存在する場合、新しい方が優先される。 + +### テーブルの自動作成 +`application.properties` に1行追加することで、存在しないテーブルを自動的に作成できます: +```properties +database.autocreate=true +``` + +- **デフォルトはオフ。** これをオンにしない限り、スキーマは何も変更されません。オンにする場合、データベースユーザーは `CREATE` 権限が必要です。 +- マッピングされたクラスが最初にインスタンス化される際に、テーブルが存在しなければ作成されます。これはプロセスごと・クラスごとに一度だけ発生し、アノテーションとXMLマッピングの両方で機能し、以降のインスタンスには何も追加されません。 +- `CREATE TABLE IF NOT EXISTS` という単一ステートメントが使用され、複数のインスタンスが同時に起動しても安全です(SQL Server では `OBJECT_ID` ガードが使われます)。 +- `@Id` カラムが主キーになり、`autoIncrement = true` の場合はデータベース固有のオートインクリメントまたはIDENTITY構文が使われます。 +- カラム型はターゲットデータベース固有の型にレンダリングされます(例:`TIMESTAMP` はMySQLでは `DATETIME`、SQL Serverでは `DATETIME2`、`TEXT` はSQL Serverでは `VARCHAR(MAX)`、`BLOB` はPostgreSQLでは `BYTEA`)。`length` なしの `VARCHAR` は255になります。 +- 失敗(例:権限不足)は警告としてログに記録され、オブジェクトの生成は停止しません。最初の実際のステートメントでデータベース固有のエラーが報告されます。 + +注意点: +- `DECIMAL` カラムはマッピングが単一の `length` のみを持つため、精度やスケールなしで作成されます。 +- `FieldType` の名前に含まれないカラム型は拒否されます。 +- テーブル、カラム、主キーのみが作成されます。インデックス、外部キー、デフォルト値、他カラムへの `NOT NULL` は作成されません。 +- 既存のテーブルはマッピングと異なっていても変更・マイグレーションされません。 +- Redisはテーブルを持たず、サポートされていません。 + +## 重要なルール + +1. **ファイルの配置**(XMLマッピングのみ):マッピングXMLは **`src/main/resources/`** 以下にPOJOのパッケージパスを反映した場所に置く必要があります。 +2. **命名規則**:テーブル名はクラス名に単数化されます(`users` → `User`)。アンダースコアのカラムはキャメルケースのフィールドになります(`created_at` → `createdAt`)。 +3. **セッター**:セッターでは(例:`setFieldAsString`)`setFieldAsXxx` メソッドを使用して内部フィールドマップと状態を同期させてください。 +4. **Idフィールド**:Javaでの主キーフィールドは常に `Id` という名前です(`AbstractData` から継承)。 +5. **マッピングソースは1つ**:クラスに対してアノテーションかXMLのどちらかを使用してください。両方が存在する場合、アノテーションが優先されてXMLは無視されます。 From 95aac7a101426dbb402f766e75290c7559934abe Mon Sep 17 00:00:00 2001 From: "James M. ZHOU" Date: Thu, 24 Sep 2026 22:01:52 +0800 Subject: [PATCH 10/12] Update docs/ja-JP/skills/tinystruct-patterns/SKILL.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docs/ja-JP/skills/tinystruct-patterns/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ja-JP/skills/tinystruct-patterns/SKILL.md b/docs/ja-JP/skills/tinystruct-patterns/SKILL.md index c3b576349..0295bd8c2 100644 --- a/docs/ja-JP/skills/tinystruct-patterns/SKILL.md +++ b/docs/ja-JP/skills/tinystruct-patterns/SKILL.md @@ -227,7 +227,7 @@ MCP API(例:`org.tinystruct.mcp.MCPTool`、`org.tinystruct.mcp.MCPServer`、 ``` > **セキュリティ警告(プロンプトインジェクション):** -> ツールの戻り値は、そのままAIモデルのコンテキストウィンドウに渡されます。ツールの戻り値文字列に含める前に、呼び出し元から渡されたすべての引数を検証・サニタイズする**必要があります**。入力をサニタイズしないと、攻撃者がモデルの挙動を上書きする敵対的な指示(プロンプトインジェクション)を注入できてしまいます。常に長さ、文字種、null であるかどうかを検証してください。 +> MCPクライアントまたはモデルホストがツールの戻り値をモデルのコンテキストに渡す場合、その戻り値は信頼できないデータです。呼び出し元から渡された引数だけでなく、データベース、ファイル、ネットワークなどから取得した**すべてのツール出力を検証し、モデルの指示と分離する必要があります**。戻り値内の命令を信頼できる指示として扱うと、攻撃者がモデルの挙動を上書きするプロンプトインジェクションを許してしまいます。 **MCP ツールを作成するには:** 1. `org.tinystruct.mcp.MCPTool` を拡張する。 From 704e360486185ac76d0c9255b209058ec38070ad Mon Sep 17 00:00:00 2001 From: "@James" Date: Thu, 24 Sep 2026 23:19:04 +0800 Subject: [PATCH 11/12] docs: add missing AbstractData and Serializable imports to ja-JP database.md example --- docs/ja-JP/skills/tinystruct-patterns/references/database.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/ja-JP/skills/tinystruct-patterns/references/database.md b/docs/ja-JP/skills/tinystruct-patterns/references/database.md index 433b1a1f5..7817957cf 100644 --- a/docs/ja-JP/skills/tinystruct-patterns/references/database.md +++ b/docs/ja-JP/skills/tinystruct-patterns/references/database.md @@ -106,6 +106,8 @@ Table filtered = user.find( import org.tinystruct.data.annotation.Column; import org.tinystruct.data.annotation.Id; import org.tinystruct.data.annotation.Table; +import org.tinystruct.data.component.AbstractData; +import java.io.Serializable; @Table(name = "users", id = @Id(name = "Id", column = "id", type = "int", length = 11, autoIncrement = true)) From d0b4652c9dbed913ddea63abd2f6b6f2d70dec4b Mon Sep 17 00:00:00 2001 From: "@James" Date: Tue, 29 Sep 2026 22:38:05 +0800 Subject: [PATCH 12/12] docs: pin tinystruct-patterns frontmatter to upstream framework version Add metadata.upstream: tinystruct==1.7.34 to both the English skill and its Japanese translation, so a future sync can mechanically diff the pinned version against the current tinystruct release instead of rediscovering drift from prose. Also migrates the legacy top-level `origin: ECC` key into the metadata block, matching the schema used by the rest of the skills in this repo (tests/commands/learn-skill-discovery.test.js already asserts top-level `origin:` is unsupported). Co-Authored-By: Claude Sonnet 5 --- docs/ja-JP/skills/tinystruct-patterns/SKILL.md | 4 +++- skills/tinystruct-patterns/SKILL.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/ja-JP/skills/tinystruct-patterns/SKILL.md b/docs/ja-JP/skills/tinystruct-patterns/SKILL.md index c3b576349..ae028679b 100644 --- a/docs/ja-JP/skills/tinystruct-patterns/SKILL.md +++ b/docs/ja-JP/skills/tinystruct-patterns/SKILL.md @@ -1,7 +1,9 @@ --- name: tinystruct-patterns description: tinystruct Java フレームワークで開発する際の専門ガイダンス。tinystruct コードベース、または tinystruct 上に構築されたあらゆるプロジェクトで作業する際に使用します — プロジェクトに存在しない場合の bin/dispatcher・bin/dispatcher.cmd 起動スクリプトの生成、Application クラスの作成、@Action によるルート定義、ユニットテスト、ActionRegistry、HTTP/CLI デュアルモード対応、組み込み HTTP サーバー、イベントシステム、Builder/Builders による JSON 処理、AbstractData によるデータベース永続化、POJO 生成、Server-Sent Events (SSE)、ファイルアップロード、アウトバウンド HTTP 通信を含みます。 -origin: ECC +metadata: + origin: ECC + upstream: tinystruct==1.7.34 --- # tinystruct 開発パターン diff --git a/skills/tinystruct-patterns/SKILL.md b/skills/tinystruct-patterns/SKILL.md index 52067f8f6..f354debc0 100644 --- a/skills/tinystruct-patterns/SKILL.md +++ b/skills/tinystruct-patterns/SKILL.md @@ -1,7 +1,9 @@ --- name: tinystruct-patterns description: Expert guidance for developing with the tinystruct Java framework. Use when working on the tinystruct codebase or any project built on tinystruct — including generating the bin/dispatcher and bin/dispatcher.cmd launcher scripts when a project lacks them, creating Application classes, @Action-mapped routes, unit tests, ActionRegistry, HTTP/CLI dual-mode handling, the built-in HTTP server, the event system, JSON with Builder/Builders, database persistence with AbstractData, POJO generation, Server-Sent Events (SSE), file uploads, and outbound HTTP networking. -origin: ECC +metadata: + origin: ECC + upstream: tinystruct==1.7.34 --- # tinystruct Development Patterns