@intrig/plugin-react 0.0.2-0 → 0.0.2-3

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/dist/index.cjs ADDED
@@ -0,0 +1,4329 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var pluginSdk = require('@intrig/plugin-sdk');
6
+ var path = require('path');
7
+ var fsx = require('fs-extra');
8
+ var mimeType = require('mime-types');
9
+
10
+ function _interopNamespaceDefault(e) {
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
28
+ var fsx__namespace = /*#__PURE__*/_interopNamespaceDefault(fsx);
29
+ var mimeType__namespace = /*#__PURE__*/_interopNamespaceDefault(mimeType);
30
+
31
+ class InternalGeneratorContext {
32
+ getCounter(sourceId) {
33
+ this.codeGenerationBreakdown[sourceId] = this.codeGenerationBreakdown[sourceId] || new pluginSdk.StatsCounter(sourceId);
34
+ return this.codeGenerationBreakdown[sourceId];
35
+ }
36
+ getCounters() {
37
+ return [
38
+ ...Object.values(this.codeGenerationBreakdown)
39
+ ];
40
+ }
41
+ constructor(potentiallyConflictingDescriptors){
42
+ this.potentiallyConflictingDescriptors = potentiallyConflictingDescriptors;
43
+ this.codeGenerationBreakdown = {};
44
+ }
45
+ }
46
+
47
+ function packageJsonTemplate(ctx) {
48
+ var _ctx_rootDir;
49
+ const projectDir = (_ctx_rootDir = ctx.rootDir) != null ? _ctx_rootDir : process.cwd();
50
+ const packageJson = fsx__namespace.readJsonSync(path__namespace.resolve(projectDir, 'package.json'));
51
+ const json = pluginSdk.jsonLiteral(path__namespace.resolve('package.json'));
52
+ var _packageJson_devDependencies_typescript;
53
+ return json`
54
+ {
55
+ "name": "@intrig/generated",
56
+ "version": "d${Date.now()}",
57
+ "private": true,
58
+ "main": "dist/index.js",
59
+ "types": "dist/index.d.ts",
60
+ "scripts": {
61
+ "build": "swc src -d dist --copy-files --strip-leading-paths && tsc --emitDeclarationOnly"
62
+ },
63
+ "dependencies": {
64
+ "axios": "^1.7.7",
65
+ "date-fns": "^4.1.0",
66
+ "eventsource-parser": "^3.0.2",
67
+ "fast-xml-parser": "^4.5.0",
68
+ "immer": "^10.1.1",
69
+ "loglevel": "1.8.1",
70
+ "module-alias": "^2.2.2",
71
+ "zod": "^3.23.8"
72
+ },
73
+ "devDependencies": {
74
+ "@swc/cli": "^0.7.7",
75
+ "@swc/core": "^1.12.6",
76
+ "@types/node": "^24.0.4",
77
+ "typescript": "${(_packageJson_devDependencies_typescript = packageJson.devDependencies.typescript) != null ? _packageJson_devDependencies_typescript : packageJson.dependencies.typescript}",
78
+ "react": "${packageJson.dependencies.react}",
79
+ "react-dom": "${packageJson.dependencies['react-dom']}"
80
+ },
81
+ "peerDependencies": {
82
+ "react": "^18.0.0 || ^19.0.0",
83
+ "react-dom": "^18.0.0 || ^19.0.0"
84
+ },
85
+ "_moduleAliases": {
86
+ "@intrig/react": "./src"
87
+ },
88
+ "type": "module",
89
+ "exports": {
90
+ ".": {
91
+ "import": "./src/index.js",
92
+ "require": "./src/index.js",
93
+ "types": "./src/index.d.ts"
94
+ },
95
+ "./*": {
96
+ "import": "./src/*.js",
97
+ "require": "./src/*.js",
98
+ "types": "./src/*.d.ts"
99
+ }
100
+ },
101
+ "typesVersions": {
102
+ "*": {
103
+ "*": ["src/*"]
104
+ }
105
+ }
106
+ }
107
+ `;
108
+ }
109
+
110
+ function indexTemplate() {
111
+ const ts = pluginSdk.typescript(path__namespace.resolve("src", "index.ts"));
112
+ return ts`
113
+ export * from './intrig-provider-main';
114
+ export * from './network-state';
115
+ export * from './extra';
116
+ export * from './media-type-utils';
117
+ `;
118
+ }
119
+
120
+ function networkStateTemplate() {
121
+ const ts = pluginSdk.typescript(path__namespace.resolve("src", "network-state.tsx"));
122
+ return ts`import { ZodError } from 'zod';
123
+ import {AxiosResponseHeaders, RawAxiosResponseHeaders} from "axios";
124
+
125
+ /**
126
+ * State of an asynchronous call. Network state follows the state diagram given below.
127
+ *
128
+ *
129
+ * <pre>
130
+ * ┌──────┐
131
+ * ┌─────────────► Init ◄────────────┐
132
+ * │ └▲────┬┘ │
133
+ * │ │ │ │
134
+ * │ Reset Execute │
135
+ * Reset │ │ Reset
136
+ * │ ┌──┴────┴──┐ │
137
+ * │ ┌────► Pending ◄────┐ │
138
+ * │ │ └──┬────┬──┘ │ │
139
+ * │ Execute │ │ Execute │
140
+ * │ │ │ │ │ │
141
+ * │ │ OnSuccess OnError │ │
142
+ * │ ┌────┴──┐ │ │ ┌──┴───┐ │
143
+ * └─┤Success◄────┘ └────►Error ├─┘
144
+ * └───────┘ └──────┘
145
+ *
146
+ * </pre>
147
+ */
148
+ export interface NetworkState<T = unknown> {
149
+ state: 'init' | 'pending' | 'success' | 'error';
150
+ }
151
+
152
+ /**
153
+ * Network call is not yet started
154
+ */
155
+ export interface InitState<T> extends NetworkState<T> {
156
+ state: 'init';
157
+ }
158
+
159
+ /**
160
+ * Checks whether the state is init state
161
+ * @param state
162
+ */
163
+ export function isInit<T>(
164
+ state: NetworkState<T>,
165
+ ): state is InitState<T> {
166
+ return state.state === 'init';
167
+ }
168
+
169
+ /**
170
+ * Initializes a new state.
171
+ *
172
+ * @template T The type of the state.
173
+ * @return {InitState<T>} An object representing the initial state.
174
+ */
175
+ export function init<T>(): InitState<T> {
176
+ return {
177
+ state: 'init',
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Network call is not yet completed
183
+ */
184
+ export interface PendingState<T> extends NetworkState<T> {
185
+ state: 'pending';
186
+ progress?: Progress;
187
+ data?: T;
188
+ }
189
+
190
+ /**
191
+ * Interface representing progress information for an upload or download operation.
192
+ *
193
+ * @typedef {object} Progress
194
+ *
195
+ * @property {'upload' | 'download'} type - The type of the operation.
196
+ *
197
+ * @property {number} loaded - The amount of data that has been loaded so far.
198
+ *
199
+ * @property {number} [total] - The total amount of data to be loaded (if known).
200
+ */
201
+ export interface Progress {
202
+ type?: 'upload' | 'download';
203
+ loaded: number;
204
+ total?: number;
205
+ }
206
+
207
+ /**
208
+ * Checks whether the state is pending state
209
+ * @param state
210
+ */
211
+ export function isPending<T>(
212
+ state: NetworkState<T>,
213
+ ): state is PendingState<T> {
214
+ return state.state === 'pending';
215
+ }
216
+
217
+ /**
218
+ * Generates a PendingState object with a state of "pending".
219
+ *
220
+ * @return {PendingState<T>} An object representing the pending state.
221
+ */
222
+ export function pending<T>(
223
+ progress: Progress | undefined = undefined,
224
+ data: T | undefined = undefined,
225
+ ): PendingState<T> {
226
+ return {
227
+ state: 'pending',
228
+ progress,
229
+ data,
230
+ };
231
+ }
232
+
233
+ /**
234
+ * Network call is completed with success state
235
+ */
236
+ export interface SuccessState<T> extends NetworkState<T> {
237
+ state: 'success';
238
+ data: T;
239
+ headers?: Record<string, any | undefined>;
240
+ }
241
+
242
+ /**
243
+ * Checks whether the state is success response
244
+ * @param state
245
+ */
246
+ export function isSuccess<T>(
247
+ state: NetworkState<T>,
248
+ ): state is SuccessState<T> {
249
+ return state.state === 'success';
250
+ }
251
+
252
+ /**
253
+ * Creates a success state object with the provided data.
254
+ *
255
+ * @param {T} data - The data to be included in the success state.
256
+ * @param headers
257
+ * @return {SuccessState<T>} An object representing a success state containing the provided data.
258
+ */
259
+ export function success<T>(
260
+ data: T,
261
+ headers?: Record<string, any | undefined>,
262
+ ): SuccessState<T> {
263
+ return {
264
+ state: 'success',
265
+ data,
266
+ headers,
267
+ };
268
+ }
269
+
270
+ /**
271
+ * Network call is completed with error response
272
+ */
273
+ export interface ErrorState<T> extends NetworkState<T> {
274
+ state: 'error';
275
+ error: IntrigError;
276
+ }
277
+
278
+ /**
279
+ * Checks whether the state is error state
280
+ * @param state
281
+ */
282
+ export function isError<T>(
283
+ state: NetworkState<T>,
284
+ ): state is ErrorState<T> {
285
+ return state.state === 'error';
286
+ }
287
+
288
+ /**
289
+ * Constructs an ErrorState object representing an error.
290
+ *
291
+ * @param {any} error - The error object or message.
292
+ * @param {string} [statusCode] - An optional status code associated with the error.
293
+ * @return {ErrorState<T>} An object representing the error state.
294
+ */
295
+ export function error<T>(
296
+ error: IntrigError,
297
+ ): ErrorState<T> {
298
+ return {
299
+ state: 'error',
300
+ error
301
+ };
302
+ }
303
+
304
+ /**
305
+ * Represents the base structure for error information in the application.
306
+ *
307
+ * This interface is used to define the type of error encountered in various contexts.
308
+ *
309
+ * Properties:
310
+ * - type: Specifies the category of the error which determines its nature and source.
311
+ * - 'http': Indicates the error is related to HTTP operations.
312
+ * - 'network': Indicates the error occurred due to network issues.
313
+ * - 'request-validation': Represents errors that occurred during request validation.
314
+ * - 'response-validation': Represents errors that occurred during response validation.
315
+ * - 'config': Pertains to errors associated with configuration issues.
316
+ */
317
+ export interface IntrigErrorBase {
318
+ type: 'http' | 'network' | 'request-validation' | 'response-validation' | 'config';
319
+ }
320
+
321
+ /**
322
+ * Interface representing an HTTP-related error.
323
+ * Extends the \`IntrigErrorBase\` base interface to provide information specific to HTTP errors.
324
+ *
325
+ * @property type - The type identifier for the error, always set to 'http'.
326
+ * @property status - The HTTP status code associated with the error.
327
+ * @property url - The URL that caused the error.
328
+ * @property method - The HTTP method used in the request that resulted in the error.
329
+ * @property headers - Optional. The HTTP headers relevant to the request and/or response, represented as a record of key-value pairs.
330
+ * @property body - Optional. The parsed body of the server error, if available.
331
+ */
332
+ export interface HttpError extends IntrigErrorBase {
333
+ type: 'http';
334
+ status: number;
335
+ url: string;
336
+ method: string;
337
+ headers?: RawAxiosResponseHeaders | AxiosResponseHeaders;
338
+ body?: unknown; // parsed server error body if any
339
+ }
340
+
341
+ /**
342
+ * Determines if the given error is an HTTP error.
343
+ *
344
+ * @param {IntrigError} error - The error object to check.
345
+ * @return {boolean} Returns true if the error is an instance of HttpError; otherwise, false.
346
+ */
347
+ export function isHttpError(error: IntrigError): error is HttpError {
348
+ return error.type === 'http';
349
+ }
350
+
351
+ /**
352
+ * Creates an object representing an HTTP error.
353
+ *
354
+ * @param {number} status - The HTTP status code of the error.
355
+ * @param {string} url - The URL associated with the HTTP error.
356
+ * @param {string} method - The HTTP method that resulted in the error.
357
+ * @param {Record<string, string | string[] | undefined>} [headers] - Optional headers involved in the HTTP error.
358
+ * @param {unknown} [body] - Optional body data related to the HTTP error.
359
+ * @return {HttpError} An object encapsulating details about the HTTP error.
360
+ */
361
+ export function httpError(
362
+ status: number,
363
+ url: string,
364
+ method: string,
365
+ headers?: RawAxiosResponseHeaders | AxiosResponseHeaders,
366
+ body?: unknown
367
+ ): HttpError {
368
+ return {
369
+ type: 'http',
370
+ status,
371
+ url,
372
+ method,
373
+ headers,
374
+ body
375
+ };
376
+ }
377
+
378
+ /**
379
+ * Represents a network-related error. This error type is used to indicate issues during network operations.
380
+ * Extends the base error functionality from IntrigErrorBase.
381
+ *
382
+ * Properties:
383
+ * - \`type\`: Specifies the type of error as 'network'.
384
+ * - \`reason\`: Indicates the specific reason for the network error. Possible values include:
385
+ * - 'timeout': Occurs when the network request times out.
386
+ * - 'dns': Represents DNS resolution issues.
387
+ * - 'offline': Indicates the device is offline or has no network connectivity.
388
+ * - 'aborted': The network request was aborted.
389
+ * - 'unknown': An unspecified network issue occurred.
390
+ * - \`request\`: Optional property representing the network request that caused the error. Its structure can vary depending on the implementation context.
391
+ */
392
+ export interface NetworkError extends IntrigErrorBase {
393
+ type: 'network';
394
+ reason: 'timeout' | 'dns' | 'offline' | 'aborted' | 'unknown';
395
+ request?: any;
396
+ }
397
+
398
+ /**
399
+ * Determines if the provided error is of type NetworkError.
400
+ *
401
+ * @param {IntrigError} error - The error object to be checked.
402
+ * @return {boolean} Returns true if the error is of type NetworkError, otherwise false.
403
+ */
404
+ export function isNetworkError(error: IntrigError): error is NetworkError {
405
+ return error.type === 'network';
406
+ }
407
+
408
+ /**
409
+ * Creates a network error object with the specified reason and optional request details.
410
+ *
411
+ * @param {'timeout' | 'dns' | 'offline' | 'aborted' | 'unknown'} reason - The reason for the network error.
412
+ * @param {any} [request] - Optional information about the network request that caused the error.
413
+ * @return {NetworkError} An object representing the network error.
414
+ */
415
+ export function networkError(
416
+ reason: 'timeout' | 'dns' | 'offline' | 'aborted' | 'unknown',
417
+ request?: any,
418
+ ): NetworkError {
419
+ return {
420
+ type: 'network',
421
+ reason,
422
+ request,
423
+ };
424
+ }
425
+
426
+ /**
427
+ * Represents an error that occurs during request validation.
428
+ *
429
+ * This interface extends the \`IntrigErrorBase\` to provide
430
+ * additional details about validation errors in the request.
431
+ *
432
+ * \`RequestValidationError\` includes a specific error type
433
+ * identifier, details about the validation error, and information
434
+ * about the fields that failed validation.
435
+ *
436
+ * The \`type\` property indicates the error type as 'request-validation'.
437
+ * The \`error\` property holds the ZodError object with detailed validation
438
+ * errors from the Zod library.
439
+ * The \`fieldErrors\` property is a mapping of field names to an array
440
+ * of validation error messages for that field.
441
+ */
442
+ export interface RequestValidationError extends IntrigErrorBase {
443
+ type: 'request-validation';
444
+ error: ZodError;
445
+ }
446
+
447
+ /**
448
+ * Checks if the given error is of type RequestValidationError.
449
+ *
450
+ * @param {IntrigError} error - The error object to be checked.
451
+ * @return {boolean} Returns true if the error is a RequestValidationError; otherwise, false.
452
+ */
453
+ export function isRequestValidationError(error: IntrigError): error is RequestValidationError {
454
+ return error.type === 'request-validation';
455
+ }
456
+
457
+ /**
458
+ * Constructs a RequestValidationError object, capturing details about validation errors.
459
+ *
460
+ * @param {ZodError} error - The primary Zod validation error object containing detailed error information.
461
+ * @param {Record<string, string[]>} fieldErrors - An object mapping field names to arrays of validation error messages.
462
+ * @return {RequestValidationError} An object representing the request validation error, including the error type, detailed error, and field-specific errors.
463
+ */
464
+ export function requestValidationError(
465
+ error: ZodError
466
+ ): RequestValidationError {
467
+ return {
468
+ type: 'request-validation',
469
+ error
470
+ };
471
+ }
472
+
473
+ /**
474
+ * Describes an error encountered during response validation, typically
475
+ * when the structure or content of a response does not meet the expected schema.
476
+ *
477
+ * This interface extends the \`IntrigErrorBase\` to provide additional
478
+ * details specific to validation issues.
479
+ *
480
+ * The \`type\` property is a discriminative field, always set to 'response-validation',
481
+ * for identifying this specific kind of error.
482
+ *
483
+ * The \`error\` property contains a \`ZodError\` object, which provides structured
484
+ * details about the validation failure, including paths and specific issues.
485
+ *
486
+ * The optional \`raw\` property may hold the unprocessed or unparsed response data,
487
+ * which can be useful for debugging and troubleshooting.
488
+ */
489
+ export interface ResponseValidationError extends IntrigErrorBase {
490
+ type: 'response-validation';
491
+ error: ZodError;
492
+ // optional: raw/unparsed response for troubleshooting
493
+ raw?: unknown;
494
+ }
495
+
496
+ /**
497
+ * Determines if the provided error is of type ResponseValidationError.
498
+ *
499
+ * @param {IntrigError} error - The error object to be evaluated.
500
+ * @return {boolean} Returns true if the error is a ResponseValidationError, otherwise false.
501
+ */
502
+ export function isResponseValidationError(error: IntrigError): error is ResponseValidationError {
503
+ return error.type === 'response-validation';
504
+ }
505
+
506
+ /**
507
+ * Constructs a ResponseValidationError object to represent a response validation failure.
508
+ *
509
+ * @param {ZodError} error - The error object representing the validation issue.
510
+ * @param {unknown} [raw] - Optional raw data related to the validation error.
511
+ * @return {ResponseValidationError} An object containing the type of error, the validation error object, and optional raw data.
512
+ */
513
+ export function responseValidationError(
514
+ error: ZodError,
515
+ raw?: unknown,
516
+ ): ResponseValidationError {
517
+ return {
518
+ type: 'response-validation',
519
+ error,
520
+ raw,
521
+ };
522
+ }
523
+
524
+ /**
525
+ * Represents an error related to configuration issues.
526
+ * ConfigError is an extension of IntrigErrorBase, designed specifically
527
+ * to handle and provide details about errors encountered in application
528
+ * configuration.
529
+ *
530
+ * @interface ConfigError
531
+ * @extends IntrigErrorBase
532
+ *
533
+ * @property type - Identifies the error type as 'config'.
534
+ * @property message - Describes the details of the configuration error encountered.
535
+ */
536
+ export interface ConfigError extends IntrigErrorBase {
537
+ type: 'config';
538
+ message: string;
539
+ }
540
+
541
+ /**
542
+ * Determines if the provided error is a configuration error.
543
+ *
544
+ * @param {IntrigError} error - The error object to be checked.
545
+ * @return {boolean} Returns true if the error is of type 'ConfigError', otherwise false.
546
+ */
547
+ export function isConfigError(error: IntrigError): error is ConfigError {
548
+ return error.type === 'config';
549
+ }
550
+
551
+ /**
552
+ * Generates a configuration error object with a specified error message.
553
+ *
554
+ * @param {string} message - The error message to be associated with the configuration error.
555
+ * @return {ConfigError} The configuration error object containing the error type and message.
556
+ */
557
+ export function configError(message: string): ConfigError {
558
+ return {
559
+ type: 'config',
560
+ message,
561
+ };
562
+ }
563
+
564
+ /**
565
+ * Represents a union type for errors that may occur while handling HTTP requests,
566
+ * network operations, request and response validations, or configuration issues.
567
+ *
568
+ * This type encompasses various error types to provide a unified representation
569
+ * for different error scenarios during the execution of a program.
570
+ *
571
+ * Types:
572
+ * - HttpError: Represents an error occurred in HTTP responses.
573
+ * - NetworkError: Represents an error related to underlying network operations.
574
+ * - RequestValidationError: Represents an error in request validation logic.
575
+ * - ResponseValidationError: Represents an error in response validation logic.
576
+ * - ConfigError: Represents an error related to configuration issues.
577
+ */
578
+ export type IntrigError =
579
+ | HttpError
580
+ | NetworkError
581
+ | RequestValidationError
582
+ | ResponseValidationError
583
+ | ConfigError;
584
+
585
+ /**
586
+ * Represents an error state with additional contextual information.
587
+ *
588
+ * @typedef {Object} ErrorWithContext
589
+ * @template T
590
+ * @extends ErrorState<T>
591
+ *
592
+ * @property {string} source - The origin of the error.
593
+ * @property {string} operation - The operation being performed when the error occurred.
594
+ * @property {string} key - A unique key identifying the specific error instance.
595
+ */
596
+ export interface ErrorWithContext<T = unknown>
597
+ extends ErrorState<T> {
598
+ source: string;
599
+ operation: string;
600
+ key: string;
601
+ }
602
+
603
+ /**
604
+ * Represents an action in the network context.
605
+ *
606
+ * @template T - The type of data associated with the network action
607
+ *
608
+ * @property {NetworkState<any>} state - The current state of the network action
609
+ * @property {string} key - The unique identifier for the network action
610
+ */
611
+ export interface NetworkAction<T> {
612
+ key: string;
613
+ source: string;
614
+ operation: string;
615
+ state: NetworkState<T>;
616
+ handled?: boolean;
617
+ }
618
+
619
+ type HookWithKey = {
620
+ key: string;
621
+ };
622
+
623
+ export type UnitHookOptions =
624
+ | { key?: string; fetchOnMount?: false; clearOnUnmount?: boolean }
625
+ | {
626
+ key?: string;
627
+ fetchOnMount: true;
628
+ params?: Record<string, any>;
629
+ clearOnUnmount?: boolean;
630
+ };
631
+ export type UnitHook = ((
632
+ options: UnitHookOptions,
633
+ ) => [
634
+ NetworkState<never>,
635
+ (params?: Record<string, any>) => DispatchState<any>,
636
+ () => void,
637
+ ]) &
638
+ HookWithKey;
639
+ export type ConstantHook<T> = ((
640
+ options: UnitHookOptions,
641
+ ) => [
642
+ NetworkState<T>,
643
+ (params?: Record<string, any>) => DispatchState<any>,
644
+ () => void,
645
+ ]) &
646
+ HookWithKey;
647
+
648
+ export type UnaryHookOptions<P> =
649
+ | { key?: string; fetchOnMount?: false; clearOnUnmount?: boolean }
650
+ | { key?: string; fetchOnMount: true; params: P; clearOnUnmount?: boolean };
651
+ export type UnaryProduceHook<P> = ((
652
+ options?: UnaryHookOptions<P>,
653
+ ) => [NetworkState<never>, (params: P) => DispatchState<any>, () => void]) &
654
+ HookWithKey;
655
+ export type UnaryFunctionHook<P, T> = ((
656
+ options?: UnaryHookOptions<P>,
657
+ ) => [NetworkState<T>, (params: P) => DispatchState<any>, () => void]) &
658
+ HookWithKey;
659
+
660
+ export type BinaryHookOptions<P, B> =
661
+ | { key?: string; fetchOnMount?: false; clearOnUnmount?: boolean }
662
+ | {
663
+ key?: string;
664
+ fetchOnMount: true;
665
+ params: P;
666
+ body: B;
667
+ clearOnUnmount?: boolean;
668
+ };
669
+ export type BinaryProduceHook<P, B> = ((
670
+ options?: BinaryHookOptions<P, B>,
671
+ ) => [
672
+ NetworkState<never>,
673
+ (body: B, params: P) => DispatchState<any>,
674
+ () => void,
675
+ ]) &
676
+ HookWithKey;
677
+ export type BinaryFunctionHook<P, B, T> = ((
678
+ options?: BinaryHookOptions<P, B>,
679
+ ) => [
680
+ NetworkState<T>,
681
+ (body: B, params: P) => DispatchState<any>,
682
+ () => void,
683
+ ]) &
684
+ HookWithKey;
685
+
686
+ export type IntrigHookOptions<P = undefined, B = undefined> =
687
+ | UnitHookOptions
688
+ | UnaryHookOptions<P>
689
+ | BinaryHookOptions<P, B>;
690
+ export type IntrigHook<P = undefined, B = undefined, T = any> =
691
+ | UnitHook
692
+ | ConstantHook<T>
693
+ | UnaryProduceHook<P>
694
+ | UnaryFunctionHook<P, T>
695
+ | BinaryProduceHook<P, B>
696
+ | BinaryFunctionHook<P, B, T>;
697
+
698
+ export interface AsyncRequestOptions {
699
+ hydrate?: boolean;
700
+ key?: string;
701
+ }
702
+
703
+ // Async hook variants for transient (promise-returning) network requests
704
+
705
+ export type UnaryFunctionAsyncHook<P, T> = (() => [
706
+ (params: P) => Promise<T>,
707
+ () => void,
708
+ ]) & {
709
+ key: string;
710
+ };
711
+
712
+ export type BinaryFunctionAsyncHook<P, B, T> = (() => [
713
+ (body: B, params: P) => Promise<T>,
714
+ () => void,
715
+ ]) & {
716
+ key: string;
717
+ };
718
+
719
+ export type UnaryProduceAsyncHook<P> = (() => [
720
+ (params: P) => Promise<void>,
721
+ () => void,
722
+ ]) & {
723
+ key: string;
724
+ };
725
+
726
+ export type BinaryProduceAsyncHook<P, B> = (() => [
727
+ (body: B, params: P) => Promise<void>,
728
+ () => void,
729
+ ]) & {
730
+ key: string;
731
+ };
732
+
733
+ /**
734
+ * Represents the dispatch state of a process.
735
+ *
736
+ * @template T The type of the state information.
737
+ * @interface
738
+ *
739
+ * @property {string} state The current state of the dispatch process.
740
+ */
741
+ export interface DispatchState<T> {
742
+ state: string;
743
+ }
744
+
745
+ /**
746
+ * Represents a successful dispatch state.
747
+ *
748
+ * @template T - Type of the data associated with the dispatch.
749
+ *
750
+ * @extends DispatchState<T>
751
+ *
752
+ * @property {string} state - The state of the dispatch, always 'success'.
753
+ */
754
+ export interface SuccessfulDispatch<T> extends DispatchState<T> {
755
+ state: 'success';
756
+ }
757
+
758
+ /**
759
+ * Indicates a successful dispatch state.
760
+ *
761
+ * @return {DispatchState<T>} An object representing a successful state.
762
+ */
763
+ export function successfulDispatch<T>(): DispatchState<T> {
764
+ return {
765
+ state: 'success',
766
+ };
767
+ }
768
+
769
+ /**
770
+ * Determines if the provided dispatch state represents a successful dispatch.
771
+ *
772
+ * @param {DispatchState<T>} value - The dispatch state to check.
773
+ * @return {value is SuccessfulDispatch<T>} - True if the dispatch state indicates success, false otherwise.
774
+ */
775
+ export function isSuccessfulDispatch<T>(
776
+ value: DispatchState<T>,
777
+ ): value is SuccessfulDispatch<T> {
778
+ return value.state === 'success';
779
+ }
780
+
781
+ /**
782
+ * ValidationError interface represents a specific type of dispatch state
783
+ * where a validation error has occurred.
784
+ *
785
+ * @typeparam T - The type of the data associated with this dispatch state.
786
+ */
787
+ export interface ValidationError<T> extends DispatchState<T> {
788
+ state: 'validation-error';
789
+ error: any;
790
+ }
791
+
792
+ /**
793
+ * Generates a ValidationError object.
794
+ *
795
+ * @param error The error details that caused the validation to fail.
796
+ * @return The ValidationError object containing the error state and details.
797
+ */
798
+ export function validationError<T>(error: any): ValidationError<T> {
799
+ return {
800
+ state: 'validation-error',
801
+ error,
802
+ };
803
+ }
804
+
805
+ /**
806
+ * Determines if a provided DispatchState object is a ValidationError.
807
+ *
808
+ * @param {DispatchState<T>} value - The DispatchState object to evaluate.
809
+ * @return {boolean} - Returns true if the provided DispatchState object is a ValidationError, otherwise returns false.
810
+ */
811
+ export function isValidationError<T>(
812
+ value: DispatchState<T>,
813
+ ): value is ValidationError<T> {
814
+ return value.state === 'validation-error';
815
+ }
816
+
817
+ `;
818
+ }
819
+
820
+ function contextTemplate(apisToSync) {
821
+ const ts = pluginSdk.typescript(path__namespace.resolve("src", "intrig-context.ts"));
822
+ const configType = `{
823
+ defaults?: DefaultConfigs,
824
+ ${apisToSync.map((a)=>`${a.id}?: DefaultConfigs`).join(",\n ")}
825
+ }`;
826
+ return ts`
827
+ import { NetworkAction, NetworkState } from '@intrig/react/network-state';
828
+ import { AxiosProgressEvent, AxiosRequestConfig } from 'axios';
829
+ import { ZodSchema, ZodType, ZodTypeDef } from 'zod';
830
+ import { createContext, useContext, Dispatch } from 'react';
831
+ import { DefaultConfigs } from './interfaces';
832
+
833
+ type GlobalState = Record<string, NetworkState>;
834
+
835
+ interface RequestType<T = any> extends AxiosRequestConfig {
836
+ originalData?: T; // Keeps track of the original data type.
837
+ key: string;
838
+ source: string
839
+ }
840
+
841
+ export type SchemaOf<T> = ZodType<T, ZodTypeDef, any>;
842
+
843
+ /**
844
+ * Defines the ContextType interface for managing global state, dispatching actions,
845
+ * and holding a collection of Axios instances.
846
+ *
847
+ * @interface ContextType
848
+ * @property {GlobalState} state - The global state of the application.
849
+ * @property {Dispatch<NetworkAction<unknown>>} dispatch - The dispatch function to send network actions.
850
+ * @property {Record<string, AxiosInstance>} axios - A record of Axios instances for making HTTP requests.
851
+ */
852
+ export interface ContextType {
853
+ state: GlobalState;
854
+ filteredState: GlobalState;
855
+ dispatch: Dispatch<NetworkAction<unknown>>;
856
+ configs: ${configType};
857
+ execute: <T>(request: RequestType, dispatch: (state: NetworkState<T>) => void, schema: SchemaOf<T> | undefined, errorSchema: SchemaOf<T> | undefined) => Promise<void>;
858
+ }
859
+
860
+ /**
861
+ * Context object created using \`createContext\` function. Provides a way to share state, dispatch functions,
862
+ * and axios instance across components without having to pass props down manually at every level.
863
+ *
864
+ * @type {ContextType}
865
+ */
866
+ const Context = createContext<ContextType>({
867
+ state: {},
868
+ filteredState: {},
869
+ dispatch() {
870
+ //intentionally left blank
871
+ },
872
+ configs: {},
873
+ async execute() {
874
+ //intentionally left blank
875
+ },
876
+ });
877
+
878
+ export function useIntrigContext() {
879
+ return useContext(Context);
880
+ }
881
+
882
+ export {
883
+ Context,
884
+ GlobalState,
885
+ RequestType,
886
+ }
887
+ `;
888
+ }
889
+
890
+ function reactLoggerTemplate() {
891
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', 'logger.ts'));
892
+ return ts`
893
+ // logger.ts
894
+
895
+ import log, { LogLevelDesc } from 'loglevel';
896
+
897
+ // Extend the global interfaces
898
+ declare global {
899
+ interface Window {
900
+ setLogLevel?: (level: LogLevelDesc) => void;
901
+ }
902
+ }
903
+
904
+ // 1) Build-time default via Vite (if available)
905
+ // Cast import.meta to any to avoid TS errors if env isn't typed
906
+ const buildDefault =
907
+ typeof import.meta !== 'undefined'
908
+ ? ((import.meta as any).env?.VITE_LOG_LEVEL as string | undefined)
909
+ : undefined;
910
+
911
+ // 2) Stored default in localStorage
912
+ const storedLevel =
913
+ typeof localStorage !== 'undefined'
914
+ ? (localStorage.getItem('LOG_LEVEL') as string | null)
915
+ : null;
916
+
917
+ // Determine initial log level: build-time → stored → 'error'
918
+ const defaultLevel: LogLevelDesc =
919
+ (buildDefault as LogLevelDesc) ?? (storedLevel as LogLevelDesc) ?? 'error';
920
+
921
+ // Apply initial level
922
+ log.setLevel(defaultLevel);
923
+
924
+ // Expose a console setter to change level at runtime
925
+ if (typeof window !== 'undefined') {
926
+ window.setLogLevel = (level: LogLevelDesc): void => {
927
+ log.setLevel(level);
928
+ try {
929
+ localStorage.setItem('LOG_LEVEL', String(level));
930
+ } catch {
931
+ // ignore if storage is unavailable
932
+ }
933
+ console.log(\`✏️ loglevel set to '\${level}'\`);
934
+ };
935
+ }
936
+
937
+ // Consistent wrapper API
938
+ export const logger = {
939
+ info: (msg: unknown, meta?: unknown): void =>
940
+ meta ? log.info(msg, meta) : log.info(msg),
941
+ warn: (msg: unknown, meta?: unknown): void =>
942
+ meta ? log.warn(msg, meta) : log.warn(msg),
943
+ error: (msg: unknown, meta?: unknown): void =>
944
+ meta ? log.error(msg, meta) : log.error(msg),
945
+ debug: (msg: unknown, meta?: unknown): void =>
946
+ meta ? log.debug(msg, meta) : log.debug(msg),
947
+ };
948
+
949
+ export default logger;
950
+
951
+ `;
952
+ }
953
+
954
+ function reactExtraTemplate() {
955
+ const ts = pluginSdk.typescript(path__namespace.resolve("src", "extra.ts"));
956
+ return ts`import {
957
+ BinaryFunctionHook,
958
+ BinaryHookOptions,
959
+ BinaryProduceHook,
960
+ ConstantHook,
961
+ error,
962
+ init,
963
+ IntrigHook,
964
+ IntrigHookOptions,
965
+ isSuccess,
966
+ NetworkState,
967
+ pending,
968
+ success,
969
+ UnaryFunctionHook,
970
+ UnaryHookOptions,
971
+ UnaryProduceHook,
972
+ UnitHook,
973
+ UnitHookOptions,
974
+ } from '@intrig/react/network-state';
975
+ import {
976
+ useCallback,
977
+ useEffect,
978
+ useId,
979
+ useMemo,
980
+ useState,
981
+ } from 'react';
982
+ import { useIntrigContext } from '@intrig/react/intrig-context';
983
+
984
+ /**
985
+ * A custom hook that manages and returns the network state of a promise-based function,
986
+ * providing a way to execute the function and clear its state.
987
+ *
988
+ * @param fn The promise-based function whose network state is to be managed. It should be a function that returns a promise.
989
+ * @param key An optional identifier for the network state. Defaults to 'default'.
990
+ * @return A tuple containing the current network state, a function to execute the promise, and a function to clear the state.
991
+ */
992
+ export function useAsNetworkState<T, F extends (...args: any) => Promise<T>>(
993
+ fn: F,
994
+ options: any = {},
995
+ ): [NetworkState<T>, (...params: Parameters<F>) => void, () => void] {
996
+ const id = useId();
997
+
998
+ const context = useIntrigContext();
999
+
1000
+ const key = options.key ?? 'default';
1001
+
1002
+ const networkState = useMemo(() => {
1003
+ return context.state?.[${"`promiseState:${id}:${key}}`"}] ?? init();
1004
+ }, [context.state?.[${"`promiseState:${id}:${key}}`"}]]);
1005
+
1006
+ const dispatch = useCallback(
1007
+ (state: NetworkState<T>) => {
1008
+ context.dispatch({ key, operation: id, source: 'promiseState', state });
1009
+ },
1010
+ [key, context.dispatch],
1011
+ );
1012
+
1013
+ const execute = useCallback((...args: any[]) => {
1014
+ dispatch(pending());
1015
+ return fn(...args).then(
1016
+ (data) => {
1017
+ dispatch(success(data));
1018
+ },
1019
+ (e) => {
1020
+ dispatch(error(e));
1021
+ },
1022
+ );
1023
+ }, []);
1024
+
1025
+ const clear = useCallback(() => {
1026
+ dispatch(init());
1027
+ }, []);
1028
+
1029
+ return [networkState, execute, clear];
1030
+ }
1031
+
1032
+ /**
1033
+ * A custom hook that resolves the value from the provided hook's state and updates it whenever the state changes.
1034
+ *
1035
+ * @param {IntrigHook<P, B, T>} hook - The hook that provides the state to observe and resolve data from.
1036
+ * @param options
1037
+ * @return {T | undefined} The resolved value from the hook's state or undefined if the state is not successful.
1038
+ */
1039
+ export function useResolvedValue(
1040
+ hook: UnitHook,
1041
+ options: UnitHookOptions,
1042
+ ): undefined;
1043
+
1044
+ export function useResolvedValue<T>(
1045
+ hook: ConstantHook<T>,
1046
+ options: UnitHookOptions,
1047
+ ): T | undefined;
1048
+
1049
+ export function useResolvedValue<P>(
1050
+ hook: UnaryProduceHook<P>,
1051
+ options: UnaryHookOptions<P>,
1052
+ ): undefined;
1053
+
1054
+ export function useResolvedValue<P, T>(
1055
+ hook: UnaryFunctionHook<P, T>,
1056
+ options: UnaryHookOptions<P>,
1057
+ ): T | undefined;
1058
+
1059
+ export function useResolvedValue<P, B>(
1060
+ hook: BinaryProduceHook<P, B>,
1061
+ options: BinaryHookOptions<P, B>,
1062
+ ): undefined;
1063
+
1064
+ export function useResolvedValue<P, B, T>(
1065
+ hook: BinaryFunctionHook<P, B, T>,
1066
+ options: BinaryHookOptions<P, B>,
1067
+ ): T | undefined;
1068
+
1069
+ // **Implementation**
1070
+ export function useResolvedValue<P, B, T>(
1071
+ hook: IntrigHook<P, B, T>,
1072
+ options: IntrigHookOptions<P, B>,
1073
+ ): T | undefined {
1074
+ const [value, setValue] = useState<T | undefined>();
1075
+
1076
+ const [state] = hook(options as any); // Ensure compatibility with different hook types
1077
+
1078
+ useEffect(() => {
1079
+ if (isSuccess(state)) {
1080
+ setValue(state.data);
1081
+ } else {
1082
+ setValue(undefined);
1083
+ }
1084
+ }, [state]);
1085
+
1086
+ return value;
1087
+ }
1088
+
1089
+ /**
1090
+ * A custom hook that resolves and caches the value from a successful state provided by the given hook.
1091
+ * The state is updated only when it is in a successful state.
1092
+ *
1093
+ * @param {IntrigHook<P, B, T>} hook - The hook that provides the state to observe and cache data from.
1094
+ * @param options
1095
+ * @return {T | undefined} The cached value from the hook's state or undefined if the state is not successful.
1096
+ */
1097
+ export function useResolvedCachedValue(
1098
+ hook: UnitHook,
1099
+ options: UnitHookOptions,
1100
+ ): undefined;
1101
+
1102
+ export function useResolvedCachedValue<T>(
1103
+ hook: ConstantHook<T>,
1104
+ options: UnitHookOptions,
1105
+ ): T | undefined;
1106
+
1107
+ export function useResolvedCachedValue<P>(
1108
+ hook: UnaryProduceHook<P>,
1109
+ options: UnaryHookOptions<P>,
1110
+ ): undefined;
1111
+
1112
+ export function useResolvedCachedValue<P, T>(
1113
+ hook: UnaryFunctionHook<P, T>,
1114
+ options: UnaryHookOptions<P>,
1115
+ ): T | undefined;
1116
+
1117
+ export function useResolvedCachedValue<P, B>(
1118
+ hook: BinaryProduceHook<P, B>,
1119
+ options: BinaryHookOptions<P, B>,
1120
+ ): undefined;
1121
+
1122
+ export function useResolvedCachedValue<P, B, T>(
1123
+ hook: BinaryFunctionHook<P, B, T>,
1124
+ options: BinaryHookOptions<P, B>,
1125
+ ): T | undefined;
1126
+
1127
+ // **Implementation**
1128
+ export function useResolvedCachedValue<P, B, T>(
1129
+ hook: IntrigHook<P, B, T>,
1130
+ options: IntrigHookOptions<P, B>,
1131
+ ): T | undefined {
1132
+ const [cachedValue, setCachedValue] = useState<T | undefined>();
1133
+
1134
+ const [state] = hook(options as any); // Ensure compatibility with different hook types
1135
+
1136
+ useEffect(() => {
1137
+ if (isSuccess(state)) {
1138
+ setCachedValue(state.data);
1139
+ }
1140
+ // Do not clear cached value if state is unsuccessful
1141
+ }, [state]);
1142
+
1143
+ return cachedValue;
1144
+ }
1145
+
1146
+ `;
1147
+ }
1148
+
1149
+ function reactMediaTypeUtilsTemplate() {
1150
+ const ts = pluginSdk.typescript(path__namespace.resolve("src", "media-type-utils.ts"));
1151
+ return ts`
1152
+ import { ZodSchema } from 'zod';
1153
+ import { XMLParser } from 'fast-xml-parser';
1154
+ type EncodersSync = {
1155
+ [k: string]: <T>(request: T,
1156
+ mediaType: string,
1157
+ schema: ZodSchema) => any;
1158
+ };
1159
+
1160
+ const encoders: EncodersSync = {};
1161
+
1162
+ export function encode<T>(request: T, mediaType: string, schema: ZodSchema) {
1163
+ if (encoders[mediaType]) {
1164
+ return encoders[mediaType](request, mediaType, schema);
1165
+ }
1166
+ return request;
1167
+ }
1168
+
1169
+ encoders['application/json'] = (request, mediaType, schema) => {
1170
+ return request;
1171
+ }
1172
+
1173
+ function appendFormData(
1174
+ formData: FormData,
1175
+ data: any,
1176
+ parentKey: string
1177
+ ): void {
1178
+ if (data instanceof Blob || typeof data === 'string') {
1179
+ formData.append(parentKey, data);
1180
+ } else if (data !== null && typeof data === 'object') {
1181
+ if (Array.isArray(data)) {
1182
+ data.forEach((item: any, index: number) => {
1183
+ const key = ${"`${parentKey}`"};
1184
+ appendFormData(formData, item, key);
1185
+ });
1186
+ } else {
1187
+ Object.keys(data).forEach((key: string) => {
1188
+ const newKey = parentKey ? ${"`${parentKey}[${key}]`"} : key;
1189
+ appendFormData(formData, data[key], newKey);
1190
+ });
1191
+ }
1192
+ } else {
1193
+ formData.append(parentKey, data == null ? '' : String(data));
1194
+ }
1195
+ }
1196
+
1197
+ encoders['multipart/form-data'] = (request, mediaType, schema) => {
1198
+ const _request = request as Record<string, any>;
1199
+ const formData = new FormData();
1200
+ Object.keys(_request).forEach((key: string) => {
1201
+ appendFormData(formData, _request[key], key);
1202
+ });
1203
+ return formData;
1204
+ }
1205
+
1206
+ encoders['application/octet-stream'] = (request, mediaType, schema) => {
1207
+ return request;
1208
+ }
1209
+
1210
+ encoders['application/x-www-form-urlencoded'] = (request, mediaType, schema) => {
1211
+ const formData = new FormData();
1212
+ for (const key in request) {
1213
+ const value = request[key];
1214
+ formData.append(key, value instanceof Blob || typeof value === 'string' ? value : String(value));
1215
+ }
1216
+ return formData;
1217
+ }
1218
+
1219
+ type Transformers = {
1220
+ [k: string]: <T>(
1221
+ request: Request,
1222
+ mediaType: string,
1223
+ schema: ZodSchema
1224
+ ) => Promise<T>;
1225
+ };
1226
+
1227
+ const transformers: Transformers = {};
1228
+
1229
+ export function transform<T>(
1230
+ request: Request,
1231
+ mediaType: string,
1232
+ schema: ZodSchema
1233
+ ): Promise<T> {
1234
+ if (transformers[mediaType]) {
1235
+ return transformers[mediaType](request, mediaType, schema);
1236
+ }
1237
+ throw new Error(\`Unsupported media type: \` + mediaType);
1238
+ }
1239
+
1240
+ transformers['application/json'] = async (request, mediaType, schema) => {
1241
+ return schema.parse(await request.json());
1242
+ };
1243
+
1244
+ transformers['multipart/form-data'] = async (request, mediaType, schema) => {
1245
+ const formData = await request.formData();
1246
+ const content: Record<string, any> = {};
1247
+ formData.forEach((value, key) => {
1248
+ if (content[key]) {
1249
+ if (!(content[key] instanceof Array)) {
1250
+ content[key] = [content[key]];
1251
+ }
1252
+ content[key].push(value);
1253
+ } else {
1254
+ content[key] = value
1255
+ }
1256
+ });
1257
+ return schema.parse(content);
1258
+ };
1259
+
1260
+ transformers['application/octet-stream'] = async (
1261
+ request,
1262
+ mediaType,
1263
+ schema
1264
+ ) => {
1265
+ return schema.parse(
1266
+ new Blob([await request.arrayBuffer()], {
1267
+ type: 'application/octet-stream',
1268
+ })
1269
+ );
1270
+ };
1271
+
1272
+ transformers['application/x-www-form-urlencoded'] = async (
1273
+ request,
1274
+ mediaType,
1275
+ schema
1276
+ ) => {
1277
+ const formData = await request.formData();
1278
+ const content: Record<string, any> = {};
1279
+ formData.forEach((value, key) => (content[key] = value));
1280
+ return schema.parse(content);
1281
+ };
1282
+
1283
+ transformers['application/xml'] = async (request, mediaType, schema) => {
1284
+ const xmlParser = new XMLParser();
1285
+ const content = await xmlParser.parse(await request.text());
1286
+ return schema.parse(await content);
1287
+ };
1288
+
1289
+ transformers['text/plain'] = async (request, mediaType, schema) => {
1290
+ return schema.parse(await request.text());
1291
+ };
1292
+
1293
+ transformers['text/html'] = async (request, mediaType, schema) => {
1294
+ return schema.parse(await request.text());
1295
+ };
1296
+
1297
+ transformers['text/css'] = async (request, mediaType, schema) => {
1298
+ return schema.parse(await request.text());
1299
+ };
1300
+
1301
+ transformers['text/javascript'] = async (request, mediaType, schema) => {
1302
+ return schema.parse(await request.text());
1303
+ };
1304
+
1305
+ type ResponseTransformers = {
1306
+ [k: string]: <T>(
1307
+ data: any,
1308
+ mediaType: string,
1309
+ schema: ZodSchema
1310
+ ) => Promise<T>;
1311
+ };
1312
+
1313
+ const responseTransformers: ResponseTransformers = {};
1314
+
1315
+ responseTransformers['application/json'] = async (data, mediaType, schema) => {
1316
+ return schema.parse(data);
1317
+ };
1318
+
1319
+ responseTransformers['application/xml'] = async (data, mediaType, schema) => {
1320
+ const parsed = new XMLParser().parse(data);
1321
+ return schema.parse(parsed);
1322
+ }
1323
+
1324
+ export async function transformResponse<T>(
1325
+ data: any,
1326
+ mediaType: string,
1327
+ schema: ZodSchema
1328
+ ): Promise<T> {
1329
+ if (responseTransformers[mediaType]) {
1330
+ return await responseTransformers[mediaType](data, mediaType, schema);
1331
+ }
1332
+ return data
1333
+ }
1334
+ `;
1335
+ }
1336
+
1337
+ function typeUtilsTemplate() {
1338
+ const ts = pluginSdk.typescript(path.resolve('src', 'type-utils.ts'));
1339
+ return ts`import { z } from 'zod';
1340
+
1341
+ export type BinaryData = Blob;
1342
+ export const BinaryDataSchema: z.ZodType<BinaryData> = z.instanceof(Blob);
1343
+
1344
+ // Base64 helpers (browser + Node compatible; no Buffer required)
1345
+ export function base64ToUint8Array(b64: string): Uint8Array {
1346
+ if (typeof atob === 'function') {
1347
+ // Browser
1348
+ const bin = atob(b64);
1349
+ const bytes = new Uint8Array(bin.length);
1350
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
1351
+ return bytes;
1352
+ } else {
1353
+ // Node
1354
+ const buf = Buffer.from(b64, 'base64');
1355
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
1356
+ }
1357
+ }
1358
+
1359
+ `;
1360
+ }
1361
+
1362
+ function reactTsConfigTemplate() {
1363
+ const json = pluginSdk.jsonLiteral(path__namespace.resolve('tsconfig.json'));
1364
+ return json`
1365
+ {
1366
+ "compilerOptions": {
1367
+ "target": "es2020",
1368
+ "module": "ESNext",
1369
+ "declaration": true,
1370
+ "outDir": "./dist",
1371
+ "strict": true,
1372
+ "esModuleInterop": true,
1373
+ "noImplicitAny": false,
1374
+ "moduleResolution": "node",
1375
+ "baseUrl": "./",
1376
+ "paths": {
1377
+ "@intrig/react": [
1378
+ "./src"
1379
+ ],
1380
+ "@intrig/react/*": [
1381
+ "./src/*"
1382
+ ],
1383
+ "intrig-hook": ["src/config/intrig"]
1384
+ },
1385
+ "jsx": "react-jsx",
1386
+ "skipLibCheck": true
1387
+ },
1388
+ "exclude": [
1389
+ "node_modules",
1390
+ "../../node_modules",
1391
+ "**/__tests__/*"
1392
+ ]
1393
+ }
1394
+ `;
1395
+ }
1396
+
1397
+ function reactSwcrcTemplate() {
1398
+ const json = pluginSdk.jsonLiteral(path__namespace.resolve('.swcrc'));
1399
+ return json`
1400
+ {
1401
+ "jsc": {
1402
+ "parser": {
1403
+ "syntax": "typescript",
1404
+ "decorators": false,
1405
+ "dynamicImport": true
1406
+ },
1407
+ "target": "es2022",
1408
+ "externalHelpers": false
1409
+ },
1410
+ "module": {
1411
+ "type": "es6",
1412
+ "noInterop": false
1413
+ },
1414
+ "sourceMaps": true,
1415
+ "exclude": ["../../node_modules"]
1416
+ }
1417
+ `;
1418
+ }
1419
+
1420
+ function intrigMiddlewareTemplate() {
1421
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', 'intrig-middleware.ts'));
1422
+ return ts`
1423
+ import axios from 'axios';
1424
+ import { requestInterceptor } from 'intrig-hook';
1425
+
1426
+ export function getAxiosInstance(key: string) {
1427
+ let axiosInstance = axios.create({
1428
+ baseURL: process.env[${"`${key.toUpperCase()}_API_URL`"}],
1429
+ });
1430
+
1431
+ axiosInstance.interceptors.request.use(requestInterceptor);
1432
+
1433
+ return axiosInstance;
1434
+ }
1435
+ `;
1436
+ }
1437
+
1438
+ function flushSyncUtilTemplate(ctx) {
1439
+ var _packageJson_dependencies, _packageJson_devDependencies, _packageJson_peerDependencies;
1440
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', 'utils', 'flush-sync.ts'));
1441
+ var _ctx_rootDir;
1442
+ const projectDir = (_ctx_rootDir = ctx.rootDir) != null ? _ctx_rootDir : process.cwd();
1443
+ const packageJson = fsx__namespace.readJsonSync(path__namespace.resolve(projectDir, 'package.json'));
1444
+ // Check if react-dom is available at generation time
1445
+ const hasReactDom = !!(((_packageJson_dependencies = packageJson.dependencies) == null ? void 0 : _packageJson_dependencies['react-dom']) || ((_packageJson_devDependencies = packageJson.devDependencies) == null ? void 0 : _packageJson_devDependencies['react-dom']) || ((_packageJson_peerDependencies = packageJson.peerDependencies) == null ? void 0 : _packageJson_peerDependencies['react-dom']));
1446
+ if (hasReactDom) {
1447
+ // Generate DOM-compatible version
1448
+ return ts`/**
1449
+ * Platform-compatible flushSync utility
1450
+ *
1451
+ * This utility provides flushSync functionality for React DOM environments.
1452
+ * Uses the native flushSync from react-dom for synchronous updates.
1453
+ */
1454
+
1455
+ import { flushSync as reactDomFlushSync } from 'react-dom';
1456
+
1457
+ /**
1458
+ * Cross-platform flushSync implementation
1459
+ *
1460
+ * Forces React to flush any pending updates synchronously.
1461
+ * Uses react-dom's native flushSync implementation.
1462
+ *
1463
+ * @param callback - The callback to execute synchronously
1464
+ */
1465
+ export const flushSync = reactDomFlushSync;
1466
+
1467
+ /**
1468
+ * Check if we're running in a DOM environment
1469
+ * Always true when react-dom is available
1470
+ */
1471
+ export const isDOMEnvironment = true;
1472
+ `;
1473
+ } else {
1474
+ // Generate React Native/non-DOM version
1475
+ return ts`/**
1476
+ * Platform-compatible flushSync utility
1477
+ *
1478
+ * This utility provides flushSync functionality for React Native and other non-DOM environments.
1479
+ * In React Native, we don't have the same concurrent rendering concerns,
1480
+ * so we execute the callback immediately.
1481
+ */
1482
+
1483
+ /**
1484
+ * Cross-platform flushSync implementation
1485
+ *
1486
+ * Forces React to flush any pending updates synchronously.
1487
+ * In React Native, executes the callback immediately.
1488
+ *
1489
+ * @param callback - The callback to execute synchronously
1490
+ */
1491
+ export const flushSync = (callback: () => void) => {
1492
+ callback();
1493
+ };
1494
+
1495
+ /**
1496
+ * Check if we're running in a DOM environment
1497
+ * Always false when react-dom is not available
1498
+ */
1499
+ export const isDOMEnvironment = false;
1500
+ `;
1501
+ }
1502
+ }
1503
+
1504
+ function providerMainTemplate(apisToSync) {
1505
+ const ts = pluginSdk.typescript(path__namespace.resolve("src", "intrig-provider-main.tsx"));
1506
+ return ts`// Re-export all provider functionality from modular templates
1507
+ export * from './interfaces';
1508
+ export * from './reducer';
1509
+ export * from './axios-config';
1510
+ export * from './intrig-provider';
1511
+ export * from './intrig-provider-stub';
1512
+ export * from './status-trap';
1513
+ export * from './provider-hooks';
1514
+
1515
+ // Main provider exports for backward compatibility
1516
+ export { IntrigProvider } from './intrig-provider';
1517
+ export { IntrigProviderStub } from './intrig-provider-stub';
1518
+ export { StatusTrap } from './status-trap';
1519
+
1520
+ export {
1521
+ useNetworkState,
1522
+ useTransitionCall,
1523
+ useCentralError,
1524
+ useCentralPendingState,
1525
+ } from './provider-hooks';
1526
+
1527
+ export {
1528
+ requestReducer,
1529
+ inferNetworkReason,
1530
+ debounce,
1531
+ } from './reducer';
1532
+
1533
+ export {
1534
+ createAxiosInstance,
1535
+ createAxiosInstances,
1536
+ } from './axios-config';
1537
+
1538
+ export type {
1539
+ DefaultConfigs,
1540
+ IntrigProviderProps,
1541
+ IntrigProviderStubProps,
1542
+ StatusTrapProps,
1543
+ NetworkStateProps,
1544
+ StubType,
1545
+ WithStubSupport,
1546
+ } from './interfaces';
1547
+ `;
1548
+ }
1549
+
1550
+ function providerHooksTemplate() {
1551
+ const ts = pluginSdk.typescript(path__namespace.resolve("src", "provider-hooks.ts"));
1552
+ return ts`import React, {
1553
+ useCallback,
1554
+ useContext,
1555
+ useMemo,
1556
+ useState,
1557
+ useRef,
1558
+ } from 'react';
1559
+ import {
1560
+ ErrorState,
1561
+ ErrorWithContext,
1562
+ isSuccess,
1563
+ isError,
1564
+ isPending,
1565
+ NetworkState,
1566
+ pending,
1567
+ Progress,
1568
+ init,
1569
+ } from './network-state';
1570
+ import {
1571
+ AxiosProgressEvent,
1572
+ } from 'axios';
1573
+ import { ZodSchema } from 'zod';
1574
+ import logger from './logger';
1575
+
1576
+ import { Context, RequestType, SchemaOf } from './intrig-context';
1577
+ import { NetworkStateProps } from './interfaces';
1578
+ import { debounce } from './reducer';
1579
+
1580
+ /**
1581
+ * useNetworkState is a custom hook that manages the network state within the specified context.
1582
+ * It handles making network requests, dispatching appropriate states based on the request lifecycle,
1583
+ * and allows aborting ongoing requests.
1584
+ */
1585
+ export function useNetworkState<T>({
1586
+ key,
1587
+ operation,
1588
+ source,
1589
+ schema,
1590
+ errorSchema,
1591
+ debounceDelay: requestDebounceDelay,
1592
+ }: NetworkStateProps<T>): [
1593
+ NetworkState<T>,
1594
+ (request: RequestType) => void,
1595
+ () => void,
1596
+ (state: NetworkState<T>) => void,
1597
+ ] {
1598
+ const context = useContext(Context);
1599
+
1600
+ const [abortController, setAbortController] = useState<AbortController>();
1601
+
1602
+ const networkState = useMemo(() => {
1603
+ logger.info(${"`Updating status ${key} ${operation} ${source}`"});
1604
+ logger.debug("<=", context.state?.[${"`${source}:${operation}:${key}`"}])
1605
+ return (
1606
+ (context.state?.[${"`${source}:${operation}:${key}`"}] as NetworkState<T>) ??
1607
+ init()
1608
+ );
1609
+ }, [JSON.stringify(context.state?.[${"`${source}:${operation}:${key}`"}])]);
1610
+
1611
+ const dispatch = useCallback(
1612
+ (state: NetworkState<T>) => {
1613
+ context.dispatch({ key, operation, source, state });
1614
+ },
1615
+ [key, operation, source, context.dispatch],
1616
+ );
1617
+
1618
+ const debounceDelay = useMemo(() => {
1619
+ return (
1620
+ requestDebounceDelay ?? context.configs?.[source as keyof (typeof context)['configs']]?.debounceDelay ?? 0
1621
+ );
1622
+ }, [context.configs, requestDebounceDelay, source]);
1623
+
1624
+ const execute = useCallback(
1625
+ async (request: RequestType) => {
1626
+ logger.info(${"`Executing request ${key} ${operation} ${source}`"});
1627
+ logger.debug("=>", request)
1628
+
1629
+ const abortController = new AbortController();
1630
+ setAbortController(abortController);
1631
+
1632
+ const requestConfig: RequestType = {
1633
+ ...request,
1634
+ onUploadProgress(event: AxiosProgressEvent) {
1635
+ dispatch(
1636
+ pending({
1637
+ type: 'upload',
1638
+ loaded: event.loaded,
1639
+ total: event.total,
1640
+ }),
1641
+ );
1642
+ request.onUploadProgress?.(event);
1643
+ },
1644
+ onDownloadProgress(event: AxiosProgressEvent) {
1645
+ dispatch(
1646
+ pending({
1647
+ type: 'download',
1648
+ loaded: event.loaded,
1649
+ total: event.total,
1650
+ }),
1651
+ );
1652
+ request.onDownloadProgress?.(event);
1653
+ },
1654
+ signal: abortController.signal,
1655
+ };
1656
+
1657
+ await context.execute(
1658
+ requestConfig,
1659
+ dispatch,
1660
+ schema,
1661
+ errorSchema as any,
1662
+ );
1663
+ },
1664
+ [networkState, context.dispatch],
1665
+ );
1666
+
1667
+ const deboundedExecute = useMemo(
1668
+ () => debounce(execute, debounceDelay ?? 0),
1669
+ [execute],
1670
+ );
1671
+
1672
+ const clear = useCallback(() => {
1673
+ logger.info(${"`Clearing request ${key} ${operation} ${source}`"});
1674
+ dispatch(init());
1675
+ setAbortController((abortController) => {
1676
+ logger.info(${"`Aborting request ${key} ${operation} ${source}`"});
1677
+ abortController?.abort();
1678
+ return undefined;
1679
+ });
1680
+ }, [dispatch, abortController]);
1681
+
1682
+ return [networkState, deboundedExecute, clear, dispatch];
1683
+ }
1684
+
1685
+ /**
1686
+ * A hook for making transient calls that can be aborted and validated against schemas.
1687
+ */
1688
+ export function useTransitionCall<T>({
1689
+ schema,
1690
+ errorSchema,
1691
+ }: {
1692
+ schema?: SchemaOf<T>;
1693
+ errorSchema?: SchemaOf<T>;
1694
+ }): [(request: RequestType) => Promise<T>, () => void] {
1695
+ const ctx = useContext(Context);
1696
+ const controller = useRef<AbortController | undefined>(undefined);
1697
+
1698
+ const call = useCallback(
1699
+ async (request: RequestType) => {
1700
+ controller.current?.abort();
1701
+ const abort = new AbortController();
1702
+ controller.current = abort;
1703
+
1704
+ return new Promise<T>((resolve, reject) => {
1705
+ ctx.execute(
1706
+ { ...request, signal: abort.signal },
1707
+ (state) => {
1708
+ if (isSuccess(state)) {
1709
+ resolve(state.data as T);
1710
+ } else if (isError(state)) {
1711
+ reject(state.error);
1712
+ }
1713
+ },
1714
+ schema,
1715
+ errorSchema,
1716
+ );
1717
+ });
1718
+ },
1719
+ [ctx, schema, errorSchema],
1720
+ );
1721
+
1722
+ const abort = useCallback(() => {
1723
+ controller.current?.abort();
1724
+ }, []);
1725
+
1726
+ return [call, abort];
1727
+ }
1728
+
1729
+ /**
1730
+ * Handles central error extraction from the provided context.
1731
+ * It filters the state to retain error states and maps them to a structured error object with additional context information.
1732
+ * @return {Object[]} An array of objects representing the error states with context information such as source, operation, and key.
1733
+ */
1734
+ export function useCentralError() {
1735
+ const ctx = useContext(Context);
1736
+
1737
+ return useMemo(() => {
1738
+ return Object.entries(ctx.filteredState as Record<string, NetworkState>)
1739
+ .filter(([, state]) => isError(state))
1740
+ .map(([k, state]) => {
1741
+ const [source, operation, key] = k.split(':');
1742
+ return {
1743
+ ...(state as ErrorState<unknown>),
1744
+ source,
1745
+ operation,
1746
+ key,
1747
+ } satisfies ErrorWithContext;
1748
+ });
1749
+ }, [ctx.filteredState]);
1750
+ }
1751
+
1752
+ /**
1753
+ * Uses central pending state handling by aggregating pending states from context.
1754
+ * It calculates the overall progress of pending states if any, or returns an initial state otherwise.
1755
+ *
1756
+ * @return {NetworkState} The aggregated network state based on the pending states and their progress.
1757
+ */
1758
+ export function useCentralPendingState() {
1759
+ const ctx = useContext(Context);
1760
+
1761
+ const result: NetworkState = useMemo(() => {
1762
+ const pendingStates = Object.values(
1763
+ ctx.filteredState as Record<string, NetworkState>,
1764
+ ).filter(isPending);
1765
+ if (!pendingStates.length) {
1766
+ return init();
1767
+ }
1768
+
1769
+ const progress = pendingStates
1770
+ .filter((a) => a.progress)
1771
+ .reduce(
1772
+ (progress, current) => {
1773
+ return {
1774
+ total: progress.total + (current.progress?.total ?? 0),
1775
+ loaded: progress.loaded + (current.progress?.loaded ?? 0),
1776
+ };
1777
+ },
1778
+ { total: 0, loaded: 0 } satisfies Progress,
1779
+ );
1780
+ return pending(progress.total ? progress : undefined);
1781
+ }, [ctx.filteredState]);
1782
+
1783
+ return result;
1784
+ }
1785
+ `;
1786
+ }
1787
+
1788
+ function providerInterfacesTemplate(apisToSync) {
1789
+ const configType = `{
1790
+ defaults?: DefaultConfigs,
1791
+ ${apisToSync.map((a)=>`${a.id}?: DefaultConfigs`).join(",\n ")}
1792
+ }`;
1793
+ const ts = pluginSdk.typescript(path__namespace.resolve("src", "interfaces.ts"));
1794
+ return ts`import {
1795
+ CreateAxiosDefaults,
1796
+ InternalAxiosRequestConfig,
1797
+ AxiosResponse,
1798
+ } from 'axios';
1799
+ import {
1800
+ IntrigHook,
1801
+ NetworkState,
1802
+ } from './network-state';
1803
+ import { SchemaOf } from './intrig-context';
1804
+
1805
+ export interface DefaultConfigs extends CreateAxiosDefaults {
1806
+ debounceDelay?: number;
1807
+ requestInterceptor?: (
1808
+ config: InternalAxiosRequestConfig,
1809
+ ) => Promise<InternalAxiosRequestConfig>;
1810
+ responseInterceptor?: (
1811
+ config: AxiosResponse<any>,
1812
+ ) => Promise<AxiosResponse<any>>;
1813
+ }
1814
+
1815
+ export interface IntrigProviderProps {
1816
+ configs?: ${configType};
1817
+ children: React.ReactNode;
1818
+ }
1819
+
1820
+ export interface StubType {
1821
+ <P, B, T>(
1822
+ hook: IntrigHook<P, B, T>,
1823
+ fn: (
1824
+ params: P,
1825
+ body: B,
1826
+ dispatch: (state: NetworkState<T>) => void,
1827
+ ) => Promise<void>,
1828
+ ): void;
1829
+ }
1830
+
1831
+ export type WithStubSupport<T> = T & {
1832
+ stubs?: (stub: StubType) => void;
1833
+ };
1834
+
1835
+ export interface IntrigProviderStubProps {
1836
+ configs?: ${configType};
1837
+ stubs?: (stub: StubType) => void;
1838
+ children: React.ReactNode;
1839
+ }
1840
+
1841
+ export interface StatusTrapProps {
1842
+ type: 'pending' | 'error' | 'pending + error';
1843
+ propagate?: boolean;
1844
+ }
1845
+
1846
+ export interface NetworkStateProps<T> {
1847
+ key: string;
1848
+ operation: string;
1849
+ source: string;
1850
+ schema?: SchemaOf<T>;
1851
+ errorSchema?: SchemaOf<any>;
1852
+ debounceDelay?: number;
1853
+ }
1854
+ `;
1855
+ }
1856
+
1857
+ function providerReducerTemplate() {
1858
+ const ts = pluginSdk.typescript(path__namespace.resolve("src", "reducer.ts"));
1859
+ return ts`import {
1860
+ NetworkAction,
1861
+ } from './network-state';
1862
+ import { GlobalState } from './intrig-context';
1863
+
1864
+ /**
1865
+ * Handles state updates for network requests based on the provided action.
1866
+ *
1867
+ * @param {GlobalState} state - The current state of the application.
1868
+ * @param {NetworkAction<unknown>} action - The action containing source, operation, key, and state.
1869
+ * @return {GlobalState} - The updated state after applying the action.
1870
+ */
1871
+ export function requestReducer(
1872
+ state: GlobalState,
1873
+ action: NetworkAction<unknown>,
1874
+ ): GlobalState {
1875
+ return {
1876
+ ...state,
1877
+ [${"`${action.source}:${action.operation}:${action.key}`"}]: action.state,
1878
+ };
1879
+ }
1880
+
1881
+ function inferNetworkReason(e: any): 'timeout' | 'dns' | 'offline' | 'aborted' | 'unknown' {
1882
+ if (e?.code === 'ECONNABORTED') return 'timeout';
1883
+ if (typeof navigator !== 'undefined' && navigator.onLine === false) return 'offline';
1884
+ if (e?.name === 'AbortError') return 'aborted';
1885
+ return 'unknown';
1886
+ }
1887
+
1888
+ function debounce<T extends (...args: any[]) => void>(func: T, delay: number) {
1889
+ let timeoutId: any;
1890
+
1891
+ return (...args: Parameters<T>) => {
1892
+ if (timeoutId) {
1893
+ clearTimeout(timeoutId);
1894
+ }
1895
+ timeoutId = setTimeout(() => {
1896
+ func(...args);
1897
+ }, delay);
1898
+ };
1899
+ }
1900
+
1901
+ export { inferNetworkReason, debounce };
1902
+ `;
1903
+ }
1904
+
1905
+ function providerAxiosConfigTemplate(apisToSync) {
1906
+ const axiosConfigs = apisToSync.map((a)=>`
1907
+ ${a.id}: createAxiosInstance(configs.defaults, configs['${a.id}']),
1908
+ `).join("\n");
1909
+ const ts = pluginSdk.typescript(path__namespace.resolve("src", "axios-config.ts"));
1910
+ return ts`import axios, {
1911
+ Axios,
1912
+ AxiosResponse,
1913
+ InternalAxiosRequestConfig,
1914
+ } from 'axios';
1915
+ import { DefaultConfigs } from './interfaces';
1916
+
1917
+ export function createAxiosInstance(
1918
+ defaultConfig?: DefaultConfigs,
1919
+ config?: DefaultConfigs,
1920
+ ) {
1921
+ const axiosInstance = axios.create({
1922
+ ...(defaultConfig ?? {}),
1923
+ ...(config ?? {}),
1924
+ });
1925
+
1926
+ async function requestInterceptor(cfg: InternalAxiosRequestConfig) {
1927
+ const intermediate = (await defaultConfig?.requestInterceptor?.(cfg)) ?? cfg;
1928
+ return config?.requestInterceptor?.(intermediate) ?? intermediate;
1929
+ }
1930
+
1931
+ async function responseInterceptor(cfg: AxiosResponse<any>) {
1932
+ const intermediate = (await defaultConfig?.responseInterceptor?.(cfg)) ?? cfg;
1933
+ return config?.responseInterceptor?.(intermediate) ?? intermediate;
1934
+ }
1935
+
1936
+ axiosInstance.interceptors.request.use(requestInterceptor);
1937
+ axiosInstance.interceptors.response.use(responseInterceptor, (error) => {
1938
+ return Promise.reject(error);
1939
+ });
1940
+ return axiosInstance;
1941
+ }
1942
+
1943
+ export function createAxiosInstances(configs: any): Record<string, Axios> {
1944
+ return {
1945
+ ${axiosConfigs}
1946
+ };
1947
+ }
1948
+ `;
1949
+ }
1950
+
1951
+ function reactIntrigProviderTemplate(_apisToSync) {
1952
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', 'intrig-provider.tsx'));
1953
+ return ts`import React, { useMemo, useReducer } from 'react';
1954
+ import {
1955
+ error,
1956
+ NetworkState,
1957
+ pending,
1958
+ success,
1959
+ responseValidationError,
1960
+ configError,
1961
+ httpError,
1962
+ networkError,
1963
+ } from './network-state';
1964
+ import { Axios, AxiosResponse, isAxiosError } from 'axios';
1965
+ import { ZodError, ZodSchema } from 'zod';
1966
+ import { flushSync } from './utils/flush-sync';
1967
+ import { createParser } from 'eventsource-parser';
1968
+
1969
+ import { Context, RequestType, GlobalState } from './intrig-context';
1970
+ import { IntrigProviderProps } from './interfaces';
1971
+ import { createAxiosInstances } from './axios-config';
1972
+ import { requestReducer, inferNetworkReason } from './reducer';
1973
+
1974
+ /**
1975
+ * IntrigProvider is a context provider component that sets up global state management
1976
+ * and provides Axios instances for API requests.
1977
+ */
1978
+ export function IntrigProvider({ children, configs = {} }: IntrigProviderProps) {
1979
+ const [state, dispatch] = useReducer(requestReducer, {} as GlobalState);
1980
+
1981
+ const axiosInstances: Record<string, Axios> = useMemo(() => {
1982
+ return createAxiosInstances(configs);
1983
+ }, [configs]);
1984
+
1985
+ const contextValue = useMemo(() => {
1986
+ async function execute<T>(
1987
+ request: RequestType,
1988
+ setState: (s: NetworkState<T>) => void,
1989
+ schema?: ZodSchema<T>, // success payload schema (optional)
1990
+ errorSchema?: ZodSchema<any>, // error-body schema for non-2xx (optional)
1991
+ ) {
1992
+ try {
1993
+ setState(pending());
1994
+
1995
+ const axios = axiosInstances[request.source];
1996
+ if (!axios) {
1997
+ setState(error(configError(${"`Unknown axios source '${request.source}'`"})));
1998
+ return;
1999
+ }
2000
+
2001
+ const response = await axios.request(request);
2002
+ const status = response.status;
2003
+ const method = (response.config?.method || 'GET').toUpperCase();
2004
+ const url = response.config?.url || '';
2005
+ const ctype = String(response.headers?.['content-type'] || '');
2006
+
2007
+ // -------------------- 2xx branch --------------------
2008
+ if (status >= 200 && status < 300) {
2009
+ // SSE stream
2010
+ if (ctype.includes('text/event-stream')) {
2011
+ const reader = response.data.getReader();
2012
+ const decoder = new TextDecoder();
2013
+
2014
+ let lastMessage: any;
2015
+
2016
+ const parser = createParser({
2017
+ onEvent(evt) {
2018
+ let decoded: unknown = evt.data;
2019
+
2020
+ // Try JSON parse; if schema is defined, we require valid JSON for validation
2021
+ try {
2022
+ let parsed: unknown = JSON.parse(String(decoded));
2023
+ if (schema) {
2024
+ const vr = schema.safeParse(parsed);
2025
+ if (!vr.success) {
2026
+ setState(error(responseValidationError(vr.error, parsed)));
2027
+ return;
2028
+ }
2029
+ parsed = vr.data;
2030
+ }
2031
+ decoded = parsed;
2032
+ } catch (ignore) {
2033
+ if (schema) {
2034
+ // schema expects structured data but chunk wasn't JSON
2035
+ setState(error(responseValidationError(new ZodError([]), decoded)));
2036
+ return;
2037
+ }
2038
+ // if no schema, pass raw text
2039
+ }
2040
+
2041
+ lastMessage = decoded;
2042
+ flushSync(() => setState(pending(undefined, decoded as T)));
2043
+ },
2044
+ });
2045
+
2046
+ while (true) {
2047
+ const { done, value } = await reader.read();
2048
+ if (done) {
2049
+ flushSync(() => setState(success(lastMessage as T, response.headers)));
2050
+ break;
2051
+ }
2052
+ parser.feed(decoder.decode(value, { stream: true }));
2053
+ }
2054
+ return;
2055
+ }
2056
+
2057
+ // Non-SSE: validate body if a schema is provided
2058
+ if (schema) {
2059
+ const parsed = schema.safeParse(response.data);
2060
+ if (!parsed.success) {
2061
+ setState(error(responseValidationError(parsed.error, response.data)));
2062
+ return;
2063
+ }
2064
+ setState(success(parsed.data, response.headers));
2065
+ return;
2066
+ }
2067
+
2068
+ // No schema → pass through
2069
+ setState(success(response.data as T, response.headers));
2070
+ return;
2071
+ }
2072
+
2073
+ // -------------------- non-2xx (HTTP error) --------------------
2074
+ let errorBody: unknown = response.data;
2075
+
2076
+ if (errorSchema) {
2077
+ const ev = errorSchema.safeParse(errorBody ?? {});
2078
+ if (!ev.success) {
2079
+ setState(error(responseValidationError(ev.error, errorBody)));
2080
+ return;
2081
+ }
2082
+ errorBody = ev.data;
2083
+ }
2084
+
2085
+ setState(error(httpError(status, url, method, response.headers, errorBody)));
2086
+
2087
+ } catch (e: any) {
2088
+ // -------------------- thrown / transport --------------------
2089
+ if (isAxiosError(e)) {
2090
+ const status = e.response?.status;
2091
+ const method = (e.config?.method || 'GET').toUpperCase();
2092
+ const url = e.config?.url || '';
2093
+
2094
+ if (status != null) {
2095
+ // HTTP error with response
2096
+ let errorBody: unknown = e.response?.data;
2097
+
2098
+ if (errorSchema) {
2099
+ const ev = errorSchema.safeParse(errorBody ?? {});
2100
+ if (!ev.success) {
2101
+ setState(error(responseValidationError(ev.error, errorBody)));
2102
+ return;
2103
+ }
2104
+ errorBody = ev.data;
2105
+ }
2106
+
2107
+ setState(error(httpError(status, url, method, e.response?.headers, errorBody)));
2108
+ return;
2109
+ }
2110
+
2111
+ // No response → network layer
2112
+ setState(error(networkError(inferNetworkReason(e), e.request)));
2113
+ return;
2114
+ }
2115
+
2116
+ // Non-Axios exception → treat as unknown network-ish failure
2117
+ setState(error(networkError('unknown')));
2118
+ }
2119
+ }
2120
+ return {
2121
+ state,
2122
+ dispatch,
2123
+ filteredState: state,
2124
+ configs,
2125
+ execute,
2126
+ };
2127
+ }, [state, axiosInstances]);
2128
+
2129
+ return <Context.Provider value={contextValue}>{children}</Context.Provider>;
2130
+ }
2131
+ `;
2132
+ }
2133
+
2134
+ function reactIntrigProviderStubTemplate(_apisToSync) {
2135
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', 'intrig-provider-stub.tsx'));
2136
+ return ts`import { useMemo, useReducer } from 'react';
2137
+ import { ZodSchema } from 'zod';
2138
+ import { Context, RequestType, GlobalState } from './intrig-context';
2139
+ import { IntrigProviderStubProps } from './interfaces';
2140
+ import { error, configError, init, NetworkState } from './network-state';
2141
+ import { requestReducer } from './reducer';
2142
+
2143
+ export function IntrigProviderStub({
2144
+ children,
2145
+ configs = {},
2146
+ stubs = () => {
2147
+ //intentionally left blank
2148
+ },
2149
+ }: IntrigProviderStubProps) {
2150
+ const [state, dispatch] = useReducer(requestReducer, {} as GlobalState);
2151
+
2152
+ const collectedStubs = useMemo(() => {
2153
+ const fns: Record<string, (
2154
+ params: any,
2155
+ body: any,
2156
+ dispatch: (state: NetworkState<any>) => void,
2157
+ ) => Promise<void>> = {};
2158
+ function stub<P, B, T>(
2159
+ hook: any,
2160
+ fn: (
2161
+ params: P,
2162
+ body: B,
2163
+ dispatch: (state: NetworkState<T>) => void,
2164
+ ) => Promise<void>,
2165
+ ) {
2166
+ fns[hook.key] = fn;
2167
+ }
2168
+ stubs(stub);
2169
+ return fns;
2170
+ }, [stubs]);
2171
+
2172
+ const contextValue = useMemo(() => {
2173
+ async function execute<T>(
2174
+ request: RequestType,
2175
+ dispatch: (state: NetworkState<T>) => void,
2176
+ schema: ZodSchema<T> | undefined,
2177
+ ) {
2178
+ const stub = collectedStubs[request.key];
2179
+
2180
+ if (stub) {
2181
+ try {
2182
+ await stub(request.params, request.data, dispatch);
2183
+ } catch (e: any) {
2184
+ dispatch(error(configError(e?.message ?? '')));
2185
+ }
2186
+ } else {
2187
+ dispatch(init());
2188
+ }
2189
+ }
2190
+
2191
+ return {
2192
+ state,
2193
+ dispatch,
2194
+ filteredState: state,
2195
+ configs,
2196
+ execute,
2197
+ };
2198
+ }, [state, dispatch, configs, collectedStubs]);
2199
+
2200
+ return <Context.Provider value={contextValue}>{children}</Context.Provider>;
2201
+ }
2202
+ `;
2203
+ }
2204
+
2205
+ function reactStatusTrapTemplate(_apisToSync) {
2206
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', 'status-trap.tsx'));
2207
+ return ts`import React, { PropsWithChildren, useCallback, useContext, useMemo, useState } from 'react';
2208
+ import { isError, isPending, NetworkState } from './network-state';
2209
+ import { Context } from './intrig-context';
2210
+ import { StatusTrapProps } from './interfaces';
2211
+
2212
+ /**
2213
+ * StatusTrap component is used to track and manage network request states.
2214
+ */
2215
+ export function StatusTrap({
2216
+ children,
2217
+ type,
2218
+ propagate = true,
2219
+ }: PropsWithChildren<StatusTrapProps>) {
2220
+ const ctx = useContext(Context);
2221
+
2222
+ const [requests, setRequests] = useState<string[]>([]);
2223
+
2224
+ const shouldHandleEvent = useCallback(
2225
+ (state: NetworkState) => {
2226
+ switch (type) {
2227
+ case 'error':
2228
+ return isError(state);
2229
+ case 'pending':
2230
+ return isPending(state);
2231
+ case 'pending + error':
2232
+ return isPending(state) || isError(state);
2233
+ default:
2234
+ return false;
2235
+ }
2236
+ },
2237
+ [type],
2238
+ );
2239
+
2240
+ const dispatch = useCallback(
2241
+ (event: any) => {
2242
+ const composite = ${"`${event.source}:${event.operation}:${event.key}`"};
2243
+ if (!event.handled) {
2244
+ if (shouldHandleEvent(event.state)) {
2245
+ setRequests((prev) => [...prev, composite]);
2246
+ if (!propagate) {
2247
+ ctx.dispatch({
2248
+ ...event,
2249
+ handled: true,
2250
+ });
2251
+ return;
2252
+ }
2253
+ } else {
2254
+ setRequests((prev) => prev.filter((k) => k !== composite));
2255
+ }
2256
+ }
2257
+ ctx.dispatch(event);
2258
+ },
2259
+ [ctx, propagate, shouldHandleEvent],
2260
+ );
2261
+
2262
+ const filteredState = useMemo(() => {
2263
+ return Object.fromEntries(
2264
+ Object.entries(ctx.state).filter(([key]) => requests.includes(key)),
2265
+ );
2266
+ }, [ctx.state, requests]);
2267
+
2268
+ return (
2269
+ <Context.Provider
2270
+ value={{
2271
+ ...ctx,
2272
+ dispatch,
2273
+ filteredState,
2274
+ }}
2275
+ >
2276
+ {children}
2277
+ </Context.Provider>
2278
+ );
2279
+ }
2280
+ `;
2281
+ }
2282
+
2283
+ function extractHookShapeAndOptionsShape$1(response, requestBody, imports) {
2284
+ if (response) {
2285
+ if (requestBody) {
2286
+ imports.add(`import { BinaryFunctionHook, BinaryHookOptions } from "@intrig/react"`);
2287
+ return {
2288
+ hookShape: `BinaryFunctionHook<Params, RequestBody, Response>`,
2289
+ optionsShape: `BinaryHookOptions<Params, RequestBody>`
2290
+ };
2291
+ } else {
2292
+ imports.add(`import { UnaryFunctionHook, UnaryHookOptions } from "@intrig/react"`);
2293
+ return {
2294
+ hookShape: `UnaryFunctionHook<Params, Response>`,
2295
+ optionsShape: `UnaryHookOptions<Params>`
2296
+ };
2297
+ }
2298
+ } else {
2299
+ if (requestBody) {
2300
+ imports.add(`import { BinaryProduceHook, BinaryHookOptions } from "@intrig/react"`);
2301
+ return {
2302
+ hookShape: `BinaryProduceHook<Params, RequestBody>`,
2303
+ optionsShape: `BinaryHookOptions<Params, RequestBody>`
2304
+ };
2305
+ } else {
2306
+ imports.add(`import { UnaryProduceHook, UnaryHookOptions } from "@intrig/react"`);
2307
+ return {
2308
+ hookShape: `UnaryProduceHook<Params>`,
2309
+ optionsShape: `UnaryHookOptions<Params>`
2310
+ };
2311
+ }
2312
+ }
2313
+ }
2314
+ function extractParamDeconstruction$2(variables, requestBody) {
2315
+ const isParamMandatory = (variables == null ? void 0 : variables.some((a)=>a.in === 'path')) || false;
2316
+ if (requestBody) {
2317
+ if (isParamMandatory) {
2318
+ return {
2319
+ paramExpression: 'data, p',
2320
+ paramType: 'data: RequestBody, params: Params'
2321
+ };
2322
+ } else {
2323
+ return {
2324
+ paramExpression: 'data, p = {}',
2325
+ paramType: 'data: RequestBody, params?: Params'
2326
+ };
2327
+ }
2328
+ } else {
2329
+ if (isParamMandatory) {
2330
+ return {
2331
+ paramExpression: 'p',
2332
+ paramType: 'params: Params'
2333
+ };
2334
+ } else {
2335
+ return {
2336
+ paramExpression: 'p = {}',
2337
+ paramType: 'params?: Params'
2338
+ };
2339
+ }
2340
+ }
2341
+ }
2342
+ function extractErrorParams$2(errorTypes) {
2343
+ switch(errorTypes.length){
2344
+ case 0:
2345
+ return `
2346
+ export type _ErrorType = any
2347
+ const errorSchema = z.any()`;
2348
+ case 1:
2349
+ return `
2350
+ export type _ErrorType = ${errorTypes[0]}
2351
+ const errorSchema = ${errorTypes[0]}Schema`;
2352
+ default:
2353
+ return `
2354
+ export type _ErrorType = ${errorTypes.join(' | ')}
2355
+ const errorSchema = z.union([${errorTypes.map((a)=>`${a}Schema`).join(', ')}])`;
2356
+ }
2357
+ }
2358
+ async function requestHookTemplate({ source, data: { paths, operationId, response, requestUrl, variables, requestBody, contentType, responseType, errorResponses, method } }, ctx) {
2359
+ var _ctx_getCounter;
2360
+ const postfix = ctx.potentiallyConflictingDescriptors.includes(operationId) ? pluginSdk.generatePostfix(contentType, responseType) : '';
2361
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', source, ...paths, pluginSdk.camelCase(operationId), `use${pluginSdk.pascalCase(operationId)}${postfix}.ts`));
2362
+ (_ctx_getCounter = ctx.getCounter(source)) == null ? void 0 : _ctx_getCounter.inc("Stateful Hooks");
2363
+ const modifiedRequestUrl = `${requestUrl == null ? void 0 : requestUrl.replace(/\{/g, "${")}`;
2364
+ const imports = new Set();
2365
+ imports.add(`import { z } from 'zod'`);
2366
+ imports.add(`import { useCallback, useEffect } from 'react'`);
2367
+ imports.add(`import {useNetworkState, NetworkState, DispatchState, error, successfulDispatch, validationError, encode, requestValidationError} from "@intrig/react"`);
2368
+ const { hookShape, optionsShape } = extractHookShapeAndOptionsShape$1(response, requestBody, imports);
2369
+ const { paramExpression, paramType } = extractParamDeconstruction$2(variables != null ? variables : [], requestBody);
2370
+ if (requestBody) {
2371
+ imports.add(`import { ${requestBody} as RequestBody, ${requestBody}Schema as requestBodySchema } from "@intrig/react/${source}/components/schemas/${requestBody}"`);
2372
+ }
2373
+ if (response) {
2374
+ imports.add(`import { ${response} as Response, ${response}Schema as schema } from "@intrig/react/${source}/components/schemas/${response}"`);
2375
+ }
2376
+ imports.add(`import {${pluginSdk.pascalCase(operationId)}Params as Params} from './${pluginSdk.pascalCase(operationId)}.params'`);
2377
+ const errorTypes = [
2378
+ ...new Set(Object.values(errorResponses != null ? errorResponses : {}).map((a)=>a.response))
2379
+ ];
2380
+ errorTypes.forEach((ref)=>imports.add(`import {${ref}, ${ref}Schema } from "@intrig/react/${source}/components/schemas/${ref}"`));
2381
+ var _variables_filter_map;
2382
+ const paramExplode = [
2383
+ ...(_variables_filter_map = variables == null ? void 0 : variables.filter((a)=>a.in === "path").map((a)=>a.name)) != null ? _variables_filter_map : [],
2384
+ "...params"
2385
+ ].join(",");
2386
+ const finalRequestBodyBlock = requestBody ? `,data: encode(data, "${contentType}", requestBodySchema)` : '';
2387
+ function responseTypePart() {
2388
+ switch(responseType){
2389
+ case "application/octet-stream":
2390
+ return `responseType: 'blob', adapter: 'fetch',`;
2391
+ case "text/event-stream":
2392
+ return `responseType: 'stream', adapter: 'fetch',`;
2393
+ }
2394
+ return '';
2395
+ }
2396
+ return ts`
2397
+ ${[
2398
+ ...imports
2399
+ ].join('\n')}
2400
+
2401
+ ${!response ? `
2402
+ type Response = any;
2403
+ const schema = z.any();
2404
+ ` : ''}
2405
+
2406
+ ${extractErrorParams$2(errorTypes.map((a)=>a))}
2407
+
2408
+ const operation = "${method.toUpperCase()} ${requestUrl}| ${contentType} -> ${responseType}"
2409
+ const source = "${source}"
2410
+
2411
+ function use${pluginSdk.pascalCase(operationId)}Hook(options: ${optionsShape} = {}): [NetworkState<Response>, (${paramType}) => DispatchState<any>, () => void] {
2412
+ const [state, dispatch, clear, dispatchState] = useNetworkState<Response>({
2413
+ key: options?.key ?? 'default',
2414
+ operation,
2415
+ source,
2416
+ schema,
2417
+ errorSchema
2418
+ });
2419
+
2420
+ const doExecute = useCallback<(${paramType}) => DispatchState<any>>((${paramExpression}) => {
2421
+ const { ${paramExplode}} = p
2422
+
2423
+ ${requestBody ? `
2424
+ const validationResult = requestBodySchema.safeParse(data);
2425
+ if (!validationResult.success) {
2426
+ dispatchState(error(requestValidationError(validationResult.error)));
2427
+ return validationError(validationResult.error.errors);
2428
+ }
2429
+ ` : ``}
2430
+
2431
+ dispatch({
2432
+ method: '${method}',
2433
+ url: \`${modifiedRequestUrl}\`,
2434
+ headers: {
2435
+ ${contentType ? `"Content-Type": "${contentType}",` : ''}
2436
+ },
2437
+ params,
2438
+ key: \`${"${source}: ${operation}"}\`,
2439
+ source: '${source}'
2440
+ ${requestBody ? finalRequestBodyBlock : ''},
2441
+ ${responseTypePart()}
2442
+ })
2443
+ return successfulDispatch();
2444
+ }, [dispatch])
2445
+
2446
+ useEffect(() => {
2447
+ if (options.fetchOnMount) {
2448
+ doExecute(${[
2449
+ requestBody ? `options.body!` : undefined,
2450
+ "options.params!"
2451
+ ].filter((a)=>a).join(",")});
2452
+ }
2453
+
2454
+ return () => {
2455
+ if (options.clearOnUnmount) {
2456
+ clear();
2457
+ }
2458
+ }
2459
+ }, [])
2460
+
2461
+ return [
2462
+ state,
2463
+ doExecute,
2464
+ clear
2465
+ ]
2466
+ }
2467
+
2468
+ use${pluginSdk.pascalCase(operationId)}Hook.key = \`${"${source}: ${operation}"}\`
2469
+
2470
+ export const use${pluginSdk.pascalCase(operationId)}: ${hookShape} = use${pluginSdk.pascalCase(operationId)}Hook;
2471
+ `;
2472
+ }
2473
+
2474
+ async function paramsTemplate({ source, data: { paths, operationId, variables } }, ctx) {
2475
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', source, ...paths, pluginSdk.camelCase(operationId), `${pluginSdk.pascalCase(operationId)}.params.ts`));
2476
+ const { variableImports, variableTypes } = pluginSdk.decodeVariables(variables != null ? variables : [], source, "@intrig/react");
2477
+ if (variableTypes.length === 0) return ts`
2478
+ export type ${pluginSdk.pascalCase(operationId)}Params = Record<string, any>
2479
+ `;
2480
+ return ts`
2481
+ ${variableImports}
2482
+
2483
+ export interface ${pluginSdk.pascalCase(operationId)}Params extends Record<string, any> {
2484
+ ${variableTypes}
2485
+ }
2486
+ `;
2487
+ }
2488
+
2489
+ function extractAsyncHookShape(response, requestBody, imports) {
2490
+ if (response) {
2491
+ if (requestBody) {
2492
+ imports.add(`import { BinaryFunctionAsyncHook } from "@intrig/react"`);
2493
+ return `BinaryFunctionAsyncHook<Params, RequestBody, Response>`;
2494
+ } else {
2495
+ imports.add(`import { UnaryFunctionAsyncHook } from "@intrig/react"`);
2496
+ return `UnaryFunctionAsyncHook<Params, Response>`;
2497
+ }
2498
+ } else {
2499
+ if (requestBody) {
2500
+ imports.add(`import { BinaryProduceAsyncHook } from "@intrig/react"`);
2501
+ return `BinaryProduceAsyncHook<Params, RequestBody>`;
2502
+ } else {
2503
+ imports.add(`import { UnaryProduceAsyncHook } from "@intrig/react"`);
2504
+ return `UnaryProduceAsyncHook<Params>`;
2505
+ }
2506
+ }
2507
+ }
2508
+ function extractErrorParams$1(errorTypes) {
2509
+ switch(errorTypes.length){
2510
+ case 0:
2511
+ return `
2512
+ export type _ErrorType = any
2513
+ const errorSchema = z.any()`;
2514
+ case 1:
2515
+ return `
2516
+ export type _ErrorType = ${errorTypes[0]}
2517
+ const errorSchema = ${errorTypes[0]}Schema`;
2518
+ default:
2519
+ return `
2520
+ export type _ErrorType = ${errorTypes.join(' | ')}
2521
+ const errorSchema = z.union([${errorTypes.map((a)=>`${a}Schema`).join(', ')}])`;
2522
+ }
2523
+ }
2524
+ function extractParamDeconstruction$1(variables, requestBody) {
2525
+ const isParamMandatory = (variables == null ? void 0 : variables.some((a)=>a.in === 'path')) || false;
2526
+ if (requestBody) {
2527
+ if (isParamMandatory) {
2528
+ return {
2529
+ paramExpression: 'data, p',
2530
+ paramType: 'data: RequestBody, params: Params'
2531
+ };
2532
+ } else {
2533
+ return {
2534
+ paramExpression: 'data, p = {}',
2535
+ paramType: 'data: RequestBody, params?: Params'
2536
+ };
2537
+ }
2538
+ } else {
2539
+ if (isParamMandatory) {
2540
+ return {
2541
+ paramExpression: 'p',
2542
+ paramType: 'params: Params'
2543
+ };
2544
+ } else {
2545
+ return {
2546
+ paramExpression: 'p = {}',
2547
+ paramType: 'params?: Params'
2548
+ };
2549
+ }
2550
+ }
2551
+ }
2552
+ async function asyncFunctionHookTemplate({ source, data: { paths, operationId, response, requestUrl, variables, requestBody, contentType, responseType, errorResponses, method } }, ctx) {
2553
+ var _ctx_getCounter;
2554
+ const postfix = ctx.potentiallyConflictingDescriptors.includes(operationId) ? pluginSdk.generatePostfix(contentType, responseType) : '';
2555
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', source, ...paths, pluginSdk.camelCase(operationId), `use${pluginSdk.pascalCase(operationId)}Async${postfix}.ts`));
2556
+ (_ctx_getCounter = ctx.getCounter(source)) == null ? void 0 : _ctx_getCounter.inc("Stateless Hooks");
2557
+ const modifiedRequestUrl = `${requestUrl == null ? void 0 : requestUrl.replace(/\{/g, "${")}`;
2558
+ const imports = new Set();
2559
+ // Basic imports
2560
+ imports.add(`import { z } from 'zod'`);
2561
+ imports.add(`import { useCallback } from 'react'`);
2562
+ imports.add(`import { useTransitionCall, encode, isError, isSuccess } from '@intrig/react'`);
2563
+ // Hook signature type
2564
+ const hookShape = extractAsyncHookShape(response, requestBody, imports);
2565
+ // Add body/response param imports
2566
+ if (requestBody) {
2567
+ imports.add(`import { ${requestBody} as RequestBody, ${requestBody}Schema as requestBodySchema } from "@intrig/react/${source}/components/schemas/${requestBody}"`);
2568
+ }
2569
+ if (response) {
2570
+ imports.add(`import { ${response} as Response, ${response}Schema as schema } from "@intrig/react/${source}/components/schemas/${response}"`);
2571
+ }
2572
+ imports.add(`import { ${pluginSdk.pascalCase(operationId)}Params as Params } from './${pluginSdk.pascalCase(operationId)}.params'`);
2573
+ // Error types
2574
+ const errorTypes = [
2575
+ ...new Set(Object.values(errorResponses != null ? errorResponses : {}).map((a)=>a.response))
2576
+ ];
2577
+ errorTypes.forEach((ref)=>imports.add(`import { ${ref}, ${ref}Schema } from "@intrig/react/${source}/components/schemas/${ref}"`));
2578
+ // Error schema block
2579
+ const errorSchemaBlock = extractErrorParams$1(errorTypes.map((a)=>a));
2580
+ // Param deconstruction
2581
+ const { paramExpression, paramType } = extractParamDeconstruction$1(variables != null ? variables : [], requestBody);
2582
+ var _variables_filter_map;
2583
+ const paramExplode = [
2584
+ ...(_variables_filter_map = variables == null ? void 0 : variables.filter((a)=>a.in === 'path').map((a)=>a.name)) != null ? _variables_filter_map : [],
2585
+ '...params'
2586
+ ].join(',');
2587
+ const finalRequestBodyBlock = requestBody ? `, data: encode(data, "${contentType}", requestBodySchema)` : '';
2588
+ function responseTypePart() {
2589
+ switch(responseType){
2590
+ case "application/octet-stream":
2591
+ return `responseType: 'blob', adapter: 'fetch',`;
2592
+ case "text/event-stream":
2593
+ return `responseType: 'stream', adapter: 'fetch',`;
2594
+ }
2595
+ return '';
2596
+ }
2597
+ return ts`
2598
+ ${[
2599
+ ...imports
2600
+ ].join('\n')}
2601
+
2602
+ ${!response ? `
2603
+ type Response = any;
2604
+ const schema = z.any();
2605
+ ` : ''}
2606
+
2607
+ ${errorSchemaBlock}
2608
+
2609
+ const operation = "${method.toUpperCase()} ${requestUrl}| ${contentType} -> ${responseType}";
2610
+ const source = "${source}";
2611
+
2612
+ function use${pluginSdk.pascalCase(operationId)}AsyncHook(): [(${paramType}) => Promise<Response>, () => void] {
2613
+ const [call, abort] = useTransitionCall<Response>({
2614
+ schema,
2615
+ errorSchema
2616
+ });
2617
+
2618
+ const doExecute = useCallback<(${paramType}) => Promise<Response>>(async (${paramExpression}) => {
2619
+ const { ${paramExplode} } = p;
2620
+
2621
+ ${requestBody ? `
2622
+ const validationResult = requestBodySchema.safeParse(data);
2623
+ if (!validationResult.success) {
2624
+ return Promise.reject(validationResult.error);
2625
+ }
2626
+ ` : ''}
2627
+
2628
+ return await call({
2629
+ method: '${method}',
2630
+ url: \`${modifiedRequestUrl}\`,
2631
+ headers: {
2632
+ ${contentType ? `"Content-Type": "${contentType}",` : ''}
2633
+ },
2634
+ params,
2635
+ key: \`${"${source}: ${operation}"}\`,
2636
+ source: '${source}'
2637
+ ${requestBody ? finalRequestBodyBlock : ''},
2638
+ ${responseTypePart()}
2639
+ });
2640
+ }, [call]);
2641
+
2642
+ return [doExecute, abort];
2643
+ }
2644
+
2645
+ use${pluginSdk.pascalCase(operationId)}AsyncHook.key = \`${"${source}: ${operation}"}\`;
2646
+
2647
+ export const use${pluginSdk.pascalCase(operationId)}Async: ${hookShape} = use${pluginSdk.pascalCase(operationId)}AsyncHook;
2648
+ `;
2649
+ }
2650
+
2651
+ async function clientIndexTemplate(descriptors, ctx) {
2652
+ var _ctx_getCounter;
2653
+ const { source, data: { paths, operationId, responseType, contentType } } = descriptors[0];
2654
+ (_ctx_getCounter = ctx.getCounter(source)) == null ? void 0 : _ctx_getCounter.inc("Endpoints");
2655
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', source, ...paths, pluginSdk.camelCase(operationId), `client.ts`));
2656
+ const postfix = ctx.potentiallyConflictingDescriptors.includes(operationId) ? pluginSdk.generatePostfix(contentType, responseType) : '';
2657
+ if (descriptors.length === 1) return ts`
2658
+ export { use${pluginSdk.pascalCase(operationId)} } from './use${pluginSdk.pascalCase(operationId)}${postfix}'
2659
+ export { use${pluginSdk.pascalCase(operationId)}Async } from './use${pluginSdk.pascalCase(operationId)}Async${postfix}'
2660
+ `;
2661
+ const exports = descriptors.map(({ data: { contentType, responseType } })=>{
2662
+ const postfix = ctx.potentiallyConflictingDescriptors.includes(operationId) ? pluginSdk.generatePostfix(contentType, responseType) : '';
2663
+ return `
2664
+ export { use${pluginSdk.pascalCase(operationId)} as use${pluginSdk.pascalCase(operationId)}${postfix} } from './use${pluginSdk.pascalCase(operationId)}${postfix}'
2665
+ export { use${pluginSdk.pascalCase(operationId)}Async as use${pluginSdk.pascalCase(operationId)}Async${postfix} } from './use${pluginSdk.pascalCase(operationId)}Async${postfix}'
2666
+ `;
2667
+ }).join('\n');
2668
+ return ts`
2669
+ ${exports}
2670
+ `;
2671
+ }
2672
+
2673
+ function extractHookShapeAndOptionsShape(response, requestBody, imports) {
2674
+ if (response) {
2675
+ if (requestBody) {
2676
+ imports.add(`import { BinaryFunctionHook, BinaryHookOptions } from "@intrig/react"`);
2677
+ return {
2678
+ hookShape: `BinaryFunctionHook<Params, RequestBody, Response>`,
2679
+ optionsShape: `BinaryHookOptions<Params, RequestBody>`
2680
+ };
2681
+ } else {
2682
+ imports.add(`import { UnaryFunctionHook, UnaryHookOptions } from "@intrig/react"`);
2683
+ return {
2684
+ hookShape: `UnaryFunctionHook<Params, Response>`,
2685
+ optionsShape: `UnaryHookOptions<Params>`
2686
+ };
2687
+ }
2688
+ } else {
2689
+ if (requestBody) {
2690
+ imports.add(`import { BinaryProduceHook, BinaryHookOptions } from "@intrig/react"`);
2691
+ return {
2692
+ hookShape: `BinaryProduceHook<Params, RequestBody>`,
2693
+ optionsShape: `BinaryHookOptions<Params, RequestBody>`
2694
+ };
2695
+ } else {
2696
+ imports.add(`import { UnaryProduceHook, UnaryHookOptions } from "@intrig/react"`);
2697
+ return {
2698
+ hookShape: `UnaryProduceHook<Params>`,
2699
+ optionsShape: `UnaryHookOptions<Params>`
2700
+ };
2701
+ }
2702
+ }
2703
+ }
2704
+ function extractParamDeconstruction(variables, requestBody) {
2705
+ const isParamMandatory = (variables == null ? void 0 : variables.some((a)=>a.in === 'path')) || false;
2706
+ if (requestBody) {
2707
+ if (isParamMandatory) {
2708
+ return {
2709
+ paramExpression: 'data, p',
2710
+ paramType: 'data: RequestBody, params: Params'
2711
+ };
2712
+ } else {
2713
+ return {
2714
+ paramExpression: 'data, p = {}',
2715
+ paramType: 'data: RequestBody, params?: Params'
2716
+ };
2717
+ }
2718
+ } else {
2719
+ if (isParamMandatory) {
2720
+ return {
2721
+ paramExpression: 'p',
2722
+ paramType: 'params: Params'
2723
+ };
2724
+ } else {
2725
+ return {
2726
+ paramExpression: 'p = {}',
2727
+ paramType: 'params?: Params'
2728
+ };
2729
+ }
2730
+ }
2731
+ }
2732
+ function extractErrorParams(errorTypes) {
2733
+ switch(errorTypes.length){
2734
+ case 0:
2735
+ return `
2736
+ export type _ErrorType = any
2737
+ const errorSchema = z.any()`;
2738
+ case 1:
2739
+ return `
2740
+ export type _ErrorType = ${errorTypes[0]}
2741
+ const errorSchema = ${errorTypes[0]}Schema`;
2742
+ default:
2743
+ return `
2744
+ export type _ErrorType = ${errorTypes.join(' | ')}
2745
+ const errorSchema = z.union([${errorTypes.map((a)=>`${a}Schema`).join(', ')}])`;
2746
+ }
2747
+ }
2748
+ async function downloadHookTemplate({ source, data: { paths, operationId, response, requestUrl, variables, requestBody, contentType, responseType, errorResponses, method } }, ctx) {
2749
+ var _ctx_getCounter;
2750
+ const postfix = ctx.potentiallyConflictingDescriptors.includes(operationId) ? pluginSdk.generatePostfix(contentType, responseType) : '';
2751
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', source, ...paths, pluginSdk.camelCase(operationId), `use${pluginSdk.pascalCase(operationId)}${postfix}Download.ts`));
2752
+ (_ctx_getCounter = ctx.getCounter(source)) == null ? void 0 : _ctx_getCounter.inc("Download Hooks");
2753
+ const modifiedRequestUrl = `${requestUrl == null ? void 0 : requestUrl.replace(/\{/g, "${")}`;
2754
+ const imports = new Set();
2755
+ imports.add(`import { z } from 'zod'`);
2756
+ imports.add(`import { useCallback, useEffect } from 'react'`);
2757
+ imports.add(`import {useNetworkState, NetworkState, DispatchState, pending, success, error, init, successfulDispatch, validationError, encode, isSuccess, requestValidationError} from "@intrig/react"`);
2758
+ const { hookShape, optionsShape } = extractHookShapeAndOptionsShape(response, requestBody, imports);
2759
+ const { paramExpression, paramType } = extractParamDeconstruction(variables, requestBody);
2760
+ if (requestBody) {
2761
+ imports.add(`import { ${requestBody} as RequestBody, ${requestBody}Schema as requestBodySchema } from "@intrig/react/${source}/components/schemas/${requestBody}"`);
2762
+ }
2763
+ if (response) {
2764
+ imports.add(`import { ${response} as Response, ${response}Schema as schema } from "@intrig/react/${source}/components/schemas/${response}"`);
2765
+ }
2766
+ imports.add(`import {${pluginSdk.pascalCase(operationId)}Params as Params} from './${pluginSdk.pascalCase(operationId)}.params'`);
2767
+ const errorTypes = [
2768
+ ...new Set(Object.values(errorResponses != null ? errorResponses : {}).map((a)=>a.response))
2769
+ ];
2770
+ errorTypes.forEach((ref)=>imports.add(`import {${ref}, ${ref}Schema } from "@intrig/react/${source}/components/schemas/${ref}"`));
2771
+ var _variables_filter_map;
2772
+ const paramExplode = [
2773
+ ...(_variables_filter_map = variables == null ? void 0 : variables.filter((a)=>a.in === "path").map((a)=>a.name)) != null ? _variables_filter_map : [],
2774
+ "...params"
2775
+ ].join(",");
2776
+ const finalRequestBodyBlock = requestBody ? `,data: encode(data, "${contentType}", requestBodySchema)` : '';
2777
+ function responseTypePart() {
2778
+ switch(responseType){
2779
+ case "application/octet-stream":
2780
+ return `responseType: 'blob', adapter: 'fetch',`;
2781
+ case "text/event-stream":
2782
+ return `responseType: 'stream', adapter: 'fetch',`;
2783
+ }
2784
+ return '';
2785
+ }
2786
+ return ts`
2787
+ ${[
2788
+ ...imports
2789
+ ].join('\n')}
2790
+
2791
+ ${!response ? `
2792
+ type Response = any;
2793
+ const schema = z.any();
2794
+ ` : ''}
2795
+
2796
+ ${extractErrorParams(errorTypes.map((a)=>a))}
2797
+
2798
+ const operation = "${method.toUpperCase()} ${requestUrl}| ${contentType} -> ${responseType}"
2799
+ const source = "${source}"
2800
+
2801
+ function use${pluginSdk.pascalCase(operationId)}Hook(options: ${optionsShape} = {}): [NetworkState<Response>, (${paramType}) => DispatchState<any>, () => void] {
2802
+ let [state, dispatch, clear, dispatchState] = useNetworkState<Response>({
2803
+ key: options?.key ?? 'default',
2804
+ operation,
2805
+ source,
2806
+ schema,
2807
+ errorSchema
2808
+ });
2809
+
2810
+ useEffect(() => {
2811
+ if (isSuccess(state)) {
2812
+ let a = document.createElement('a');
2813
+ const ct =
2814
+ state.headers?.['content-type'] ?? 'application/octet-stream';
2815
+ let data: any = state.data;
2816
+ if (ct.startsWith('application/json')) {
2817
+ let data: any[];
2818
+ if (ct.startsWith('application/json')) {
2819
+ data = [JSON.stringify(state.data, null, 2)];
2820
+ } else {
2821
+ data = [state.data];
2822
+ }
2823
+ }
2824
+ a.href = URL.createObjectURL(new Blob(Array.isArray(data) ? data : [data], {type: ct}));
2825
+ const contentDisposition = state.headers?.['content-disposition'];
2826
+ let filename = '${pluginSdk.pascalCase(operationId)}.${mimeType__namespace.extension(contentType)}';
2827
+ if (contentDisposition) {
2828
+ const rx = /filename\\*=(?:UTF-8'')?([^;\\r\\n]+)|filename="?([^";\\r\\n]+)"?/i;
2829
+ const m = contentDisposition.match(rx);
2830
+ if (m && m[1]) {
2831
+ filename = decodeURIComponent(m[1].replace(/\\+/g, ' '));
2832
+ } else if (m && m[2]) {
2833
+ filename = decodeURIComponent(m[2].replace(/\\+/g, ' '));
2834
+ }
2835
+ }
2836
+ a.download = filename;
2837
+ document.body.appendChild(a);
2838
+ a.click();
2839
+ document.body.removeChild(a);
2840
+ dispatchState(init())
2841
+ }
2842
+ }, [state])
2843
+
2844
+ let doExecute = useCallback<(${paramType}) => DispatchState<any>>((${paramExpression}) => {
2845
+ let { ${paramExplode}} = p
2846
+
2847
+ ${requestBody ? `
2848
+ const validationResult = requestBodySchema.safeParse(data);
2849
+ if (!validationResult.success) {
2850
+ dispatchState(error(requestValidationError(validationResult.error)));
2851
+ return validationError(validationResult.error.errors);
2852
+ }
2853
+ ` : ``}
2854
+
2855
+ dispatch({
2856
+ method: '${method}',
2857
+ url: \`${modifiedRequestUrl}\`,
2858
+ headers: {
2859
+ ${contentType ? `"Content-Type": "${contentType}",` : ''}
2860
+ },
2861
+ params,
2862
+ key: \`${"${source}: ${operation}"}\`,
2863
+ source: '${source}'
2864
+ ${requestBody ? finalRequestBodyBlock : ''},
2865
+ ${responseTypePart()}
2866
+ })
2867
+ return successfulDispatch();
2868
+ }, [dispatch])
2869
+
2870
+ useEffect(() => {
2871
+ if (options.fetchOnMount) {
2872
+ doExecute(${[
2873
+ requestBody ? `options.body!` : undefined,
2874
+ "options.params!"
2875
+ ].filter((a)=>a).join(",")});
2876
+ }
2877
+
2878
+ return () => {
2879
+ if (options.clearOnUnmount) {
2880
+ clear();
2881
+ }
2882
+ }
2883
+ }, [])
2884
+
2885
+ return [
2886
+ state,
2887
+ doExecute,
2888
+ clear
2889
+ ]
2890
+ }
2891
+
2892
+ use${pluginSdk.pascalCase(operationId)}Hook.key = \`${"${source}: ${operation}"}\`
2893
+
2894
+ export const use${pluginSdk.pascalCase(operationId)}Download: ${hookShape} = use${pluginSdk.pascalCase(operationId)}Hook;
2895
+ `;
2896
+ }
2897
+
2898
+ async function typeTemplate(descriptor) {
2899
+ const { data: { schema, name: typeName }, source } = descriptor;
2900
+ const { imports, zodSchema, tsType } = openApiSchemaToZod(schema);
2901
+ const ts = pluginSdk.typescript(path__namespace.resolve('src', source, 'components', 'schemas', `${typeName}.ts`));
2902
+ const simpleType = (await pluginSdk.jsonLiteral('')`${JSON.stringify(schema)}`).content;
2903
+ const transport = schema.type === 'string' && schema.format === 'binary' ? 'binary' : 'json';
2904
+ var _JSON_stringify;
2905
+ return ts`
2906
+ import { z } from 'zod'
2907
+
2908
+ ${[
2909
+ ...imports
2910
+ ].join('\n')}
2911
+
2912
+ //--- Zod Schemas ---//
2913
+
2914
+ export const ${typeName}Schema = ${zodSchema}
2915
+
2916
+ //--- Typescript Type ---//
2917
+
2918
+ export type ${typeName} = ${tsType}
2919
+
2920
+ //--- JSON Schema ---//
2921
+
2922
+ export const ${typeName}_jsonschema = ${(_JSON_stringify = JSON.stringify(schema)) != null ? _JSON_stringify : "{}"}
2923
+
2924
+ //--- Simple Type ---//
2925
+ /*[${simpleType}]*/
2926
+
2927
+ // Transport hint for clients ("binary" => use arraybuffer/blob)
2928
+ export const ${typeName}_transport = '${transport}' as const;
2929
+ `;
2930
+ }
2931
+ function isRef(schema) {
2932
+ return '$ref' in (schema != null ? schema : {});
2933
+ }
2934
+ // Helper function to convert OpenAPI schema types to TypeScript types and Zod schemas
2935
+ function openApiSchemaToZod(schema, imports = new Set()) {
2936
+ if (!schema) {
2937
+ return {
2938
+ tsType: 'any',
2939
+ zodSchema: 'z.any()',
2940
+ imports: new Set()
2941
+ };
2942
+ }
2943
+ if (isRef(schema)) {
2944
+ return handleRefSchema(schema.$ref, imports);
2945
+ }
2946
+ if (!schema.type) {
2947
+ if ('properties' in schema) {
2948
+ schema.type = 'object';
2949
+ } else if ('items' in schema) {
2950
+ schema.type = 'array';
2951
+ }
2952
+ }
2953
+ switch(schema.type){
2954
+ case 'string':
2955
+ return handleStringSchema(schema);
2956
+ case 'number':
2957
+ return handleNumberSchema(schema);
2958
+ case 'integer':
2959
+ return handleIntegerSchema(schema);
2960
+ case 'boolean':
2961
+ return handleBooleanSchema();
2962
+ case 'array':
2963
+ return handleArraySchema(schema, imports);
2964
+ case 'object':
2965
+ return handleObjectSchema(schema, imports);
2966
+ default:
2967
+ return handleComplexSchema(schema, imports);
2968
+ }
2969
+ }
2970
+ function handleRefSchema(ref, imports) {
2971
+ const refParts = ref.split('/');
2972
+ const refName = refParts[refParts.length - 1];
2973
+ imports.add(`import { ${refName}, ${refName}Schema } from './${refName}';`);
2974
+ return {
2975
+ tsType: refName,
2976
+ zodSchema: `z.lazy(() => ${refName}Schema)`,
2977
+ imports
2978
+ };
2979
+ }
2980
+ function handleStringSchema(schema) {
2981
+ const imports = new Set();
2982
+ let binaryish = false;
2983
+ if (schema.enum) {
2984
+ const enumValues = schema.enum.map((value)=>`'${value}'`).join(' | ');
2985
+ const zodEnum = `z.enum([${schema.enum.map((value)=>`'${value}'`).join(', ')}])`;
2986
+ return {
2987
+ tsType: enumValues,
2988
+ zodSchema: zodEnum,
2989
+ imports: new Set()
2990
+ };
2991
+ }
2992
+ let zodSchema = 'z.string()';
2993
+ let tsType = 'string';
2994
+ if (schema.format === 'date' && !schema.pattern) {
2995
+ tsType = 'Date';
2996
+ zodSchema = 'z.coerce.date()';
2997
+ zodSchema += `.transform((val) => {
2998
+ const parsedDate = new Date(val);
2999
+ if (isNaN(parsedDate.getTime())) {
3000
+ throw new Error('Invalid date format');
3001
+ }
3002
+ return parsedDate;
3003
+ })`;
3004
+ } else if (schema.format === 'time') {
3005
+ zodSchema = 'z.string()';
3006
+ if (schema.pattern) {
3007
+ zodSchema += `.regex(new RegExp('${schema.pattern}'))`;
3008
+ }
3009
+ } else if (schema.format === 'date-time' && !schema.pattern) {
3010
+ tsType = 'Date';
3011
+ zodSchema = 'z.coerce.date()';
3012
+ zodSchema += `.transform((val) => {
3013
+ const parsedDateTime = new Date(val);
3014
+ if (isNaN(parsedDateTime.getTime())) {
3015
+ throw new Error('Invalid date-time format');
3016
+ }
3017
+ return parsedDateTime;
3018
+ })`;
3019
+ } else if (schema.format === 'binary') {
3020
+ tsType = 'BinaryData';
3021
+ zodSchema = 'BinaryDataSchema';
3022
+ imports.add(`import { BinaryData, BinaryDataSchema } from '@intrig/react/type-utils'`);
3023
+ binaryish = true;
3024
+ } else if (schema.format === 'byte') {
3025
+ tsType = 'Uint8Array';
3026
+ zodSchema = 'z.string().transform((val) => base64ToUint8Array(val))';
3027
+ imports.add(`import { base64ToUint8Array } from '@intrig/react/type-utils'`);
3028
+ binaryish = true;
3029
+ } else if (schema.format === 'email') {
3030
+ zodSchema = 'z.string().email()';
3031
+ } else if (schema.format === 'uuid') {
3032
+ zodSchema = 'z.string().uuid()';
3033
+ } else if (schema.format === 'uri') {
3034
+ zodSchema = 'z.string().url()';
3035
+ } else if (schema.format === 'hostname') {
3036
+ zodSchema = 'z.string()'; // Zod does not have a direct hostname validator
3037
+ } else if (schema.format === 'ipv4') {
3038
+ zodSchema = 'z.string().regex(/^((25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$/)';
3039
+ } else if (schema.format === 'ipv6') {
3040
+ zodSchema = 'z.string().regex(/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6}|:)|::(ffff(:0{1,4}){0,1}:)?((25[0-5]|(2[0-4]|1[0-9]|[0-9])\\.){3}(25[0-5]|(2[0-4]|1[0-9]|[0-9]))))$/)';
3041
+ } else {
3042
+ if (schema.minLength !== undefined) zodSchema += `.min(${schema.minLength})`;
3043
+ if (schema.maxLength !== undefined) zodSchema += `.max(${schema.maxLength})`;
3044
+ if (schema.pattern !== undefined) zodSchema += `.regex(new RegExp('${schema.pattern}'))`;
3045
+ }
3046
+ return {
3047
+ tsType,
3048
+ zodSchema,
3049
+ imports,
3050
+ binaryish
3051
+ };
3052
+ }
3053
+ function handleNumberSchema(schema) {
3054
+ let zodSchema = 'z.number()';
3055
+ if (schema.minimum !== undefined) zodSchema += `.min(${schema.minimum})`;
3056
+ if (schema.maximum !== undefined) zodSchema += `.max(${schema.maximum})`;
3057
+ return {
3058
+ tsType: 'number',
3059
+ zodSchema,
3060
+ imports: new Set()
3061
+ };
3062
+ }
3063
+ function handleIntegerSchema(schema) {
3064
+ let zodSchema = 'z.number().int()';
3065
+ if (schema.minimum !== undefined) zodSchema += `.min(${schema.minimum})`;
3066
+ if (schema.maximum !== undefined) zodSchema += `.max(${schema.maximum})`;
3067
+ return {
3068
+ tsType: 'number',
3069
+ zodSchema,
3070
+ imports: new Set()
3071
+ };
3072
+ }
3073
+ function handleBooleanSchema() {
3074
+ const zodSchema = 'z.boolean()';
3075
+ return {
3076
+ tsType: 'boolean',
3077
+ zodSchema,
3078
+ imports: new Set()
3079
+ };
3080
+ }
3081
+ function handleArraySchema(schema, imports) {
3082
+ if (!schema.items) {
3083
+ throw new Error('Array schema must have an items property');
3084
+ }
3085
+ const { tsType, zodSchema: itemZodSchema, imports: itemImports, binaryish } = openApiSchemaToZod(schema.items, imports);
3086
+ let zodSchema = binaryish ? `z.array(${itemZodSchema})` : `(z.preprocess((raw) => (Array.isArray(raw) ? raw : [raw]), z.array(${itemZodSchema})) as z.ZodType<${tsType}[], z.ZodTypeDef, ${tsType}[]>)`;
3087
+ if (schema.minItems !== undefined) zodSchema += `.min(${schema.minItems})`;
3088
+ if (schema.maxItems !== undefined) zodSchema += `.max(${schema.maxItems})`;
3089
+ return {
3090
+ tsType: `(${tsType})[]`,
3091
+ zodSchema,
3092
+ imports: new Set([
3093
+ ...imports,
3094
+ ...itemImports
3095
+ ])
3096
+ };
3097
+ }
3098
+ function handleObjectSchema(schema, imports) {
3099
+ const updatedRequiredFields = schema.required || [];
3100
+ if (schema.properties) {
3101
+ const propertiesTs = Object.entries(schema.properties).map(([key, value])=>{
3102
+ const { tsType, optional } = openApiSchemaToZod(value);
3103
+ const isRequired = !optional && updatedRequiredFields.includes(key);
3104
+ return `${key}${isRequired ? '' : '?'}: ${tsType} ${isRequired ? '' : ' | null'};`;
3105
+ });
3106
+ const propertiesZod = Object.entries(schema.properties).map(([key, value])=>{
3107
+ const { zodSchema, imports: propImports } = openApiSchemaToZod(value);
3108
+ imports = new Set([
3109
+ ...imports,
3110
+ ...propImports
3111
+ ]);
3112
+ const isRequired = updatedRequiredFields.includes(key);
3113
+ return `${key}: ${isRequired ? zodSchema : zodSchema.includes('.optional().nullable()') ? zodSchema : zodSchema + '.optional().nullable()'}`;
3114
+ });
3115
+ return {
3116
+ tsType: `{ ${propertiesTs.join(' ')} }`,
3117
+ zodSchema: `z.object({ ${propertiesZod.join(', ')} })`,
3118
+ imports
3119
+ };
3120
+ }
3121
+ return {
3122
+ tsType: 'any',
3123
+ zodSchema: 'z.any()',
3124
+ imports: new Set(),
3125
+ optional: true
3126
+ };
3127
+ }
3128
+ function handleComplexSchema(schema, imports) {
3129
+ if (schema.oneOf) {
3130
+ const options = schema.oneOf.map((subSchema)=>openApiSchemaToZod(subSchema));
3131
+ const zodSchemas = options.map((option)=>option.zodSchema);
3132
+ const tsTypes = options.map((option)=>option.tsType);
3133
+ return {
3134
+ tsType: tsTypes.join(' | '),
3135
+ zodSchema: `z.union([${zodSchemas.join(', ')}])`,
3136
+ imports: new Set([
3137
+ ...imports,
3138
+ ...options.flatMap((option)=>Array.from(option.imports))
3139
+ ])
3140
+ };
3141
+ }
3142
+ if (schema.anyOf) {
3143
+ const options = schema.anyOf.map((subSchema)=>openApiSchemaToZod(subSchema));
3144
+ const zodSchemas = options.map((option)=>option.zodSchema);
3145
+ const tsTypes = options.map((option)=>option.tsType);
3146
+ return {
3147
+ tsType: tsTypes.join(' | '),
3148
+ zodSchema: `z.union([${zodSchemas.join(', ')}])`,
3149
+ imports: new Set([
3150
+ ...imports,
3151
+ ...options.flatMap((option)=>Array.from(option.imports))
3152
+ ])
3153
+ };
3154
+ }
3155
+ if (schema.allOf) {
3156
+ const options = schema.allOf.map((subSchema)=>openApiSchemaToZod(subSchema));
3157
+ const zodSchemas = options.map((option)=>option.zodSchema);
3158
+ const tsTypes = options.map((option)=>option.tsType);
3159
+ if (zodSchemas.length === 1) return {
3160
+ tsType: tsTypes.join(' & '),
3161
+ zodSchema: zodSchemas[0],
3162
+ imports: new Set([
3163
+ ...imports,
3164
+ ...options.flatMap((option)=>Array.from(option.imports))
3165
+ ])
3166
+ };
3167
+ return {
3168
+ tsType: tsTypes.join(' & '),
3169
+ zodSchema: `z.intersection(${zodSchemas.join(', ')})`,
3170
+ imports: new Set([
3171
+ ...imports,
3172
+ ...options.flatMap((option)=>Array.from(option.imports))
3173
+ ])
3174
+ };
3175
+ }
3176
+ return {
3177
+ tsType: 'any',
3178
+ zodSchema: 'z.any()',
3179
+ imports
3180
+ };
3181
+ }
3182
+
3183
+ async function generateCode(ctx) {
3184
+ // Root/project files
3185
+ await ctx.dump(packageJsonTemplate(ctx));
3186
+ await ctx.dump(reactTsConfigTemplate());
3187
+ await ctx.dump(reactSwcrcTemplate());
3188
+ // Top-level src files
3189
+ await ctx.dump(indexTemplate());
3190
+ await ctx.dump(networkStateTemplate());
3191
+ await ctx.dump(contextTemplate(ctx.sources));
3192
+ await ctx.dump(reactLoggerTemplate());
3193
+ await ctx.dump(reactExtraTemplate());
3194
+ await ctx.dump(reactMediaTypeUtilsTemplate());
3195
+ await ctx.dump(typeUtilsTemplate());
3196
+ await ctx.dump(intrigMiddlewareTemplate());
3197
+ await ctx.dump(flushSyncUtilTemplate(ctx));
3198
+ // Provider modular files (placed under src)
3199
+ await ctx.dump(providerMainTemplate(ctx.sources));
3200
+ await ctx.dump(providerHooksTemplate());
3201
+ await ctx.dump(providerInterfacesTemplate(ctx.sources));
3202
+ await ctx.dump(providerReducerTemplate());
3203
+ await ctx.dump(providerAxiosConfigTemplate(ctx.sources));
3204
+ await ctx.dump(reactIntrigProviderTemplate(ctx.sources));
3205
+ await ctx.dump(reactIntrigProviderStubTemplate(ctx.sources));
3206
+ await ctx.dump(reactStatusTrapTemplate(ctx.sources));
3207
+ const potentiallyConflictingDescriptors = ctx.restDescriptors.sort((a, b)=>(a.data.contentType === "application/json" ? -1 : 0) - (b.data.contentType === "application/json" ? -1 : 0)).filter((descriptor, index, array)=>array.findIndex((other)=>other.data.operationId === descriptor.data.operationId) !== index).map((descriptor)=>descriptor.id);
3208
+ const internalGeneratorContext = new InternalGeneratorContext(potentiallyConflictingDescriptors);
3209
+ for (const restDescriptor of ctx.restDescriptors){
3210
+ await ctx.dump(requestHookTemplate(restDescriptor, internalGeneratorContext));
3211
+ await ctx.dump(paramsTemplate(restDescriptor));
3212
+ await ctx.dump(asyncFunctionHookTemplate(restDescriptor, internalGeneratorContext));
3213
+ if (restDescriptor.data.isDownloadable) {
3214
+ await ctx.dump(downloadHookTemplate(restDescriptor, internalGeneratorContext));
3215
+ }
3216
+ await ctx.dump(clientIndexTemplate([
3217
+ restDescriptor
3218
+ ], internalGeneratorContext));
3219
+ }
3220
+ for (const schemaDescriptor of ctx.schemaDescriptors){
3221
+ ctx.dump(typeTemplate(schemaDescriptor));
3222
+ }
3223
+ return internalGeneratorContext.getCounters();
3224
+ }
3225
+
3226
+ /**
3227
+ * Schema documentation tab builders for React binding.
3228
+ * We keep a small, composable API similar to other docs templates.
3229
+ */ async function schemaTypescriptDoc(result) {
3230
+ const md = pluginSdk.mdLiteral('schema-typescript.md');
3231
+ const name = result.data.name;
3232
+ const source = result.source;
3233
+ const { tsType } = openApiSchemaToZod(result.data.schema);
3234
+ const ts = pluginSdk.typescript(path.resolve('src', source, 'temp', name, `${name}.ts`));
3235
+ const importContent = await ts`
3236
+ import type { ${name} } from '@intrig/react/${source}/components/schemas/${name}';
3237
+ `;
3238
+ const codeContent = await ts`
3239
+ export type ${name} = ${tsType};
3240
+ `;
3241
+ return md`
3242
+ # Typescript Type
3243
+ Use this TypeScript type anywhere you need static typing for this object shape in your app code: component props, function params/returns, reducers, and local state in .ts/.tsx files.
3244
+
3245
+ ## Import
3246
+ ${'```ts'}
3247
+ ${importContent}
3248
+ ${'```'}
3249
+
3250
+ ## Definition
3251
+ ${'```ts'}
3252
+ ${codeContent}
3253
+ ${'```'}
3254
+ `;
3255
+ }
3256
+ async function schemaJsonSchemaDoc(result) {
3257
+ const md = pluginSdk.mdLiteral('schema-json.md');
3258
+ const name = result.data.name;
3259
+ const source = result.source;
3260
+ const ts = pluginSdk.typescript(path.resolve('src', source, 'temp', name, `${name}.ts`));
3261
+ const importContent = await ts`
3262
+ import { ${name}_jsonschema } from '@intrig/react/${source}/components/schemas/${name}';
3263
+ `;
3264
+ var _JSON_stringify;
3265
+ const codeContent = await ts`
3266
+ export const ${name}_jsonschema = ${(_JSON_stringify = JSON.stringify(result.data.schema, null, 2)) != null ? _JSON_stringify : "{}"};
3267
+ `;
3268
+ return md`
3269
+ # JSON Schema
3270
+ Use this JSON Schema with tools that consume JSON Schema: UI form builders (e.g. react-jsonschema-form), validators (AJV, validators in backends), and generators.
3271
+
3272
+ ## Import
3273
+ ${'```ts'}
3274
+ ${importContent}
3275
+ ${'```'}
3276
+
3277
+ ## Definition
3278
+ ${'```ts'}
3279
+ ${codeContent}
3280
+ ${'```'}
3281
+ `;
3282
+ }
3283
+ async function schemaZodSchemaDoc(result) {
3284
+ const md = pluginSdk.mdLiteral('schema-zod.md');
3285
+ const name = result.data.name;
3286
+ const source = result.source;
3287
+ const { zodSchema } = openApiSchemaToZod(result.data.schema);
3288
+ const ts = pluginSdk.typescript(path.resolve('src', source, 'temp', name, `${name}.ts`));
3289
+ const importContent = await ts`
3290
+ import { ${name}Schema } from '@intrig/react/${source}/components/schemas/${name}';
3291
+ `;
3292
+ const codeContent = await ts`
3293
+ export const ${name}Schema = ${zodSchema};
3294
+ `;
3295
+ return md`
3296
+ # Zod Schema
3297
+ Use this Zod schema for runtime validation and parsing: form validation, client/server payload guards, and safe transformations before using or storing data.
3298
+
3299
+ ## Import
3300
+ ${'```ts'}
3301
+ ${importContent}
3302
+ ${'```'}
3303
+
3304
+ ## Definition
3305
+ ${'```ts'}
3306
+ ${codeContent}
3307
+ ${'```'}
3308
+ `;
3309
+ }
3310
+
3311
+ async function getSchemaDocumentation(result) {
3312
+ const tabs = [];
3313
+ tabs.push({
3314
+ name: 'Typescript Type',
3315
+ content: (await schemaTypescriptDoc(result)).content
3316
+ });
3317
+ tabs.push({
3318
+ name: 'JSON Schema',
3319
+ content: (await schemaJsonSchemaDoc(result)).content
3320
+ });
3321
+ tabs.push({
3322
+ name: 'Zod Schema',
3323
+ content: (await schemaZodSchemaDoc(result)).content
3324
+ });
3325
+ return tabs;
3326
+ }
3327
+
3328
+ function reactSseHookDocs(descriptor) {
3329
+ const md = pluginSdk.mdLiteral("sse-hook.md");
3330
+ var _descriptor_data_variables;
3331
+ // ===== Derived names =====
3332
+ const hasPathParams = ((_descriptor_data_variables = descriptor.data.variables) != null ? _descriptor_data_variables : []).some((v)=>{
3333
+ var _v_in;
3334
+ return ((_v_in = v.in) == null ? void 0 : _v_in.toUpperCase()) === "PATH";
3335
+ });
3336
+ const actionName = pluginSdk.camelCase(descriptor.name); // e.g. streamBuildLogs
3337
+ const respVar = `${actionName}Resp`; // e.g. streamBuildLogsResp
3338
+ const clearName = `clear${pluginSdk.pascalCase(descriptor.name)}`; // e.g. clearStreamBuildLogs
3339
+ const requestBodyVar = descriptor.data.requestBody ? pluginSdk.camelCase(descriptor.data.requestBody) : undefined;
3340
+ const requestBodyType = descriptor.data.requestBody ? pluginSdk.pascalCase(descriptor.data.requestBody) : undefined;
3341
+ const paramsVar = hasPathParams ? `${actionName}Params` : undefined; // e.g. streamBuildLogsParams
3342
+ const paramsType = hasPathParams ? `${pluginSdk.pascalCase(descriptor.name)}Params` : undefined; // e.g. StreamBuildLogsParams
3343
+ const responseTypeName = `${pluginSdk.pascalCase(descriptor.name)}ResponseBody`; // if generated by your build
3344
+ const callArgs = [
3345
+ requestBodyVar,
3346
+ paramsVar != null ? paramsVar : "{}"
3347
+ ].filter(Boolean).join(", ");
3348
+ return md`
3349
+ # Intrig SSE Hooks — Quick Guide
3350
+
3351
+ ## When should I use the SSE hook?
3352
+ - **Your endpoint streams events** (Server-Sent Events) and you want **incremental updates** in the UI → use this **SSE hook**.
3353
+ - **You only need a final result** → use the regular **stateful hook**.
3354
+ - **One-off validate/submit/update** with no shared state → use the **async hook**.
3355
+
3356
+ > Intrig SSE hooks are **stateful hooks** under the hood. **Events arrive while the hook is in \`Pending\`**. When the stream completes, the hook transitions to **\`Success\`** (or **\`Error\`**).
3357
+
3358
+ ---
3359
+
3360
+ ## Copy-paste starter (fast lane)
3361
+
3362
+ ### 1) Hook import
3363
+ \`\`\`ts
3364
+ import { use${pluginSdk.pascalCase(descriptor.name)} } from '@intrig/react/${descriptor.path}/client';
3365
+ \`\`\`
3366
+
3367
+ ### 2) Utility guards
3368
+ \`\`\`ts
3369
+ import { isPending, isSuccess, isError } from '@intrig/react';
3370
+ \`\`\`
3371
+
3372
+ ### 3) Hook instance (auto-clear on unmount)
3373
+ \`\`\`ts
3374
+ const [${respVar}, ${actionName}] = use${pluginSdk.pascalCase(descriptor.name)}({ clearOnUnmount: true });
3375
+ \`\`\`
3376
+
3377
+ ---
3378
+
3379
+ ## TL;DR (copy–paste)
3380
+
3381
+ \`\`\`tsx
3382
+ import { use${pluginSdk.pascalCase(descriptor.name)} } from '@intrig/react/${descriptor.path}/client';
3383
+ import { isPending, isSuccess, isError } from '@intrig/react';
3384
+ import { useEffect, useState } from 'react';
3385
+
3386
+ export default function Example() {
3387
+ const [${respVar}, ${actionName}] = use${pluginSdk.pascalCase(descriptor.name)}({ clearOnUnmount: true });
3388
+ const [messages, setMessages] = useState<any[]>([]);
3389
+
3390
+ useEffect(() => {
3391
+ ${actionName}(${callArgs}); // start stream
3392
+ }, [${actionName}]);
3393
+
3394
+ useEffect(() => {
3395
+ // SSE delivers messages while state is Pending
3396
+ if (isPending(${respVar})) {
3397
+ setMessages((prev) => [...prev, ${respVar}.data]);
3398
+ }
3399
+ }, [${respVar}]);
3400
+
3401
+ if (isError(${respVar})) return <>An error occurred</>;
3402
+ if (isPending(${respVar})) return <pre>{JSON.stringify(messages, null, 2)}</pre>;
3403
+ if (isSuccess(${respVar})) return <>Completed</>;
3404
+
3405
+ return null;
3406
+ }
3407
+ \`\`\`
3408
+
3409
+ ${requestBodyType || paramsType ? `### Optional types (if generated by your build)
3410
+ \`\`\`ts
3411
+ ${requestBodyType ? `import type { ${requestBodyType} } from '@intrig/react/${descriptor.source}/components/schemas/${requestBodyType}';\n` : ''}${paramsType ? `import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.params';\n` : ''}import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.response';
3412
+ \`\`\`
3413
+ ` : ''}
3414
+
3415
+ ---
3416
+
3417
+ ## Hook API
3418
+
3419
+ \`\`\`ts
3420
+ // Signature (shape shown; concrete generics vary per generated hook)
3421
+ declare function use${pluginSdk.pascalCase(descriptor.name)}(options?: {
3422
+ fetchOnMount?: boolean;
3423
+ clearOnUnmount?: boolean; // recommended for streams
3424
+ key?: string; // isolate multiple subscriptions
3425
+ params?: ${paramsType != null ? paramsType : 'unknown'};
3426
+ body?: ${requestBodyType != null ? requestBodyType : 'unknown'};
3427
+ }): [
3428
+ // While streaming: isPending(state) === true and state.data is the latest event
3429
+ NetworkState<${responseTypeName} /* or event payload type */, any>,
3430
+ // Start streaming:
3431
+ (req: { params?: ${paramsType != null ? paramsType : 'unknown'}; body?: ${requestBodyType != null ? requestBodyType : 'unknown'} }) => void,
3432
+ // Clear/close stream:
3433
+ () => void
3434
+ ];
3435
+ \`\`\`
3436
+
3437
+ > **Important:** For SSE, **each incoming event** is surfaced as \`${respVar}.data\` **only while** \`isPending(${respVar})\` is true. On stream completion the hook flips to \`isSuccess\`.
3438
+
3439
+ ---
3440
+
3441
+ ## Usage patterns
3442
+
3443
+ ### 1) Lifecycle-bound stream (start on mount, auto-clear)
3444
+ \`\`\`tsx
3445
+ const [${respVar}, ${actionName}] = use${pluginSdk.pascalCase(descriptor.name)}({ clearOnUnmount: true });
3446
+
3447
+ useEffect(() => {
3448
+ ${actionName}(${callArgs});
3449
+ }, [${actionName}]);
3450
+ \`\`\`
3451
+ <details><summary>Description</summary>
3452
+ Starts the stream when the component mounts and closes it when the component unmounts.
3453
+ </details>
3454
+
3455
+ ### 2) Collect messages into an array (simple collector)
3456
+ \`\`\`tsx
3457
+ const [messages, setMessages] = useState<any[]>([]);
3458
+
3459
+ useEffect(() => {
3460
+ if (isPending(${respVar})) setMessages((m) => [...m, ${respVar}.data]);
3461
+ }, [${respVar}]);
3462
+ \`\`\`
3463
+ <details><summary>Description</summary>
3464
+ Appends each event to an in-memory array. Good for logs and chat-like feeds; consider capping length to avoid memory growth.
3465
+ </details>
3466
+
3467
+ ### 3) Keep only the latest event (cheap UI)
3468
+ \`\`\`tsx
3469
+ const latest = isPending(${respVar}) ? ${respVar}.data : undefined;
3470
+ \`\`\`
3471
+ <details><summary>Description</summary>
3472
+ When you only need the most recent message (progress percentage, status line).
3473
+ </details>
3474
+
3475
+ ### 4) Controlled start/stop (user-triggered)
3476
+ \`\`\`tsx
3477
+ const [${respVar}, ${actionName}, ${clearName}] = use${pluginSdk.pascalCase(descriptor.name)}();
3478
+
3479
+ const start = () => ${actionName}(${callArgs});
3480
+ const stop = () => ${clearName}();
3481
+ \`\`\`
3482
+ <details><summary>Description</summary>
3483
+ Expose play/pause UI for long streams or admin tools.
3484
+ </details>
3485
+
3486
+ ---
3487
+
3488
+ ## Full example (with flushSync option)
3489
+
3490
+ \`\`\`tsx
3491
+ import { use${pluginSdk.pascalCase(descriptor.name)} } from '@intrig/react/${descriptor.path}/client';
3492
+ import { isPending, isSuccess, isError } from '@intrig/react';
3493
+ import { useEffect, useState } from 'react';
3494
+ import { flushSync } from '../utils/flush-sync';
3495
+
3496
+ function MyComponent() {
3497
+ const [${respVar}, ${actionName}] = use${pluginSdk.pascalCase(descriptor.name)}({ clearOnUnmount: true });
3498
+ const [events, setEvents] = useState<any[]>([]);
3499
+
3500
+ useEffect(() => {
3501
+ ${actionName}(${callArgs});
3502
+ }, [${actionName}]);
3503
+
3504
+ useEffect(() => {
3505
+ if (isPending(${respVar})) {
3506
+ // Use flushSync only if you must render every single event (high-frequency streams).
3507
+ flushSync(() => setEvents((xs) => [...xs, ${respVar}.data]));
3508
+ }
3509
+ }, [${respVar}]);
3510
+
3511
+ if (isError(${respVar})) return <>Stream error</>;
3512
+ return (
3513
+ <>
3514
+ {isPending(${respVar}) && <pre>{JSON.stringify(events, null, 2)}</pre>}
3515
+ {isSuccess(${respVar}) && <>Completed ({events.length} events)</>}
3516
+ </>
3517
+ );
3518
+ }
3519
+ \`\`\`
3520
+
3521
+ ---
3522
+
3523
+ ## Tips, anti-patterns & gotchas
3524
+
3525
+ - **Prefer \`clearOnUnmount: true\`** so the EventSource/web request is closed when the component disappears.
3526
+ - **Don’t store unbounded arrays** for infinite streams—cap the length or batch to IndexedDB.
3527
+ - **Avoid unnecessary \`flushSync\`**; it’s expensive. Use it only when you truly must render every event.
3528
+ - **Multiple streams:** supply a unique \`key\` to isolate independent subscriptions.
3529
+ - **Server requirements:** SSE endpoints should send \`Content-Type: text/event-stream\`, disable buffering, and flush regularly; add relevant CORS headers if needed.
3530
+ - **Completion:** UI can switch from progress view (\`isPending\`) to final view (\`isSuccess\`) automatically.
3531
+
3532
+ ---
3533
+
3534
+ ## Troubleshooting
3535
+
3536
+ - **No intermediate messages:** ensure the server is truly streaming SSE (correct content type + flush) and that proxies/CDNs aren’t buffering responses.
3537
+ - **UI not updating for each event:** remove expensive work from the event effect, consider throttling; only use \`flushSync\` if absolutely necessary.
3538
+ - **Stream never completes:** check server end conditions and that you call \`${clearName}\` when appropriate.
3539
+
3540
+ `;
3541
+ }
3542
+
3543
+ function reactHookDocs(descriptor) {
3544
+ const md = pluginSdk.mdLiteral('react-hook.md');
3545
+ var _descriptor_data_variables;
3546
+ // ===== Derived names (preserve these) =====
3547
+ const hasPathParams = ((_descriptor_data_variables = descriptor.data.variables) != null ? _descriptor_data_variables : []).some((v)=>{
3548
+ var _v_in;
3549
+ return ((_v_in = v.in) == null ? void 0 : _v_in.toUpperCase()) === 'PATH';
3550
+ });
3551
+ const actionName = pluginSdk.camelCase(descriptor.name) // e.g. getUser
3552
+ ;
3553
+ const respVar = `${actionName}Resp` // e.g. getUserResp
3554
+ ;
3555
+ const dataVar = `${actionName}Data` // e.g. getUserData
3556
+ ;
3557
+ const clearName = `clear${pluginSdk.pascalCase(descriptor.name)}` // e.g. clearGetUser
3558
+ ;
3559
+ const requestBodyVar = descriptor.data.requestBody ? pluginSdk.camelCase(descriptor.data.requestBody) : undefined;
3560
+ const requestBodyType = descriptor.data.requestBody ? pluginSdk.pascalCase(descriptor.data.requestBody) : undefined;
3561
+ const paramsVar = hasPathParams ? `${actionName}Params` : undefined // e.g. getUserParams
3562
+ ;
3563
+ const paramsType = hasPathParams ? `${pluginSdk.pascalCase(descriptor.name)}Params` : undefined // e.g. GetUserParams
3564
+ ;
3565
+ const responseTypeName = `${pluginSdk.pascalCase(descriptor.name)}ResponseBody`;
3566
+ return md`
3567
+ # Intrig React Hooks — Quick Guide
3568
+
3569
+ ## Copy-paste starter (fast lane)
3570
+
3571
+ ### 1) Hook import
3572
+ ${"```ts"}
3573
+ import { use${pluginSdk.pascalCase(descriptor.name)} } from '@intrig/react/${descriptor.path}/client';
3574
+ ${"```"}
3575
+
3576
+ ### 2) Utility guards
3577
+ ${"```ts"}
3578
+ import { isPending, isError, isSuccess } from '@intrig/react';
3579
+ ${"```"}
3580
+
3581
+ ### 3) Hook instance (auto-clear on unmount)
3582
+ ${"```ts"}
3583
+ const [${respVar}, ${actionName}] = use${pluginSdk.pascalCase(descriptor.name)}({ clearOnUnmount: true });
3584
+ ${"```"}
3585
+
3586
+ Intrig stateful hooks expose a **NetworkState** plus **actions** to fetch / clear.
3587
+ Use this when you want a cached, reusable result tied to a global store.
3588
+
3589
+ ---
3590
+
3591
+ ## TL;DR (copy–paste)
3592
+ ${"```tsx"}
3593
+ import { use${pluginSdk.pascalCase(descriptor.name)} } from '@intrig/react/${descriptor.path}/client';
3594
+ import { isPending, isError, isSuccess } from '@intrig/react';
3595
+ import { useEffect, useMemo } from 'react';
3596
+
3597
+ export default function Example() {
3598
+ const [${respVar}, ${actionName}] = use${pluginSdk.pascalCase(descriptor.name)}({ clearOnUnmount: true });
3599
+
3600
+ useEffect(() => {
3601
+ ${actionName}(${[
3602
+ requestBodyVar,
3603
+ paramsVar != null ? paramsVar : '{}'
3604
+ ].filter(Boolean).join(', ')});
3605
+ }, [${[
3606
+ '' + actionName,
3607
+ requestBodyVar,
3608
+ paramsVar
3609
+ ].filter(Boolean).join(', ')}]);
3610
+
3611
+ const ${dataVar} = useMemo(
3612
+ () => (isSuccess(${respVar}) ? ${respVar}.data : undefined),
3613
+ [${respVar}]
3614
+ );
3615
+
3616
+ if (isPending(${respVar})) return <>Loading…</>;
3617
+ if (isError(${respVar})) return <>Error: {String(${respVar}.error)}</>;
3618
+ return <pre>{JSON.stringify(${dataVar}, null, 2)}</pre>;
3619
+ }
3620
+ ${"```"}
3621
+
3622
+ ${requestBodyType || paramsType ? `### Optional types (if generated by your build)
3623
+ ${"```ts"}
3624
+ ${requestBodyType ? `import type { ${requestBodyType} } from '@intrig/react/${descriptor.source}/components/schemas/${requestBodyType}';
3625
+ ` : ''}${paramsType ? `import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.params';
3626
+ ` : ''}// Prefer the concrete response type:
3627
+ import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.response';
3628
+ ${"```"}
3629
+ ` : ''}
3630
+
3631
+ ---
3632
+
3633
+ ## Hook API
3634
+ ${"```ts"}
3635
+ // Options are consistent across hooks.
3636
+ type UseHookOptions = {
3637
+ /** Execute once after mount with provided params/body (if required). */
3638
+ fetchOnMount?: boolean;
3639
+ /** Reset the state on unmount (recommended). */
3640
+ clearOnUnmount?: boolean;
3641
+ /** Distinguish multiple instances of the same hook. */
3642
+ key?: string;
3643
+ /** Initial path params for endpoints that require them. */
3644
+ params?: ${paramsType != null ? paramsType : 'unknown'};
3645
+ /** Initial request body (for POST/PUT/etc.). */
3646
+ body?: ${requestBodyType != null ? requestBodyType : 'unknown'};
3647
+ };
3648
+
3649
+ // Prefer concrete types if your build emits them:
3650
+ // import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.response';
3651
+
3652
+ type ${pluginSdk.pascalCase(descriptor.name)}Data = ${'typeof ' + responseTypeName !== 'undefined' ? responseTypeName : 'unknown'}; // replace with ${responseTypeName} if generated
3653
+ type ${pluginSdk.pascalCase(descriptor.name)}Request = { params?: ${paramsType != null ? paramsType : 'unknown'}; body?: ${requestBodyType != null ? requestBodyType : 'unknown'}; };
3654
+
3655
+ // Signature (shape shown; concrete generics vary per generated hook)
3656
+ declare function use${pluginSdk.pascalCase(descriptor.name)}(options?: UseHookOptions): [
3657
+ NetworkState<${pluginSdk.pascalCase(descriptor.name)}Data>,
3658
+ (req: ${pluginSdk.pascalCase(descriptor.name)}Request) => void,
3659
+ () => void
3660
+ ];
3661
+ ${"```"}
3662
+
3663
+ ### NetworkState & guards
3664
+ ${"```ts"}
3665
+ import { isPending, isError, isSuccess } from '@intrig/react';
3666
+ ${"```"}
3667
+
3668
+ ---
3669
+
3670
+ ## Conceptual model (Stateful = single source of truth)
3671
+ - Lives in a shared store keyed by \`key\` + request signature.
3672
+ - Best for **read** endpoints you want to **reuse** or keep **warm** (lists, details, search).
3673
+ - Lifecycle helpers:
3674
+ - \`fetchOnMount\` to kick off automatically.
3675
+ - \`clearOnUnmount\` to clean up (recommended default).
3676
+ - \`key\` to isolate multiple independent instances.
3677
+
3678
+ ---
3679
+
3680
+ ## Usage patterns
3681
+
3682
+ ### 1) Controlled (most explicit)
3683
+ ${"```tsx"}
3684
+ const [${respVar}, ${actionName}, ${clearName}] = use${pluginSdk.pascalCase(descriptor.name)}();
3685
+
3686
+ useEffect(() => {
3687
+ ${actionName}(${[
3688
+ requestBodyVar,
3689
+ paramsVar != null ? paramsVar : '{}'
3690
+ ].filter(Boolean).join(', ')});
3691
+ return ${clearName}; // optional cleanup
3692
+ }, [${[
3693
+ '' + actionName,
3694
+ clearName
3695
+ ].join(', ')}]);
3696
+ ${"```"}
3697
+
3698
+ <details><summary>Description</summary>
3699
+ <p><strong>Use when</strong> you need explicit control over when a request fires, what params/body it uses, and when to clean up. Ideal for search forms, pagination, conditional fetches.</p>
3700
+ </details>
3701
+
3702
+ ### 2) Lifecycle-bound (shorthand)
3703
+ ${"```tsx"}
3704
+ const [${respVar}] = use${pluginSdk.pascalCase(descriptor.name)}({
3705
+ fetchOnMount: true,
3706
+ clearOnUnmount: true,
3707
+ ${requestBodyType ? `body: ${requestBodyVar},` : ''} ${paramsType ? `params: ${paramsVar != null ? paramsVar : '{}'},` : 'params: {},'}
3708
+ });
3709
+ ${"```"}
3710
+
3711
+ <details><summary>Description</summary>
3712
+ <p><strong>Use when</strong> the data should follow the component lifecycle: fetch once on mount, reset on unmount.</p>
3713
+ </details>
3714
+
3715
+ ### 3) Passive observer (render when data arrives)
3716
+ ${"```tsx"}
3717
+ const [${respVar}] = use${pluginSdk.pascalCase(descriptor.name)}();
3718
+ return isSuccess(${respVar}) ? <>{String(${respVar}.data)}</> : null;
3719
+ ${"```"}
3720
+
3721
+ <details><summary>Description</summary>
3722
+ <p><strong>Use when</strong> another part of the app triggers the fetch and this component only reads the state.</p>
3723
+ </details>
3724
+
3725
+ ### 4) Keep previous success while refetching (sticky)
3726
+ ${"```tsx"}
3727
+ const [${dataVar}, set${pluginSdk.pascalCase(descriptor.name)}Data] = useState<any>();
3728
+ const [${respVar}, ${actionName}] = use${pluginSdk.pascalCase(descriptor.name)}();
3729
+
3730
+ useEffect(() => {
3731
+ if (isSuccess(${respVar})) set${pluginSdk.pascalCase(descriptor.name)}Data(${respVar}.data);
3732
+ }, [${respVar}]);
3733
+
3734
+ return (
3735
+ <>
3736
+ {isPending(${respVar}) && ${dataVar} ? <div>Refreshing…</div> : null}
3737
+ <pre>{JSON.stringify(isSuccess(${respVar}) ? ${respVar}.data : ${dataVar}, null, 2)}</pre>
3738
+ </>
3739
+ );
3740
+ ${"```"}
3741
+
3742
+ <details><summary>Description</summary>
3743
+ <p><strong>Use when</strong> you want SWR-like UX without flicker.</p>
3744
+ </details>
3745
+
3746
+ ### 5) Multiple instances of the same hook (isolate with \`key\`)
3747
+ ${"```tsx"}
3748
+ const a = use${pluginSdk.pascalCase(descriptor.name)}({ key: 'A', fetchOnMount: true, ${paramsType ? `params: ${paramsVar != null ? paramsVar : '{}'} ` : 'params: {}'} });
3749
+ const b = use${pluginSdk.pascalCase(descriptor.name)}({ key: 'B', fetchOnMount: true, ${paramsType ? `params: ${paramsVar != null ? paramsVar : '{}'} ` : 'params: {}'} });
3750
+ ${"```"}
3751
+
3752
+ <details><summary>Description</summary>
3753
+ <p>Use unique keys to prevent state collisions.</p>
3754
+ </details>
3755
+
3756
+ ---
3757
+
3758
+ ## Before / After mini-migrations
3759
+
3760
+ ### If you mistakenly used Stateful for a simple submit → switch to Async
3761
+ ${"```diff"}
3762
+ - const [${respVar}, ${actionName}] = use${pluginSdk.pascalCase(descriptor.name)}();
3763
+ - ${actionName}(${[
3764
+ requestBodyVar,
3765
+ paramsVar != null ? paramsVar : '{}'
3766
+ ].filter(Boolean).join(', ')});
3767
+ + const [fn] = use${pluginSdk.pascalCase(descriptor.name)}Async();
3768
+ + await fn(${[
3769
+ requestBodyVar,
3770
+ paramsVar
3771
+ ].filter(Boolean).join(', ')});
3772
+ ${"```"}
3773
+
3774
+ ### If you started with Async but need to read later in another component → Stateful
3775
+ ${"```diff"}
3776
+ - const [fn] = use${pluginSdk.pascalCase(descriptor.name)}Async();
3777
+ - const data = await fn(${[
3778
+ requestBodyVar,
3779
+ paramsVar
3780
+ ].filter(Boolean).join(', ')});
3781
+ + const [${respVar}, ${actionName}] = use${pluginSdk.pascalCase(descriptor.name)}({ fetchOnMount: true, clearOnUnmount: true, ${paramsType ? `params: ${paramsVar != null ? paramsVar : '{}'},` : 'params: {},'} ${requestBodyType ? `body: ${requestBodyVar}` : ''} });
3782
+ + // read from ${respVar} anywhere with use${pluginSdk.pascalCase(descriptor.name)}()
3783
+ ${"```"}
3784
+
3785
+ ---
3786
+
3787
+ ## Anti-patterns
3788
+ <details><summary>Don’t use Stateful for field validations or a one-off submit</summary>
3789
+ Use the Async variant instead: \`const [fn] = use${pluginSdk.pascalCase(descriptor.name)}Async()\`.
3790
+ </details>
3791
+ <details><summary>Don’t use Async for long-lived lists or detail views</summary>
3792
+ Use Stateful so other components can read the same data and you can avoid refetch churn.
3793
+ </details>
3794
+ <details><summary>Don’t forget required \`params\` when using \`fetchOnMount\`</summary>
3795
+ Provide \`params\` (and \`body\` if applicable) or switch to the controlled pattern.
3796
+ </details>
3797
+ <details><summary>Rendering the same hook twice without a \`key\`</summary>
3798
+ If they should be independent, add a unique \`key\`.
3799
+ </details>
3800
+
3801
+ ---
3802
+
3803
+ ## Error & UX guidance
3804
+ - **Loading:** early return or inline spinner. Prefer **sticky data** to avoid blanking content.
3805
+ - **Errors:** show a banner or inline errors depending on UX; keep previous good state if possible.
3806
+ - **Cleanup:** prefer \`clearOnUnmount: true\` as the default.
3807
+
3808
+ ---
3809
+
3810
+ ## Concurrency patterns
3811
+ - **Refresh:** call the action again; combine with sticky data for smooth UX.
3812
+ - **Dedupe:** isolate instances with \`key\`.
3813
+ - **Parallel:** render two keyed instances; don’t share the same key.
3814
+
3815
+ ---
3816
+
3817
+ ## Full examples
3818
+
3819
+ ### Short format (lifecycle-bound)
3820
+ ${"```tsx"}
3821
+ import { use${pluginSdk.pascalCase(descriptor.name)} } from '@intrig/react/${descriptor.path}/client';
3822
+ import { isPending, isError, isSuccess } from '@intrig/react';
3823
+ import { useMemo } from 'react';
3824
+
3825
+ function ShortExample() {
3826
+ const [${respVar}] = use${pluginSdk.pascalCase(descriptor.name)}({
3827
+ fetchOnMount: true,
3828
+ clearOnUnmount: true,
3829
+ ${requestBodyType ? `body: ${requestBodyVar},` : ''} ${paramsType ? `params: ${paramsVar != null ? paramsVar : '{}'},` : 'params: {},'}
3830
+ });
3831
+
3832
+ const ${dataVar} = useMemo(() => (isSuccess(${respVar}) ? ${respVar}.data : undefined), [${respVar}]);
3833
+
3834
+ if (isPending(${respVar})) return <>Loading…</>;
3835
+ if (isError(${respVar})) return <>Error: {String(${respVar}.error)}</>;
3836
+ return <pre>{JSON.stringify(${dataVar}, null, 2)}</pre>;
3837
+ }
3838
+ ${"```"}
3839
+
3840
+ <details><summary>Description</summary>
3841
+ <p>Compact lifecycle-bound approach. Great for read-only pages that load once and clean up on unmount.</p>
3842
+ </details>
3843
+
3844
+ ### Controlled format (explicit actions)
3845
+ ${"```tsx"}
3846
+ import { use${pluginSdk.pascalCase(descriptor.name)} } from '@intrig/react/${descriptor.path}/client';
3847
+ import { isPending, isError, isSuccess } from '@intrig/react';
3848
+ import { useEffect, useMemo } from 'react';
3849
+
3850
+ function ControlledExample() {
3851
+ const [${respVar}, ${actionName}, ${clearName}] = use${pluginSdk.pascalCase(descriptor.name)}();
3852
+
3853
+ useEffect(() => {
3854
+ ${actionName}(${[
3855
+ requestBodyVar,
3856
+ paramsVar != null ? paramsVar : '{}'
3857
+ ].filter(Boolean).join(', ')});
3858
+ return ${clearName};
3859
+ }, [${[
3860
+ '' + actionName,
3861
+ clearName
3862
+ ].join(', ')}]);
3863
+
3864
+ const ${dataVar} = useMemo(() => (isSuccess(${respVar}) ? ${respVar}.data : undefined), [${respVar}]);
3865
+
3866
+ if (isPending(${respVar})) return <>Loading…</>;
3867
+ if (isError(${respVar})) return <>An error occurred: {String(${respVar}.error)}</>;
3868
+ return <pre>{JSON.stringify(${dataVar}, null, 2)}</pre>;
3869
+ }
3870
+ ${"```"}
3871
+
3872
+ ---
3873
+
3874
+ ## Gotchas & Tips
3875
+ - Prefer **\`clearOnUnmount: true\`** in most components.
3876
+ - Use **\`key\`** for multiple independent instances.
3877
+ - Memoize derived values with **\`useMemo\`** to avoid churn.
3878
+ - Inline indicators keep the rest of the page interactive.
3879
+
3880
+ ---
3881
+
3882
+ ## Reference: State helpers
3883
+ ${"```ts"}
3884
+ if (isPending(${respVar})) { /* show spinner */ }
3885
+ if (isError(${respVar})) { /* show error */ }
3886
+ if (isSuccess(${respVar})) { /* read ${respVar}.data */ }
3887
+ ${"```"}
3888
+ `;
3889
+ }
3890
+
3891
+ function reactAsyncFunctionHookDocs(descriptor) {
3892
+ const md = pluginSdk.mdLiteral('async-hook.md');
3893
+ var _descriptor_data_variables;
3894
+ // ===== Derived names (preserve these) =====
3895
+ const hasPathParams = ((_descriptor_data_variables = descriptor.data.variables) != null ? _descriptor_data_variables : []).some((v)=>{
3896
+ var _v_in;
3897
+ return ((_v_in = v.in) == null ? void 0 : _v_in.toUpperCase()) === 'PATH';
3898
+ });
3899
+ const actionName = pluginSdk.camelCase(descriptor.name) // e.g. getUser
3900
+ ;
3901
+ const abortName = `abort${pluginSdk.pascalCase(descriptor.name)}` // e.g. abortGetUser
3902
+ ;
3903
+ const requestBodyVar = descriptor.data.requestBody ? pluginSdk.camelCase(descriptor.data.requestBody) // e.g. createUser
3904
+ : undefined;
3905
+ const requestBodyType = descriptor.data.requestBody ? pluginSdk.pascalCase(descriptor.data.requestBody) // e.g. CreateUser
3906
+ : undefined;
3907
+ const paramsVar = hasPathParams ? `${actionName}Params` : undefined // e.g. getUserParams
3908
+ ;
3909
+ const paramsType = hasPathParams ? `${pluginSdk.pascalCase(descriptor.name)}Params` : undefined // e.g. GetUserParams
3910
+ ;
3911
+ const responseTypeName = `${pluginSdk.pascalCase(descriptor.name)}ResponseBody` // e.g. GetUserResponseBody
3912
+ ;
3913
+ const callArgs = [
3914
+ requestBodyVar,
3915
+ paramsVar
3916
+ ].filter(Boolean).join(', ');
3917
+ return md`
3918
+ # Intrig Async Hooks — Quick Guide
3919
+
3920
+ ## Copy-paste starter (fast lane)
3921
+
3922
+ ### 1) Hook import
3923
+ ${"```ts"}
3924
+ import { use${pluginSdk.pascalCase(descriptor.name)}Async } from '@intrig/react/${descriptor.path}/client';
3925
+ ${"```"}
3926
+
3927
+ ### 2) Create an instance
3928
+ ${"```ts"}
3929
+ const [${actionName}, ${abortName}] = use${pluginSdk.pascalCase(descriptor.name)}Async();
3930
+ ${"```"}
3931
+
3932
+ ### 3) Call it (awaitable)
3933
+ ${"```ts"}
3934
+ // body?, params? — pass what your endpoint needs (order: body, params)
3935
+ await ${actionName}(${callArgs});
3936
+ ${"```"}
3937
+
3938
+ Async hooks are for one-off, low-friction calls (e.g., validations, submissions). They return an **awaitable function** plus an **abort** function. No NetworkState.
3939
+
3940
+ ---
3941
+
3942
+ ## TL;DR (copy–paste)
3943
+ ${"```tsx"}
3944
+ import { use${pluginSdk.pascalCase(descriptor.name)}Async } from '@intrig/react/${descriptor.path}/client';
3945
+ import { useCallback, useEffect } from 'react';
3946
+
3947
+ export default function Example() {
3948
+ const [${actionName}, ${abortName}] = use${pluginSdk.pascalCase(descriptor.name)}Async();
3949
+
3950
+ const run = useCallback(async () => {
3951
+ try {
3952
+ const result = await ${actionName}(${callArgs});
3953
+ // do something with result
3954
+ console.log(result);
3955
+ } catch (e) {
3956
+ // request failed or was aborted
3957
+ console.error(e);
3958
+ }
3959
+ }, [${actionName}]);
3960
+
3961
+ // Optional: abort on unmount
3962
+ useEffect(() => ${abortName}, [${abortName}]);
3963
+
3964
+ return <button onClick={run}>Call</button>;
3965
+ }
3966
+ ${"```"}
3967
+
3968
+ ${requestBodyType || paramsType ? `### Optional types (if generated by your build)
3969
+ ${"```ts"}
3970
+ ${requestBodyType ? `import type { ${requestBodyType} } from '@intrig/react/${descriptor.source}/components/schemas/${requestBodyType}';
3971
+ ` : ''}${paramsType ? `import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.params';
3972
+ ` : ''}import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.response';
3973
+ ${"```"}
3974
+ ` : ''}
3975
+
3976
+ ---
3977
+
3978
+ ## Hook API
3979
+ ${"```ts"}
3980
+ // Prefer concrete types if your build emits them:
3981
+ // import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.response';
3982
+ // ${paramsType ? `import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.params';` : ''}
3983
+
3984
+ type ${pluginSdk.pascalCase(descriptor.name)}Data = ${'unknown'}; // replace with ${responseTypeName} if generated
3985
+ type ${pluginSdk.pascalCase(descriptor.name)}Request = {
3986
+ body?: ${requestBodyType != null ? requestBodyType : 'unknown'};
3987
+ params?: ${paramsType != null ? paramsType : 'unknown'};
3988
+ };
3989
+
3990
+ // Signature (shape shown; return type depends on your endpoint)
3991
+ declare function use${pluginSdk.pascalCase(descriptor.name)}Async(): [
3992
+ (body?: ${pluginSdk.pascalCase(descriptor.name)}Request['body'], params?: ${pluginSdk.pascalCase(descriptor.name)}Request['params']) => Promise<${pluginSdk.pascalCase(descriptor.name)}Data>,
3993
+ () => void // abort
3994
+ ];
3995
+ ${"```"}
3996
+
3997
+ ### Why async hooks?
3998
+ - **No state machine:** just \`await\` the result.
3999
+ - **Great for validations & submits:** uniqueness checks, field-level checks, updates.
4000
+ - **Abortable:** cancel in-flight work on demand.
4001
+
4002
+ ---
4003
+
4004
+ ## Usage Patterns
4005
+
4006
+ ### 1) Simple try/catch (recommended)
4007
+ ${"```tsx"}
4008
+ const [${actionName}] = use${pluginSdk.pascalCase(descriptor.name)}Async();
4009
+
4010
+ try {
4011
+ const res = await ${actionName}(${callArgs});
4012
+ // use res
4013
+ } catch (e) {
4014
+ // network error or abort
4015
+ }
4016
+ ${"```"}
4017
+
4018
+ <details><summary>Description</summary>
4019
+ <p><strong>Use when</strong> you just need the value or an error. Ideal for validators, uniqueness checks, or quick lookups.</p>
4020
+ </details>
4021
+
4022
+ ### 2) Abort on unmount (safe cleanup)
4023
+ ${"```tsx"}
4024
+ const [${actionName}, ${abortName}] = use${pluginSdk.pascalCase(descriptor.name)}Async();
4025
+
4026
+ useEffect(() => ${abortName}, [${abortName}]);
4027
+ ${"```"}
4028
+
4029
+ <details><summary>Description</summary>
4030
+ <p><strong>Use when</strong> the component may unmount while a request is in-flight (route changes, conditional UI).</p>
4031
+ </details>
4032
+
4033
+ ### 3) Debounced validation (e.g., on input change)
4034
+ ${"```tsx"}
4035
+ const [${actionName}, ${abortName}] = use${pluginSdk.pascalCase(descriptor.name)}Async();
4036
+
4037
+ const onChange = useMemo(() => {
4038
+ let t: any;
4039
+ return (${requestBodyVar ? `${requestBodyVar}: ${requestBodyType != null ? requestBodyType : 'any'}` : 'value: string'}) => {
4040
+ clearTimeout(t);
4041
+ t = setTimeout(async () => {
4042
+ try {
4043
+ // Optionally abort before firing a new request
4044
+ ${abortName}();
4045
+ await ${actionName}(${[
4046
+ requestBodyVar != null ? requestBodyVar : '/* body from value */',
4047
+ paramsVar != null ? paramsVar : '/* params? */'
4048
+ ].join(', ')});
4049
+ } catch {}
4050
+ }, 250);
4051
+ };
4052
+ }, [${actionName}, ${abortName}]);
4053
+ ${"```"}
4054
+
4055
+ <details><summary>Description</summary>
4056
+ <p><strong>Use when</strong> validating as the user types. Debounce to reduce chatter; consider <code>${abortName}()</code> before firing a new call.</p>
4057
+ </details>
4058
+
4059
+ ### 4) Guard against races (latest-only)
4060
+ ${"```tsx"}
4061
+ const [${actionName}, ${abortName}] = use${pluginSdk.pascalCase(descriptor.name)}Async();
4062
+
4063
+ const latestOnly = async () => {
4064
+ ${abortName}();
4065
+ return ${actionName}(${callArgs});
4066
+ };
4067
+ ${"```"}
4068
+
4069
+ <details><summary>Description</summary>
4070
+ <p><strong>Use when</strong> only the most recent call should win (search suggestions, live filters).</p>
4071
+ </details>
4072
+
4073
+ ---
4074
+
4075
+ ## Full example
4076
+ ${"```tsx"}
4077
+ import { use${pluginSdk.pascalCase(descriptor.name)}Async } from '@intrig/react/${descriptor.path}/client';
4078
+ import { useCallback } from 'react';
4079
+
4080
+ function MyComponent() {
4081
+ const [${actionName}, ${abortName}] = use${pluginSdk.pascalCase(descriptor.name)}Async();
4082
+
4083
+ const run = useCallback(async () => {
4084
+ try {
4085
+ const data = await ${actionName}(${callArgs});
4086
+ alert(JSON.stringify(data));
4087
+ } catch (e) {
4088
+ console.error('Call failed/aborted', e);
4089
+ }
4090
+ }, [${actionName}]);
4091
+
4092
+ return (
4093
+ <>
4094
+ <button onClick={run}>Call remote</button>
4095
+ <button onClick={${abortName}}>Abort</button>
4096
+ </>
4097
+ );
4098
+ }
4099
+ ${"```"}
4100
+
4101
+ ---
4102
+
4103
+ ## Gotchas & Tips
4104
+ - **No \`NetworkState\`:** async hooks return a Promise, not a state machine.
4105
+ - **Abort:** always available; call it to cancel the latest in-flight request.
4106
+ - **Errors:** wrap calls with \`try/catch\` to handle network failures or abort errors.
4107
+ - **Debounce & throttle:** combine with timers to cut down chatter for typeahead/validators.
4108
+ - **Types:** prefer generated \`${responseTypeName}\` and \`${paramsType != null ? paramsType : '...Params'}\` if your build emits them.
4109
+
4110
+ ---
4111
+
4112
+ ## Reference: Minimal cheat sheet
4113
+ ${"```ts"}
4114
+ const [fn, abort] = use${pluginSdk.pascalCase(descriptor.name)}Async();
4115
+ await fn(${callArgs});
4116
+ abort(); // optional
4117
+ ${"```"}
4118
+ `;
4119
+ }
4120
+
4121
+ function reactDownloadHookDocs(descriptor) {
4122
+ const md = pluginSdk.mdLiteral('download-hook.md');
4123
+ var _descriptor_data_variables;
4124
+ // ===== Derived names (preserve these) =====
4125
+ const hasPathParams = ((_descriptor_data_variables = descriptor.data.variables) != null ? _descriptor_data_variables : []).some((v)=>{
4126
+ var _v_in;
4127
+ return ((_v_in = v.in) == null ? void 0 : _v_in.toUpperCase()) === 'PATH';
4128
+ });
4129
+ const actionName = pluginSdk.camelCase(descriptor.name); // e.g. downloadTaskFile
4130
+ const respVar = `${actionName}Resp`; // e.g. downloadTaskFileResp
4131
+ const paramsVar = hasPathParams ? `${actionName}Params` : undefined; // e.g. downloadTaskFileParams
4132
+ const paramsType = hasPathParams ? `${pluginSdk.pascalCase(descriptor.name)}Params` : undefined; // e.g. DownloadTaskFileParams
4133
+ const pascal = pluginSdk.pascalCase(descriptor.name);
4134
+ const responseTypeName = `${pascal}ResponseBody`; // e.g. DownloadTaskFileResponseBody
4135
+ return md`
4136
+ # Intrig Download Hooks — Quick Guide
4137
+
4138
+ ## Copy-paste starter (fast lane)
4139
+
4140
+ ### Auto-download (most common)
4141
+ ${"```ts"}
4142
+ import { use${pascal}Download } from '@intrig/react/${descriptor.path}/client';
4143
+ ${"```"}
4144
+ ${"```ts"}
4145
+ import { isPending, isError } from '@intrig/react';
4146
+ ${"```"}
4147
+ ${"```tsx"}
4148
+ const [${respVar}, ${actionName}] = use${pascal}Download({ clearOnUnmount: true });
4149
+ // e.g., in a click handler:
4150
+ ${actionName}(${paramsType ? paramsVar != null ? paramsVar : '{}' : '{}'});
4151
+ ${"```"}
4152
+
4153
+ ### Manual/stateful (you handle the blob/UI)
4154
+ ${"```ts"}
4155
+ import { use${pascal} } from '@intrig/react/${descriptor.path}/client';
4156
+ ${"```"}
4157
+ ${"```ts"}
4158
+ import { isSuccess, isPending, isError } from '@intrig/react';
4159
+ ${"```"}
4160
+ ${"```tsx"}
4161
+ const [${respVar}, ${actionName}] = use${pascal}({ clearOnUnmount: true });
4162
+ // later:
4163
+ ${actionName}(${paramsType ? paramsVar != null ? paramsVar : '{}' : '{}'});
4164
+ ${"```"}
4165
+
4166
+ ---
4167
+
4168
+ ## TL;DR (auto-download)
4169
+ ${"```tsx"}
4170
+ import { use${pascal}Download } from '@intrig/react/${descriptor.path}/client';
4171
+ import { isPending, isError } from '@intrig/react';
4172
+
4173
+ export default function Example() {
4174
+ const [${respVar}, ${actionName}] = use${pascal}Download({ clearOnUnmount: true });
4175
+
4176
+ return (
4177
+ <button
4178
+ onClick={() => ${actionName}(${paramsType ? paramsVar != null ? paramsVar : '{}' : '{}'})}
4179
+ disabled={isPending(${respVar})}
4180
+ >
4181
+ {isPending(${respVar}) ? 'Downloading…' : 'Download'}
4182
+ </button>
4183
+ );
4184
+ }
4185
+ ${"```"}
4186
+
4187
+ ${paramsType ? `### Optional types (if generated by your build)
4188
+ ${"```ts"}
4189
+ import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pascal}.params';
4190
+ import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pascal}.response';
4191
+ ${"```"}
4192
+ ` : ''}
4193
+
4194
+ ---
4195
+
4196
+ ## Hook APIs
4197
+
4198
+ ### \`use${pascal}Download\` (auto-download)
4199
+ - **What it does:** requests the file with \`responseType: 'blob'\` + \`adapter: 'fetch'\`, derives filename from \`Content-Disposition\` if present, creates a temporary object URL, clicks a hidden \`<a>\`, **downloads**, then resets state to \`init\`.
4200
+ - **Signature:** \`[state, download, clear]\`
4201
+ - \`download(params: ${paramsType != null ? paramsType : 'Record<string, unknown>'}) => void\`
4202
+
4203
+ ### \`use${pascal}\` (manual/stateful)
4204
+ - **What it does:** same request but **does not** auto-save. You control preview/saving using \`state.data\` + \`state.headers\`.
4205
+ - **Signature:** \`[state, fetchFile, clear]\`
4206
+ - \`fetchFile(params: ${paramsType != null ? paramsType : 'Record<string, unknown>'}) => void\`
4207
+
4208
+ ---
4209
+
4210
+ ## Usage Patterns
4211
+
4212
+ ### 1) Auto-download on click (recommended)
4213
+ ${"```tsx"}
4214
+ const [${respVar}, ${actionName}] = use${pascal}Download({ clearOnUnmount: true });
4215
+
4216
+ <button
4217
+ onClick={() => ${actionName}(${paramsType ? paramsVar != null ? paramsVar : '{}' : '{}'})}
4218
+ disabled={isPending(${respVar})}
4219
+ >
4220
+ {isPending(${respVar}) ? 'Downloading…' : 'Download file'}
4221
+ </button>
4222
+ {isError(${respVar}) ? <p className="text-red-500">Failed to download.</p> : null}
4223
+ ${"```"}
4224
+
4225
+ <details><summary>Description</summary>
4226
+ <p>Most users just need a button that saves the file. This variant handles object URL creation, filename extraction, click, and state reset.</p>
4227
+ </details>
4228
+
4229
+ ### 2) Auto-download on mount (e.g., “Your file is ready” page)
4230
+ ${"```tsx"}
4231
+ useEffect(() => {
4232
+ ${actionName}(${paramsType ? paramsVar != null ? paramsVar : '{}' : '{}'});
4233
+ }, [${actionName}]);
4234
+ ${"```"}
4235
+
4236
+ <details><summary>Description</summary>
4237
+ <p>Good for post-processing routes that immediately start a download.</p>
4238
+ </details>
4239
+
4240
+ ### 3) Manual handling (preview or custom filename)
4241
+ ${"```tsx"}
4242
+ const [${respVar}, ${actionName}] = use${pascal}({ clearOnUnmount: true });
4243
+
4244
+ useEffect(() => {
4245
+ if (isSuccess(${respVar})) {
4246
+ const ct = ${respVar}.headers?.['content-type'] ?? 'application/octet-stream';
4247
+ const parts = Array.isArray(${respVar}.data) ? ${respVar}.data : [${respVar}.data];
4248
+ const url = URL.createObjectURL(new Blob(parts, { type: ct }));
4249
+ // preview/save with your own UI...
4250
+ return () => URL.revokeObjectURL(url);
4251
+ }
4252
+ }, [${respVar}]);
4253
+ ${"```"}
4254
+
4255
+ <details><summary>Description</summary>
4256
+ <p>Use when you need to inspect headers, show a preview, or control the filename/UI flow.</p>
4257
+ </details>
4258
+
4259
+ ---
4260
+
4261
+ ## Behavior notes (what the auto-download variant does)
4262
+ - Forces \`responseType: 'blob'\` and \`adapter: 'fetch'\`.
4263
+ - If \`content-type\` is JSON, stringifies payload so the saved file is human-readable.
4264
+ - Parses \`Content-Disposition\` to derive a filename; falls back to a default.
4265
+ - Creates and clicks a temporary link, then **resets state to \`init\`**.
4266
+
4267
+ ---
4268
+
4269
+ ## Gotchas & Tips
4270
+ - **Expose headers in CORS:** server should send
4271
+ \`Access-Control-Expose-Headers: Content-Disposition, Content-Type\`
4272
+ - **Disable double clicks:** guard with \`isPending(state)\`.
4273
+ - **Revoke URLs** when you create them manually in the stateful variant.
4274
+ - **iOS Safari:** blob downloads may open a new tab—consider server-side direct-download URLs for a smoother UX.
4275
+
4276
+ ---
4277
+
4278
+ ## Troubleshooting
4279
+ - **No filename shown:** your server didn’t include \`Content-Disposition\`. Add it.
4280
+ - **Got JSON instead of a file:** server returned \`application/json\` (maybe an error). The auto hook saves it as text; surface the error in UI.
4281
+ - **Nothing happens on click:** ensure you’re using the **Download** variant and the request succeeds (check Network tab); verify CORS headers.
4282
+
4283
+ ---
4284
+ `;
4285
+ }
4286
+
4287
+ async function getEndpointDocumentation(result) {
4288
+ const tabs = [];
4289
+ if (result.data.responseType === 'text/event-stream') {
4290
+ tabs.push({
4291
+ name: 'SSE Hook',
4292
+ content: (await reactSseHookDocs(result)).content
4293
+ });
4294
+ } else {
4295
+ tabs.push({
4296
+ name: 'Stateful Hook',
4297
+ content: (await reactHookDocs(result)).content
4298
+ });
4299
+ }
4300
+ tabs.push({
4301
+ name: 'Stateless Hook',
4302
+ content: (await reactAsyncFunctionHookDocs(result)).content
4303
+ });
4304
+ if (result.data.isDownloadable) {
4305
+ tabs.push({
4306
+ name: 'Download Hook',
4307
+ content: (await reactDownloadHookDocs(result)).content
4308
+ });
4309
+ }
4310
+ return tabs;
4311
+ }
4312
+
4313
+ function createPlugin() {
4314
+ return {
4315
+ meta () {
4316
+ return {
4317
+ name: 'intrig-binding',
4318
+ version: '0.0.1',
4319
+ compat: '^0.0.15'
4320
+ };
4321
+ },
4322
+ generate: generateCode,
4323
+ getSchemaDocumentation,
4324
+ getEndpointDocumentation
4325
+ };
4326
+ }
4327
+
4328
+ exports.createPlugin = createPlugin;
4329
+ exports.default = createPlugin;