...
This commit is contained in:
143
lib/hero/typescriptgenerator/generate.py
Normal file
143
lib/hero/typescriptgenerator/generate.py
Normal file
@@ -0,0 +1,143 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
def to_snake_case(name):
|
||||
import re
|
||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||
|
||||
def ts_type(v_type):
|
||||
if v_type in ['string']:
|
||||
return 'string'
|
||||
if v_type in ['int', 'integer', 'u32', 'u64', 'i64', 'f32', 'f64']:
|
||||
return 'number'
|
||||
if v_type in ['bool', 'boolean']:
|
||||
return 'boolean'
|
||||
if v_type.startswith('[]'):
|
||||
return ts_type(v_type[2:]) + '[]'
|
||||
if v_type == 'array':
|
||||
return 'any[]'
|
||||
return v_type
|
||||
|
||||
def generate_interface(schema_name, schema):
|
||||
content = f'export interface {schema_name} {{\n'
|
||||
if 'properties' in schema:
|
||||
for prop_name, prop_schema in schema.get('properties', {}).items():
|
||||
prop_type = ts_type(prop_schema.get('type', 'any'))
|
||||
if '$ref' in prop_schema:
|
||||
prop_type = prop_schema['$ref'].split('/')[-1]
|
||||
required = '?' if prop_name not in schema.get('required', []) else ''
|
||||
content += f' {prop_name}{required}: {prop_type};\n'
|
||||
if schema.get('allOf'):
|
||||
for item in schema['allOf']:
|
||||
if '$ref' in item:
|
||||
ref_name = item['$ref'].split('/')[-1]
|
||||
content += f' // Properties from {ref_name} are inherited\n'
|
||||
|
||||
|
||||
content += '}\n'
|
||||
return content
|
||||
|
||||
def generate_client(spec):
|
||||
methods_str = ''
|
||||
for method in spec['methods']:
|
||||
params = []
|
||||
if 'params' in method:
|
||||
for param in method['params']:
|
||||
param_type = 'any'
|
||||
if 'schema' in param:
|
||||
if '$ref' in param['schema']:
|
||||
param_type = param['schema']['$ref'].split('/')[-1]
|
||||
else:
|
||||
param_type = ts_type(param['schema'].get('type', 'any'))
|
||||
|
||||
params.append(f"{param['name']}: {param_type}")
|
||||
|
||||
params_str = ', '.join(params)
|
||||
|
||||
result_type = 'any'
|
||||
if 'result' in method and 'schema' in method['result']:
|
||||
if '$ref' in method['result']['schema']:
|
||||
result_type = method['result']['schema']['$ref'].split('/')[-1]
|
||||
else:
|
||||
result_type = ts_type(method['result']['schema'].get('type', 'any'))
|
||||
|
||||
method_name_snake = to_snake_case(method['name'])
|
||||
|
||||
methods_str += f"""
|
||||
async {method_name_snake}(params: {{ {params_str} }}): Promise<{result_type}> {{
|
||||
return this.send('{method['name']}', params);
|
||||
}}
|
||||
"""
|
||||
|
||||
schemas = spec.get('components', {}).get('schemas', {})
|
||||
imports_str = '\n'.join([f"import {{ {name} }} from './{name}';" for name in schemas.keys()])
|
||||
|
||||
base_url = 'http://localhost:8086/api/heromodels'
|
||||
client_class = f"""
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
{imports_str}
|
||||
|
||||
export class HeroModelsClient {{
|
||||
private baseUrl: string;
|
||||
|
||||
constructor(baseUrl: string = '{base_url}') {{
|
||||
this.baseUrl = baseUrl;
|
||||
}}
|
||||
|
||||
private async send<T>(method: string, params: any): Promise<T> {{
|
||||
const response = await fetch(this.baseUrl, {{
|
||||
method: 'POST',
|
||||
headers: {{
|
||||
'Content-Type': 'application/json',
|
||||
}},
|
||||
body: JSON.stringify({{
|
||||
jsonrpc: '2.0',
|
||||
method: method,
|
||||
params: params,
|
||||
id: 1,
|
||||
}}),
|
||||
}});
|
||||
|
||||
if (!response.ok) {{
|
||||
throw new Error(`HTTP error! status: ${{response.status}}`);
|
||||
}}
|
||||
|
||||
const jsonResponse:any = await response.json();
|
||||
if (jsonResponse.error) {{
|
||||
throw new Error(`RPC error: ${{jsonResponse.error.message}}`);
|
||||
}}
|
||||
|
||||
return jsonResponse.result;
|
||||
}}
|
||||
{methods_str}
|
||||
}}
|
||||
"""
|
||||
return client_class
|
||||
|
||||
def main():
|
||||
script_dir = os.path.dirname(__file__)
|
||||
openrpc_path = os.path.abspath(os.path.join(script_dir, '..', '..', 'hero', 'heromodels', 'openrpc.json'))
|
||||
output_dir = os.path.join(script_dir, 'generated_ts_client')
|
||||
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
with open(openrpc_path, 'r') as f:
|
||||
spec = json.load(f)
|
||||
|
||||
schemas = spec.get('components', {}).get('schemas', {})
|
||||
for name, schema in schemas.items():
|
||||
interface_content = generate_interface(name, schema)
|
||||
with open(os.path.join(output_dir, f'{name}.ts'), 'w') as f:
|
||||
f.write(interface_content)
|
||||
|
||||
client_content = generate_client(spec)
|
||||
with open(os.path.join(output_dir, 'client.ts'), 'w') as f:
|
||||
f.write(client_content)
|
||||
|
||||
print(f"TypeScript client generated successfully in {output_dir}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
36
lib/hero/typescriptgenerator/generate.vsh
Normal file
36
lib/hero/typescriptgenerator/generate.vsh
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env -S v -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.hero.typescriptgenerator
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
|
||||
const openrpc_path = os.dir(@FILE) + '/../../heromodels/openrpc.json'
|
||||
const output_dir = os.dir(@FILE) + '/generated_ts_client'
|
||||
|
||||
fn main() {
|
||||
spec_text := os.read_file(openrpc_path) or {
|
||||
eprintln('Failed to read openrpc.json: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
openrpc_spec := openrpc.decode(spec_text) or {
|
||||
eprintln('Failed to decode openrpc spec: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
config := typescriptgenerator.IntermediateConfig{
|
||||
base_url: 'http://localhost:8086/api/heromodels'
|
||||
}
|
||||
|
||||
intermediate_spec := typescriptgenerator.from_openrpc(openrpc_spec, config) or {
|
||||
eprintln('Failed to create intermediate spec: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
typescriptgenerator.generate_typescript_client(intermediate_spec, output_dir) or {
|
||||
eprintln('Failed to generate typescript client: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println("TypeScript client generated successfully in ${output_dir}")
|
||||
}
|
||||
10
lib/hero/typescriptgenerator/generated_ts_client/Base.ts
Normal file
10
lib/hero/typescriptgenerator/generated_ts_client/Base.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface Base {
|
||||
id?: number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
created_at?: number;
|
||||
updated_at?: number;
|
||||
securitypolicy?: number;
|
||||
tags?: number;
|
||||
messages?: any[];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface Calendar {
|
||||
events?: any[];
|
||||
color?: string;
|
||||
timezone?: string;
|
||||
is_public?: boolean;
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface CalendarEvent {
|
||||
title?: string;
|
||||
start_time?: number;
|
||||
end_time?: number;
|
||||
location?: string;
|
||||
attendees?: any[];
|
||||
fs_items?: any[];
|
||||
calendar_id?: number;
|
||||
status?: string;
|
||||
is_all_day?: boolean;
|
||||
is_recurring?: boolean;
|
||||
recurrence?: any[];
|
||||
reminder_mins?: any[];
|
||||
color?: string;
|
||||
timezone?: string;
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface ChatGroup {
|
||||
chat_type?: string;
|
||||
last_activity?: number;
|
||||
is_archived?: boolean;
|
||||
group_id?: number;
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface ChatMessage {
|
||||
content?: string;
|
||||
chat_group_id?: number;
|
||||
sender_id?: number;
|
||||
parent_messages?: any[];
|
||||
fs_files?: any[];
|
||||
message_type?: string;
|
||||
status?: string;
|
||||
reactions?: any[];
|
||||
mentions?: any[];
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
12
lib/hero/typescriptgenerator/generated_ts_client/Contact.ts
Normal file
12
lib/hero/typescriptgenerator/generated_ts_client/Contact.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface Contact {
|
||||
emails?: any[];
|
||||
user_id?: number;
|
||||
phones?: any[];
|
||||
addresses?: any[];
|
||||
avatar_url?: string;
|
||||
bio?: string;
|
||||
timezone?: string;
|
||||
status?: string;
|
||||
profile_ids?: any[];
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface Education {
|
||||
school?: string;
|
||||
degree?: string;
|
||||
field_of_study?: string;
|
||||
start_date?: number;
|
||||
end_date?: number;
|
||||
description?: string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface Experience {
|
||||
title?: string;
|
||||
company?: string;
|
||||
location?: string;
|
||||
start_date?: number;
|
||||
end_date?: number;
|
||||
current?: boolean;
|
||||
description?: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface Group {
|
||||
members?: any[];
|
||||
subgroups?: any[];
|
||||
parent_group?: number;
|
||||
is_public?: boolean;
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface GroupMember {
|
||||
user_id?: number;
|
||||
role?: string;
|
||||
joined_at?: number;
|
||||
}
|
||||
10
lib/hero/typescriptgenerator/generated_ts_client/Message.ts
Normal file
10
lib/hero/typescriptgenerator/generated_ts_client/Message.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface Message {
|
||||
subject?: string;
|
||||
message?: string;
|
||||
parent?: number;
|
||||
author?: number;
|
||||
to?: any[];
|
||||
cc?: any[];
|
||||
send_log?: any[];
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface MessageLink {
|
||||
message_id?: number;
|
||||
link_type?: string;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface MessageReaction {
|
||||
user_id?: number;
|
||||
emoji?: string;
|
||||
timestamp?: number;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface Milestone {
|
||||
name?: string;
|
||||
description?: string;
|
||||
due_date?: number;
|
||||
completed?: boolean;
|
||||
issues?: any[];
|
||||
}
|
||||
12
lib/hero/typescriptgenerator/generated_ts_client/Planning.ts
Normal file
12
lib/hero/typescriptgenerator/generated_ts_client/Planning.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface Planning {
|
||||
color?: string;
|
||||
timezone?: string;
|
||||
is_public?: boolean;
|
||||
calendar_template_id?: number;
|
||||
registration_desk_id?: number;
|
||||
autoschedule_rules?: any[];
|
||||
invite_rules?: any[];
|
||||
attendees_required?: any[];
|
||||
attendees_optional?: any[];
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface PlanningRecurrenceRule {
|
||||
until?: number;
|
||||
by_weekday?: any[];
|
||||
by_monthday?: any[];
|
||||
hour_from?: number;
|
||||
hour_to?: number;
|
||||
duration?: number;
|
||||
priority?: number;
|
||||
}
|
||||
17
lib/hero/typescriptgenerator/generated_ts_client/Profile.ts
Normal file
17
lib/hero/typescriptgenerator/generated_ts_client/Profile.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export interface Profile {
|
||||
user_id?: number;
|
||||
summary?: string;
|
||||
headline?: string;
|
||||
location?: string;
|
||||
industry?: string;
|
||||
picture_url?: string;
|
||||
background_image_url?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
website?: string;
|
||||
experience?: any[];
|
||||
education?: any[];
|
||||
skills?: any[];
|
||||
languages?: any[];
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface Project {
|
||||
swimlanes?: any[];
|
||||
milestones?: any[];
|
||||
fs_files?: any[];
|
||||
status?: string;
|
||||
start_date?: number;
|
||||
end_date?: number;
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface ProjectIssue {
|
||||
title?: string;
|
||||
project_id?: number;
|
||||
issue_type?: string;
|
||||
priority?: string;
|
||||
status?: string;
|
||||
swimlane?: string;
|
||||
assignees?: any[];
|
||||
reporter?: number;
|
||||
milestone?: string;
|
||||
deadline?: number;
|
||||
estimate?: number;
|
||||
fs_files?: any[];
|
||||
parent_id?: number;
|
||||
children?: any[];
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface RecurrenceRule {
|
||||
frequency?: string;
|
||||
interval?: number;
|
||||
until?: number;
|
||||
count?: number;
|
||||
by_weekday?: any[];
|
||||
by_monthday?: any[];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface Registration {
|
||||
user_id?: number;
|
||||
accepted?: boolean;
|
||||
accepted_by?: number;
|
||||
timestamp?: number;
|
||||
timestamp_acceptation?: number;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface RegistrationDesk {
|
||||
fs_items?: any[];
|
||||
white_list?: any[];
|
||||
white_list_accepted?: any[];
|
||||
required_list?: any[];
|
||||
black_list?: any[];
|
||||
start_time?: number;
|
||||
end_time?: number;
|
||||
acceptance_required?: boolean;
|
||||
registrations?: any[];
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface RegistrationFileAttachment {
|
||||
fs_item?: number;
|
||||
cat?: string;
|
||||
public?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface SendLog {
|
||||
to?: any[];
|
||||
cc?: any[];
|
||||
status?: string;
|
||||
timestamp?: number;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface Swimlane {
|
||||
name?: string;
|
||||
description?: string;
|
||||
order?: number;
|
||||
color?: string;
|
||||
is_done?: boolean;
|
||||
}
|
||||
7
lib/hero/typescriptgenerator/generated_ts_client/User.ts
Normal file
7
lib/hero/typescriptgenerator/generated_ts_client/User.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface User {
|
||||
user_id?: number;
|
||||
contact_id?: number;
|
||||
status?: string;
|
||||
profile_ids?: any[];
|
||||
// Properties from Base are inherited
|
||||
}
|
||||
324
lib/hero/typescriptgenerator/generated_ts_client/client.ts
Normal file
324
lib/hero/typescriptgenerator/generated_ts_client/client.ts
Normal file
@@ -0,0 +1,324 @@
|
||||
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import { Base } from './Base';
|
||||
import { Calendar } from './Calendar';
|
||||
import { CalendarEvent } from './CalendarEvent';
|
||||
import { RecurrenceRule } from './RecurrenceRule';
|
||||
import { ChatGroup } from './ChatGroup';
|
||||
import { ChatMessage } from './ChatMessage';
|
||||
import { MessageLink } from './MessageLink';
|
||||
import { MessageReaction } from './MessageReaction';
|
||||
import { Contact } from './Contact';
|
||||
import { Group } from './Group';
|
||||
import { GroupMember } from './GroupMember';
|
||||
import { Message } from './Message';
|
||||
import { SendLog } from './SendLog';
|
||||
import { Planning } from './Planning';
|
||||
import { PlanningRecurrenceRule } from './PlanningRecurrenceRule';
|
||||
import { Profile } from './Profile';
|
||||
import { Experience } from './Experience';
|
||||
import { Education } from './Education';
|
||||
import { Project } from './Project';
|
||||
import { Swimlane } from './Swimlane';
|
||||
import { Milestone } from './Milestone';
|
||||
import { ProjectIssue } from './ProjectIssue';
|
||||
import { RegistrationDesk } from './RegistrationDesk';
|
||||
import { RegistrationFileAttachment } from './RegistrationFileAttachment';
|
||||
import { Registration } from './Registration';
|
||||
import { User } from './User';
|
||||
|
||||
export class HeroModelsClient {
|
||||
private baseUrl: string;
|
||||
|
||||
constructor(baseUrl: string = 'http://localhost:8086/api/heromodels') {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
private async send<T>(method: string, params: any): Promise<T> {
|
||||
const response = await fetch(this.baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: method,
|
||||
params: params,
|
||||
id: 1,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const jsonResponse:any = await response.json();
|
||||
if (jsonResponse.error) {
|
||||
throw new Error(`RPC error: ${jsonResponse.error.message}`);
|
||||
}
|
||||
|
||||
return jsonResponse.result;
|
||||
}
|
||||
|
||||
async calendar_get(params: { id: number }): Promise<Calendar> {
|
||||
return this.send('calendar_get', params);
|
||||
}
|
||||
|
||||
async calendar_set(params: { calendar: Calendar }): Promise<number> {
|
||||
return this.send('calendar_set', params);
|
||||
}
|
||||
|
||||
async calendar_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('calendar_delete', params);
|
||||
}
|
||||
|
||||
async calendar_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('calendar_exist', params);
|
||||
}
|
||||
|
||||
async calendar_list(params: { }): Promise<any[]> {
|
||||
return this.send('calendar_list', params);
|
||||
}
|
||||
|
||||
async calendar_event_get(params: { id: number }): Promise<CalendarEvent> {
|
||||
return this.send('calendar_event_get', params);
|
||||
}
|
||||
|
||||
async calendar_event_set(params: { calendar_event: CalendarEvent }): Promise<number> {
|
||||
return this.send('calendar_event_set', params);
|
||||
}
|
||||
|
||||
async calendar_event_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('calendar_event_delete', params);
|
||||
}
|
||||
|
||||
async calendar_event_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('calendar_event_exist', params);
|
||||
}
|
||||
|
||||
async calendar_event_list(params: { }): Promise<any[]> {
|
||||
return this.send('calendar_event_list', params);
|
||||
}
|
||||
|
||||
async chat_group_get(params: { id: number }): Promise<ChatGroup> {
|
||||
return this.send('chat_group_get', params);
|
||||
}
|
||||
|
||||
async chat_group_set(params: { chat_group: ChatGroup }): Promise<number> {
|
||||
return this.send('chat_group_set', params);
|
||||
}
|
||||
|
||||
async chat_group_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('chat_group_delete', params);
|
||||
}
|
||||
|
||||
async chat_group_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('chat_group_exist', params);
|
||||
}
|
||||
|
||||
async chat_group_list(params: { }): Promise<any[]> {
|
||||
return this.send('chat_group_list', params);
|
||||
}
|
||||
|
||||
async chat_message_get(params: { id: number }): Promise<ChatMessage> {
|
||||
return this.send('chat_message_get', params);
|
||||
}
|
||||
|
||||
async chat_message_set(params: { chat_message: ChatMessage }): Promise<number> {
|
||||
return this.send('chat_message_set', params);
|
||||
}
|
||||
|
||||
async chat_message_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('chat_message_delete', params);
|
||||
}
|
||||
|
||||
async chat_message_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('chat_message_exist', params);
|
||||
}
|
||||
|
||||
async chat_message_list(params: { }): Promise<any[]> {
|
||||
return this.send('chat_message_list', params);
|
||||
}
|
||||
|
||||
async contact_get(params: { id: number }): Promise<Contact> {
|
||||
return this.send('contact_get', params);
|
||||
}
|
||||
|
||||
async contact_set(params: { contact: Contact }): Promise<number> {
|
||||
return this.send('contact_set', params);
|
||||
}
|
||||
|
||||
async contact_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('contact_delete', params);
|
||||
}
|
||||
|
||||
async contact_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('contact_exist', params);
|
||||
}
|
||||
|
||||
async contact_list(params: { }): Promise<any[]> {
|
||||
return this.send('contact_list', params);
|
||||
}
|
||||
|
||||
async group_get(params: { id: number }): Promise<Group> {
|
||||
return this.send('group_get', params);
|
||||
}
|
||||
|
||||
async group_set(params: { group: Group }): Promise<number> {
|
||||
return this.send('group_set', params);
|
||||
}
|
||||
|
||||
async group_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('group_delete', params);
|
||||
}
|
||||
|
||||
async group_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('group_exist', params);
|
||||
}
|
||||
|
||||
async group_list(params: { }): Promise<any[]> {
|
||||
return this.send('group_list', params);
|
||||
}
|
||||
|
||||
async message_get(params: { id: number }): Promise<Message> {
|
||||
return this.send('message_get', params);
|
||||
}
|
||||
|
||||
async message_set(params: { message: Message }): Promise<number> {
|
||||
return this.send('message_set', params);
|
||||
}
|
||||
|
||||
async message_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('message_delete', params);
|
||||
}
|
||||
|
||||
async message_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('message_exist', params);
|
||||
}
|
||||
|
||||
async message_list(params: { }): Promise<any[]> {
|
||||
return this.send('message_list', params);
|
||||
}
|
||||
|
||||
async planning_get(params: { id: number }): Promise<Planning> {
|
||||
return this.send('planning_get', params);
|
||||
}
|
||||
|
||||
async planning_set(params: { planning: Planning }): Promise<number> {
|
||||
return this.send('planning_set', params);
|
||||
}
|
||||
|
||||
async planning_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('planning_delete', params);
|
||||
}
|
||||
|
||||
async planning_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('planning_exist', params);
|
||||
}
|
||||
|
||||
async planning_list(params: { }): Promise<any[]> {
|
||||
return this.send('planning_list', params);
|
||||
}
|
||||
|
||||
async profile_get(params: { id: number }): Promise<Profile> {
|
||||
return this.send('profile_get', params);
|
||||
}
|
||||
|
||||
async profile_set(params: { profile: Profile }): Promise<number> {
|
||||
return this.send('profile_set', params);
|
||||
}
|
||||
|
||||
async profile_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('profile_delete', params);
|
||||
}
|
||||
|
||||
async profile_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('profile_exist', params);
|
||||
}
|
||||
|
||||
async profile_list(params: { }): Promise<any[]> {
|
||||
return this.send('profile_list', params);
|
||||
}
|
||||
|
||||
async project_get(params: { id: number }): Promise<Project> {
|
||||
return this.send('project_get', params);
|
||||
}
|
||||
|
||||
async project_set(params: { project: Project }): Promise<number> {
|
||||
return this.send('project_set', params);
|
||||
}
|
||||
|
||||
async project_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('project_delete', params);
|
||||
}
|
||||
|
||||
async project_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('project_exist', params);
|
||||
}
|
||||
|
||||
async project_list(params: { }): Promise<any[]> {
|
||||
return this.send('project_list', params);
|
||||
}
|
||||
|
||||
async project_issue_get(params: { id: number }): Promise<ProjectIssue> {
|
||||
return this.send('project_issue_get', params);
|
||||
}
|
||||
|
||||
async project_issue_set(params: { project_issue: ProjectIssue }): Promise<number> {
|
||||
return this.send('project_issue_set', params);
|
||||
}
|
||||
|
||||
async project_issue_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('project_issue_delete', params);
|
||||
}
|
||||
|
||||
async project_issue_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('project_issue_exist', params);
|
||||
}
|
||||
|
||||
async project_issue_list(params: { }): Promise<any[]> {
|
||||
return this.send('project_issue_list', params);
|
||||
}
|
||||
|
||||
async registration_desk_get(params: { id: number }): Promise<RegistrationDesk> {
|
||||
return this.send('registration_desk_get', params);
|
||||
}
|
||||
|
||||
async registration_desk_set(params: { registration_desk: RegistrationDesk }): Promise<number> {
|
||||
return this.send('registration_desk_set', params);
|
||||
}
|
||||
|
||||
async registration_desk_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('registration_desk_delete', params);
|
||||
}
|
||||
|
||||
async registration_desk_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('registration_desk_exist', params);
|
||||
}
|
||||
|
||||
async registration_desk_list(params: { }): Promise<any[]> {
|
||||
return this.send('registration_desk_list', params);
|
||||
}
|
||||
|
||||
async user_get(params: { id: number }): Promise<User> {
|
||||
return this.send('user_get', params);
|
||||
}
|
||||
|
||||
async user_set(params: { user: User }): Promise<number> {
|
||||
return this.send('user_set', params);
|
||||
}
|
||||
|
||||
async user_delete(params: { id: number }): Promise<boolean> {
|
||||
return this.send('user_delete', params);
|
||||
}
|
||||
|
||||
async user_exist(params: { id: number }): Promise<boolean> {
|
||||
return this.send('user_exist', params);
|
||||
}
|
||||
|
||||
async user_list(params: { }): Promise<any[]> {
|
||||
return this.send('user_list', params);
|
||||
}
|
||||
|
||||
}
|
||||
101
lib/hero/typescriptgenerator/generator.v
Normal file
101
lib/hero/typescriptgenerator/generator.v
Normal file
@@ -0,0 +1,101 @@
|
||||
module typescriptgenerator
|
||||
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
pub fn generate_typescript_client(spec IntermediateSpec, dest_path string) ! {
|
||||
mut dest := pathlib.get_dir(path: dest_path, create: true)!
|
||||
|
||||
// Generate a file for each schema
|
||||
for name, schema in spec.schemas {
|
||||
mut file_content := generate_interface(schema)
|
||||
mut file_path := pathlib.get_file(path: '${dest.path}/${name}.ts', create: true)!
|
||||
file_path.write(file_content) or { panic(err) }
|
||||
}
|
||||
|
||||
// Generate the main client file
|
||||
mut client_content := generate_client(spec)
|
||||
mut client_file_path := pathlib.get_file(path: '${dest.path}/client.ts', create: true)!
|
||||
client_file_path.write(client_content) or { panic(err) }
|
||||
}
|
||||
|
||||
fn generate_interface(schema IntermediateSchema) string {
|
||||
mut content := 'export interface ${schema.name} {\n'
|
||||
for prop in schema.properties {
|
||||
content += ' ${prop.name}${if prop.required { '' } else { '?' }}: ${ts_type(prop.type_info)};\n'
|
||||
}
|
||||
content += '}\n'
|
||||
return content
|
||||
}
|
||||
|
||||
fn generate_client(spec IntermediateSpec) string {
|
||||
mut methods_str := ''
|
||||
for method in spec.methods {
|
||||
params_str := method.params.map('${it.name}: ${ts_type(it.type_info)}').join(', ')
|
||||
methods_str += '
|
||||
async ${texttools.snake_case(method.name)}(params: { ${params_str} }): Promise<${ts_type(method.result.type_info)}> {
|
||||
return this.send(\'${method.name}\', params);
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
mut imports_str := ''
|
||||
for schema_name in spec.schemas.keys() {
|
||||
imports_str += "import { ${schema_name} } from './${schema_name}';\n"
|
||||
}
|
||||
|
||||
return "
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
${imports_str}
|
||||
|
||||
export class HeroModelsClient {
|
||||
private baseUrl: string;
|
||||
|
||||
constructor(baseUrl: string = '${spec.base_url}') {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
private async send<T>(method: string, params: any): Promise<T> {
|
||||
const response = await fetch(this.baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: method,
|
||||
params: params,
|
||||
id: 1,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${"$"}{response.status}`);
|
||||
}
|
||||
|
||||
const jsonResponse:any = await response.json();
|
||||
if (jsonResponse.error) {
|
||||
throw new Error(`RPC error: ${"$"}{jsonResponse.error.message}`);
|
||||
}
|
||||
|
||||
return jsonResponse.result;
|
||||
}
|
||||
${methods_str}
|
||||
}
|
||||
"
|
||||
}
|
||||
|
||||
|
||||
fn ts_type(v_type string) string {
|
||||
return match v_type {
|
||||
'string' { 'string' }
|
||||
'int', 'integer', 'u32', 'u64', 'i64' { 'number' }
|
||||
'bool', 'boolean' { 'boolean' }
|
||||
'f32', 'f64' { 'number' }
|
||||
'[]string' { 'string[]' }
|
||||
'[]int' { 'number[]' }
|
||||
'array' { 'any[]' }
|
||||
else { v_type }
|
||||
}
|
||||
}
|
||||
36
lib/hero/typescriptgenerator/generator_test.v
Normal file
36
lib/hero/typescriptgenerator/generator_test.v
Normal file
@@ -0,0 +1,36 @@
|
||||
module typescriptgenerator
|
||||
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
|
||||
const openrpc_path = os.dir(@FILE) + '/../../heromodels/openrpc.json'
|
||||
const output_dir = os.dir(@FILE) + '/test_generated_ts_client'
|
||||
|
||||
|
||||
fn test_generate_typescript_client() {
|
||||
spec_text := os.read_file(openrpc_path) or {
|
||||
eprintln('Failed to read openrpc.json: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
openrpc_spec := openrpc.decode(spec_text) or {
|
||||
eprintln('Failed to decode openrpc spec: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
config := IntermediateConfig{
|
||||
base_url: 'http://localhost:8086/api/heromodels'
|
||||
}
|
||||
|
||||
intermediate_spec := from_openrpc(openrpc_spec, config) or {
|
||||
eprintln('Failed to create intermediate spec: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
generate_typescript_client(intermediate_spec, output_dir) or {
|
||||
eprintln('Failed to generate typescript client: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println("TypeScript client generated successfully in ${output_dir}")
|
||||
}
|
||||
186
lib/hero/typescriptgenerator/intermediate_model.v
Normal file
186
lib/hero/typescriptgenerator/intermediate_model.v
Normal file
@@ -0,0 +1,186 @@
|
||||
module typescriptgenerator
|
||||
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
|
||||
// IntermediateSpec is the main object passed to the typescript generator.
|
||||
pub struct IntermediateSpec {
|
||||
pub mut:
|
||||
info openrpc.Info
|
||||
methods []IntermediateMethod
|
||||
schemas map[string]IntermediateSchema
|
||||
base_url string
|
||||
}
|
||||
|
||||
// IntermediateSchema represents a schema in a format that's easy to consume for generation.
|
||||
pub struct IntermediateSchema {
|
||||
pub mut:
|
||||
name string
|
||||
description string
|
||||
properties []IntermediateProperty
|
||||
}
|
||||
|
||||
// IntermediateMethod holds the information for a single method to be displayed.
|
||||
pub struct IntermediateMethod {
|
||||
pub mut:
|
||||
name string
|
||||
summary string
|
||||
description string
|
||||
params []IntermediateParam
|
||||
result IntermediateParam
|
||||
endpoint_url string
|
||||
}
|
||||
|
||||
// IntermediateParam represents a parameter or result in the documentation
|
||||
pub struct IntermediateParam {
|
||||
pub mut:
|
||||
name string
|
||||
description string
|
||||
type_info string
|
||||
required bool
|
||||
}
|
||||
|
||||
// IntermediateProperty represents a property of a schema
|
||||
pub struct IntermediateProperty {
|
||||
pub mut:
|
||||
name string
|
||||
description string
|
||||
type_info string
|
||||
required bool
|
||||
}
|
||||
|
||||
// IntermediateConfig holds configuration for documentation generation
|
||||
pub struct IntermediateConfig {
|
||||
pub mut:
|
||||
base_url string = 'http://localhost:8080'
|
||||
handler_type string = 'heromodels'
|
||||
}
|
||||
|
||||
pub fn from_openrpc(openrpc_spec openrpc.OpenRPC, config IntermediateConfig) !IntermediateSpec {
|
||||
if config.handler_type.trim_space() == '' {
|
||||
return error('handler_type cannot be empty')
|
||||
}
|
||||
|
||||
mut intermediate_spec := IntermediateSpec{
|
||||
info: openrpc_spec.info
|
||||
base_url: config.base_url
|
||||
schemas: process_schemas(openrpc_spec.components.schemas)!
|
||||
}
|
||||
|
||||
// Process all methods
|
||||
for method in openrpc_spec.methods {
|
||||
intermediate_method := process_method(method, config)!
|
||||
intermediate_spec.methods << intermediate_method
|
||||
}
|
||||
|
||||
return intermediate_spec
|
||||
}
|
||||
|
||||
fn process_method(method openrpc.Method, config IntermediateConfig) !IntermediateMethod {
|
||||
// Convert parameters
|
||||
intermediate_params := process_parameters(method.params)!
|
||||
|
||||
// Convert result
|
||||
intermediate_result := process_result(method.result)!
|
||||
|
||||
intermediate_method := IntermediateMethod{
|
||||
name: method.name
|
||||
summary: method.summary
|
||||
description: method.description
|
||||
params: intermediate_params
|
||||
result: intermediate_result
|
||||
endpoint_url: '${config.base_url}/api/${config.handler_type}'
|
||||
}
|
||||
|
||||
return intermediate_method
|
||||
}
|
||||
|
||||
fn process_parameters(params []openrpc.ContentDescriptorRef) ![]IntermediateParam {
|
||||
mut intermediate_params := []IntermediateParam{}
|
||||
|
||||
for param in params {
|
||||
if param is openrpc.ContentDescriptor {
|
||||
type_info := extract_type_from_schema(param.schema)
|
||||
|
||||
intermediate_params << IntermediateParam{
|
||||
name: param.name
|
||||
description: param.description
|
||||
type_info: type_info
|
||||
required: param.required
|
||||
}
|
||||
} else if param is jsonschema.Reference {
|
||||
//TODO: handle reference
|
||||
}
|
||||
}
|
||||
|
||||
return intermediate_params
|
||||
}
|
||||
|
||||
fn process_result(result openrpc.ContentDescriptorRef) !IntermediateParam {
|
||||
mut intermediate_result := IntermediateParam{}
|
||||
|
||||
if result is openrpc.ContentDescriptor {
|
||||
type_info := extract_type_from_schema(result.schema)
|
||||
|
||||
intermediate_result = IntermediateParam{
|
||||
name: result.name
|
||||
description: result.description
|
||||
type_info: type_info
|
||||
required: false // Results are never required
|
||||
}
|
||||
} else if result is jsonschema.Reference {
|
||||
// handle reference
|
||||
ref := result as jsonschema.Reference
|
||||
type_info := ref.ref.all_after_last('/')
|
||||
intermediate_result = IntermediateParam{
|
||||
name: type_info.to_lower()
|
||||
type_info: type_info
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return intermediate_result
|
||||
}
|
||||
|
||||
fn extract_type_from_schema(schema_ref jsonschema.SchemaRef) string {
|
||||
schema := match schema_ref {
|
||||
jsonschema.Schema {
|
||||
schema_ref
|
||||
}
|
||||
jsonschema.Reference {
|
||||
ref := schema_ref as jsonschema.Reference
|
||||
return ref.ref.all_after_last('/')
|
||||
}
|
||||
}
|
||||
|
||||
if schema.typ.len > 0 {
|
||||
return schema.typ
|
||||
}
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
|
||||
fn process_schemas(schemas map[string]jsonschema.SchemaRef) !map[string]IntermediateSchema {
|
||||
mut intermediate_schemas := map[string]IntermediateSchema{}
|
||||
for name, schema_ref in schemas {
|
||||
if schema_ref is jsonschema.Schema {
|
||||
schema := schema_ref as jsonschema.Schema
|
||||
mut properties := []IntermediateProperty{}
|
||||
for prop_name, prop_schema_ref in schema.properties {
|
||||
prop_type := extract_type_from_schema(prop_schema_ref)
|
||||
properties << IntermediateProperty {
|
||||
name: prop_name
|
||||
description: "" // TODO
|
||||
type_info: prop_type
|
||||
required: prop_name in schema.required
|
||||
}
|
||||
}
|
||||
intermediate_schemas[name] = IntermediateSchema {
|
||||
name: name
|
||||
description: schema.description
|
||||
properties: properties
|
||||
}
|
||||
}
|
||||
}
|
||||
return intermediate_schemas
|
||||
}
|
||||
Reference in New Issue
Block a user