An AI generated, agnostic prompt language for describing software logic to AI systems.
LogicScript sits between plain English (too vague) and production code (too specific). Describe your system's logic once — any AI turns it into TypeScript, Python, SQL, Java, Rust, C++ — or whatever!.
Sponsored by Lee Mellinger, Mellinger Consulting, LLC
Create a Python script from this LogicScript SHAPE User id : UUID required auto email : String required unique role : Enum[admin, user] default=user FUNC createUser(email, password, name) --- Creates a new user account. --- VALIDATE email matches email pattern password length >= 8 email not exists in User DO hash = bcrypt(password) user = User.create(email, hash, name) EMIT UserCreated WITH user RETURN user ON FAIL THROW ValidationError ON UserCreated TRIGGER EmailService.sendWelcome(user.email) LOG "New signup: {user.email}"
Write one LogicScript prompt. Generate TypeScript, Python, SQL, Java, Rust, or C++ from the same source. The AI handles the translation.
LogicScript describes intent, not implementation. Plain English is valid inside any block. The AI fills gaps with sensible defaults for your target stack.
Power users write prompts without programming experience. Developers use the same format to generate production-grade code with precise contracts.
Use SHAPE to describe records — fields, types, and constraints like required,
unique, and default.
Use FUNC with VALIDATE and DO blocks to describe actions, rules,
and what happens when things go wrong.
Pick any language or framework. TypeScript, Python, SQL, Java, Rust, C++. The same prompt works for all of them.
Paste your LogicScript prompt with the instruction: "Implement this in [language]." Review and deploy.
Implement the following LogicScript prompt in [TARGET LANGUAGE]. Rules: - Honor every VALIDATE condition as a precondition check. - Map ON FAIL clauses to the language's error/exception mechanism. - Translate EMIT to the appropriate event or message-bus call. - Do not add behavior not described in the prompt. [LOGICSCRIPT PROMPT] REFERENCE: https://github.com/logicscript/logicscript/blob/main/logicscript-spec.md
User Guide
A practical guide for power users and developers. No programming experience required.
LogicScript is a way of writing down the rules of a piece of software in plain, structured English — precise enough that an AI can turn it into working code, but readable enough that a non-programmer can write and understand it.
Think of it like writing a Standard Operating Procedure. You describe what information exists, what actions are allowed, what the rules are, and what happens automatically. You do not describe how the computer carries out those actions — that is the AI's job.
The key idea: Write what, not how.
LogicScript is a prompt language, not an execution runtime. It has no interpreter. Its output is always AI-generated code in a target language.
A LogicScript prompt follows a few simple rules:
SHAPE, FUNC, VALIDATE,
etc.--SHAPE Product id : UUID required auto name : String required price : Float required min=0 stock : Int required default=0 FUNC buyProduct(productId, quantity, userId) --- Lets a user purchase a product. --- VALIDATE quantity > 0 product exists with id = productId product.stock >= quantity user has valid payment method DO charge user for quantity * product.price reduce product.stock by quantity create an Order record EMIT OrderPlaced WITH order RETURN order ON FAIL THROW PurchaseError
LogicScript uses indentation (two spaces) to express nesting. No braces, brackets, or end keywords.
If a condition or step is hard to express structurally, write it out in plain English. The AI infers the most idiomatic implementation.
A SHAPE describes a type of record — its fields, types, and constraints.
| Type | Stores |
|---|---|
| String | Text of any length |
| Int | Whole number |
| Float | Decimal number |
| Bool | True or false |
| UUID | Unique identifier (auto-generated) |
| Timestamp | Date and time |
| Enum[a,b,c] | One of a fixed list of values |
| List<Type> | Ordered collection |
| Constraint | Meaning |
|---|---|
| required | Must always be present |
| optional | May be blank or null |
| unique | No two records share this value |
| auto | System-generated automatically |
| default=X | Use X when not supplied |
| min=N / max=N | Value or length limits |
| immutable | Cannot change after creation |
| indexed | Optimise lookups on this field |
A FUNC defines something the system can do. It has four optional blocks:
FUNC submitExpenseReport(reportId, userId) --- Submits a draft expense report for approval. --- VALIDATE report exists with id = reportId report.status IS draft report.ownerId IS userId report.totalAmount > 0 DO report.status = pending report.submittedAt = NOW EMIT ReportSubmitted WITH { reportId, userId } RETURN report ON FAIL LOG error, THROW SubmissionError
A FLOW is for processes with distinct named stages. Use PARALLEL to run steps
concurrently.
FLOW OnboardNewEmployee(employeeId) STEP createAccounts create email, Slack, and HR system accounts STEP scheduleOrientation find next available orientation session send calendar invite to employee and manager STEP notify EMIT EmployeeOnboarded WITH { employeeId } send welcome email to employee
Each line in a VALIDATE block is one check. If any fails, execution stops before any side
effects run.
VALIDATE email not empty email matches email pattern password length >= 8 age between 18 and 120 productId exists in Product email not exists in User startDate is before endDate cart.items not empty MESSAGE "Cart cannot be empty"
Define reusable access rules once and apply them anywhere.
GUARD AdminOnly REQUIRE user.role IS admin ON FAIL THROW ForbiddenError FUNC deleteRecord(recordId) GUARD AdminOnly DO Record.delete(recordId) ALLOW publish WHEN user.role IS admin OR user.role IS editor DENY delete WHEN document.locked IS true
POLICY RateLimit APPLIES TO all API endpoints ALLOW 100 requests PER user PER minute ON EXCEED return error "Too many requests" POLICY DataRetention APPLIES TO CustomerActivityLog KEEP 2 years THEN archive to cold storage DELETE after 7 years
QUERY overdueInvoices FROM Invoice WHERE status IS sent AND dueDate < TODAY ORDER BY dueDate ASC QUERY recentOrdersForUser(userId) FROM Order WHERE userId IS userId AND createdAt WITHIN 90 days INCLUDE items, shippingAddress ORDER BY createdAt DESC
Events decouple producers from consumers. EMIT fires an event; ON reacts to it.
EMIT OrderPlaced WITH { orderId, userId, total } ON OrderPlaced send order confirmation to user.email notify fulfillment team LOG "Order {orderId} placed" ON PaymentFailed 3 times suspend user account ALERT billing-team
STATE Invoice STATES draft, sent, paid, overdue, cancelled TRANSITION draft -> sent ON sendInvoice TRANSITION sent -> paid ON paymentReceived TRANSITION sent -> overdue ON dueDatePassed TRANSITION ANY -> cancelled ON cancelInvoice WHEN status NOT IN [paid] ON ENTER overdue send reminder to invoice.customerEmail ALERT accounts-receivable-team
SCHEDULE cleanExpiredSessions EVERY 1 hour DO deleted = Session.deleteExpired() LOG "Purged {deleted} sessions" SCHEDULE monthlyBilling AT "first day of month 00:01 UTC" DO FOR EACH active subscription charge subscriber for their plan
| Annotation | Meaning |
|---|---|
| @transaction | All steps succeed or all roll back |
| @retryable attempts=3 | Retry on failure up to N times |
| @cached ttl=5m | Cache the result for 5 minutes |
| @idempotent key="..." | Only run once per unique input |
| @rateLimit 10/minute | Limit calls per user per minute |
| @deprecated since="2.0" | Mark as replaced by a newer function |
submitExpenseReport is better than submit.
ON FAIL so the AI knows what
error contract to implement.@transaction for money. Any function that moves funds or adjusts
inventory should be atomic.REFERENCE: https://github.com/logicscript/logicscript/blob/main/logicscript-spec.md to your
prompt so the AI gets up to speed quickly.
SHAPE Name — define a data record FUNC name(inputs) — define an action FLOW Name(inputs) — multi-step process STEP stepName — one stage of a flow PARALLEL … WAIT all — concurrent steps MODULE Name — group related functions ENTRY functionName — mark a function as public
VALIDATE — pre-flight checks (stop if any fail) DO — sequential side-effect steps RETURN value — output value ON FAIL THROW Error — error handling REQUIRE x ELSE THROW — inline check inside DO IF condition … ELSE — branching FOR EACH item — iteration
GUARD Name — define a reusable access rule ALLOW action WHEN x — grant permission DENY action WHEN x — block permission EMIT Event WITH data — fire an event ON EventName — react to an event TRIGGER fn(args) — call a function ALERT team-name — notify a team
Language Specification
Complete keyword reference, type system, constraints, and formal grammar.
Groups related functions and declares the public API surface. ENTRY marks a function as
publicly callable.
MODULE ServiceName [IMPORT Symbol1, Symbol2 FROM source] [USE Symbol.method AS alias] ENTRY functionName(params)
Defines a data structure with typed, constrained fields. Types and constraints are implementation hints — the AI maps them to the most appropriate constructs in the target language.
SHAPE ShapeName fieldName : Type constraint [constraint ...]
Defines a unit of logic. All four blocks are optional, but at least DO or
RETURN must be present.
FUNC functionName(param1, param2, ...)
[--- doc comment ---]
[VALIDATE
condition ...]
[DO
step ...]
[RETURN value]
[ON FAIL strategy]
ON FAIL THROW ErrorName ON FAIL LOG error ON FAIL RETURN defaultValue ON FAIL retry N times THEN THROW ErrorName ON FAIL LOG error, THROW ErrorName
A block of precondition checks inside a FUNC. Each line is one condition, evaluated top to
bottom. The first failure stops execution and triggers the ON FAIL handler. Nothing in
DO runs if any check fails.
VALIDATE email not empty email matches email pattern password length >= 8 age between 18 and 120 price > 0 productId exists in Product email not exists in User startDate is before endDate cart.items not empty MESSAGE "Cart cannot be empty"
Plain English is valid for complex conditions the AI will infer the implementation for.
A named, multi-step operation. Use when a process has distinct named stages. PARALLEL runs
steps concurrently; WAIT all or WAIT any synchronises.
FLOW FlowName(params)
STEP stepName
logic ...
PARALLEL
STEP stepA ...
STEP stepB ...
WAIT all
GUARD GuardName REQUIRE condition [OR condition] ON FAIL THROW ErrorName
POLICY PolicyName APPLIES TO target [, target ...] ALLOW N requests PER unit PER period ON EXCEED action
QUERY queryName(params) FROM ShapeName [WHERE condition [AND condition ...]] [INCLUDE relatedShape, ...] [ORDER BY field ASC|DESC] [LIMIT N] [OFFSET N]
EMIT EventName WITH { field1, field2 }
ON EventName
TRIGGER functionName(args)
SEND MessageType TO recipient
LOG "message {variable}"
ALERT team-name
ON EventName N times -- fires after N occurrences
...
STATE ShapeName
STATES state1, state2, ...
TRANSITION a -> b ON eventName
TRANSITION ANY -> b ON eventName
WHEN status NOT IN [excludedState]
ON ENTER stateName
steps ...
ON EXIT stateName
steps ...
SCHEDULE jobName
EVERY N unit -- minutes | hours | days
DO
steps ...
SCHEDULE jobName
AT "schedule expression"
DO
steps ...
Examples: "9:00 AM daily",
"Monday 8:00 AM", "first day of month 08:00 UTC"
| Annotation | Meaning | Hint |
|---|---|---|
| @transaction | Atomic block | DB transaction + rollback on error |
| @retryable attempts=N backoff=X | Retry on failure | X: linear, exponential, or duration |
| @cached ttl=Ns key="..." | Cache result | In-memory, Redis, or equivalent |
| @idempotent key="..." | Deduplicate calls | Return stored result for same key |
| @deprecated since="X" use=fn | Mark as replaced | Add deprecation warning |
| @observable metrics=[m1,m2] | Instrument with metrics | Latency, error_rate, throughput |
| @rateLimit N/period per=field | Per-caller rate limit | Middleware or decorator |
| Type | Description |
|---|---|
| String | UTF-8 text of any length |
| Int | 64-bit signed integer |
| Float | 64-bit floating-point |
| Bool | Boolean true or false |
| UUID | RFC 4122 UUID |
| Timestamp | ISO 8601 date-time with timezone |
| List<T> | Ordered collection of T |
| Map<K,V> | Key-value map |
| Enum[a,b,c] | Closed set of named values |
| JSON | Arbitrary JSON-serializable value |
| Constraint | Applies to | Meaning |
|---|---|---|
| required | All | Must be present and non-null |
| optional | All | May be null or absent |
| unique | All | Value must be unique across all records |
| auto | UUID, Timestamp | System-generated automatically |
| default=X | All | Default when not supplied |
| min=N | Int, Float, String, List | Minimum value or length |
| max=N | Int, Float, String, List | Maximum value or length |
| pattern="regex" | String | Must match the regular expression |
| immutable | All | Cannot change after creation |
| indexed | All | Add a database index |
| Keyword | Category | Role |
|---|---|---|
| MODULE | Structure | Named service/component boundary |
| ENTRY | Structure | Public function within MODULE |
| SHAPE | Structure | Data structure definition |
| FUNC | Structure | Function definition |
| FLOW | Structure | Multi-step process |
| STEP | Structure | Named stage within a FLOW |
| PARALLEL / WAIT | Structure | Concurrent steps |
| VALIDATE | Logic | Precondition block |
| DO | Logic | Ordered steps |
| RETURN | Logic | Output value |
| ON FAIL | Logic | Error handling |
| REQUIRE … ELSE | Logic | Inline assertion in DO |
| IF / ELSE | Logic | Conditional branching |
| FOR EACH | Logic | Iteration |
| ALLOW / DENY | Access | Permission rules |
| GUARD | Access | Reusable access block |
| POLICY | Access | Cross-cutting rule |
| QUERY | Data | Named data retrieval |
| EMIT … WITH | Events | Publish a named event |
| ON [event] | Events | Subscribe to an event |
| TRIGGER | Events | Invoke from event handler |
| ALERT | Events | Notify a team or channel |
| STATE | State | State machine definition |
| TRANSITION | State | Valid state change |
| ON ENTER / ON EXIT | State | State lifecycle hooks |
| SCHEDULE | Jobs | Recurring job |
| EVERY / AT | Jobs | Job timing |
program ::= (declaration | comment)*
declaration ::= module | shape | func | flow | guard
| policy | query | on_handler | state | schedule
IDENT ::= [a-zA-Z][a-zA-Z0-9_]*
PASCAL ::= [A-Z][a-zA-Z0-9]*
CAMEL ::= [a-z][a-zA-Z0-9]*
module ::= "MODULE" PASCAL NL
(INDENT import_stmt)*
(INDENT "ENTRY" CAMEL "(" params? ")")*
shape ::= "SHAPE" PASCAL NL
(INDENT field_decl)*
field_decl ::= CAMEL ":" type constraint*
type ::= "String" | "Int" | "Float" | "Bool"
| "UUID" | "Timestamp" | "JSON"
| "List<" type ">" | "Map<" type "," type ">"
| "Enum[" CAMEL ("," CAMEL)* "]"
func ::= annotation* "FUNC" CAMEL "(" params? ")" NL
doc_comment?
validate_block?
do_block?
return_stmt?
on_fail?
NL ::= newline
INDENT ::= " " (two spaces)
Examples
Each example shows the complete prompt you'd give an AI — the instruction plus the LogicScript — followed by the generated code in every supported language.
The simplest possible LogicScript prompt — one function, one validation rule, one return value. A good starting point to see how each language handles the basics.
Generate Python from the following LogicScript.
FUNC greet(name)
VALIDATE
name not empty
DO
message = "Hello, " + name + "."
RETURN message
ON FAIL THROW ValidationError
// Generated from LogicScript — greet
class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = "ValidationError";
}
}
export function greet(name: string): string {
// VALIDATE: name not empty
if (!name || name.trim() === "") {
throw new ValidationError("name must not be empty");
}
// DO
const message = `Hello, ${name}.`;
// RETURN
return message;
}
# Generated from LogicScript — greet
class ValidationError(Exception):
pass
def greet(name: str) -> str:
"""Returns a personalised greeting.
Raises:
ValidationError: If name is empty.
"""
# VALIDATE: name not empty
if not name or not name.strip():
raise ValidationError("name must not be empty")
# DO
message = f"Hello, {name}."
# RETURN
return message
if __name__ == "__main__":
print(greet("world")) # Hello, world.
-- Generated from LogicScript — greet (PostgreSQL)
CREATE OR REPLACE FUNCTION greet(p_name TEXT)
RETURNS TEXT
LANGUAGE plpgsql AS $$
BEGIN
-- VALIDATE: name not empty
IF p_name IS NULL OR trim(p_name) = '' THEN
RAISE EXCEPTION 'ValidationError: name must not be empty';
END IF;
-- DO + RETURN
RETURN 'Hello, ' || p_name || '.';
END;
$$;
-- Usage:
-- SELECT greet('world'); -- Hello, world.
// Generated from LogicScript — greet
public class Greeter {
public static class ValidationException extends RuntimeException {
public ValidationException(String message) { super(message); }
}
/**
* Returns a personalised greeting.
* @throws ValidationException if name is null or blank
*/
public static String greet(String name) {
// VALIDATE: name not empty
if (name == null || name.isBlank()) {
throw new ValidationException("name must not be empty");
}
// DO + RETURN
return "Hello, " + name + ".";
}
public static void main(String[] args) {
System.out.println(greet("world")); // Hello, world.
}
}
// Generated from LogicScript — greet
#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
#[error("name must not be empty")]
EmptyName,
}
/// Returns a personalised greeting.
///
/// # Errors
/// Returns `ValidationError::EmptyName` if `name` is empty or whitespace.
pub fn greet(name: &str) -> Result {
// VALIDATE: name not empty
if name.trim().is_empty() {
return Err(ValidationError::EmptyName);
}
// DO + RETURN
Ok(format!("Hello, {}.", name))
}
fn main() {
println!("{}", greet("world").unwrap()); // Hello, world.
}
// Generated from LogicScript — greet
#include <stdexcept>
#include <string>
#include <iostream>
class ValidationError : public std::runtime_error {
public:
explicit ValidationError(const std::string& msg)
: std::runtime_error(msg) {}
};
/// Returns a personalised greeting.
/// @throws ValidationError if name is empty or whitespace.
std::string greet(const std::string& name) {
// VALIDATE: name not empty
bool blank = name.find_first_not_of("
") == std::string::npos;
if (name.empty() || blank) {
throw ValidationError("name must not be empty");
}
// DO + RETURN
return "Hello, " + name + ".";
}
int main() {
std::cout << greet("world") << std::endl; // Hello, world.
}
A realistic function with a data shape, multiple validation rules, password hashing, and an event emission. This is the pattern most apps need from day one.
Generate TypeScript (Node.js, Prisma, bcryptjs) from the following LogicScript.
SHAPE User
id : UUID required auto
email : String required unique
name : String required min=2
passwordHash : String required
role : Enum[admin, user, guest] default=user
createdAt : Timestamp auto
FUNC createUser(email, password, name)
VALIDATE
email matches email pattern
password length >= 8
name length >= 2
email not exists in User
DO
hash = bcrypt(password)
user = User.create(email, hash, name)
EMIT UserCreated WITH user
RETURN user
ON FAIL THROW ValidationError
ON UserCreated
TRIGGER EmailService.sendWelcome(user.email, user.name)
LOG "New signup: {user.email}"
// Generated from LogicScript — SHAPE User + FUNC createUser
import bcrypt from "bcryptjs";
import { v4 as uuidv4 } from "uuid";
import { db } from "./db"; // Prisma client
import { eventBus } from "./events";
export interface User {
id: string;
email: string;
name: string;
passwordHash: string;
role: "admin" | "user" | "guest";
createdAt: Date;
}
export class ValidationError extends Error {
statusCode = 422;
constructor(message: string) { super(message); this.name = "ValidationError"; }
}
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
/** @throws {ValidationError} */
export async function createUser(
email: string, password: string, name: string
): Promise<User> {
// VALIDATE
if (!EMAIL_RE.test(email)) throw new ValidationError("Invalid email format");
if (password.length < 8) throw new ValidationError("Password must be at least 8 characters");
if (name.length < 2) throw new ValidationError("Name must be at least 2 characters");
if (await db.user.findUnique({ where: { email } }))
throw new ValidationError("Email already registered");
// DO: hash + create
const passwordHash = await bcrypt.hash(password, 12);
const user = await db.user.create({
data: { id: uuidv4(), email, name, passwordHash, role: "user", createdAt: new Date() },
});
// EMIT UserCreated
eventBus.emit("UserCreated", user);
return user;
}
// ON UserCreated
eventBus.on("UserCreated", async (user: User) => {
await EmailService.sendWelcome(user.email, user.name); // TRIGGER
console.log(`New signup: ${user.email}`); // LOG
});
# Generated from LogicScript — SHAPE User + FUNC createUser
import re, uuid, bcrypt, logging
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
log = logging.getLogger(__name__)
class Role(Enum):
ADMIN = "admin"; USER = "user"; GUEST = "guest"
@dataclass
class User:
id: str = field(default_factory=lambda: str(uuid.uuid4()))
email: str = ""
name: str = ""
password_hash: str = ""
role: Role = Role.USER
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
class ValidationError(Exception):
status_code = 422
EMAIL_RE = re.compile(r"^[^\s@]+@[^\s@]+\.[^\s@]+$")
def create_user(email: str, password: str, name: str, db, event_bus) -> User:
"""Creates a new user. Raises ValidationError if any check fails."""
# VALIDATE
if not EMAIL_RE.match(email):
raise ValidationError("Invalid email format")
if len(password) < 8:
raise ValidationError("Password must be at least 8 characters")
if len(name) < 2:
raise ValidationError("Name must be at least 2 characters")
if db.user_exists(email):
raise ValidationError("Email already registered")
# DO: hash + create
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
user = User(email=email, password_hash=hashed, name=name)
db.save(user)
# EMIT UserCreated
event_bus.emit("UserCreated", user)
return user
# ON UserCreated
def on_user_created(user: User):
email_service.send_welcome(user.email, user.name) # TRIGGER
log.info(f"New signup: {user.email}") # LOG
-- Generated from LogicScript — SHAPE User + FUNC createUser (PostgreSQL)
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TYPE user_role AS ENUM ('admin', 'user', 'guest');
-- SHAPE User → CREATE TABLE
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL CHECK (length(name) >= 2),
password_hash TEXT NOT NULL,
role user_role NOT NULL DEFAULT 'user',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_users_email ON users (email);
-- FUNC createUser → plpgsql function
CREATE OR REPLACE FUNCTION create_user(
p_email TEXT, p_password TEXT, p_name TEXT
) RETURNS users LANGUAGE plpgsql AS $$
DECLARE v_hash TEXT; v_user users;
BEGIN
-- VALIDATE
IF p_email !~ '^[^\s@]+@[^\s@]+\.[^\s@]+$' THEN
RAISE EXCEPTION 'ValidationError: invalid email format'; END IF;
IF length(p_password) < 8 THEN
RAISE EXCEPTION 'ValidationError: password too short'; END IF;
IF length(p_name) < 2 THEN
RAISE EXCEPTION 'ValidationError: name too short'; END IF;
IF EXISTS(SELECT 1 FROM users WHERE email = p_email) THEN
RAISE EXCEPTION 'ValidationError: email already registered'; END IF;
-- DO: hash + insert
v_hash := crypt(p_password, gen_salt('bf', 12));
INSERT INTO users(email, name, password_hash)
VALUES(p_email, p_name, v_hash) RETURNING * INTO v_user;
-- EMIT UserCreated via pg_notify
PERFORM pg_notify('user_created', row_to_json(v_user)::text);
RETURN v_user;
END; $$;
// Generated from LogicScript — SHAPE User + FUNC createUser
import java.time.Instant;
import java.util.UUID;
import java.util.regex.Pattern;
public record User(UUID id, String email, String name,
String passwordHash, Role role, Instant createdAt) {
public enum Role { ADMIN, USER, GUEST }
}
public class ValidationException extends RuntimeException {
public final int statusCode = 422;
public ValidationException(String msg) { super(msg); }
}
@Service
public class UserService {
private static final Pattern EMAIL =
Pattern.compile("^[^\s@]+@[^\s@]+\.[^\s@]+$");
private final UserRepository repo;
private final PasswordEncoder encoder;
private final ApplicationEventPublisher publisher;
private static final Logger log = LoggerFactory.getLogger(UserService.class);
public User createUser(String email, String password, String name) {
// VALIDATE
if (!EMAIL.matcher(email).matches())
throw new ValidationException("Invalid email format");
if (password.length() < 8)
throw new ValidationException("Password must be at least 8 characters");
if (name.length() < 2)
throw new ValidationException("Name must be at least 2 characters");
if (repo.existsByEmail(email))
throw new ValidationException("Email already registered");
// DO: hash + create
String hash = encoder.encode(password);
User user = new User(UUID.randomUUID(), email, name, hash, User.Role.USER, Instant.now());
user = repo.save(user);
// EMIT UserCreated
publisher.publishEvent(new UserCreatedEvent(this, user));
return user;
}
}
// ON UserCreated
@EventListener
public void onUserCreated(UserCreatedEvent event) {
emailService.sendWelcome(event.user().email(), event.user().name()); // TRIGGER
log.info("New signup: {}", event.user().email()); // LOG
}
// Generated from LogicScript — SHAPE User + FUNC createUser
use uuid::Uuid;
use chrono::{DateTime, Utc};
use regex::Regex;
use std::sync::OnceLock;
#[derive(Debug, Clone, serde::Serialize)]
pub struct User {
pub id: Uuid,
pub email: String,
pub name: String,
pub password_hash: String,
pub role: Role,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, serde::Serialize, sqlx::Type)]
#[sqlx(type_name = "user_role", rename_all = "lowercase")]
pub enum Role { Admin, User, Guest }
#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
#[error("Invalid email format")] InvalidEmail,
#[error("Password must be >= 8 chars")] PasswordTooShort,
#[error("Name must be >= 2 chars")] NameTooShort,
#[error("Email already registered")] EmailTaken,
#[error(transparent)] Db(#[from] sqlx::Error),
}
static EMAIL_RE: OnceLock<Regex> = OnceLock::new();
pub async fn create_user(
pool: &sqlx::PgPool, bus: &impl EventBus,
email: String, password: String, name: String,
) -> Result<User, ValidationError> {
let re = EMAIL_RE.get_or_init(|| Regex::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").unwrap());
// VALIDATE
if !re.is_match(&email) { return Err(ValidationError::InvalidEmail); }
if password.len() < 8 { return Err(ValidationError::PasswordTooShort); }
if name.len() < 2 { return Err(ValidationError::NameTooShort); }
let exists: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM users WHERE email=$1)")
.bind(&email).fetch_one(pool).await?;
if exists { return Err(ValidationError::EmailTaken); }
// DO: hash + insert
let hash = bcrypt::hash(&password, bcrypt::DEFAULT_COST).unwrap();
let user: User = sqlx::query_as(
"INSERT INTO users(id,email,name,password_hash,role,created_at)
VALUES($1,$2,$3,$4,'user',NOW()) RETURNING *")
.bind(Uuid::new_v4()).bind(&email).bind(&name).bind(&hash)
.fetch_one(pool).await?;
// EMIT UserCreated
bus.emit("UserCreated", &user).await;
tracing::info!("New signup: {}", user.email); // LOG
Ok(user)
}
// Generated from LogicScript — SHAPE User + FUNC createUser
#pragma once
#include <string>, <stdexcept>, <regex>, <chrono>
#include "uuid.hpp", "db.hpp", "event_bus.hpp", "bcrypt.hpp"
enum class Role { Admin, User, Guest };
struct User {
std::string id, email, name, password_hash;
Role role{Role::User};
std::chrono::system_clock::time_point created_at;
};
class ValidationError : public std::runtime_error {
public:
int status_code = 422;
explicit ValidationError(const std::string& msg) : std::runtime_error(msg) {}
};
class UserService {
public:
UserService(Database& db, EventBus& bus)
: db_(db), bus_(bus)
, email_re_(R"(^[^\s@]+@[^\s@]+\.[^\s@]+$)", std::regex::ECMAScript) {}
User createUser(std::string_view email, std::string_view password, std::string_view name) {
// VALIDATE
if (!std::regex_match(email.begin(), email.end(), email_re_))
throw ValidationError("Invalid email format");
if (password.size() < 8)
throw ValidationError("Password must be at least 8 characters");
if (name.size() < 2)
throw ValidationError("Name must be at least 2 characters");
if (db_.user_exists_by_email(email))
throw ValidationError("Email already registered");
// DO: hash + insert
auto hash = bcrypt::hash(password, 12);
User user{ uuid::v4(), std::string(email), std::string(name),
std::move(hash), Role::User, std::chrono::system_clock::now() };
user = db_.insert_user(user);
// EMIT UserCreated
bus_.emit("UserCreated", user);
return user;
}
private:
Database& db_; EventBus& bus_; std::regex email_re_;
};
Shows @transaction — all steps must succeed together or none of them happen. The AI maps this
to the target language's transaction mechanism with automatic rollback.
Generate Java (Spring Boot, @Transactional) from the following LogicScript.
@transaction
FUNC transferFunds(fromId, toId, amount)
VALIDATE
amount > 0
fromId not equals toId
Account.find(fromId).balance >= amount
DO
Account.debit(fromId, amount)
Account.credit(toId, amount)
EMIT FundsTransferred WITH { fromId, toId, amount }
RETURN { success: true, timestamp: NOW }
ON FAIL THROW TransferError, ROLLBACK
// Generated from LogicScript — @transaction FUNC transferFunds
import { db } from "./db";
import { eventBus } from "./events";
export class TransferError extends Error {
constructor(msg: string) { super(msg); this.name = "TransferError"; }
}
interface TransferResult { success: boolean; timestamp: string; }
/** @throws {TransferError} */
export async function transferFunds(
fromId: string, toId: string, amount: number
): Promise<TransferResult> {
// VALIDATE
if (amount <= 0) throw new TransferError("Amount must be positive");
if (fromId === toId) throw new TransferError("Cannot transfer to the same account");
const from = await db.account.findUniqueOrThrow({ where: { id: fromId } });
if (from.balance < amount) throw new TransferError("Insufficient funds");
// @transaction — db.$transaction rolls back on any thrown error
try {
await db.$transaction(async (tx) => {
await tx.account.update({ where: { id: fromId }, data: { balance: { decrement: amount } } });
await tx.account.update({ where: { id: toId }, data: { balance: { increment: amount } } });
});
} catch (err) {
throw new TransferError(`Transaction failed: ${(err as Error).message}`);
}
// EMIT FundsTransferred
eventBus.emit("FundsTransferred", { fromId, toId, amount });
return { success: true, timestamp: new Date().toISOString() };
}
# Generated from LogicScript — @transaction FUNC transferFunds
from datetime import datetime, timezone
class TransferError(Exception): pass
def transfer_funds(from_id: str, to_id: str, amount: float, db, event_bus) -> dict:
"""Moves funds atomically. Raises TransferError on any failure."""
# VALIDATE
if amount <= 0:
raise TransferError("Amount must be positive")
if from_id == to_id:
raise TransferError("Cannot transfer to the same account")
from_acct = db.query(Account).filter_by(id=from_id).one()
if from_acct.balance < amount:
raise TransferError("Insufficient funds")
# @transaction → SQLAlchemy context manager; rolls back on any exception
try:
with db.begin():
from_acct.balance -= amount
to_acct = db.query(Account).filter_by(id=to_id).one()
to_acct.balance += amount
except Exception as exc:
raise TransferError(f"Transaction failed: {exc}") from exc
# EMIT FundsTransferred
event_bus.emit("FundsTransferred", {"from": from_id, "to": to_id, "amount": amount})
return {"success": True, "timestamp": datetime.now(timezone.utc).isoformat()}
-- Generated from LogicScript — @transaction FUNC transferFunds (PostgreSQL)
-- Procedures run inside an implicit transaction; RAISE causes automatic rollback.
CREATE OR REPLACE PROCEDURE transfer_funds(
p_from_id UUID, p_to_id UUID, p_amount NUMERIC(18,2)
) LANGUAGE plpgsql AS $$
DECLARE v_balance NUMERIC(18,2);
BEGIN
-- VALIDATE
IF p_amount <= 0 THEN
RAISE EXCEPTION 'TransferError: amount must be positive'; END IF;
IF p_from_id = p_to_id THEN
RAISE EXCEPTION 'TransferError: cannot transfer to the same account'; END IF;
-- Lock the source row to prevent race conditions
SELECT balance INTO v_balance
FROM accounts WHERE id = p_from_id FOR UPDATE;
IF v_balance < p_amount THEN
RAISE EXCEPTION 'TransferError: insufficient funds'; END IF;
-- DO: atomic debit/credit — any error here triggers automatic ROLLBACK
UPDATE accounts SET balance = balance - p_amount WHERE id = p_from_id;
UPDATE accounts SET balance = balance + p_amount WHERE id = p_to_id;
-- EMIT FundsTransferred via pg_notify
PERFORM pg_notify('funds_transferred',
json_build_object('from', p_from_id, 'to', p_to_id, 'amount', p_amount)::text);
END; $$;
-- Usage (caller controls outer transaction):
-- CALL transfer_funds('uuid-a', 'uuid-b', 100.00);
// Generated from LogicScript — @transaction FUNC transferFunds
import java.math.BigDecimal;
import java.time.Instant;
import java.util.Map;
public class TransferException extends RuntimeException {
public TransferException(String msg) { super(msg); }
}
record TransferResult(boolean success, Instant timestamp) {}
@Service
public class TransferService {
private final AccountRepository accountRepository;
private final ApplicationEventPublisher publisher;
// @transaction → Spring @Transactional rolls back on any RuntimeException
@Transactional
public TransferResult transferFunds(String fromId, String toId, BigDecimal amount) {
// VALIDATE
if (amount.compareTo(BigDecimal.ZERO) <= 0)
throw new TransferException("Amount must be positive");
if (fromId.equals(toId))
throw new TransferException("Cannot transfer to the same account");
Account from = accountRepository.findByIdWithLock(fromId) // SELECT FOR UPDATE
.orElseThrow(() -> new TransferException("Account not found"));
if (from.getBalance().compareTo(amount) < 0)
throw new TransferException("Insufficient funds");
// DO: debit + credit
accountRepository.debit(fromId, amount);
accountRepository.credit(toId, amount);
// EMIT FundsTransferred
publisher.publishEvent(new FundsTransferredEvent(this, fromId, toId, amount));
return new TransferResult(true, Instant.now());
}
}
// Generated from LogicScript — @transaction FUNC transferFunds
use rust_decimal::Decimal;
use uuid::Uuid;
use serde_json::json;
#[derive(Debug, thiserror::Error)]
pub enum TransferError {
#[error("Amount must be positive")] InvalidAmount,
#[error("Cannot transfer to the same account")] SameAccount,
#[error("Insufficient funds")] InsufficientFunds,
#[error(transparent)] Db(#[from] sqlx::Error),
}
pub async fn transfer_funds(
pool: &sqlx::PgPool, bus: &impl EventBus,
from_id: Uuid, to_id: Uuid, amount: Decimal,
) -> Result<(), TransferError> {
// VALIDATE
if amount <= Decimal::ZERO { return Err(TransferError::InvalidAmount); }
if from_id == to_id { return Err(TransferError::SameAccount); }
// @transaction — explicit sqlx transaction; rollback on any ? error
let mut tx = pool.begin().await?;
let balance: Decimal = sqlx::query_scalar(
"SELECT balance FROM accounts WHERE id = $1 FOR UPDATE")
.bind(from_id).fetch_one(&mut *tx).await?;
if balance < amount {
tx.rollback().await?;
return Err(TransferError::InsufficientFunds);
}
// DO: debit + credit
sqlx::query("UPDATE accounts SET balance = balance - $1 WHERE id = $2")
.bind(amount).bind(from_id).execute(&mut *tx).await?;
sqlx::query("UPDATE accounts SET balance = balance + $1 WHERE id = $2")
.bind(amount).bind(to_id).execute(&mut *tx).await?;
tx.commit().await?; // rolls back automatically if any step above failed
// EMIT FundsTransferred
bus.emit("FundsTransferred", &json!({"from": from_id, "to": to_id, "amount": amount})).await;
Ok(())
}
// Generated from LogicScript — @transaction FUNC transferFunds
#include <stdexcept>, <string>, <chrono>
#include "db.hpp", "event_bus.hpp"
class TransferError : public std::runtime_error {
public:
explicit TransferError(const std::string& msg) : std::runtime_error(msg) {}
};
struct TransferResult { bool success; std::chrono::system_clock::time_point timestamp; };
class PaymentService {
public:
TransferResult transferFunds(
const std::string& from_id, const std::string& to_id, double amount)
{
// VALIDATE
if (amount <= 0.0) throw TransferError("Amount must be positive");
if (from_id == to_id) throw TransferError("Cannot transfer to the same account");
auto from = db_.get_account_for_update(from_id); // locks the row
if (from.balance < amount) throw TransferError("Insufficient funds");
// @transaction — RAII: rolls back in destructor if not committed
auto txn = db_.begin_transaction();
try {
db_.debit(from_id, amount, txn);
db_.credit(to_id, amount, txn);
txn.commit();
} catch (...) {
txn.rollback(); // ROLLBACK
throw;
}
// EMIT FundsTransferred
bus_.emit("FundsTransferred", {from_id, to_id, amount});
return { true, std::chrono::system_clock::now() };
}
private:
Database& db_;
EventBus& bus_;
};
Shows STATE — a finite state machine with valid transitions and ON ENTER hooks.
The AI generates a transition table and enforces that only valid status changes are allowed.
Generate Python from the following LogicScript.
STATE Order
STATES draft, pending, paid, shipped, delivered, cancelled
TRANSITION draft -> pending ON submitOrder
TRANSITION pending -> paid ON paymentReceived
TRANSITION paid -> shipped ON fulfillOrder
TRANSITION shipped -> delivered ON deliveryConfirmed
TRANSITION ANY -> cancelled ON cancelOrder
WHEN status NOT IN [delivered]
ON ENTER shipped
EMIT OrderShipped WITH order
NOTIFY user via email WITH trackingInfo
ON ENTER cancelled
TRIGGER refundIfPaid(order)
LOG "Order {order.id} cancelled"
// Generated from LogicScript — STATE Order
type OrderStatus = "draft"|"pending"|"paid"|"shipped"|"delivered"|"cancelled";
interface Transition { from: OrderStatus[]; to: OrderStatus; }
const TRANSITIONS: Record<string, Transition> = {
submitOrder: { from: ["draft"], to: "pending" },
paymentReceived: { from: ["pending"], to: "paid" },
fulfillOrder: { from: ["paid"], to: "shipped" },
deliveryConfirmed: { from: ["shipped"], to: "delivered" },
cancelOrder: { from: ["draft","pending","paid","shipped"], to: "cancelled" },
};
export async function transitionOrder(order: Order, event: string) {
const t = TRANSITIONS[event];
if (!t) throw new Error(`Unknown event: ${event}`);
if (!t.from.includes(order.status)) throw new Error(`Invalid transition from ${order.status}`);
order.status = t.to;
await db.order.update({ where: { id: order.id }, data: { status: t.to } });
// ON ENTER hooks
if (t.to === "shipped") {
eventBus.emit("OrderShipped", order); // EMIT
await notifyUser(order.userId, { trackingInfo: order.trackingInfo }); // NOTIFY
}
if (t.to === "cancelled") {
await refundIfPaid(order); // TRIGGER
console.log(`Order ${order.id} cancelled`); // LOG
}
return order;
}
# Generated from LogicScript — STATE Order
import logging
from enum import Enum
log = logging.getLogger(__name__)
class OrderStatus(Enum):
DRAFT = "draft"
PENDING = "pending"
PAID = "paid"
SHIPPED = "shipped"
DELIVERED = "delivered"
CANCELLED = "cancelled"
TRANSITIONS = {
"submitOrder": ([OrderStatus.DRAFT], OrderStatus.PENDING),
"paymentReceived": ([OrderStatus.PENDING], OrderStatus.PAID),
"fulfillOrder": ([OrderStatus.PAID], OrderStatus.SHIPPED),
"deliveryConfirmed": ([OrderStatus.SHIPPED], OrderStatus.DELIVERED),
"cancelOrder": ([OrderStatus.DRAFT, OrderStatus.PENDING,
OrderStatus.PAID, OrderStatus.SHIPPED], OrderStatus.CANCELLED),
}
def transition_order(order, event: str, db, event_bus, notification_service):
if event not in TRANSITIONS:
raise ValueError(f"Unknown event: {event}")
from_states, to_status = TRANSITIONS[event]
if order.status not in from_states:
raise ValueError(f"Invalid transition from {order.status.value} via {event}")
order.status = to_status
db.save(order)
# ON ENTER hooks
if to_status == OrderStatus.SHIPPED:
event_bus.emit("OrderShipped", order) # EMIT
notification_service.notify_user(order.user_id, order.tracking_info) # NOTIFY
if to_status == OrderStatus.CANCELLED:
refund_if_paid(order) # TRIGGER
log.info(f"Order {order.id} cancelled") # LOG
return order
-- Generated from LogicScript — STATE Order (PostgreSQL)
CREATE TYPE order_status AS ENUM
('draft','pending','paid','shipped','delivered','cancelled');
CREATE OR REPLACE PROCEDURE transition_order(p_order_id UUID, p_event TEXT)
LANGUAGE plpgsql AS $$
DECLARE
v_current order_status;
v_next order_status;
BEGIN
SELECT status INTO v_current FROM orders WHERE id = p_order_id FOR UPDATE;
-- TRANSITION table
v_next := CASE
WHEN p_event = 'submitOrder' AND v_current = 'draft' THEN 'pending'
WHEN p_event = 'paymentReceived' AND v_current = 'pending' THEN 'paid'
WHEN p_event = 'fulfillOrder' AND v_current = 'paid' THEN 'shipped'
WHEN p_event = 'deliveryConfirmed' AND v_current = 'shipped' THEN 'delivered'
WHEN p_event = 'cancelOrder'
AND v_current IN ('draft','pending','paid','shipped') THEN 'cancelled'
ELSE NULL
END;
IF v_next IS NULL THEN
RAISE EXCEPTION 'Invalid transition: % via %', v_current, p_event;
END IF;
UPDATE orders SET status = v_next WHERE id = p_order_id;
-- ON ENTER hooks via pg_notify
IF v_next = 'shipped' THEN
PERFORM pg_notify('order_shipped', p_order_id::text);
END IF;
IF v_next = 'cancelled' THEN
PERFORM pg_notify('order_cancelled', p_order_id::text);
PERFORM refund_if_paid(p_order_id);
END IF;
END; $$;
// Generated from LogicScript — STATE Order
import java.util.*;
public enum OrderStatus { DRAFT, PENDING, PAID, SHIPPED, DELIVERED, CANCELLED }
@Service
public class OrderStateMachine {
private record Transition(Set<OrderStatus> from, OrderStatus to) {}
private static final Logger log = LoggerFactory.getLogger(OrderStateMachine.class);
private static final Map<String, Transition> TRANSITIONS = Map.of(
"submitOrder", new Transition(Set.of(DRAFT), PENDING),
"paymentReceived", new Transition(Set.of(PENDING), PAID),
"fulfillOrder", new Transition(Set.of(PAID), SHIPPED),
"deliveryConfirmed", new Transition(Set.of(SHIPPED), DELIVERED),
"cancelOrder", new Transition(Set.of(DRAFT,PENDING,PAID,SHIPPED), CANCELLED)
);
public Order transition(Order order, String event) {
Transition t = TRANSITIONS.get(event);
if (t == null) throw new IllegalArgumentException("Unknown event: " + event);
if (!t.from().contains(order.getStatus()))
throw new IllegalStateException("Invalid transition from " + order.getStatus());
order.setStatus(t.to());
order = orderRepository.save(order);
// ON ENTER hooks
if (t.to() == SHIPPED) {
publisher.publishEvent(new OrderShippedEvent(this, order)); // EMIT
notifService.notifyUser(order.getUserId(), order.getTracking()); // NOTIFY
}
if (t.to() == CANCELLED) {
refundService.refundIfPaid(order); // TRIGGER
log.info("Order {} cancelled", order.getId()); // LOG
}
return order;
}
}
// Generated from LogicScript — STATE Order
#[derive(Debug, Clone, PartialEq, sqlx::Type, serde::Serialize)]
#[sqlx(type_name = "order_status", rename_all = "lowercase")]
pub enum OrderStatus { Draft, Pending, Paid, Shipped, Delivered, Cancelled }
#[derive(Debug, thiserror::Error)]
pub enum TransitionError {
#[error("Unknown event: {0}")] UnknownEvent(String),
#[error("Invalid transition from {0:?} via {1}")] Invalid(OrderStatus, String),
#[error(transparent)] Db(#[from] sqlx::Error),
}
pub async fn transition_order(
pool: &sqlx::PgPool, bus: &impl EventBus,
order: &mut Order, event: &str,
) -> Result<(), TransitionError> {
use OrderStatus::*;
let next = match (event, &order.status) {
("submitOrder", Draft) => Pending,
("paymentReceived", Pending) => Paid,
("fulfillOrder", Paid) => Shipped,
("deliveryConfirmed", Shipped) => Delivered,
("cancelOrder", Draft | Pending | Paid | Shipped) => Cancelled,
("cancelOrder", _) => return Err(TransitionError::Invalid(order.status.clone(), event.into())),
_ => return Err(TransitionError::UnknownEvent(event.into())),
};
order.status = next.clone();
sqlx::query("UPDATE orders SET status=$1 WHERE id=$2")
.bind(&next).bind(order.id).execute(pool).await?;
// ON ENTER hooks
if next == Shipped {
bus.emit("OrderShipped", order).await; // EMIT
notify_user(pool, order.user_id, &order.tracking_info).await?; // NOTIFY
}
if next == Cancelled {
refund_if_paid(pool, order).await?; // TRIGGER
tracing::info!("Order {} cancelled", order.id); // LOG
}
Ok(())
}
// Generated from LogicScript — STATE Order
#include <unordered_map>, <unordered_set>, <stdexcept>, <string>
#include "db.hpp", "event_bus.hpp", "notification_service.hpp"
enum class OrderStatus { Draft, Pending, Paid, Shipped, Delivered, Cancelled };
class OrderStateMachine {
using StatusSet = std::unordered_set<OrderStatus>;
struct Transition { StatusSet from; OrderStatus to; };
const std::unordered_map<std::string, Transition> transitions_ = {
{"submitOrder", {{OrderStatus::Draft}, OrderStatus::Pending}},
{"paymentReceived", {{OrderStatus::Pending}, OrderStatus::Paid}},
{"fulfillOrder", {{OrderStatus::Paid}, OrderStatus::Shipped}},
{"deliveryConfirmed", {{OrderStatus::Shipped}, OrderStatus::Delivered}},
{"cancelOrder", {{OrderStatus::Draft, OrderStatus::Pending,
OrderStatus::Paid, OrderStatus::Shipped}, OrderStatus::Cancelled}},
};
public:
Order& transition(Order& order, const std::string& event) {
auto it = transitions_.find(event);
if (it == transitions_.end())
throw std::invalid_argument("Unknown event: " + event);
if (!it->second.from.count(order.status))
throw std::logic_error("Invalid transition via: " + event);
order.status = it->second.to;
db_.save(order);
// ON ENTER hooks
if (it->second.to == OrderStatus::Shipped) {
bus_.emit("OrderShipped", order); // EMIT
notif_.notify_user(order.user_id, order.tracking); // NOTIFY
}
if (it->second.to == OrderStatus::Cancelled) {
refund_if_paid(order); // TRIGGER
std::clog << "Order " << order.id << " cancelled
"; // LOG
}
return order;
}
private:
Database& db_;
EventBus& bus_;
NotificationService& notif_;
};