@abapify/openai-codegen 0.0.0 → 0.3.6

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 (2) hide show
  1. package/README.md +344 -1
  2. package/package.json +49 -3
package/README.md CHANGED
@@ -1,3 +1,346 @@
1
1
  # @abapify/openai-codegen
2
2
 
3
- Placeholder. The real release is published via CI/CD.
3
+ **Deterministic OpenAPI → ABAP client code generator.** Reads an OpenAPI
4
+ 3.x spec and emits three global ABAP interfaces, one global exception
5
+ class, and one thin implementation class bundled with a small set of
6
+ local helper classes. The result targets SAP BTP / Steampunk
7
+ (`s4-cloud`) out of the box and has **zero Z-dependencies** — the
8
+ bundled locals wrap kernel APIs (`cl_web_http_client_manager`,
9
+ `cl_http_destination_provider`, `/ui2/cl_json`, `cl_abap_conv_codepage`,
10
+ …) only.
11
+
12
+ Re-running the generator with the same inputs produces byte-identical
13
+ output. Generated artifacts are safe to commit and diff.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ bun add -D @abapify/openai-codegen
19
+ bunx openai-codegen --help
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ ```bash
25
+ bunx openai-codegen \
26
+ --input ./petstore3.json \
27
+ --out ./out/abapgit \
28
+ --base petstore3 \
29
+ --format abapgit \
30
+ --target s4-cloud \
31
+ --description "Petstore v3 client"
32
+ ```
33
+
34
+ With the default `abapgit` layout this writes **11 files**:
35
+
36
+ ```text
37
+ out/abapgit/
38
+ ├── package.devc.xml
39
+ └── src/
40
+ ├── zif_petstore3_types.intf.abap # Layer 1 — TYPES interface
41
+ ├── zif_petstore3_types.intf.xml
42
+ ├── zif_petstore3.intf.abap # Layer 2 — operations interface
43
+ ├── zif_petstore3.intf.xml
44
+ ├── zcx_petstore3_error.clas.abap # global exception class
45
+ ├── zcx_petstore3_error.clas.xml
46
+ ├── zcl_petstore3.clas.abap # Layer 3 — thin implementation
47
+ ├── zcl_petstore3.clas.xml
48
+ ├── zcl_petstore3.clas.locals_def.abap # bundled helper locals
49
+ └── zcl_petstore3.clas.locals_imp.abap
50
+ ```
51
+
52
+ ## Architecture
53
+
54
+ ```text
55
+ ZIF_<BASE>_TYPES ── all schemas as ABAP TYPES (+ @openapi-schema markers)
56
+ ▲
57
+ │
58
+ ZIF_<BASE> ── all operations as typed METHODS (+ @openapi-operation markers)
59
+ │ ── RAISING ZCX_<BASE>_ERROR
60
+ │
61
+ ZCL_<BASE> ── implements ZIF_<BASE>
62
+ one private attribute: client TYPE REF TO lcl_http
63
+ each method body = fetch(...) + CASE response->status( )
64
+ ─────────────────────────────────────────────
65
+ local classes (same .clas file, locals_def/imp):
66
+ lcl_http (HTTP transport)
67
+ lcl_response (status / body / headers)
68
+ json (stringify / render / parse)
69
+ lcl_json_parser (internal JSON parser)
70
+
71
+ ZCX_<BASE>_ERROR ── inherits cx_static_check
72
+ carries status / description / body / headers
73
+ ```
74
+
75
+ The implementation class is deliberately minimal:
76
+
77
+ - exactly one private attribute (`client`),
78
+ - no private helper methods besides `constructor`,
79
+ - each operation method is **one `fetch` call** followed by a `CASE`
80
+ block that decodes the success branch via `json=>parse( ... )->to( … )`
81
+ and raises `ZCX_<BASE>_ERROR` for every other status.
82
+
83
+ Schema `TYPES` intentionally live on their own **interface** — not on
84
+ the class. ABAP's unified component namespace for types / attributes /
85
+ methods on a class would otherwise force awkward name mangling; putting
86
+ types on `ZIF_<BASE>_TYPES` keeps the main class and the operations
87
+ interface collision-free.
88
+
89
+ ## Generated API surface
90
+
91
+ ### Layer 1 — types interface (`zif_petstore3_types.intf.abap`)
92
+
93
+ ```abap
94
+ "! Generated types for ZCL_PETSTORE3.
95
+ INTERFACE ZIF_PETSTORE3_TYPES PUBLIC.
96
+ "! @openapi-schema Category
97
+ TYPES: BEGIN OF category,
98
+ id TYPE int8,
99
+ name TYPE string,
100
+ END OF category.
101
+ "! @openapi-schema Pet
102
+ TYPES: BEGIN OF pet,
103
+ id TYPE int8,
104
+ name TYPE string,
105
+ category TYPE category,
106
+ photo_urls TYPE STANDARD TABLE OF string WITH EMPTY KEY,
107
+ tags TYPE STANDARD TABLE OF tag WITH EMPTY KEY,
108
+ status TYPE string,
109
+ END OF pet.
110
+ ENDINTERFACE.
111
+ ```
112
+
113
+ ### Layer 2 — operations interface (`zif_petstore3.intf.abap`)
114
+
115
+ ```abap
116
+ INTERFACE zif_petstore3 PUBLIC.
117
+ "! @openapi-operation addPet
118
+ "! @openapi-path POST /pet
119
+ "! Add a new pet to the store.
120
+ METHODS add_pet
121
+ IMPORTING body TYPE zif_petstore3_types=>pet
122
+ RETURNING VALUE(pet) TYPE zif_petstore3_types=>pet
123
+ RAISING zcx_petstore3_error.
124
+ "! @openapi-operation getPetById
125
+ "! @openapi-path GET /pet/{petId}
126
+ "! Find pet by ID.
127
+ METHODS get_pet_by_id
128
+ IMPORTING pet_id TYPE int8
129
+ RETURNING VALUE(pet) TYPE zif_petstore3_types=>pet
130
+ RAISING zcx_petstore3_error.
131
+ ENDINTERFACE.
132
+ ```
133
+
134
+ ### Layer 3 — implementation class (`zcl_petstore3.clas.abap`)
135
+
136
+ ```abap
137
+ CLASS ZCL_PETSTORE3 DEFINITION PUBLIC CREATE PUBLIC.
138
+ INTERFACES zif_petstore3.
139
+ PUBLIC SECTION.
140
+ METHODS constructor
141
+ IMPORTING
142
+ destination TYPE string
143
+ server TYPE string DEFAULT '/api/v3'.
144
+ PRIVATE SECTION.
145
+ DATA client TYPE REF TO lcl_http.
146
+ ENDCLASS.
147
+
148
+ CLASS ZCL_PETSTORE3 IMPLEMENTATION.
149
+ METHOD constructor.
150
+ client = NEW lcl_http( destination = destination server = server ).
151
+ ENDMETHOD.
152
+
153
+ METHOD zif_petstore3~add_pet.
154
+ DATA(response) = client->fetch(
155
+ method = 'POST'
156
+ path = '/pet'
157
+ body = json=>stringify( body )
158
+ headers = VALUE #( ( name = 'Content-Type' value = 'application/json' ) ) ).
159
+ CASE response->status( ).
160
+ WHEN 200.
161
+ json=>parse( response->body( ) )->to( REF #( pet ) ).
162
+ WHEN 400.
163
+ RAISE EXCEPTION NEW zcx_petstore3_error(
164
+ status = 400
165
+ description = 'Invalid input'
166
+ body = response->body( ) ).
167
+ WHEN OTHERS.
168
+ RAISE EXCEPTION NEW zcx_petstore3_error(
169
+ status = response->status( )
170
+ description = 'Unexpected error'
171
+ body = response->body( ) ).
172
+ ENDCASE.
173
+ ENDMETHOD.
174
+ ENDCLASS.
175
+ ```
176
+
177
+ ### Bundled locals (`zcl_petstore3.clas.locals_def.abap` / `locals_imp.abap`)
178
+
179
+ The local classes expose a small, stable API that is **transport-only**
180
+ for HTTP and **typed-by-reference** for JSON:
181
+
182
+ ```abap
183
+ " Transport (HTTP): lcl_http / lcl_response
184
+ DATA(response) = client->fetch(
185
+ method = 'GET'
186
+ path = '/pet/123'
187
+ query = VALUE #( ( name = 'detail' value = 'full' ) )
188
+ headers = VALUE #( ( name = 'Accept' value = 'application/json' ) ) ).
189
+ response->status( ). " -> i
190
+ response->body( ). " -> xstring
191
+ response->text( ). " -> string (decoded)
192
+ response->header( 'etag' ).
193
+ response->headers( ).
194
+
195
+ " JSON: json helper (mirrors abapify/json)
196
+ DATA(payload) = json=>stringify( pet ). " string
197
+ DATA(raw) = json=>render( pet ). " xstring
198
+ json=>parse( response->body( ) )->to( REF #( pet ) ).
199
+ ```
200
+
201
+ `lcl_http->fetch` is deliberately ignorant of JSON, status codes, and
202
+ operation identities. All policy lives in the per-operation method body
203
+ in `ZCL_<BASE>`. The JSON API surface mirrors
204
+ [`abapify/json`](https://github.com/abapify) and wraps `/ui2/cl_json` +
205
+ `cl_abap_conv_codepage`.
206
+
207
+ ## Configuration
208
+
209
+ All four global names and all four local names are configurable. The
210
+ simplest path is to provide `--base`; every name then derives from it.
211
+ Any individual override wins.
212
+
213
+ | CLI flag | Library field (`NamesConfig`) | Default |
214
+ | ------------------------------- | ----------------------------- | --------------------------------------- |
215
+ | `--base <name>` | `base` | _required unless all 4 overrides given_ |
216
+ | `--types-interface <name>` | `typesInterface` | `ZIF_<BASE>_TYPES` |
217
+ | `--operations-interface <name>` | `operationsInterface` | `ZIF_<BASE>` |
218
+ | `--class-name <name>` | `implementationClass` | `ZCL_<BASE>` |
219
+ | `--exception-class <name>` | `exceptionClass` | `ZCX_<BASE>_ERROR` |
220
+ | _(library only)_ | `localHttpClass` | `lcl_http` |
221
+ | _(library only)_ | `localResponseClass` | `lcl_response` |
222
+ | _(library only)_ | `localJsonClass` | `json` |
223
+ | _(library only)_ | `localJsonParserClass` | `lcl_json_parser` |
224
+
225
+ Other flags:
226
+
227
+ | Flag | Purpose |
228
+ | ------------------------- | ---------------------------------------------------------------- |
229
+ | `-i, --input <path>` | Path / URL / file for the OpenAPI spec (JSON or YAML). Required. |
230
+ | `-o, --out <dir>` | Output directory. Required. |
231
+ | `-f, --format <list>` | `abapgit` (default), `gcts`, or `abapgit,gcts`. |
232
+ | `-t, --target <profile>` | Target profile (`s4-cloud` only, default). |
233
+ | `-d, --description <txt>` | Description stored in the generated `.clas.xml` / `.intf.xml`. |
234
+
235
+ ## Library usage
236
+
237
+ ```ts
238
+ import { generate } from '@abapify/openai-codegen';
239
+
240
+ const result = await generate({
241
+ input: './petstore3.json',
242
+ outDir: './out/abapgit',
243
+ format: 'abapgit',
244
+ target: 's4-cloud',
245
+ names: { base: 'petstore3' },
246
+ description: 'Petstore v3 client',
247
+ });
248
+
249
+ console.log(result.files); // sorted, deduped relative paths
250
+ console.log(result.typeCount, result.operationCount);
251
+ console.log(result.resolvedNames);
252
+ ```
253
+
254
+ ## Pipeline
255
+
256
+ ```text
257
+ OpenAPI spec
258
+ → loadSpec ($ref-deref via @apidevtools/swagger-parser)
259
+ → planTypes (topological sort of schemas + cycle detection)
260
+ → resolveNames (NamesConfig → ResolvedNames; validates ABAP idents)
261
+ → emitTypesInterface (Layer 1 — ZIF_<BASE>_TYPES AST)
262
+ → emitOperationsInterface (Layer 2 — ZIF_<BASE> AST)
263
+ → emitExceptionClass (ZCX_<BASE>_ERROR AST)
264
+ → emitImplementationClass (Layer 3 — ZCL_<BASE> AST)
265
+ → emitLocalClasses (locals_def + locals_imp templates)
266
+ → print (four AST → ABAP source, via @abapify/abap-ast)
267
+ → writeClientBundle (abapgit or gcts on-disk tree + envelopes)
268
+ ```
269
+
270
+ ## Type mapping
271
+
272
+ | JSON Schema | ABAP |
273
+ | ----------------------------------------- | ----------------------------------------------------------- |
274
+ | `boolean` | `abap_bool` |
275
+ | `integer` / `integer, format: int64` | `i` / `int8` |
276
+ | `number` (default / `float` / `double`) | `decfloat34` / `f` |
277
+ | `string` (default) | `string` |
278
+ | `string, format: date` | `d` |
279
+ | `string, format: date-time` | `timestampl` |
280
+ | `string, format: uuid` | `sysuuid_x16` |
281
+ | `string, format: byte` / `binary` | `xstring` |
282
+ | `array of X` | `STANDARD TABLE OF <X> WITH EMPTY KEY` |
283
+ | `object` (properties) | `BEGIN OF <name> … END OF <name>.` |
284
+ | `object` with `additionalProperties` only | `_map` table of `BEGIN OF entry, key, value, END OF entry` |
285
+ | `enum` | Field typed as base scalar; values documented via ABAPDoc. |
286
+ | `allOf` | Flattened into one structure. |
287
+ | `oneOf` / `anyOf` | Union merge — fields from every branch, optional semantics. |
288
+ | `$ref` (forward) | `NamedTypeRef` into the existing plan entry. |
289
+ | `$ref` (back-edge — cyclic) | `REF TO data` with `"! @openapi-ref <field>:<Target>`. |
290
+ | `nullable: true` | Same ABAP type (nullability preserved at (de)serialise). |
291
+
292
+ Types are emitted in semantic topological order. Back-edges introduced
293
+ by cyclic schemas are broken with a `REF TO data` plus a structured
294
+ `@openapi-ref` marker, so a future reader can reconstruct the original
295
+ reference target.
296
+
297
+ ## Round-trip markers
298
+
299
+ Every generated ABAP declaration carries an ABAPDoc block that a
300
+ downstream tool (for example a future `.aclass` parser) can use to
301
+ rebuild the originating OpenAPI shape:
302
+
303
+ | Marker | Attached to | Meaning |
304
+ | ------------------------------------- | ------------------- | ---------------------------------------------------- |
305
+ | `"! @openapi-schema <Name>` | `TYPES` declaration | Original JSON-Schema component name. |
306
+ | `"! @openapi-operation <operationId>` | `METHODS` | Original OpenAPI `operationId`. |
307
+ | `"! @openapi-path <VERB> <path>` | `METHODS` | HTTP verb + templated path. |
308
+ | `"! @openapi-ref <field>:<Target>` | `TYPES` field | Field broken to `REF TO data` references `<Target>`. |
309
+
310
+ Markers are plain lines under the printer's ABAPDoc rules; they are
311
+ never rewritten or wrapped.
312
+
313
+ ## Deploying to BTP Steampunk
314
+
315
+ Short recipe (see
316
+ [`samples/petstore3-client/e2e/README.md`](../../samples/petstore3-client/e2e/README.md)
317
+ for the authoritative walk-through):
318
+
319
+ ```bash
320
+ bunx adt auth login
321
+ bunx openai-codegen \
322
+ --input ./spec/openapi.json \
323
+ --out ./generated/abapgit \
324
+ --format abapgit \
325
+ --base petstore3
326
+ bunx adt deploy --source ./generated/abapgit --package ZMY_PACKAGE --activate --unlock
327
+ ```
328
+
329
+ `S_ABPLNGVS` is granted per package on BTP. Deploy into a user-owned
330
+ `Z*` package (for example `ZMY_PACKAGE` or `ZPEPL`); targeting `$TMP`
331
+ often returns HTTP 403 on BTP.
332
+
333
+ ## Not yet supported
334
+
335
+ - OpenAPI webhooks and callbacks.
336
+ - Server-Sent Events / streaming responses.
337
+ - Full OAuth2 `authorization_code` / `client_credentials` flows
338
+ (`apiKey`, `http bearer`, `http basic` are emitted; `oauth2` exposes
339
+ an overridable hook only).
340
+ - `.aclass` DSL parser — tracked in `@abapify/abap-ast`.
341
+ - Per-target runtime bundles beyond `s4-cloud` (profile slots exist for
342
+ `s4-onprem-modern` and `on-prem-classic`; only `s4-cloud` emits in v1).
343
+
344
+ ## License
345
+
346
+ MIT
package/package.json CHANGED
@@ -1,5 +1,51 @@
1
1
  {
2
2
  "name": "@abapify/openai-codegen",
3
- "version": "0.0.0",
4
- "description": "Placeholder — real package is published via CI/CD (trusted publishing)."
5
- }
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.3.6",
7
+ "description": "Deterministic OpenAPI → ABAP client code generator. Emits a single zero-dependency ABAP class per OpenAPI spec, targeting SAP Cloud / on-prem profiles, packaged as abapGit or gCTS.",
8
+ "type": "module",
9
+ "types": "./dist/index.d.mts",
10
+ "bin": {
11
+ "openai-codegen": "./dist/cli.mjs"
12
+ },
13
+ "exports": {
14
+ ".": "./dist/index.mjs",
15
+ "./cli": "./dist/cli.mjs",
16
+ "./package.json": "./package.json"
17
+ },
18
+ "dependencies": {
19
+ "@abapify/abap-ast": "0.3.6",
20
+ "@apidevtools/swagger-parser": "^10.1.0",
21
+ "commander": "^11.1.0",
22
+ "fast-xml-parser": "^5.5.0",
23
+ "yaml": "^2.5.0"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md"
28
+ ],
29
+ "keywords": [
30
+ "openapi",
31
+ "swagger",
32
+ "abap",
33
+ "codegen",
34
+ "sap",
35
+ "steampunk"
36
+ ],
37
+ "author": "abapify",
38
+ "license": "MIT",
39
+ "devDependencies": {
40
+ "@abaplint/core": "^2.118.12"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/abapify/adt-cli.git",
45
+ "directory": "packages/openai-codegen"
46
+ },
47
+ "homepage": "https://github.com/abapify/adt-cli/tree/main/packages/openai-codegen#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/abapify/adt-cli/issues"
50
+ }
51
+ }