@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.
Files changed (145) hide show
  1. package/api-doc/boom.json +3870 -0
  2. package/api-doc/{bridge.bridge.json → bridge.json} +754 -844
  3. package/api-doc/bzAnim.json +338 -0
  4. package/api-doc/decore.json +23 -23
  5. package/api-doc/defcon.json +150 -0
  6. package/api-doc/{defmath.defmath.json → defmath.json} +6 -46
  7. package/api-doc/defsave.json +518 -0
  8. package/api-doc/deftest.json +741 -0
  9. package/api-doc/{dicebag.dicebag.json → dicebag.json} +19 -1
  10. package/api-doc/druid.json +249 -1846
  11. package/api-doc/event.json +1074 -0
  12. package/api-doc/gooey.json +2050 -0
  13. package/api-doc/immutable.json +65 -0
  14. package/api-doc/in.accelerometer.json +75 -1
  15. package/api-doc/in.button.json +17 -10
  16. package/api-doc/in.cursor.json +26 -0
  17. package/api-doc/in.gesture.json +401 -6
  18. package/api-doc/in.keyboard.json +2 -0
  19. package/api-doc/in.mapper.json +12 -8
  20. package/api-doc/in.onscreen.json +61 -5
  21. package/api-doc/in.state.json +61 -13
  22. package/api-doc/in.textbox.json +23 -12
  23. package/api-doc/in.triggers.json +336 -0
  24. package/api-doc/lang.json +528 -0
  25. package/api-doc/{event.event.json → log.json} +96 -101
  26. package/api-doc/metrics.fps.json +61 -2
  27. package/api-doc/metrics.mem.json +61 -2
  28. package/api-doc/monarch.monarch.json +249 -7
  29. package/api-doc/monarch.transitions.easings.json +19 -26
  30. package/api-doc/monarch.transitions.gui.json +782 -79
  31. package/api-doc/nakama.engine.defold.json +31 -18
  32. package/api-doc/{nakama.nakama.json → nakama.json} +4752 -2554
  33. package/api-doc/nakama.util.log.json +32 -4
  34. package/api-doc/narrator.json +647 -0
  35. package/api-doc/openapi/nakama.nakama.json +7680 -0
  36. package/api-doc/orthographic.camera.json +96 -98
  37. package/api-doc/orthographic.json +602 -0
  38. package/api-doc/panthera.json +1183 -0
  39. package/api-doc/{persist.persist.json → persist.json} +26 -1
  40. package/api-doc/{platypus.platypus.json → platypus.json} +68 -14
  41. package/api-doc/proto.json +995 -0
  42. package/api-doc/{rendy.rendy.json → rendy.json} +46 -99
  43. package/api-doc/richtext.color.json +77 -0
  44. package/api-doc/richtext.richtext.json +375 -5
  45. package/api-doc/saver.saver.json +714 -263
  46. package/api-doc/saver.storage.json +283 -52
  47. package/api-doc/squid.json +846 -0
  48. package/api-doc/{starly.starly.json → starly.json} +1 -1
  49. package/api-doc/tweener.json +277 -0
  50. package/api-doc/{yagames.yagames.json → yagames.json} +784 -189
  51. package/api-doc/{zzfx.api.json → zzfx.json} +9 -9
  52. package/authored-targets.json +403 -0
  53. package/generated/{boom.boom.d.ts → boom.d.ts} +14 -0
  54. package/generated/bridge.d.ts +468 -0
  55. package/generated/{bzAnim.bzLibrary.d.ts → bzAnim.d.ts} +6 -0
  56. package/generated/decore.d.ts +37 -37
  57. package/generated/defcon.d.ts +43 -0
  58. package/generated/{defmath.defmath.d.ts → defmath.d.ts} +3 -2
  59. package/generated/defsave.d.ts +144 -0
  60. package/generated/{dicebag.dicebag.d.ts → dicebag.d.ts} +25 -0
  61. package/generated/druid.d.ts +198 -502
  62. package/generated/event.d.ts +318 -0
  63. package/generated/{gooey.gooey.d.ts → gooey.d.ts} +61 -6
  64. package/generated/immutable.d.ts +13 -0
  65. package/generated/in.onscreen.d.ts +5 -0
  66. package/generated/in.textbox.d.ts +4 -0
  67. package/generated/lang.d.ts +101 -0
  68. package/generated/log.d.ts +36 -0
  69. package/generated/metrics.fps.d.ts +19 -1
  70. package/generated/metrics.mem.d.ts +19 -1
  71. package/generated/monarch.monarch.d.ts +44 -1
  72. package/generated/monarch.transitions.easings.d.ts +0 -1
  73. package/generated/monarch.transitions.gui.d.ts +96 -12
  74. package/generated/nakama.d.ts +1158 -0
  75. package/generated/nakama.engine.defold.d.ts +3 -1
  76. package/generated/nakama.util.log.d.ts +6 -0
  77. package/generated/narrator.d.ts +121 -0
  78. package/generated/openapi/nakama.nakama.d.ts +884 -0
  79. package/generated/orthographic.camera.d.ts +24 -38
  80. package/generated/orthographic.d.ts +155 -0
  81. package/generated/panthera.d.ts +170 -0
  82. package/generated/{persist.persist.d.ts → persist.d.ts} +3 -0
  83. package/generated/{platypus.platypus.d.ts → platypus.d.ts} +9 -0
  84. package/generated/proto.d.ts +146 -0
  85. package/generated/{rendy.rendy.d.ts → rendy.d.ts} +33 -80
  86. package/generated/richtext.color.d.ts +15 -0
  87. package/generated/saver.saver.d.ts +289 -42
  88. package/generated/saver.storage.d.ts +79 -14
  89. package/generated/squid.d.ts +127 -0
  90. package/generated/tweener.d.ts +42 -0
  91. package/generated/{yagames.yagames.d.ts → yagames.d.ts} +151 -29
  92. package/generated/{zzfx.api.d.ts → zzfx.d.ts} +7 -0
  93. package/library-classification.json +0 -227
  94. package/library-targets.json +1 -284
  95. package/luals-targets.json +168 -1
  96. package/openapi-targets.json +17 -0
  97. package/package.json +9 -88
  98. package/script-api-targets.json +15 -0
  99. package/scripts/__snapshots__/parse-luals.test.ts.snap +504 -85
  100. package/scripts/apply-luals-overrides.ts +95 -0
  101. package/scripts/authored-parity.ts +718 -0
  102. package/scripts/emit-library-dts.ts +177 -19
  103. package/scripts/extract-api-doc.ts +189 -38
  104. package/scripts/fidelity-floor.ts +178 -0
  105. package/scripts/import-upstream-docs.ts +125 -0
  106. package/scripts/lower-api-doc.ts +69 -19
  107. package/scripts/luals-fidelity.ts +22 -10
  108. package/scripts/map-luals-types.ts +86 -22
  109. package/scripts/parse-lua-surface.ts +187 -0
  110. package/scripts/parse-luals.ts +473 -22
  111. package/scripts/parse-markdown-api.ts +247 -0
  112. package/scripts/parse-openapi-api.ts +313 -0
  113. package/scripts/sync-authored-types.ts +249 -0
  114. package/scripts/sync-library-types.ts +99 -3
  115. package/scripts/sync-luals-types.ts +29 -2
  116. package/scripts/sync-markdown-types.ts +761 -0
  117. package/scripts/sync-openapi-types.ts +308 -0
  118. package/scripts/sync-script-api-types.ts +367 -0
  119. package/api-doc/boom.boom.json +0 -26
  120. package/api-doc/bzAnim.bzLibrary.json +0 -156
  121. package/api-doc/defcon.console.json +0 -109
  122. package/api-doc/defsave.defsave.json +0 -175
  123. package/api-doc/deftest.deftest.json +0 -71
  124. package/api-doc/gooey.gooey.json +0 -963
  125. package/api-doc/immutable.immutable.json +0 -63
  126. package/api-doc/lang.lang.json +0 -411
  127. package/api-doc/log.log.json +0 -50
  128. package/api-doc/narrator.narrator.json +0 -150
  129. package/api-doc/proto.proto.json +0 -355
  130. package/api-doc/squid.squid.json +0 -660
  131. package/api-doc/tweener.tweener.json +0 -419
  132. package/generated/bridge.bridge.d.ts +0 -533
  133. package/generated/defcon.console.d.ts +0 -24
  134. package/generated/defsave.defsave.d.ts +0 -31
  135. package/generated/event.event.d.ts +0 -54
  136. package/generated/immutable.immutable.d.ts +0 -13
  137. package/generated/lang.lang.d.ts +0 -33
  138. package/generated/log.log.d.ts +0 -40
  139. package/generated/nakama.nakama.d.ts +0 -594
  140. package/generated/narrator.narrator.d.ts +0 -66
  141. package/generated/proto.proto.d.ts +0 -36
  142. package/generated/squid.squid.d.ts +0 -106
  143. package/generated/tweener.tweener.d.ts +0 -151
  144. /package/generated/{deftest.deftest.d.ts → deftest.d.ts} +0 -0
  145. /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
+ }