wyre 0.2.16

wyre serialization and deserialization library
Documentation
// wyre script grammar
/*
//sample wyre script
enum UserRole {
    ADMIN,
    USER,
    GUEST
}

enum ListingStatus {
    ACTIVE,
    INACTIVE,
    SOLD
}

// Models with optional fields and default values
model User {
    id: uuid
    name: string
    email: string & unique
    password: string
    role: UserRole = UserRole.USER
    bio: string | null
    created_at: timestamp = now()
    updated_at: timestamp | null
}

model Listing {
    id: uuid
    title: string
    description: string | null
    price: decimal
    status: ListingStatus = ListingStatus.ACTIVE
    created_at: timestamp = now()
    updated_at: timestamp | null
    created_by: one<User>
    watching: many<User>
    tags: many<Tag>
    views: int = 0
}

model Tag {
    id: uuid
    name: string & unique
    description: string | null
    parent: one<Tag> | null
}

// Custom function with optional and default parameters
function generateSlug(title: string, separator: string = "-", maxLength: int? = 100): string {
    let slug = title.toLowerCase().replace(/\s+/g, separator);
    return maxLength ? slug.slice(0, maxLength) : slug;
}: js

// Services with optional and default parameters
service UserService {
    #[allow(admin)]
    create_user(user: User): User

    #[allow(admin, owner)]
    get_user(id: uuid): User

    #[allow(admin, owner)]
    update_user(id: uuid, user: User): User

    #[allow(admin)]
    delete_user(id: uuid): boolean

    #[allow(all)]
    search_users(query: string = "", role: UserRole = UserRole.USER, page: int = 1, limit: int = 20): many<User>
}

service AuthService {
    #[allow(all)]
    login(email: string, password: string): string

    #[allow(authenticated)]
    logout(token: string): boolean

    #[allow(all)]
    register(name: string, email: string, password: string, role: UserRole = UserRole.USER): User
}

service ListingService {
    #[allow(authenticated)]
    create_listing(listing: Listing): Listing

    #[allow(owner, admin)]
    update_listing(id: uuid, listing: Listing): Listing

    #[allow(owner, admin)]
    delete_listing(id: uuid): boolean

    #[allow(all)]
    get_listing(id: uuid): Listing

    #[allow(all)]
    search_listings(query: string = "", category: string = "", status: ListingStatus = ListingStatus.ACTIVE, page: int = 1, limit: int = 20): many<Listing>
}

service TagService {
    #[allow(admin)]
    create_tag(tag: Tag): Tag

    #[allow(all)]
    get_tag(id: uuid): Tag

    #[allow(admin)]
    update_tag(id: uuid, tag: Tag): Tag

    #[allow(admin)]
    delete_tag(id: uuid): boolean

    #[allow(all)]
    search_tags(query: string | null, page: int = 1, limit: int = 20): many<Tag>
}

// Hooks with explicit types
hook User.before_create(user: User) {
    user.password = bcrypt.hash(user.password, 10);
    user.email = user.email.toLowerCase();
}: js

hook User.before_update(user: User) {
    if (user.password) {
        user.password = bcrypt.hash(user.password, 10);
    }
    user.updated_at = new Date();
}: js

hook Listing.before_create(listing: Listing) {
    listing.slug = generateSlug(listing.title);
    listing.views = 0;
}: js

hook Listing.after_update(listing: Listing) {
    if (listing.status === ListingStatus.SOLD) {
        for (const watcher of listing.watching) {
            sendNotification(watcher.id, `The listing "${listing.title}" has been sold.`);
        }
    }
}: js

// Realtime with optional parameter and explicit return type
realtime listen_new_listings(category: string?): Stream<Listing> {
    let stream = Listing.stream().filter(listing => listing.status === ListingStatus.ACTIVE);
    if (category) {
        stream = stream.filter(listing => listing.tags.some(tag => tag.name === category));
    }
    return stream.map(listing => ({
        id: listing.id,
        title: listing.title,
        price: listing.price,
        slug: listing.slug,
        views: listing.views
    }));
}: js

realtime listen_user_activity(userId: uuid): Stream<User> {
    return User.stream()
        .filter(user => user.id === userId)
        .map(user => ({
            id: user.id,
            name: user.name,
            email: user.email,
            role: user.role,
            lastActive: new Date()
        }));
}: js


*/

WHITESPACE = _{ " " | "\t" | "\r" | "\n" }
LINE_COMMENT = _{ "//" ~ (!"\n" ~ ANY)* ~ "\n" }
BLOCK_COMMENT = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" }
COMMENT = _{ LINE_COMMENT | BLOCK_COMMENT }

identifier = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
number = @{ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? }
string = @{ "\"" ~ (!"\"" ~ ANY)* ~ "\"" }
member_access = { identifier ~ ("." ~ identifier)+ }
function_call = { identifier ~ "(" ~ function_parameters? ~ ")" }
method_call = { member_access ~ "(" ~ function_parameters? ~ ")" }
medium = { ":js" | ":ts" | ":dart" }

wyre_script = { SOI ~ (COMMENT | block)* ~ EOI }

block = { enum_definition | model_definition | function_definition | service_definition | hook_definition | realtime_definition }

enum_definition = { "enum" ~ identifier ~ "{" ~ (identifier ~ ("," ~ identifier)* ~ ","?)? ~ "}" }

model_definition = { "model" ~ identifier ~ "{" ~ model_content ~ "}" }

function_definition = { "function" ~ identifier ~ "(" ~ function_parameters? ~ ")" ~ ":" ~ return_type ~ "{" ~ function_content ~ "}" ~ medium? }

service_definition = { "service" ~ identifier ~ "{" ~ service_content ~ "}" }

hook_definition = { "hook" ~ member_access ~ "(" ~ function_parameters? ~ ")" ~ "{" ~ hook_content ~ "}" ~ medium? }

realtime_definition = { "realtime" ~ identifier ~ "(" ~ function_parameters? ~ ")" ~ ":" ~ return_type ~ "{" ~ realtime_content ~ "}" ~ medium? }

model_content = { field_definition* }

service_content = { method_definition* }

hook_content = { (!("}:") ~ ANY)* }

realtime_content = { (!("}:") ~ ANY)* }

function_content = { (!("}:") ~ ANY)* }

field_definition = { annotation* ~ identifier ~ ":" ~ field_type ~ field_options? }

method_definition = { annotation* ~ identifier ~ "(" ~ function_parameters? ~ ")" ~ ":" ~ return_type }

function_parameters = { function_parameter ~ ("," ~ function_parameter)* ~ ","? }

function_parameter = { identifier ~ ":" ~ parameter_type ~ default_value? }

annotation = { "#[" ~ identifier ~ ("(" ~ annotation_parameters? ~ ")")? ~ "]" }

annotation_parameters = { identifier ~ ("," ~ identifier)* }

field_type = { 
    identifier ~ 
    ("<" ~ identifier ~ (("," ~ identifier)* ~ ">")?)? ~
    (("&" ~ identifier) | ("|" ~ "null"))? 
}

field_options = { default_value | ( and_option | or_option | null_option)* }

and_option = { "&" ~ identifier }

or_option = { "|" ~ identifier }

null_option = { "|" ~ "null" }

default_value = { "=" ~ (method_call | member_access | function_call | identifier | number | string | "null") }

parameter_type = { field_type ~ ("?")? }

return_type = { "many" ~ "<" ~ identifier ~ ">" | field_type | "Stream" ~ "<" ~ identifier ~ ">" }