moeralib 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +8 -0
- package/lib/naming/index.js +14 -0
- package/lib/naming/naming.js +203 -0
- package/lib/naming/schemas.mjs +184 -0
- package/lib/naming/types.js +2 -0
- package/lib/naming/validate.js +9 -0
- package/lib/naming/validators.js +2065 -0
- package/lib/node/caller.js +267 -0
- package/lib/node/cartes.js +55 -0
- package/lib/node/index.js +13 -0
- package/lib/node/node.js +1405 -0
- package/lib/node/schemas.mjs +4582 -0
- package/lib/node/types.js +3 -0
- package/lib/node/validate.js +9 -0
- package/lib/node/validators.js +60225 -0
- package/lib/schema.js +20 -0
- package/lib/schemas-compile.mjs +42 -0
- package/lib/universal-location.js +164 -0
- package/lib/util.js +42 -0
- package/nodejs-moera-api/nodejs-moera-api +4 -0
- package/nodejs-moera-api/nodejsmoeraapi.py +578 -0
- package/package.json +65 -0
- package/src/naming/index.ts +12 -0
- package/src/naming/naming.ts +234 -0
- package/src/naming/schemas.mjs +194 -0
- package/src/naming/types.ts +39 -0
- package/src/naming/validate.ts +6 -0
- package/src/naming/validators.d.ts +3 -0
- package/src/naming/validators.js +2084 -0
- package/src/node/caller.ts +311 -0
- package/src/node/cartes.ts +51 -0
- package/src/node/index.ts +3 -0
- package/src/node/node.ts +1285 -0
- package/src/node/schemas.mjs +4715 -0
- package/src/node/types.ts +1544 -0
- package/src/node/validate.ts +6 -0
- package/src/node/validators.d.ts +3 -0
- package/src/node/validators.js +60484 -0
- package/src/schema.ts +30 -0
- package/src/schemas-compile.mjs +51 -0
- package/src/universal-location.ts +212 -0
- package/src/util.ts +42 -0
- package/tsconfig.json +112 -0
- package/typings/naming/index.d.ts +2 -0
- package/typings/naming/index.d.ts.map +1 -0
- package/typings/naming/naming.d.ts +31 -0
- package/typings/naming/naming.d.ts.map +1 -0
- package/typings/naming/schemas.d.mts +271 -0
- package/typings/naming/schemas.d.mts.map +1 -0
- package/typings/naming/types.d.ts +35 -0
- package/typings/naming/types.d.ts.map +1 -0
- package/typings/naming/validate.d.ts +3 -0
- package/typings/naming/validate.d.ts.map +1 -0
- package/typings/naming/validators.d.ts +73 -0
- package/typings/naming/validators.d.ts.map +1 -0
- package/typings/node/caller.d.ts +52 -0
- package/typings/node/caller.d.ts.map +1 -0
- package/typings/node/cartes.d.ts +13 -0
- package/typings/node/cartes.d.ts.map +1 -0
- package/typings/node/index.d.ts +4 -0
- package/typings/node/index.d.ts.map +1 -0
- package/typings/node/node.d.ts +176 -0
- package/typings/node/node.d.ts.map +1 -0
- package/typings/node/schemas.d.mts +6205 -0
- package/typings/node/schemas.d.mts.map +1 -0
- package/typings/node/types.d.ts +1340 -0
- package/typings/node/types.d.ts.map +1 -0
- package/typings/node/validate.d.ts +3 -0
- package/typings/node/validate.d.ts.map +1 -0
- package/typings/node/validators.d.ts +920 -0
- package/typings/node/validators.d.ts.map +1 -0
- package/typings/schema.d.ts +18 -0
- package/typings/schema.d.ts.map +1 -0
- package/typings/schemas-compile.d.mts +2 -0
- package/typings/schemas-compile.d.mts.map +1 -0
- package/typings/universal-location.d.ts +25 -0
- package/typings/universal-location.d.ts.map +1 -0
- package/typings/util.d.ts +6 -0
- package/typings/util.d.ts.map +1 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, TextIO
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
from camel_converter import to_pascal
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ind(n: int) -> str:
|
|
13
|
+
return n * 4 * ' '
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def read_api(ifname: str) -> Any:
|
|
17
|
+
with open(ifname, 'r') as ifile:
|
|
18
|
+
return yaml.safe_load(ifile)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def generate_enum(enum: Any, tfile: TextIO) -> None:
|
|
22
|
+
s = f'\nexport type {enum["name"]} = '
|
|
23
|
+
first = True
|
|
24
|
+
for item in enum['values']:
|
|
25
|
+
c = f'"{item["name"]}"'
|
|
26
|
+
if first:
|
|
27
|
+
s += c
|
|
28
|
+
first = False
|
|
29
|
+
else:
|
|
30
|
+
c = ' | ' + c
|
|
31
|
+
if len(s) + len(c) > 120:
|
|
32
|
+
s += '\n'
|
|
33
|
+
tfile.write(s)
|
|
34
|
+
s = ' '
|
|
35
|
+
s += c
|
|
36
|
+
s += ';\n'
|
|
37
|
+
tfile.write(s)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def schema_type(sfile: TextIO, indent: int, a_type: str, struct: bool = False, nullable: bool = False,
|
|
41
|
+
default: Any = None, min: float | None = None, max: float | None = None) -> None:
|
|
42
|
+
sfile.write('{\n')
|
|
43
|
+
if struct:
|
|
44
|
+
if nullable:
|
|
45
|
+
sfile.write(ind(indent + 1) + 'anyOf: [\n')
|
|
46
|
+
sfile.write(ind(indent + 2) + '{\n')
|
|
47
|
+
sfile.write(ind(indent + 3) + f'$ref: "node#/definitions/{a_type}",\n')
|
|
48
|
+
sfile.write(ind(indent + 3) + 'type: "object",\n')
|
|
49
|
+
sfile.write(ind(indent + 3) + 'nullable: true\n')
|
|
50
|
+
sfile.write(ind(indent + 2) + '},\n')
|
|
51
|
+
sfile.write(ind(indent + 2) + '{\n')
|
|
52
|
+
sfile.write(ind(indent + 3) + 'type: "null"\n')
|
|
53
|
+
sfile.write(ind(indent + 2) + '}\n')
|
|
54
|
+
sfile.write(ind(indent + 1) + ']')
|
|
55
|
+
else:
|
|
56
|
+
sfile.write(ind(indent + 1) + f'$ref: "node#/definitions/{a_type}"')
|
|
57
|
+
else:
|
|
58
|
+
sfile.write(ind(indent + 1) + f'type: "{a_type}"')
|
|
59
|
+
if nullable:
|
|
60
|
+
sfile.write(',\n')
|
|
61
|
+
sfile.write(ind(indent + 1) + 'nullable: true')
|
|
62
|
+
if default is not None:
|
|
63
|
+
sfile.write(',\n')
|
|
64
|
+
sfile.write(ind(indent + 1) + f'default: {default}')
|
|
65
|
+
if min is not None:
|
|
66
|
+
sfile.write(',\n')
|
|
67
|
+
sfile.write(ind(indent + 1) + f'minimum: {min}')
|
|
68
|
+
if max is not None:
|
|
69
|
+
sfile.write(',\n')
|
|
70
|
+
sfile.write(ind(indent + 1) + f'maximum: {max}')
|
|
71
|
+
sfile.write('\n')
|
|
72
|
+
sfile.write(ind(indent) + '}')
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def schema_array(sfile: TextIO, indent: int, a_type: str, struct: bool = False, nullable: bool = False,
|
|
76
|
+
default: Any = None, min_items: int | None = None, max_items: int | None = None,
|
|
77
|
+
min: float | None = None, max: float | None = None) -> None:
|
|
78
|
+
sfile.write('{\n')
|
|
79
|
+
sfile.write(ind(indent + 1) + 'type: "array",\n')
|
|
80
|
+
sfile.write(ind(indent + 1) + 'items: ')
|
|
81
|
+
schema_type(sfile, indent + 1, a_type, struct=struct, nullable=False, min=min, max=max)
|
|
82
|
+
if nullable:
|
|
83
|
+
sfile.write(',\n')
|
|
84
|
+
sfile.write(ind(indent + 1) + 'nullable: true')
|
|
85
|
+
if default is not None:
|
|
86
|
+
sfile.write(',\n')
|
|
87
|
+
sfile.write(ind(indent + 1) + f'default: {default}')
|
|
88
|
+
if min_items is not None:
|
|
89
|
+
sfile.write(',\n')
|
|
90
|
+
sfile.write(ind(indent + 1) + f'minItems: {min_items}')
|
|
91
|
+
if max_items is not None:
|
|
92
|
+
sfile.write(',\n')
|
|
93
|
+
sfile.write(ind(indent + 1) + f'maxItems: {max_items}')
|
|
94
|
+
sfile.write('\n')
|
|
95
|
+
sfile.write(ind(indent) + '}')
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def schema_map_string_int(sfile: TextIO, indent: int, nullable: bool = False) -> None:
|
|
99
|
+
sfile.write('{\n')
|
|
100
|
+
sfile.write(ind(indent + 1) + 'type: "object",\n')
|
|
101
|
+
sfile.write(ind(indent + 1) + 'patternProperties: {\n')
|
|
102
|
+
sfile.write(ind(indent + 2) + '"^.*$": ')
|
|
103
|
+
schema_type(sfile, indent + 2, 'integer')
|
|
104
|
+
sfile.write('\n')
|
|
105
|
+
sfile.write(ind(indent + 1) + '}')
|
|
106
|
+
if nullable:
|
|
107
|
+
sfile.write(',\n')
|
|
108
|
+
sfile.write(ind(indent + 1) + 'nullable: true')
|
|
109
|
+
sfile.write('\n')
|
|
110
|
+
sfile.write(ind(indent) + '}')
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def generate_operations(operations: Any, tfile: TextIO, sfile: TextIO) -> None:
|
|
114
|
+
tfile.write('\n')
|
|
115
|
+
tfile.write(f'export interface {operations["name"]} {{\n')
|
|
116
|
+
for field in operations['fields']:
|
|
117
|
+
tfile.write(f' {field["name"]}?: PrincipalValue | null;\n')
|
|
118
|
+
tfile.write('}\n')
|
|
119
|
+
|
|
120
|
+
sfile.write('\n')
|
|
121
|
+
sfile.write(f'{ind(2)}{operations["name"]}: {{\n')
|
|
122
|
+
sfile.write(f'{ind(3)}type: "object",\n')
|
|
123
|
+
sfile.write(f'{ind(3)}properties: {{\n')
|
|
124
|
+
for field in operations['fields']:
|
|
125
|
+
sfile.write(f'{ind(4)}"{field["name"]}": ')
|
|
126
|
+
schema_type(sfile, 4, "string", nullable=True)
|
|
127
|
+
sfile.write(',\n')
|
|
128
|
+
sfile.write(f'{ind(3)}}},\n')
|
|
129
|
+
sfile.write(f'{ind(3)}additionalProperties: false\n')
|
|
130
|
+
sfile.write(f'{ind(2)}}},\n')
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
JS_TYPES = {
|
|
134
|
+
'String': 'string',
|
|
135
|
+
'String[]': 'string[]',
|
|
136
|
+
'int': 'number',
|
|
137
|
+
'float': 'number',
|
|
138
|
+
'boolean': 'boolean',
|
|
139
|
+
'timestamp': 'number',
|
|
140
|
+
'byte[]': 'string',
|
|
141
|
+
'UUID': 'string',
|
|
142
|
+
'String -> int': 'Partial<Record<string, number>>'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def to_js_type(api_type: str) -> str:
|
|
147
|
+
js_type = JS_TYPES.get(api_type)
|
|
148
|
+
if js_type is None:
|
|
149
|
+
print('Unrecognized field type: ' + api_type)
|
|
150
|
+
exit(1)
|
|
151
|
+
return js_type
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
SCHEMA_TYPES = {
|
|
155
|
+
'String': ('string', False),
|
|
156
|
+
'String[]': ('string', True),
|
|
157
|
+
'int': ('integer', False),
|
|
158
|
+
'float': ('number', False),
|
|
159
|
+
'boolean': ('boolean', False),
|
|
160
|
+
'timestamp': ('integer', False),
|
|
161
|
+
'byte[]': ('string', False),
|
|
162
|
+
'UUID': ('string', False),
|
|
163
|
+
'String -> int': schema_map_string_int
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class Interface:
|
|
168
|
+
data: Any
|
|
169
|
+
output_array: bool = False
|
|
170
|
+
|
|
171
|
+
def __init__(self, data: Any) -> None:
|
|
172
|
+
self.data = data
|
|
173
|
+
|
|
174
|
+
def get_name(self) -> str:
|
|
175
|
+
raise NotImplementedError
|
|
176
|
+
|
|
177
|
+
def get_schema_fields(self) -> list[Any]:
|
|
178
|
+
return self.data.get('fields', [])
|
|
179
|
+
|
|
180
|
+
def generate_schema(self, sfile: TextIO) -> None:
|
|
181
|
+
class_name = self.get_name()
|
|
182
|
+
sfile.write(f'\n{ind(2)}{class_name}: {{\n')
|
|
183
|
+
sfile.write(f'{ind(3)}type: "object",\n')
|
|
184
|
+
sfile.write(f'{ind(3)}properties: {{\n')
|
|
185
|
+
required: list[str] = []
|
|
186
|
+
for field in self.get_schema_fields():
|
|
187
|
+
if field.get('type') == 'any':
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
sfile.write(f'{ind(4)}"{field["name"]}": ')
|
|
191
|
+
default = field.get('js-default')
|
|
192
|
+
optional = field.get('optional', False) and default is None
|
|
193
|
+
array = field.get('array', False)
|
|
194
|
+
if not optional:
|
|
195
|
+
required.append(field['name'])
|
|
196
|
+
struct = False
|
|
197
|
+
if 'struct' in field:
|
|
198
|
+
if field['struct'] == 'Body':
|
|
199
|
+
t = 'string'
|
|
200
|
+
else:
|
|
201
|
+
t = field['struct']
|
|
202
|
+
struct = True
|
|
203
|
+
elif 'enum' in field:
|
|
204
|
+
t = 'string'
|
|
205
|
+
else:
|
|
206
|
+
s_type = SCHEMA_TYPES.get(field['type'])
|
|
207
|
+
if callable(s_type):
|
|
208
|
+
t = None
|
|
209
|
+
s_type(sfile, 4, nullable=optional)
|
|
210
|
+
else:
|
|
211
|
+
assert isinstance(s_type, tuple)
|
|
212
|
+
t, array = s_type
|
|
213
|
+
if t is not None:
|
|
214
|
+
if array:
|
|
215
|
+
schema_array(sfile, 4, t, struct=struct, nullable=optional, default=default,
|
|
216
|
+
min_items=field.get('min-items'), max_items=field.get('max-items'),
|
|
217
|
+
min=field.get('min'), max=field.get('max'))
|
|
218
|
+
else:
|
|
219
|
+
schema_type(sfile, 4, t, struct=struct, nullable=optional, default=default,
|
|
220
|
+
min=field.get('min'), max=field.get('max'))
|
|
221
|
+
sfile.write(',\n')
|
|
222
|
+
sfile.write(f'{ind(3)}}},\n')
|
|
223
|
+
if len(required) > 0:
|
|
224
|
+
sfile.write(f'{ind(3)}required: [\n')
|
|
225
|
+
for name in required:
|
|
226
|
+
sfile.write(f'{ind(4)}"{name}",\n')
|
|
227
|
+
sfile.write(f'{ind(3)}],\n')
|
|
228
|
+
sfile.write(f'{ind(3)}additionalProperties: false\n')
|
|
229
|
+
sfile.write(f'{ind(2)}}},\n')
|
|
230
|
+
|
|
231
|
+
if self.output_array:
|
|
232
|
+
sfile.write(f'\n{ind(2)}{class_name}Array: ')
|
|
233
|
+
schema_array(sfile, 2, class_name, struct=True)
|
|
234
|
+
sfile.write(',\n')
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class Structure(Interface):
|
|
238
|
+
generated: bool = False
|
|
239
|
+
depends: list[str]
|
|
240
|
+
uses_body: bool = False
|
|
241
|
+
output: bool = False
|
|
242
|
+
|
|
243
|
+
def __init__(self, data: Any) -> None:
|
|
244
|
+
super().__init__(data)
|
|
245
|
+
self.depends = [field['struct'] for field in data['fields'] if 'struct' in field]
|
|
246
|
+
|
|
247
|
+
def get_name(self) -> str:
|
|
248
|
+
return self.data["name"]
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def generic(self) -> bool:
|
|
252
|
+
return self.uses_body and self.output
|
|
253
|
+
|
|
254
|
+
def generate_class(self, tfile: TextIO, structs: dict[str, Structure]) -> None:
|
|
255
|
+
if self.generic:
|
|
256
|
+
tfile.write(f'\nexport interface {self.data["name"]}Base<B> {{\n')
|
|
257
|
+
else:
|
|
258
|
+
tfile.write(f'\nexport interface {self.data["name"]} {{\n')
|
|
259
|
+
for field in self.data['fields']:
|
|
260
|
+
if field.get('optional', False) and 'js-default' not in field:
|
|
261
|
+
tmpl = ' %s?: %s | null;\n'
|
|
262
|
+
else:
|
|
263
|
+
tmpl = ' %s: %s;\n'
|
|
264
|
+
if 'struct' in field:
|
|
265
|
+
if field['struct'] == 'Body':
|
|
266
|
+
t = 'B' if self.generic else 'string'
|
|
267
|
+
else:
|
|
268
|
+
t = field['struct']
|
|
269
|
+
if self.generic and field['struct'] in structs and structs[field['struct']].generic:
|
|
270
|
+
t += 'Base<B>'
|
|
271
|
+
elif 'enum' in field:
|
|
272
|
+
t = field['enum']
|
|
273
|
+
else:
|
|
274
|
+
if field['type'] == 'any':
|
|
275
|
+
continue
|
|
276
|
+
t = to_js_type(field['type'])
|
|
277
|
+
if field.get('array', False):
|
|
278
|
+
t += '[]'
|
|
279
|
+
tfile.write(tmpl % (field['name'], t))
|
|
280
|
+
tfile.write('}\n')
|
|
281
|
+
if self.generic:
|
|
282
|
+
tfile.write('\nexport type Encoded{name} = {name}Base<string>;\n'.format(name=self.data['name']))
|
|
283
|
+
tfile.write('export type {name} = {name}Base<Body>;\n'.format(name=self.data['name']))
|
|
284
|
+
|
|
285
|
+
def generate(self, tfile: TextIO, sfile: TextIO, structs: dict[str, Structure]) -> None:
|
|
286
|
+
self.generate_class(tfile, structs)
|
|
287
|
+
if self.output:
|
|
288
|
+
self.generate_schema(sfile)
|
|
289
|
+
self.generated = True
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def scan_body_usage(structs: dict[str, Structure]) -> None:
|
|
293
|
+
for struct in structs.values():
|
|
294
|
+
if 'Body' in struct.depends:
|
|
295
|
+
struct.uses_body = True
|
|
296
|
+
|
|
297
|
+
modified = True
|
|
298
|
+
while modified:
|
|
299
|
+
modified = False
|
|
300
|
+
for struct in structs.values():
|
|
301
|
+
if struct.uses_body:
|
|
302
|
+
continue
|
|
303
|
+
for dep in struct.depends:
|
|
304
|
+
if dep in structs and structs[dep].uses_body:
|
|
305
|
+
struct.uses_body = True
|
|
306
|
+
modified = True
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def scan_output_usage(api: Any, structs: dict[str, Structure]) -> None:
|
|
310
|
+
for obj in api['objects']:
|
|
311
|
+
for request in obj.get('requests', []):
|
|
312
|
+
if 'out' not in request:
|
|
313
|
+
continue
|
|
314
|
+
if 'struct' not in request['out']:
|
|
315
|
+
continue
|
|
316
|
+
struct = request['out']['struct']
|
|
317
|
+
if struct not in structs:
|
|
318
|
+
continue
|
|
319
|
+
structs[struct].output = True
|
|
320
|
+
structs[struct].output_array |= request['out'].get('array', False)
|
|
321
|
+
|
|
322
|
+
modified = True
|
|
323
|
+
while modified:
|
|
324
|
+
modified = False
|
|
325
|
+
for struct in structs.values():
|
|
326
|
+
if not struct.output:
|
|
327
|
+
continue
|
|
328
|
+
for dep in struct.depends:
|
|
329
|
+
if dep in structs and not structs[dep].output:
|
|
330
|
+
structs[dep].output = True
|
|
331
|
+
modified = True
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def scan_structures(api: Any) -> dict[str, Structure]:
|
|
335
|
+
structs: dict[str, Structure] = {struct['name']: Structure(struct) for struct in api['structures']}
|
|
336
|
+
scan_body_usage(structs)
|
|
337
|
+
scan_output_usage(api, structs)
|
|
338
|
+
return structs
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def generate_structures(structs: dict[str, Structure], tfile: TextIO, sfile: TextIO) -> None:
|
|
342
|
+
gen = True
|
|
343
|
+
while gen:
|
|
344
|
+
gen = False
|
|
345
|
+
for struct in structs.values():
|
|
346
|
+
if struct.generated:
|
|
347
|
+
continue
|
|
348
|
+
if any(not structs[d].generated for d in struct.depends if d in structs):
|
|
349
|
+
continue
|
|
350
|
+
struct.generate(tfile, sfile, structs)
|
|
351
|
+
gen = True
|
|
352
|
+
loop = [s.data['name'] for s in structs.values() if not s.generated]
|
|
353
|
+
if len(loop) > 0:
|
|
354
|
+
print('Dependency loop in structures: ' + ', '.join(loop))
|
|
355
|
+
exit(1)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def comma_wrap(s: str, indent: int) -> str:
|
|
359
|
+
max_length = 120 - indent * 4
|
|
360
|
+
result = ''
|
|
361
|
+
while True:
|
|
362
|
+
if len(s) <= max_length:
|
|
363
|
+
result += s
|
|
364
|
+
break
|
|
365
|
+
pos = 0
|
|
366
|
+
while True:
|
|
367
|
+
next = s.find(', ', pos + 1)
|
|
368
|
+
if next < 0 or next > max_length:
|
|
369
|
+
break
|
|
370
|
+
pos = next
|
|
371
|
+
result += s[:pos] + ',\n' + ind(indent)
|
|
372
|
+
s = s[pos + 2:]
|
|
373
|
+
return result
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def params_wrap(template: str, substitute: str, indent: int) -> str:
|
|
377
|
+
line = template % substitute
|
|
378
|
+
if len(line) > 120:
|
|
379
|
+
line = template % ('\n' + ind(indent) + comma_wrap(substitute, indent) + '\n' + ind(indent - 1))
|
|
380
|
+
return line
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class AuthType(Enum):
|
|
384
|
+
NONE = 0
|
|
385
|
+
OPTIONAL = 1
|
|
386
|
+
REQUIRED = 2
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def auth_type(auth: str) -> AuthType:
|
|
390
|
+
variants = auth.split(" or ")
|
|
391
|
+
if variants == ['none'] or variants == ['signature']:
|
|
392
|
+
return AuthType.NONE
|
|
393
|
+
if 'none' in variants or 'signature' in variants or 'optional' in variants:
|
|
394
|
+
return AuthType.OPTIONAL
|
|
395
|
+
return AuthType.REQUIRED
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def generate_calls(api: Any, structs: dict[str, Structure], afile: TextIO) -> None:
|
|
399
|
+
for obj in api['objects']:
|
|
400
|
+
for request in obj.get('requests', []):
|
|
401
|
+
if 'function' not in request:
|
|
402
|
+
continue
|
|
403
|
+
|
|
404
|
+
params: list[str] = []
|
|
405
|
+
tail_params: list[str] = []
|
|
406
|
+
url_params: dict[str, str] = {}
|
|
407
|
+
flag_name: str | None = None
|
|
408
|
+
flag_js_name: str | None = None
|
|
409
|
+
flags: list[str] = []
|
|
410
|
+
for param in request.get('params', []) + request.get('query', []):
|
|
411
|
+
if 'name' not in param:
|
|
412
|
+
print('Missing name of parameter of the request "{method} {url}"'
|
|
413
|
+
.format(method=request['type'], url=request['url']))
|
|
414
|
+
exit(1)
|
|
415
|
+
name = param['name']
|
|
416
|
+
js_name = 'remoteNodeName' if name == 'nodeName' else name
|
|
417
|
+
url_params[name] = js_name
|
|
418
|
+
if 'enum' in param:
|
|
419
|
+
js_type = 'API.' + param['enum']
|
|
420
|
+
else:
|
|
421
|
+
js_type = to_js_type(param['type'])
|
|
422
|
+
if 'flags' in param:
|
|
423
|
+
flag_name = name
|
|
424
|
+
flag_js_name = js_name
|
|
425
|
+
flags = [flag['name'] for flag in param['flags']]
|
|
426
|
+
for flag in flags:
|
|
427
|
+
params.append('with{name}: boolean = false'.format(name=flag.capitalize()))
|
|
428
|
+
else:
|
|
429
|
+
if param.get('optional', False):
|
|
430
|
+
tail_params.append(f'{js_name}: {js_type} | null = null')
|
|
431
|
+
else:
|
|
432
|
+
params.append(f'{js_name}: {js_type}')
|
|
433
|
+
body = ''
|
|
434
|
+
if 'in' in request:
|
|
435
|
+
if 'type' in request['in']:
|
|
436
|
+
if request['in']['type'] != 'blob':
|
|
437
|
+
print('Unrecognised type "{type}" of the input body of the request "{method} {url}"'
|
|
438
|
+
.format(type=request['in']['type'], method=request['type'], url=request['url']))
|
|
439
|
+
exit(1)
|
|
440
|
+
body = ', body'
|
|
441
|
+
params.append('body: Buffer')
|
|
442
|
+
else:
|
|
443
|
+
if 'name' not in request['in']:
|
|
444
|
+
print('Missing name of body of the request "{method} {url}"'
|
|
445
|
+
.format(method=request['type'], url=request['url']))
|
|
446
|
+
exit(1)
|
|
447
|
+
name = request['in']['name']
|
|
448
|
+
js_type = 'API.' + request['in']['struct']
|
|
449
|
+
if request['in'].get('array', False):
|
|
450
|
+
js_type += '[]'
|
|
451
|
+
body = f', body: {name}'
|
|
452
|
+
params.append(f'{name}: {js_type}')
|
|
453
|
+
params += tail_params
|
|
454
|
+
|
|
455
|
+
method = request['type']
|
|
456
|
+
location: str = request['url']
|
|
457
|
+
if len(url_params) > 0:
|
|
458
|
+
p = re.compile(r'{(\w+)}')
|
|
459
|
+
for name in p.findall(location):
|
|
460
|
+
if name not in url_params:
|
|
461
|
+
print('Unknown parameter "{param}" referenced in location "{url}"'
|
|
462
|
+
.format(param=name, url=request['url']))
|
|
463
|
+
exit(1)
|
|
464
|
+
location = location.replace(f'{{{name}}}', f'${{{url_params[name]}}}')
|
|
465
|
+
del url_params[name]
|
|
466
|
+
location = f'ut`{location}`'
|
|
467
|
+
else:
|
|
468
|
+
location = f'"{location}"'
|
|
469
|
+
subs = []
|
|
470
|
+
for name, js_name in url_params.items():
|
|
471
|
+
if name == js_name:
|
|
472
|
+
subs.append(name)
|
|
473
|
+
else:
|
|
474
|
+
subs.append(f'{name}: {js_name}')
|
|
475
|
+
|
|
476
|
+
result = 'API.Result'
|
|
477
|
+
result_schema = 'Result'
|
|
478
|
+
result_body = False
|
|
479
|
+
if 'out' in request:
|
|
480
|
+
if 'type' in request['out']:
|
|
481
|
+
if request['out']['type'] != 'blob':
|
|
482
|
+
print('Unrecognised type "{type}" of the output body of the request "{method} {url}"'
|
|
483
|
+
.format(type=request['out']['type'], method=request['type'], url=request['url']))
|
|
484
|
+
exit(1)
|
|
485
|
+
result = 'Blob'
|
|
486
|
+
result_schema = 'blob'
|
|
487
|
+
else:
|
|
488
|
+
struct = request['out']['struct']
|
|
489
|
+
result = 'API.' + struct
|
|
490
|
+
result_schema = struct
|
|
491
|
+
if struct in structs and structs[struct].uses_body:
|
|
492
|
+
result_body = True
|
|
493
|
+
if request['out'].get('array', False):
|
|
494
|
+
result += '[]'
|
|
495
|
+
result_schema += 'Array'
|
|
496
|
+
|
|
497
|
+
name = request['function']
|
|
498
|
+
afile.write(params_wrap(f'\n{ind(1)}async {name}(%s): Promise<{result}> {{\n', ', '.join(params), 2))
|
|
499
|
+
if flag_name is not None:
|
|
500
|
+
items = ', '.join('"%s": with%s' % (flag, flag.capitalize()) for flag in flags)
|
|
501
|
+
afile.write(f'{ind(2)}const {flag_js_name} = commaSeparatedFlags({{{items}}});\n')
|
|
502
|
+
afile.write(f'{ind(2)}const location = {location};\n')
|
|
503
|
+
query_params = ''
|
|
504
|
+
if len(subs) > 0:
|
|
505
|
+
afile.write(f'{ind(2)}const params = {{{", ".join(subs)}}};\n')
|
|
506
|
+
query_params = ', params'
|
|
507
|
+
afile.write(f'{ind(2)}return await this.call("{name}", location, {{\n')
|
|
508
|
+
decode_bodies = ''
|
|
509
|
+
if result_body:
|
|
510
|
+
decode_bodies = ', bodies: true'
|
|
511
|
+
call_params = f'{ind(3)}method: "{method}"{query_params}{body}, schema: "{result_schema}"{decode_bodies}\n'
|
|
512
|
+
afile.write(comma_wrap(call_params, 2))
|
|
513
|
+
afile.write(f'{ind(2)}}}) as {result};\n')
|
|
514
|
+
afile.write(f'{ind(1)}}}\n')
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
PREAMBLE_TYPES = '''// This file is generated
|
|
518
|
+
|
|
519
|
+
export type PrincipalValue = "none" | "private" | "admin" | "owner" | "secret" | "senior" | "enigma" | "major"
|
|
520
|
+
| "signed" | "subscribed" | "public" | "unset" | string;
|
|
521
|
+
'''
|
|
522
|
+
|
|
523
|
+
PREAMBLE_SCHEMAS = '''// This file is generated for schema compiler only, do not use directly
|
|
524
|
+
|
|
525
|
+
export const NODE_API_SCHEMAS = {
|
|
526
|
+
$id: "node",
|
|
527
|
+
definitions: {
|
|
528
|
+
'''
|
|
529
|
+
|
|
530
|
+
PREAMBLE_CALLS = '''// This file is generated
|
|
531
|
+
|
|
532
|
+
import { Caller } from "./caller";
|
|
533
|
+
import * as API from "./types";
|
|
534
|
+
import { commaSeparatedFlags, ut } from "../util";
|
|
535
|
+
|
|
536
|
+
export class MoeraNode extends Caller {
|
|
537
|
+
|
|
538
|
+
constructor(nodeUrl: string | null = null) {
|
|
539
|
+
super();
|
|
540
|
+
if (nodeUrl != null) {
|
|
541
|
+
this.nodeUrl(nodeUrl);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
'''
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def generate_types(api: Any, outdir: str) -> None:
|
|
548
|
+
structs = scan_structures(api)
|
|
549
|
+
|
|
550
|
+
with open(outdir + '/node/types.ts', 'w+') as tfile:
|
|
551
|
+
with open(outdir + '/node/schemas.mjs', 'w+') as sfile:
|
|
552
|
+
tfile.write(PREAMBLE_TYPES)
|
|
553
|
+
sfile.write(PREAMBLE_SCHEMAS)
|
|
554
|
+
for enum in api['enums']:
|
|
555
|
+
generate_enum(enum, tfile)
|
|
556
|
+
for operations in api['operations']:
|
|
557
|
+
generate_operations(operations, tfile, sfile)
|
|
558
|
+
generate_structures(structs, tfile, sfile)
|
|
559
|
+
sfile.write('\n }\n')
|
|
560
|
+
sfile.write('}\n')
|
|
561
|
+
|
|
562
|
+
with open(outdir + '/node/node.ts', 'w+') as afile:
|
|
563
|
+
afile.write(PREAMBLE_CALLS)
|
|
564
|
+
generate_calls(api, structs, afile)
|
|
565
|
+
afile.write('\n}\n')
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def generate_code(outdir: str) -> None:
|
|
569
|
+
node_api = read_api(sys.argv[1])
|
|
570
|
+
generate_types(node_api, outdir)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
if len(sys.argv) < 2 or sys.argv[1] == '':
|
|
574
|
+
print("Usage: nodejs-moera-api <node_api.yml file path> <output directory>")
|
|
575
|
+
exit(1)
|
|
576
|
+
|
|
577
|
+
outdir = sys.argv[2] if len(sys.argv) >= 3 else '.'
|
|
578
|
+
generate_code(outdir)
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "moeralib",
|
|
3
|
+
"version": "0.15.0",
|
|
4
|
+
"description": "Modern Telegram Bot Framework",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/MoeraOrg/typescript-moeralib.git"
|
|
8
|
+
},
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/MoeraOrg/moera-issues/issues"
|
|
11
|
+
},
|
|
12
|
+
"author": "Shmuel Leib Melamud <balu@moera.org>",
|
|
13
|
+
"homepage": "https://moera.org",
|
|
14
|
+
"license": "Apache-2.0",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"clean": "rm -r lib/* typings/*"
|
|
18
|
+
},
|
|
19
|
+
"type": "commonjs",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.10.0"
|
|
22
|
+
},
|
|
23
|
+
"types": "./typings/index.d.ts",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@types/lodash.clonedeep": "^4.5.9",
|
|
26
|
+
"ajv": "^8.16.0",
|
|
27
|
+
"ajv-formats": "^3.0.1",
|
|
28
|
+
"change-case": "^5.4.4",
|
|
29
|
+
"json-rpc-2.0": "^1.7.0",
|
|
30
|
+
"lodash.clonedeep": "^4.5.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.14.2",
|
|
34
|
+
"typescript": "^5.4.5"
|
|
35
|
+
},
|
|
36
|
+
"exports": {
|
|
37
|
+
"./naming": {
|
|
38
|
+
"types": "./typings/naming/index.d.ts",
|
|
39
|
+
"default": "./lib/naming/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./naming/types": {
|
|
42
|
+
"types": "./typings/naming/types/index.d.ts",
|
|
43
|
+
"default": "./lib/naming/types/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./node": {
|
|
46
|
+
"types": "./typings/node/index.d.ts",
|
|
47
|
+
"default": "./lib/node/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./node/types": {
|
|
50
|
+
"types": "./typings/node/types/index.d.ts",
|
|
51
|
+
"default": "./lib/node/types/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./universal-location": {
|
|
54
|
+
"types": "./typings/universal-location.d.ts",
|
|
55
|
+
"default": "./lib/universal-location.js"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"keywords": [
|
|
59
|
+
"moera",
|
|
60
|
+
"social",
|
|
61
|
+
"network",
|
|
62
|
+
"api",
|
|
63
|
+
"decentralized"
|
|
64
|
+
]
|
|
65
|
+
}
|