@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.
Files changed (3) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +121 -0
  3. 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.2",
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",