tinystruct-patterns

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 ev

By affaan-m · 2,765 installs

npx skills add affaan-m/ecc --skill tinystruct-patterns

Source repository · Upstream listing

tinystruct Development Patterns 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. 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. Default to this workflow: implement the @Action , run it immediately via bin/dispatcher <action 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 <tinystruct.version property or the org.tinystruct:tinystruct dependency in pom.xml . The jar must be reachable, in ~/.m2/repository/org/tinystruct/tinystruct/<version / (run mvn dependency:resolve if it is not) or in the project's lib/ . 2. From the project root, run the framework once: Replace <version with the version found in step 1. On Windows use %USERPROFILE%\.m2\repository\org\tinystruct\tinystruct\<version \tinystruct <version .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 <version ) , 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: 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 <workingDirectory ${project.build.directory}</workingDirectory . 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, 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 . Configuring database connections or system settings in application.properties . 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 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 Basic Application (MyService) HTTP Mode Disambiguation (login) Native JSON Data Handling (Builder + Builders) SSE (Server Sent Events) File Upload Database Metadata Operations DatabaseOperator provides direct access to connection level catalog, schema, and metadata: 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: 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). 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. Run the server via the dispatcher: 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 . Access config values in your application: 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 Importing com.google.gson or com.fasterxml.jackson Use org.tinystruct.data.component.Builder / Builders . Using List<Builder for JSON arrays Use Builders to avoid generic type erasure issues. ApplicationRuntimeException: template not found Call setTemplateRequired(fa