@defold-typescript/library-types 0.22.0 → 0.24.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/api-doc/boom.json +3870 -0
- package/api-doc/{bridge.bridge.json → bridge.json} +754 -844
- package/api-doc/bzAnim.json +338 -0
- package/api-doc/decore.json +23 -23
- package/api-doc/defcon.json +150 -0
- package/api-doc/{defmath.defmath.json → defmath.json} +6 -46
- package/api-doc/defsave.json +518 -0
- package/api-doc/deftest.json +741 -0
- package/api-doc/{dicebag.dicebag.json → dicebag.json} +19 -1
- package/api-doc/druid.json +249 -1846
- package/api-doc/event.json +1074 -0
- package/api-doc/gooey.json +2050 -0
- package/api-doc/immutable.json +65 -0
- package/api-doc/in.accelerometer.json +75 -1
- package/api-doc/in.button.json +17 -10
- package/api-doc/in.cursor.json +26 -0
- package/api-doc/in.gesture.json +401 -6
- package/api-doc/in.keyboard.json +2 -0
- package/api-doc/in.mapper.json +12 -8
- package/api-doc/in.onscreen.json +61 -5
- package/api-doc/in.state.json +61 -13
- package/api-doc/in.textbox.json +23 -12
- package/api-doc/in.triggers.json +336 -0
- package/api-doc/lang.json +528 -0
- package/api-doc/{event.event.json → log.json} +96 -101
- package/api-doc/metrics.fps.json +61 -2
- package/api-doc/metrics.mem.json +61 -2
- package/api-doc/monarch.monarch.json +249 -7
- package/api-doc/monarch.transitions.easings.json +19 -26
- package/api-doc/monarch.transitions.gui.json +782 -79
- package/api-doc/nakama.engine.defold.json +31 -18
- package/api-doc/{nakama.nakama.json → nakama.json} +4752 -2554
- package/api-doc/nakama.util.log.json +32 -4
- package/api-doc/narrator.json +647 -0
- package/api-doc/openapi/nakama.nakama.json +7680 -0
- package/api-doc/orthographic.camera.json +96 -98
- package/api-doc/orthographic.json +602 -0
- package/api-doc/panthera.json +1183 -0
- package/api-doc/{persist.persist.json → persist.json} +26 -1
- package/api-doc/{platypus.platypus.json → platypus.json} +68 -14
- package/api-doc/proto.json +995 -0
- package/api-doc/{rendy.rendy.json → rendy.json} +46 -99
- package/api-doc/richtext.color.json +77 -0
- package/api-doc/richtext.richtext.json +375 -5
- package/api-doc/saver.saver.json +714 -263
- package/api-doc/saver.storage.json +283 -52
- package/api-doc/squid.json +846 -0
- package/api-doc/{starly.starly.json → starly.json} +1 -1
- package/api-doc/tweener.json +277 -0
- package/api-doc/{yagames.yagames.json → yagames.json} +784 -189
- package/api-doc/{zzfx.api.json → zzfx.json} +9 -9
- package/authored-targets.json +403 -0
- package/generated/{boom.boom.d.ts → boom.d.ts} +14 -0
- package/generated/bridge.d.ts +468 -0
- package/generated/{bzAnim.bzLibrary.d.ts → bzAnim.d.ts} +6 -0
- package/generated/decore.d.ts +37 -37
- package/generated/defcon.d.ts +43 -0
- package/generated/{defmath.defmath.d.ts → defmath.d.ts} +3 -2
- package/generated/defsave.d.ts +144 -0
- package/generated/{dicebag.dicebag.d.ts → dicebag.d.ts} +25 -0
- package/generated/druid.d.ts +198 -502
- package/generated/event.d.ts +318 -0
- package/generated/{gooey.gooey.d.ts → gooey.d.ts} +61 -6
- package/generated/immutable.d.ts +13 -0
- package/generated/in.onscreen.d.ts +5 -0
- package/generated/in.textbox.d.ts +4 -0
- package/generated/lang.d.ts +101 -0
- package/generated/log.d.ts +36 -0
- package/generated/metrics.fps.d.ts +19 -1
- package/generated/metrics.mem.d.ts +19 -1
- package/generated/monarch.monarch.d.ts +44 -1
- package/generated/monarch.transitions.easings.d.ts +0 -1
- package/generated/monarch.transitions.gui.d.ts +96 -12
- package/generated/nakama.d.ts +1158 -0
- package/generated/nakama.engine.defold.d.ts +3 -1
- package/generated/nakama.util.log.d.ts +6 -0
- package/generated/narrator.d.ts +121 -0
- package/generated/openapi/nakama.nakama.d.ts +884 -0
- package/generated/orthographic.camera.d.ts +24 -38
- package/generated/orthographic.d.ts +155 -0
- package/generated/panthera.d.ts +170 -0
- package/generated/{persist.persist.d.ts → persist.d.ts} +3 -0
- package/generated/{platypus.platypus.d.ts → platypus.d.ts} +9 -0
- package/generated/proto.d.ts +146 -0
- package/generated/{rendy.rendy.d.ts → rendy.d.ts} +33 -80
- package/generated/richtext.color.d.ts +15 -0
- package/generated/saver.saver.d.ts +289 -42
- package/generated/saver.storage.d.ts +79 -14
- package/generated/squid.d.ts +127 -0
- package/generated/tweener.d.ts +42 -0
- package/generated/{yagames.yagames.d.ts → yagames.d.ts} +151 -29
- package/generated/{zzfx.api.d.ts → zzfx.d.ts} +7 -0
- package/library-classification.json +0 -227
- package/library-targets.json +1 -284
- package/luals-targets.json +168 -1
- package/openapi-targets.json +17 -0
- package/package.json +9 -88
- package/script-api-targets.json +15 -0
- package/scripts/__snapshots__/parse-luals.test.ts.snap +504 -85
- package/scripts/apply-luals-overrides.ts +95 -0
- package/scripts/authored-parity.ts +718 -0
- package/scripts/emit-library-dts.ts +177 -19
- package/scripts/extract-api-doc.ts +189 -38
- package/scripts/fidelity-floor.ts +178 -0
- package/scripts/import-upstream-docs.ts +125 -0
- package/scripts/lower-api-doc.ts +69 -19
- package/scripts/luals-fidelity.ts +22 -10
- package/scripts/map-luals-types.ts +86 -22
- package/scripts/parse-lua-surface.ts +187 -0
- package/scripts/parse-luals.ts +473 -22
- package/scripts/parse-markdown-api.ts +247 -0
- package/scripts/parse-openapi-api.ts +313 -0
- package/scripts/sync-authored-types.ts +249 -0
- package/scripts/sync-library-types.ts +99 -3
- package/scripts/sync-luals-types.ts +29 -2
- package/scripts/sync-markdown-types.ts +761 -0
- package/scripts/sync-openapi-types.ts +308 -0
- package/scripts/sync-script-api-types.ts +367 -0
- package/api-doc/boom.boom.json +0 -26
- package/api-doc/bzAnim.bzLibrary.json +0 -156
- package/api-doc/defcon.console.json +0 -109
- package/api-doc/defsave.defsave.json +0 -175
- package/api-doc/deftest.deftest.json +0 -71
- package/api-doc/gooey.gooey.json +0 -963
- package/api-doc/immutable.immutable.json +0 -63
- package/api-doc/lang.lang.json +0 -411
- package/api-doc/log.log.json +0 -50
- package/api-doc/narrator.narrator.json +0 -150
- package/api-doc/proto.proto.json +0 -355
- package/api-doc/squid.squid.json +0 -660
- package/api-doc/tweener.tweener.json +0 -419
- package/generated/bridge.bridge.d.ts +0 -533
- package/generated/defcon.console.d.ts +0 -24
- package/generated/defsave.defsave.d.ts +0 -31
- package/generated/event.event.d.ts +0 -54
- package/generated/immutable.immutable.d.ts +0 -13
- package/generated/lang.lang.d.ts +0 -33
- package/generated/log.log.d.ts +0 -40
- package/generated/nakama.nakama.d.ts +0 -594
- package/generated/narrator.narrator.d.ts +0 -66
- package/generated/proto.proto.d.ts +0 -36
- package/generated/squid.squid.d.ts +0 -106
- package/generated/tweener.tweener.d.ts +0 -151
- /package/generated/{deftest.deftest.d.ts → deftest.d.ts} +0 -0
- /package/generated/{starly.starly.d.ts → starly.d.ts} +0 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The markdown ingestion front-end's parser: a third `library-types` corpus
|
|
3
|
+
* source beside the LuaLS annotations (`parse-luals.ts`) and the typed
|
|
4
|
+
* `.script_api` (`scriptApiToFixtureJson`). It reads a library's README/`.md`
|
|
5
|
+
* prose API — the only machine-readable type source Bucket-C libraries ship —
|
|
6
|
+
* and produces the same ref-doc `doc` shape those two front-ends feed the shared
|
|
7
|
+
* emitter (`generateModuleDeclaration`), so the markdown path reuses the exact
|
|
8
|
+
* emit + fidelity machinery.
|
|
9
|
+
*
|
|
10
|
+
* Scope is a **flat signature surface**, not deep prose (a PRD non-goal): the
|
|
11
|
+
* parser lifts one element per `##`- or `###`-level `<receiver>.<fn>(<args>)` API
|
|
12
|
+
* header and its `**PARAMETERS**` / `**RETURN**` bullet lists. Both levels are
|
|
13
|
+
* accepted because the corpus is split on the convention (defold-orthographic and
|
|
14
|
+
* defold-input write `###`; monarch's `README_API.md` writes `##`); h1 and h4 stay
|
|
15
|
+
* outside the range, since at those levels a dotted-call-shaped line is document
|
|
16
|
+
* structure rather than a signature. At either level the receiver may be preceded
|
|
17
|
+
* by a literal `function ` declaration keyword (rendy writes 9 of its 11 headings
|
|
18
|
+
* that way); no other prefix is accepted, so prose such as `### see mod.fn()` is
|
|
19
|
+
* still not a signature. Header-only message sections (`<verb>` with
|
|
20
|
+
* no dotted receiver or parens) and nested option-table bullets are ignored. A
|
|
21
|
+
* signature row that names a parameter but gives it no `(type)` loud-fails rather
|
|
22
|
+
* than silently emitting an untyped `any`.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** A single ref-doc parameter or return slot. `is_optional` mirrors the
|
|
26
|
+
* consumer contract's string flag (`"True"`); it is present only when set. */
|
|
27
|
+
export interface MarkdownParam {
|
|
28
|
+
name: string;
|
|
29
|
+
doc: string;
|
|
30
|
+
types: string[];
|
|
31
|
+
is_optional?: "True";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface MarkdownElement {
|
|
35
|
+
type: "FUNCTION";
|
|
36
|
+
name: string;
|
|
37
|
+
description: string;
|
|
38
|
+
parameters: MarkdownParam[];
|
|
39
|
+
returnvalues: MarkdownParam[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The ref-doc `doc` shape `generateModuleDeclaration` consumes. `info.namespace`
|
|
43
|
+
* carries the README's own module prefix; the front-end retargets it to the
|
|
44
|
+
* pinned namespace before emitting. */
|
|
45
|
+
export interface MarkdownDoc {
|
|
46
|
+
info: { namespace: string; brief: string; description: string };
|
|
47
|
+
elements: MarkdownElement[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Only the literal `function` keyword is accepted before the receiver — a general
|
|
51
|
+
// `\w+\s+` prefix would make prose like `### see rendy.set(...)` read as a signature.
|
|
52
|
+
const HEADER = /^#{2,3}\s+(?:function\s+)?([A-Za-z_][\w]*)\.([A-Za-z_][\w]*)\((.*)\)\s*$/;
|
|
53
|
+
const PARAM_MARKER = /^\*\*PARAM(?:ETER|ETERS)?\*\*\s*$/;
|
|
54
|
+
const RETURN_MARKER = /^\*\*RETURNS?\*\*\s*$/;
|
|
55
|
+
// A bullet with a backticked name and a required `(type)` group. A named bullet
|
|
56
|
+
// missing the `(type)` group is an unresolvable row (see `parseSlot`).
|
|
57
|
+
const TYPED_BULLET = /^\*\s+`([^`]+)`\s*\(([^)]*)\)\s*-?\s*(.*)$/;
|
|
58
|
+
const NAMED_BULLET = /^\*\s+`([^`]+)`/;
|
|
59
|
+
|
|
60
|
+
/** Bracketed header arguments are optional; collect their bare names. A bracket
|
|
61
|
+
* may be escaped (`duration \[, scaler]` — a README authoring artifact so the
|
|
62
|
+
* upstream renderer does not read `[, scaler]` as a link), may span a comma
|
|
63
|
+
* (`data [, overwrite]`), and may cover several arguments at once (`[b, c]`),
|
|
64
|
+
* in which case every argument inside it is optional. Unlike `splitTypes`, a
|
|
65
|
+
* comma at any depth ends the current argument: in an argument list a comma
|
|
66
|
+
* always separates arguments, whether or not a bracket group spans it. */
|
|
67
|
+
function bracketedArgs(argList: string): Set<string> {
|
|
68
|
+
const optional = new Set<string>();
|
|
69
|
+
let depth = 0;
|
|
70
|
+
let current = "";
|
|
71
|
+
let bracketed = false;
|
|
72
|
+
|
|
73
|
+
const flush = () => {
|
|
74
|
+
const arg = current.trim();
|
|
75
|
+
if (arg.length > 0 && bracketed) optional.add(arg);
|
|
76
|
+
current = "";
|
|
77
|
+
bracketed = false;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
for (const ch of argList.replace(/\\\[/g, "[").replace(/\\\]/g, "]")) {
|
|
81
|
+
if (ch === "[") {
|
|
82
|
+
depth++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (ch === "]" && depth > 0) {
|
|
86
|
+
depth--;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (ch === ",") {
|
|
90
|
+
flush();
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (depth > 0 && ch.trim().length > 0) bracketed = true;
|
|
94
|
+
current += ch;
|
|
95
|
+
}
|
|
96
|
+
flush();
|
|
97
|
+
return optional;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Split a documented `(type)` group into single tokens. Both `|` and `,` are
|
|
101
|
+
* union separators — a README is as likely to write `a|b|nil` as `a, b, nil` —
|
|
102
|
+
* but only at depth 0, so a comma inside a token's own group (`table[k, v]`,
|
|
103
|
+
* `function(self, dt`) stays part of that token. `<`/`>` are deliberately not
|
|
104
|
+
* tracked: no group in the corpus uses them, and they need the `=>`
|
|
105
|
+
* disambiguation the comparator-side splitter carries. */
|
|
106
|
+
function splitTypes(group: string): string[] {
|
|
107
|
+
const tokens: string[] = [];
|
|
108
|
+
let depth = 0;
|
|
109
|
+
let current = "";
|
|
110
|
+
for (const ch of group) {
|
|
111
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
112
|
+
else if ((ch === ")" || ch === "]" || ch === "}") && depth > 0) depth--;
|
|
113
|
+
if ((ch === "|" || ch === ",") && depth === 0) {
|
|
114
|
+
tokens.push(current);
|
|
115
|
+
current = "";
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
current += ch;
|
|
119
|
+
}
|
|
120
|
+
tokens.push(current);
|
|
121
|
+
return tokens.map((token) => token.trim()).filter((token) => token.length > 0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Parse one `* `name` (type) doc` bullet into a slot, splitting a `a|b|nil` or
|
|
125
|
+
* `a, b, nil` union into single tokens. Throws naming `fnName` when the bullet
|
|
126
|
+
* names a parameter but carries no `(type)`. */
|
|
127
|
+
function parseSlot(
|
|
128
|
+
label: string,
|
|
129
|
+
fnName: string,
|
|
130
|
+
line: string,
|
|
131
|
+
optionalNames: Set<string>,
|
|
132
|
+
): MarkdownParam {
|
|
133
|
+
const typed = TYPED_BULLET.exec(line);
|
|
134
|
+
if (typed === null) {
|
|
135
|
+
const named = NAMED_BULLET.exec(line);
|
|
136
|
+
const name = named?.[1] ?? line.trim();
|
|
137
|
+
throw new Error(
|
|
138
|
+
`parse-markdown-api: ${label}: ${fnName} row for \`${name}\` has no (type) — cannot resolve to a typed param (row: ${line.trim()})`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const name = typed[1] as string;
|
|
142
|
+
const types = splitTypes(typed[2] as string);
|
|
143
|
+
const slot: MarkdownParam = { name, doc: (typed[3] as string).trim(), types };
|
|
144
|
+
if (optionalNames.has(name)) slot.is_optional = "True";
|
|
145
|
+
return slot;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Split the README into `##`/`###` `<receiver>.<fn>(...)` sections, ignoring
|
|
149
|
+
* headers that are not dotted API signatures. */
|
|
150
|
+
function sections(lines: string[]): { header: RegExpExecArray; body: string[] }[] {
|
|
151
|
+
const starts: number[] = [];
|
|
152
|
+
lines.forEach((line, index) => {
|
|
153
|
+
if (HEADER.test(line)) starts.push(index);
|
|
154
|
+
});
|
|
155
|
+
return starts.map((start, i) => {
|
|
156
|
+
const end = i + 1 < starts.length ? (starts[i + 1] as number) : lines.length;
|
|
157
|
+
return {
|
|
158
|
+
header: HEADER.exec(lines[start] as string) as RegExpExecArray,
|
|
159
|
+
body: lines.slice(start + 1, end),
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function parseSection(
|
|
165
|
+
label: string,
|
|
166
|
+
fnName: string,
|
|
167
|
+
body: string[],
|
|
168
|
+
optionalNames: Set<string>,
|
|
169
|
+
): {
|
|
170
|
+
description: string;
|
|
171
|
+
parameters: MarkdownParam[];
|
|
172
|
+
returnvalues: MarkdownParam[];
|
|
173
|
+
} {
|
|
174
|
+
const descriptionLines: string[] = [];
|
|
175
|
+
const parameters: MarkdownParam[] = [];
|
|
176
|
+
const returnvalues: MarkdownParam[] = [];
|
|
177
|
+
let mode: "none" | "params" | "returns" = "none";
|
|
178
|
+
let sawMarker = false;
|
|
179
|
+
|
|
180
|
+
for (const line of body) {
|
|
181
|
+
if (PARAM_MARKER.test(line)) {
|
|
182
|
+
mode = "params";
|
|
183
|
+
sawMarker = true;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (RETURN_MARKER.test(line)) {
|
|
187
|
+
mode = "returns";
|
|
188
|
+
sawMarker = true;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const isBullet = line.trimStart().startsWith("* ");
|
|
192
|
+
if (isBullet && mode === "params") {
|
|
193
|
+
parameters.push(parseSlot(label, fnName, line, optionalNames));
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (isBullet && mode === "returns") {
|
|
197
|
+
returnvalues.push(parseSlot(label, fnName, line, optionalNames));
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
// Any non-bullet line closes an open list, so a blank line before an
|
|
201
|
+
// option-table's `Acceptable values:` bullets stops them being captured.
|
|
202
|
+
mode = "none";
|
|
203
|
+
if (!sawMarker && line.trim().length > 0 && !line.startsWith("---")) {
|
|
204
|
+
descriptionLines.push(line.trim());
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { description: descriptionLines.join(" "), parameters, returnvalues };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* `label` names the offending module in every loud-fail message. A library whose
|
|
213
|
+
* `.md` is usage/tutorial prose (defold-input ships six such modules) yields no
|
|
214
|
+
* signature section at all; emitting that as an empty namespace would silently
|
|
215
|
+
* publish a module with no members, so it throws instead.
|
|
216
|
+
*/
|
|
217
|
+
export function parseMarkdownApi(text: string, label = "markdown document"): MarkdownDoc {
|
|
218
|
+
const elements: MarkdownElement[] = [];
|
|
219
|
+
const prefixes = new Set<string>();
|
|
220
|
+
|
|
221
|
+
for (const { header, body } of sections(text.split("\n"))) {
|
|
222
|
+
const prefix = header[1] as string;
|
|
223
|
+
const fn = header[2] as string;
|
|
224
|
+
const name = `${prefix}.${fn}`;
|
|
225
|
+
prefixes.add(prefix);
|
|
226
|
+
const { description, parameters, returnvalues } = parseSection(
|
|
227
|
+
label,
|
|
228
|
+
name,
|
|
229
|
+
body,
|
|
230
|
+
bracketedArgs(header[3] as string),
|
|
231
|
+
);
|
|
232
|
+
elements.push({ type: "FUNCTION", name, description, parameters, returnvalues });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (prefixes.size > 1) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`parse-markdown-api: non-uniform module prefix across headers: ${[...prefixes].sort().join(", ")}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (elements.length === 0) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`parse-markdown-api: ${label} has no \`##\`/\`###\` \`<receiver>.<fn>(...)\` API signature section — refusing to emit an empty namespace`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
const namespace = [...prefixes][0] ?? "";
|
|
246
|
+
return { info: { namespace, brief: "", description: "" }, elements };
|
|
247
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OpenAPI/proto ingestion front-end's parser: a fifth `library-types` corpus
|
|
3
|
+
* source beside the LuaLS annotations (`parse-luals.ts`), the typed `.script_api`
|
|
4
|
+
* (`scriptApiToFixtureJson`), the README markdown (`parse-markdown-api.ts`), and
|
|
5
|
+
* the authored `.d.ts` lane. It reads a structured REST swagger (JSON) plus the
|
|
6
|
+
* realtime `.proto` message list — the machine-readable source a codegen'd Lua
|
|
7
|
+
* client (nakama-defold) is generated from — and produces the same ref-doc `doc`
|
|
8
|
+
* shape those front-ends feed the shared emitter (`generateModuleDeclaration`), so
|
|
9
|
+
* the OpenAPI path reuses the exact emit + fidelity machinery.
|
|
10
|
+
*
|
|
11
|
+
* The surface mirrors the client codegen's naming, not the raw wire schema:
|
|
12
|
+
*
|
|
13
|
+
* - a swagger `paths` operation (`operationId` `Nakama_AuthenticateCustom`) becomes
|
|
14
|
+
* a client RPC function `authenticate_custom` (service prefix stripped, snake);
|
|
15
|
+
* - a swagger `definitions` object (`apiAccountCustom`) becomes a constructor
|
|
16
|
+
* `create_api_account_custom`;
|
|
17
|
+
* - a realtime proto `message ChannelMessageSend` becomes a realtime message
|
|
18
|
+
* constructor `create_channel_message_send_message`.
|
|
19
|
+
*
|
|
20
|
+
* Scope is a **flat signature surface**, not deep schema modelling (a PRD
|
|
21
|
+
* non-goal): swagger scalar types and `$ref`/object/array types collapse to the
|
|
22
|
+
* emitter's token vocabulary (`string`/`number`/`boolean`/`table`), and proto
|
|
23
|
+
* scalars/messages likewise. Every parameter and return slot carries at least one
|
|
24
|
+
* resolvable token — a slot is never left type-empty (the "no silent swallow"
|
|
25
|
+
* contract the fidelity gate depends on).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** A single ref-doc parameter or return slot. `is_optional` mirrors the consumer
|
|
29
|
+
* contract's string flag (`"True"`); it is present only when set. */
|
|
30
|
+
export interface OpenApiParam {
|
|
31
|
+
name: string;
|
|
32
|
+
doc: string;
|
|
33
|
+
types: string[];
|
|
34
|
+
is_optional?: "True";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface OpenApiElement {
|
|
38
|
+
type: "FUNCTION";
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
parameters: OpenApiParam[];
|
|
42
|
+
returnvalues: OpenApiParam[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The ref-doc `doc` shape `generateModuleDeclaration` consumes. `info.namespace`
|
|
46
|
+
* is empty here — the swagger/proto source names no publish alias — and the
|
|
47
|
+
* front-end retargets the bare element names onto the pinned namespace. */
|
|
48
|
+
export interface OpenApiDoc {
|
|
49
|
+
info: { namespace: string; brief: string; description: string };
|
|
50
|
+
elements: OpenApiElement[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** camelCase / PascalCase -> snake_case, splitting acronym runs on the trailing
|
|
54
|
+
* capitalised word (`HTTPStatus` -> `http_status`). */
|
|
55
|
+
function snake(name: string): string {
|
|
56
|
+
return name
|
|
57
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
58
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
|
|
59
|
+
.toLowerCase();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Map a swagger schema type keyword to a resolvable emitter token. Object,
|
|
63
|
+
* array, and `$ref` shapes all collapse to `table` (a Lua table), the faithful
|
|
64
|
+
* shape a Lua client marshals them as. */
|
|
65
|
+
function swaggerType(type: string | undefined, hasRef: boolean): string {
|
|
66
|
+
if (hasRef) return "table";
|
|
67
|
+
switch (type) {
|
|
68
|
+
case "integer":
|
|
69
|
+
case "number":
|
|
70
|
+
return "number";
|
|
71
|
+
case "boolean":
|
|
72
|
+
return "boolean";
|
|
73
|
+
case "string":
|
|
74
|
+
return "string";
|
|
75
|
+
default:
|
|
76
|
+
// `object`, `array`, and anything unrecognised marshal as a table.
|
|
77
|
+
return "table";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface SwaggerSchema {
|
|
82
|
+
type?: string;
|
|
83
|
+
$ref?: string;
|
|
84
|
+
items?: SwaggerSchema;
|
|
85
|
+
properties?: Record<string, SwaggerSchema>;
|
|
86
|
+
required?: string[];
|
|
87
|
+
additionalProperties?: unknown;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface SwaggerParameter {
|
|
91
|
+
name: string;
|
|
92
|
+
in: string;
|
|
93
|
+
required?: boolean;
|
|
94
|
+
type?: string;
|
|
95
|
+
schema?: SwaggerSchema;
|
|
96
|
+
description?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface SwaggerOperation {
|
|
100
|
+
operationId?: string;
|
|
101
|
+
summary?: string;
|
|
102
|
+
description?: string;
|
|
103
|
+
parameters?: SwaggerParameter[];
|
|
104
|
+
responses?: Record<string, { schema?: SwaggerSchema; description?: string }>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface SwaggerRoot {
|
|
108
|
+
paths?: Record<string, Record<string, SwaggerOperation>>;
|
|
109
|
+
definitions?: Record<string, SwaggerSchema>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Strip a leading `Service_` prefix from an operationId, then snake the rest. */
|
|
113
|
+
function operationName(operationId: string): string {
|
|
114
|
+
const underscore = operationId.indexOf("_");
|
|
115
|
+
const bare = underscore >= 0 ? operationId.slice(underscore + 1) : operationId;
|
|
116
|
+
return snake(bare);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function schemaToken(schema: SwaggerSchema): string {
|
|
120
|
+
return swaggerType(schema.type, typeof schema.$ref === "string");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function swaggerParamSlot(param: SwaggerParameter): OpenApiParam {
|
|
124
|
+
const token =
|
|
125
|
+
param.in === "body" && param.schema !== undefined
|
|
126
|
+
? schemaToken(param.schema)
|
|
127
|
+
: swaggerType(param.type, false);
|
|
128
|
+
const slot: OpenApiParam = {
|
|
129
|
+
name: param.name,
|
|
130
|
+
doc: (param.description ?? "").trim(),
|
|
131
|
+
types: [token],
|
|
132
|
+
};
|
|
133
|
+
if (param.required !== true) slot.is_optional = "True";
|
|
134
|
+
return slot;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function swaggerReturnSlots(op: SwaggerOperation): OpenApiParam[] {
|
|
138
|
+
const schema = op.responses?.["200"]?.schema;
|
|
139
|
+
if (schema === undefined) return [];
|
|
140
|
+
return [{ name: "result", doc: "", types: [schemaToken(schema)] }];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function parseSwaggerOperations(root: SwaggerRoot): OpenApiElement[] {
|
|
144
|
+
const elements: OpenApiElement[] = [];
|
|
145
|
+
for (const methods of Object.values(root.paths ?? {})) {
|
|
146
|
+
for (const op of Object.values(methods)) {
|
|
147
|
+
if (typeof op.operationId !== "string") continue;
|
|
148
|
+
elements.push({
|
|
149
|
+
type: "FUNCTION",
|
|
150
|
+
name: operationName(op.operationId),
|
|
151
|
+
description: (op.summary ?? op.description ?? "").trim(),
|
|
152
|
+
parameters: (op.parameters ?? []).map(swaggerParamSlot),
|
|
153
|
+
returnvalues: swaggerReturnSlots(op),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return elements;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function parseSwaggerDefinitions(root: SwaggerRoot): OpenApiElement[] {
|
|
161
|
+
const elements: OpenApiElement[] = [];
|
|
162
|
+
for (const [name, schema] of Object.entries(root.definitions ?? {})) {
|
|
163
|
+
// Only object definitions become constructors; enum/scalar definitions carry
|
|
164
|
+
// no properties and would emit a meaningless zero-argument builder.
|
|
165
|
+
if (schema.type !== "object" || schema.properties === undefined) continue;
|
|
166
|
+
const required = new Set(schema.required ?? []);
|
|
167
|
+
const parameters = Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
168
|
+
const slot: OpenApiParam = { name: propName, doc: "", types: [schemaToken(propSchema)] };
|
|
169
|
+
if (!required.has(propName)) slot.is_optional = "True";
|
|
170
|
+
return slot;
|
|
171
|
+
});
|
|
172
|
+
elements.push({
|
|
173
|
+
type: "FUNCTION",
|
|
174
|
+
name: `create_${snake(name)}`,
|
|
175
|
+
description: (root.definitions?.[name] as { description?: string }).description?.trim() ?? "",
|
|
176
|
+
parameters,
|
|
177
|
+
returnvalues: [{ name: "result", doc: "", types: ["table"] }],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return elements;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Map a proto field type to a resolvable emitter token. Scalar numerics and
|
|
184
|
+
* `bytes` collapse per Lua marshalling; message/enum/`map`/`repeated` types are
|
|
185
|
+
* tables. */
|
|
186
|
+
function protoType(rawType: string): string {
|
|
187
|
+
const type = rawType.trim();
|
|
188
|
+
if (type.startsWith("map<") || type.startsWith("repeated ")) return "table";
|
|
189
|
+
switch (type) {
|
|
190
|
+
case "double":
|
|
191
|
+
case "float":
|
|
192
|
+
case "int32":
|
|
193
|
+
case "int64":
|
|
194
|
+
case "uint32":
|
|
195
|
+
case "uint64":
|
|
196
|
+
case "sint32":
|
|
197
|
+
case "sint64":
|
|
198
|
+
case "fixed32":
|
|
199
|
+
case "fixed64":
|
|
200
|
+
case "sfixed32":
|
|
201
|
+
case "sfixed64":
|
|
202
|
+
return "number";
|
|
203
|
+
case "bool":
|
|
204
|
+
return "boolean";
|
|
205
|
+
case "string":
|
|
206
|
+
case "bytes":
|
|
207
|
+
return "string";
|
|
208
|
+
default:
|
|
209
|
+
// A message- or enum-typed field marshals as a table.
|
|
210
|
+
return "table";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// A proto field line: an optional `repeated`, a type (`map<...>` allowed), a field
|
|
215
|
+
// name, `=`, a tag, `;`. Reserved lines, `oneof`, options, and nested blocks are
|
|
216
|
+
// ignored by requiring the `= <tag>;` tail.
|
|
217
|
+
const PROTO_FIELD = /^\s*(map<[^>]+>|repeated\s+[\w.]+|[\w.]+)\s+([a-z_]\w*)\s*=\s*\d+\s*;/;
|
|
218
|
+
const PROTO_MESSAGE_OPEN = /^message\s+([A-Za-z_]\w*)\s*\{/;
|
|
219
|
+
// A `oneof` groups alternatives that are the enclosing message's own fields; any
|
|
220
|
+
// other block (`message`/`enum`) owns its fields and must not leak them upward, so
|
|
221
|
+
// non-`oneof` openers default to an isolating `other` frame.
|
|
222
|
+
const PROTO_ONEOF_OPEN = /^\s*oneof\s+[A-Za-z_]\w*\s*\{/;
|
|
223
|
+
|
|
224
|
+
function parseProtoMessages(protoText: string): OpenApiElement[] {
|
|
225
|
+
const elements: OpenApiElement[] = [];
|
|
226
|
+
const lines = protoText.split("\n");
|
|
227
|
+
let current: { name: string; parameters: OpenApiParam[] } | null = null;
|
|
228
|
+
// Block-kind frames nested inside the current message, excluding the message
|
|
229
|
+
// root (which `current !== null` represents). Integer depth cannot tell a
|
|
230
|
+
// `oneof` alternative (a parent field) from a nested-`message` field (not one);
|
|
231
|
+
// the frame kinds carry that distinction.
|
|
232
|
+
let frames: Array<"oneof" | "other"> = [];
|
|
233
|
+
|
|
234
|
+
const emit = () => {
|
|
235
|
+
if (current === null) return;
|
|
236
|
+
elements.push({
|
|
237
|
+
type: "FUNCTION",
|
|
238
|
+
name: `create_${snake(current.name)}_message`,
|
|
239
|
+
description: "",
|
|
240
|
+
parameters: current.parameters,
|
|
241
|
+
returnvalues: [],
|
|
242
|
+
});
|
|
243
|
+
current = null;
|
|
244
|
+
frames = [];
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Account one message-body segment (a whole line, or the tail of the open line
|
|
248
|
+
// after its header brace): count a field only when the frames above the root
|
|
249
|
+
// are empty or all `oneof`, then apply the segment's braces — a `}` that finds
|
|
250
|
+
// no nested frame closes the message root.
|
|
251
|
+
const processBody = (body: string): void => {
|
|
252
|
+
if (current === null) return;
|
|
253
|
+
const field = PROTO_FIELD.exec(body);
|
|
254
|
+
if (field !== null && frames.every((f) => f === "oneof")) {
|
|
255
|
+
const slot: OpenApiParam = {
|
|
256
|
+
name: field[2] as string,
|
|
257
|
+
doc: "",
|
|
258
|
+
types: [protoType(field[1] as string)],
|
|
259
|
+
};
|
|
260
|
+
if (frames.includes("oneof")) slot.is_optional = "True";
|
|
261
|
+
current.parameters.push(slot);
|
|
262
|
+
}
|
|
263
|
+
const openKind: "oneof" | "other" = PROTO_ONEOF_OPEN.test(body) ? "oneof" : "other";
|
|
264
|
+
for (let i = 0; i < (body.match(/\{/g) ?? []).length; i++) frames.push(openKind);
|
|
265
|
+
for (let i = 0; i < (body.match(/\}/g) ?? []).length; i++) {
|
|
266
|
+
if (frames.length > 0) {
|
|
267
|
+
frames.pop();
|
|
268
|
+
} else {
|
|
269
|
+
emit();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
for (const line of lines) {
|
|
276
|
+
if (current === null) {
|
|
277
|
+
const open = PROTO_MESSAGE_OPEN.exec(line);
|
|
278
|
+
if (open !== null) {
|
|
279
|
+
current = { name: open[1] as string, parameters: [] };
|
|
280
|
+
frames = [];
|
|
281
|
+
// Process the same line's remainder so `message Ping {}` closes here
|
|
282
|
+
// instead of swallowing every following top-level message.
|
|
283
|
+
processBody(line.slice(open[0].length));
|
|
284
|
+
}
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
processBody(line);
|
|
288
|
+
}
|
|
289
|
+
return elements;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Parse the pinned swagger JSON and realtime `.proto` into the shared ref-doc
|
|
294
|
+
* `doc` shape. Swagger operations, swagger object definitions, and proto messages
|
|
295
|
+
* each contribute one element; the first occurrence of a codegen'd name wins so a
|
|
296
|
+
* definition and a like-named operation never emit a duplicate member.
|
|
297
|
+
*/
|
|
298
|
+
export function parseOpenApi(swaggerText: string, protoText: string): OpenApiDoc {
|
|
299
|
+
const root = JSON.parse(swaggerText) as SwaggerRoot;
|
|
300
|
+
const all = [
|
|
301
|
+
...parseSwaggerOperations(root),
|
|
302
|
+
...parseSwaggerDefinitions(root),
|
|
303
|
+
...parseProtoMessages(protoText),
|
|
304
|
+
];
|
|
305
|
+
const seen = new Set<string>();
|
|
306
|
+
const elements: OpenApiElement[] = [];
|
|
307
|
+
for (const element of all) {
|
|
308
|
+
if (seen.has(element.name)) continue;
|
|
309
|
+
seen.add(element.name);
|
|
310
|
+
elements.push(element);
|
|
311
|
+
}
|
|
312
|
+
return { info: { namespace: "", brief: "", description: "" }, elements };
|
|
313
|
+
}
|