@intrig/plugin-next 0.0.2-0

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