@constantant/openapi-resource-gen 1.3.2 → 1.4.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/CHANGELOG.md +11 -0
- package/README.md +121 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 1.4.0 (2026-06-08)
|
|
2
|
+
|
|
3
|
+
### 📖 Documentation
|
|
4
|
+
|
|
5
|
+
- **openapi-resource-gen:** document caching boundary and multipart/form-data mutation pattern ([88211a4](https://github.com/constantant/angular-openapi-gen/commit/88211a4))
|
|
6
|
+
|
|
7
|
+
### ❤️ Thank You
|
|
8
|
+
|
|
9
|
+
- Claude Sonnet 4.6
|
|
10
|
+
- kk
|
|
11
|
+
|
|
1
12
|
## 1.3.2 (2026-06-08)
|
|
2
13
|
|
|
3
14
|
### 🩹 Fixes
|
package/README.md
CHANGED
|
@@ -191,6 +191,80 @@ headers: {
|
|
|
191
191
|
The factory returns `(body: BodyType | Signal<BodyType>) => httpResource(...)`.
|
|
192
192
|
The resource config receives `method: 'POST'` (etc.) and `body` automatically.
|
|
193
193
|
|
|
194
|
+
#### JSON body
|
|
195
|
+
|
|
196
|
+
The common case. Pass a plain object or a signal — `HttpClient` serialises it automatically:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const addPet = inject(ADD_PET);
|
|
200
|
+
|
|
201
|
+
readonly newPet = signal<AddPetBody>({ name: 'Rex', status: 'available' });
|
|
202
|
+
readonly result = addPet(this.newPet); // re-posts whenever newPet() changes
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### `multipart/form-data` body
|
|
206
|
+
|
|
207
|
+
The generated `${pascal}Body` type is derived from the OpenAPI schema, which
|
|
208
|
+
describes the *shape* of the form fields. At runtime the actual value must be a
|
|
209
|
+
`FormData` object — Angular's `HttpClient` does not encode plain objects as
|
|
210
|
+
multipart. Cast is required at the call site:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// Generated (example from Petstore's POST /pet/{petId}/uploadImage):
|
|
214
|
+
//
|
|
215
|
+
// export type UploadFileBody =
|
|
216
|
+
// NonNullable<paths['/pet/{petId}/uploadImage']['post']['requestBody']>
|
|
217
|
+
// ['content']['multipart/form-data'];
|
|
218
|
+
// // → { additionalMetadata?: string; file?: Blob }
|
|
219
|
+
//
|
|
220
|
+
// export const UPLOAD_FILE = new InjectionToken<
|
|
221
|
+
// (petId: string, body: UploadFileBody | Signal<UploadFileBody>)
|
|
222
|
+
// => ReturnType<typeof httpResource<UploadFileResponse>>
|
|
223
|
+
// >('UPLOAD_FILE');
|
|
224
|
+
|
|
225
|
+
@Component({ ... })
|
|
226
|
+
export class UploadComponent {
|
|
227
|
+
private uploadFile = inject(UPLOAD_FILE);
|
|
228
|
+
|
|
229
|
+
readonly selectedFile = signal<File | null>(null);
|
|
230
|
+
readonly notes = signal('');
|
|
231
|
+
|
|
232
|
+
// Build FormData reactively; cast to the spec type so the token accepts it.
|
|
233
|
+
// FormData is the required runtime representation for multipart/form-data —
|
|
234
|
+
// the spec type only describes the field names and shapes, not the encoding.
|
|
235
|
+
private readonly formData = computed(() => {
|
|
236
|
+
const file = this.selectedFile();
|
|
237
|
+
if (!file) return null;
|
|
238
|
+
const fd = new FormData();
|
|
239
|
+
fd.append('file', file);
|
|
240
|
+
fd.append('additionalMetadata', this.notes());
|
|
241
|
+
return fd as unknown as UploadFileBody;
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
readonly upload = this.uploadFile(
|
|
245
|
+
'42', // petId (path param)
|
|
246
|
+
() => this.formData(), // thunk: resource stays idle when formData() is null
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
> **Why the cast?** The OpenAPI schema types `multipart/form-data` bodies as a
|
|
252
|
+
> plain object (e.g. `{ file?: Blob; additionalMetadata?: string }`). This is
|
|
253
|
+
> accurate for type-checking field names and shapes, but `HttpClient` requires
|
|
254
|
+
> an actual `FormData` instance for multipart encoding. The `as unknown as
|
|
255
|
+
> UploadFileBody` cast bridges that gap without losing the field-name safety you
|
|
256
|
+
> get from the spec type.
|
|
257
|
+
|
|
258
|
+
#### `application/x-www-form-urlencoded` body
|
|
259
|
+
|
|
260
|
+
Pass a plain object. Angular's `HttpClient` URL-encodes it automatically — no
|
|
261
|
+
`URLSearchParams` wrapping needed:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
const submitForm = inject(SUBMIT_FORM);
|
|
265
|
+
readonly result = submitForm({ username: 'alice', password: 's3cr3t' });
|
|
266
|
+
```
|
|
267
|
+
|
|
194
268
|
### Security schemes
|
|
195
269
|
|
|
196
270
|
The generator emits one file per security scheme. Two patterns are used depending on the
|
|
@@ -384,6 +458,53 @@ type PetStatus = FindPetsByStatusParams['status']; // 'available' | 'pending' |
|
|
|
384
458
|
|
|
385
459
|
---
|
|
386
460
|
|
|
461
|
+
## Sharing a resource across components
|
|
462
|
+
|
|
463
|
+
Each call to the injected factory function creates an **independent `httpResource` instance**.
|
|
464
|
+
Two components that both call `this.findPetsByStatus(...)` will fire two separate HTTP requests.
|
|
465
|
+
|
|
466
|
+
This is intentional — resources are reactive computations tied to a component's lifetime, and
|
|
467
|
+
`httpResource` does not have a built-in shared cache. For data that should be fetched once and
|
|
468
|
+
shared, hoist the resource call to a root-scoped service:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
// pets.store.ts (not generated — write this yourself)
|
|
472
|
+
@Service() // Angular 22 shorthand for @Injectable({ providedIn: 'root' })
|
|
473
|
+
export class PetsStore {
|
|
474
|
+
private findPetsByStatus = inject(FIND_PETS_BY_STATUS);
|
|
475
|
+
|
|
476
|
+
readonly status = signal<'available' | 'pending' | 'sold'>('available');
|
|
477
|
+
|
|
478
|
+
// One httpResource instance, shared across any component that injects PetsStore
|
|
479
|
+
readonly pets = this.findPetsByStatus(() => ({ status: this.status() }));
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
// Component A and Component B both inject the same PetsStore singleton —
|
|
485
|
+
// only one HTTP request fires.
|
|
486
|
+
@Component({ ... })
|
|
487
|
+
export class PetsPageComponent {
|
|
488
|
+
readonly store = inject(PetsStore);
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
For per-route isolation, create the resource inside a route-level provider instead:
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
// In the route definition
|
|
496
|
+
{
|
|
497
|
+
path: 'pets',
|
|
498
|
+
component: PetsShellComponent,
|
|
499
|
+
providers: [PetsStore, provideFindPetsByStatus()],
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
This scopes the resource to the route's injector — a new instance is created on
|
|
504
|
+
navigation in and destroyed on navigation out, with no cross-route state leakage.
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
387
508
|
## Adding a new data-access lib
|
|
388
509
|
|
|
389
510
|
1. Run the generator (pass a URL directly — no curl step needed):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constantant/openapi-resource-gen",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Nx generator: one InjectionToken per OpenAPI endpoint — tree-shakeable Angular data-access libs via httpResource",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "src/index.js",
|