@intrig/core 0.0.15-4 → 0.0.15-6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/main.js +883 -193
  2. package/package.json +2 -1
package/main.js CHANGED
@@ -981,6 +981,8 @@ SourcesCommand = sources_command_ts_decorate([
981
981
  })
982
982
  ], SourcesCommand);
983
983
 
984
+ ;// external "proper-lockfile"
985
+ const external_proper_lockfile_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("proper-lockfile");
984
986
  ;// external "lodash"
985
987
  const external_lodash_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("lodash");
986
988
  var external_lodash_default = /*#__PURE__*/__webpack_require__.n(external_lodash_namespaceObject);
@@ -1288,30 +1290,110 @@ function spec_management_service_ts_metadata(k, v) {
1288
1290
 
1289
1291
 
1290
1292
 
1293
+
1291
1294
  class SpecManagementService {
1292
1295
  constructor(config){
1293
1296
  this.config = config;
1297
+ this.logger = new common_namespaceObject.Logger(SpecManagementService.name);
1294
1298
  this.specsDir = this.config.get('specsDir') ?? external_path_namespaceObject.resolve(external_node_process_namespaceObject.cwd(), '.intrig', 'specs');
1295
1299
  }
1296
1300
  async save(apiName, content) {
1297
- const currentContent = await this.read(apiName);
1298
- if (currentContent) {
1299
- const differences = openapi3_diff(currentContent, JSON.parse(content));
1300
- if (!Object.keys(differences).length) {
1301
- return;
1301
+ const filePath = external_path_namespaceObject.join(this.specsDir, `${apiName}-latest.json`);
1302
+ const tempPath = external_path_namespaceObject.join(this.specsDir, `${apiName}-latest.json.tmp`);
1303
+ let release;
1304
+ try {
1305
+ // Ensure directory exists
1306
+ external_fs_namespaceObject.mkdirSync(this.specsDir, {
1307
+ recursive: true
1308
+ });
1309
+ // Acquire lock for the file
1310
+ release = await external_proper_lockfile_namespaceObject.lock(filePath, {
1311
+ retries: {
1312
+ retries: 5,
1313
+ factor: 2,
1314
+ minTimeout: 100,
1315
+ maxTimeout: 2000
1316
+ }
1317
+ });
1318
+ // Read current content with lock protection
1319
+ const currentContent = await this.readWithoutLock(apiName);
1320
+ if (currentContent) {
1321
+ const differences = openapi3_diff(currentContent, JSON.parse(content));
1322
+ if (!Object.keys(differences).length) {
1323
+ this.logger.debug(`No changes detected for ${apiName}, skipping save`);
1324
+ return;
1325
+ }
1326
+ }
1327
+ // Atomic write: write to temp file first, then rename
1328
+ external_fs_namespaceObject.writeFileSync(tempPath, content, 'utf-8');
1329
+ external_fs_namespaceObject.renameSync(tempPath, filePath);
1330
+ this.logger.debug(`Successfully saved spec for ${apiName}`);
1331
+ } catch (error) {
1332
+ // Clean up temp file if it exists
1333
+ if (external_fs_namespaceObject.existsSync(tempPath)) {
1334
+ try {
1335
+ external_fs_namespaceObject.unlinkSync(tempPath);
1336
+ } catch (cleanupError) {
1337
+ this.logger.warn(`Failed to cleanup temp file ${tempPath}: ${cleanupError}`);
1338
+ }
1339
+ }
1340
+ this.logger.error(`Failed to save spec for ${apiName}: ${error.message}`, error);
1341
+ throw new Error(`Failed to save spec for ${apiName}: ${error.message}`);
1342
+ } finally{
1343
+ // Always release the lock
1344
+ if (release) {
1345
+ try {
1346
+ await release();
1347
+ } catch (releaseError) {
1348
+ this.logger.warn(`Failed to release lock for ${filePath}: ${releaseError}`);
1349
+ }
1302
1350
  }
1303
1351
  }
1304
- external_fs_namespaceObject.mkdirSync(this.specsDir, {
1305
- recursive: true
1306
- });
1307
- external_fs_namespaceObject.writeFileSync(external_path_namespaceObject.join(this.specsDir, `${apiName}-latest.json`), content, 'utf-8');
1308
1352
  }
1309
1353
  async read(apiName) {
1354
+ const filePath = external_path_namespaceObject.join(this.specsDir, `${apiName}-latest.json`);
1355
+ let release;
1356
+ try {
1357
+ // Acquire lock for reading
1358
+ release = await external_proper_lockfile_namespaceObject.lock(filePath, {
1359
+ retries: {
1360
+ retries: 3,
1361
+ factor: 2,
1362
+ minTimeout: 50,
1363
+ maxTimeout: 1000
1364
+ }
1365
+ });
1366
+ return await this.readWithoutLock(apiName);
1367
+ } catch (error) {
1368
+ if (error.code === 'ENOENT' || !external_fs_namespaceObject.existsSync(filePath)) {
1369
+ // File doesn't exist, this is normal for first-time sync
1370
+ this.logger.debug(`Spec file not found for ${apiName}, returning undefined`);
1371
+ return undefined;
1372
+ }
1373
+ this.logger.error(`Failed to read spec for ${apiName}: ${error.message}`, error);
1374
+ throw new Error(`Failed to read spec for ${apiName}: ${error.message}`);
1375
+ } finally{
1376
+ // Always release the lock
1377
+ if (release) {
1378
+ try {
1379
+ await release();
1380
+ } catch (releaseError) {
1381
+ this.logger.warn(`Failed to release lock for ${filePath}: ${releaseError}`);
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+ async readWithoutLock(apiName) {
1310
1387
  const fileName = `${apiName}-latest.json`;
1311
1388
  const specPath = external_path_namespaceObject.join(this.specsDir, fileName);
1312
1389
  if (external_fs_namespaceObject.existsSync(specPath)) {
1313
- const content = external_fs_namespaceObject.readFileSync(specPath, 'utf-8');
1314
- return JSON.parse(content);
1390
+ try {
1391
+ const content = external_fs_namespaceObject.readFileSync(specPath, 'utf-8');
1392
+ return JSON.parse(content);
1393
+ } catch (error) {
1394
+ this.logger.error(`Failed to parse JSON for ${apiName}: ${error.message}`);
1395
+ throw new Error(`Invalid JSON in spec file for ${apiName}: ${error.message}`);
1396
+ }
1315
1397
  }
1316
1398
  return undefined;
1317
1399
  }
@@ -5528,84 +5610,533 @@ ${"```"}
5528
5610
  <summary>Early Return</summary>
5529
5611
  Often times you may need to show the loading indicators while the actual network call is in progress. In that case, you can add a conditional early return.
5530
5612
 
5531
- ${"```tsx"}
5613
+ ${"```tsx"}
5614
+ if (isPending(${camelCase(descriptor.name)}Resp)) {
5615
+ return <LoadingIndicator />
5616
+ }
5617
+ ${"```"}
5618
+ </details>
5619
+
5620
+ <details>
5621
+ <summary>Inline rendering</summary>
5622
+ If you need to show a non-blocking/parallel loading indicator, or change the component behavior based on the loading state, you can inline the loading condition.
5623
+
5624
+ ${"```tsx"}
5625
+ <>{isPending(${camelCase(descriptor.name)}Resp) ? <LoadingIndicator /> : null}<MyComponent /></>
5626
+ ${"```"}
5627
+ </details>
5628
+
5629
+ #### Error state
5630
+ The error state extraction is done using the \`isError\` utility method. Make sure to import the method into your component.
5631
+
5632
+ ${"```tsx"}
5633
+ import { isError } from '@intrig/next';
5634
+ ${"```"}
5635
+
5636
+ <details>
5637
+ <summary>Early return</summary>
5638
+ In most cases, in an error state, you may want to show the error message to the user instead of real component. In that case, you can add a conditional early return.
5639
+
5640
+ ${"```tsx"}
5641
+ if (isError(${camelCase(descriptor.name)}Resp)) {
5642
+ return <ErrorMessage />
5643
+ }
5644
+ ${"```"}
5645
+ </details>
5646
+
5647
+ <details>
5648
+ <summary>Inline rendering</summary>
5649
+ If you need to show a non-blocking/parallel error indicator, or change the component behavior based on the error state, you can inline the error condition.
5650
+
5651
+ ${"```tsx"}
5652
+ <>{isError(${camelCase(descriptor.name)}Resp) ? <ErrorMessage /> : null}<MyComponent /></>
5653
+ ${"```"}
5654
+ </details>
5655
+
5656
+ <details>
5657
+ <summary>Side effect upon error</summary>
5658
+
5659
+ Usually if the operation is an update, you may need to show an error message to the user. In such cases, you can add a side effect with useEffect, upon error.
5660
+
5661
+ First import \`useEffect\` to your component.
5662
+ ${"```tsx"}
5663
+ import { useEffect } from 'react'
5664
+ ${"```"}
5665
+
5666
+ Then add the side effect.
5667
+ ${"```tsx"}
5668
+ useEffect(() => {
5669
+ if (isError(${camelCase(descriptor.name)}Resp)) {
5670
+ //TODO execute error related operation.
5671
+ }
5672
+ }, [${camelCase(descriptor.name)}Resp])
5673
+ ${"```"}
5674
+ </details>
5675
+
5676
+ ### Replications
5677
+
5678
+ <details>
5679
+ <summary>Multiple instances for same hook.</summary>
5680
+
5681
+ In some case you may need to instanciate multiple instances of the same request. For an example comparision of the products.
5682
+ In that cases, you can use \`key\` property to distinguish between these Network states.
5683
+
5684
+ ${'```tsx'}
5685
+ const [${camelCase(descriptor.name)}Resp, ${camelCase(descriptor.name)}, clear${pascalCase(descriptor.name)}] = use${pascalCase(descriptor.name)}({ key: 'id1' });
5686
+ ${'```'}
5687
+ </details>
5688
+
5689
+
5690
+ `;
5691
+ }
5692
+
5693
+ ;// ../../lib/next-binding/src/lib/templates/source/controller/method/asyncFunctionHook.template.ts
5694
+
5695
+
5696
+ function extractAsyncHookShape(response, requestBody, imports) {
5697
+ if (response) {
5698
+ if (requestBody) {
5699
+ imports.add(`import { BinaryFunctionAsyncHook } from "@intrig/next"`);
5700
+ return `BinaryFunctionAsyncHook<Params, RequestBody, Response, _ErrorType>`;
5701
+ } else {
5702
+ imports.add(`import { UnaryFunctionAsyncHook } from "@intrig/next"`);
5703
+ return `UnaryFunctionAsyncHook<Params, Response, _ErrorType>`;
5704
+ }
5705
+ } else {
5706
+ if (requestBody) {
5707
+ imports.add(`import { BinaryProduceAsyncHook } from "@intrig/next"`);
5708
+ return `BinaryProduceAsyncHook<Params, RequestBody, _ErrorType>`;
5709
+ } else {
5710
+ imports.add(`import { UnaryProduceAsyncHook } from "@intrig/next"`);
5711
+ return `UnaryProduceAsyncHook<Params, _ErrorType>`;
5712
+ }
5713
+ }
5714
+ }
5715
+ function asyncFunctionHook_template_extractErrorParams(errorTypes) {
5716
+ switch(errorTypes.length){
5717
+ case 0:
5718
+ return `
5719
+ export type _ErrorType = any
5720
+ const errorSchema = z.any()`;
5721
+ case 1:
5722
+ return `
5723
+ export type _ErrorType = ${errorTypes[0]}
5724
+ const errorSchema = ${errorTypes[0]}Schema`;
5725
+ default:
5726
+ return `
5727
+ export type _ErrorType = ${errorTypes.join(' | ')}
5728
+ const errorSchema = z.union([${errorTypes.map((a)=>`${a}Schema`).join(', ')}])`;
5729
+ }
5730
+ }
5731
+ function asyncFunctionHook_template_extractParamDeconstruction(variables, requestBody) {
5732
+ const isParamMandatory = variables?.some((a)=>a.in === 'path') || false;
5733
+ if (requestBody) {
5734
+ if (isParamMandatory) {
5735
+ return {
5736
+ paramExpression: 'data, p',
5737
+ paramType: 'data: RequestBody, params: Params'
5738
+ };
5739
+ } else {
5740
+ return {
5741
+ paramExpression: 'data, p = {}',
5742
+ paramType: 'data: RequestBody, params?: Params'
5743
+ };
5744
+ }
5745
+ } else {
5746
+ if (isParamMandatory) {
5747
+ return {
5748
+ paramExpression: 'p',
5749
+ paramType: 'params: Params'
5750
+ };
5751
+ } else {
5752
+ return {
5753
+ paramExpression: 'p = {}',
5754
+ paramType: 'params?: Params'
5755
+ };
5756
+ }
5757
+ }
5758
+ }
5759
+ async function nextAsyncFunctionHookTemplate({ source, data: { paths, operationId, response, requestUrl, variables, requestBody, contentType, responseType, errorResponses, method } }, _path, ctx) {
5760
+ const postfix = ctx.potentiallyConflictingDescriptors.includes(operationId) ? generatePostfix(contentType, responseType) : '';
5761
+ const ts = typescript(external_path_namespaceObject.resolve(_path, 'src', source, ...paths, camelCase(operationId), `use${pascalCase(operationId)}Async${postfix}.ts`));
5762
+ const modifiedRequestUrl = `${requestUrl?.replace(/\{/g, "${")}`;
5763
+ const imports = new Set();
5764
+ // Basic imports
5765
+ imports.add(`import { z } from 'zod'`);
5766
+ imports.add(`import { useCallback } from 'react'`);
5767
+ imports.add(`import { useTransientCall, encode, isError, isSuccess } from '@intrig/next'`);
5768
+ // Hook signature type
5769
+ const hookShape = extractAsyncHookShape(response, requestBody, imports);
5770
+ // Add body/response param imports
5771
+ if (requestBody) {
5772
+ imports.add(`import { ${requestBody} as RequestBody, ${requestBody}Schema as requestBodySchema } from "@intrig/next/${source}/components/schemas/${requestBody}"`);
5773
+ }
5774
+ if (response) {
5775
+ imports.add(`import { ${response} as Response, ${response}Schema as schema } from "@intrig/next/${source}/components/schemas/${response}"`);
5776
+ }
5777
+ imports.add(`import { ${pascalCase(operationId)}Params as Params } from './${pascalCase(operationId)}.params'`);
5778
+ // Error types
5779
+ const errorTypes = [
5780
+ ...new Set(Object.values(errorResponses ?? {}).map((a)=>a.response))
5781
+ ];
5782
+ errorTypes.forEach((ref)=>imports.add(`import { ${ref}, ${ref}Schema } from "@intrig/next/${source}/components/schemas/${ref}"`));
5783
+ // Error schema block
5784
+ const errorSchemaBlock = asyncFunctionHook_template_extractErrorParams(errorTypes.map((a)=>a));
5785
+ // Param deconstruction
5786
+ const { paramExpression, paramType } = asyncFunctionHook_template_extractParamDeconstruction(variables ?? [], requestBody);
5787
+ const paramExplode = [
5788
+ ...variables?.filter((a)=>a.in === 'path').map((a)=>a.name) ?? [],
5789
+ '...params'
5790
+ ].join(',');
5791
+ const finalRequestBodyBlock = requestBody ? `, data: encode(data, "${contentType}", requestBodySchema)` : '';
5792
+ function responseTypePart() {
5793
+ switch(responseType){
5794
+ case "application/octet-stream":
5795
+ return `responseType: 'blob', adapter: 'fetch',`;
5796
+ case "text/event-stream":
5797
+ return `responseType: 'stream', adapter: 'fetch',`;
5798
+ }
5799
+ return '';
5800
+ }
5801
+ return ts`
5802
+ ${[
5803
+ ...imports
5804
+ ].join('\n')}
5805
+
5806
+ ${!response ? `
5807
+ type Response = any;
5808
+ const schema = z.any();
5809
+ ` : ''}
5810
+
5811
+ ${errorSchemaBlock}
5812
+
5813
+ const operation = "${method.toUpperCase()} ${requestUrl}| ${contentType} -> ${responseType}";
5814
+ const source = "${source}";
5815
+
5816
+ function use${pascalCase(operationId)}AsyncHook(): [(${paramType}) => Promise<Response>, () => void] {
5817
+ const [call, abort] = useTransientCall<Response, _ErrorType>({
5818
+ schema,
5819
+ errorSchema
5820
+ });
5821
+
5822
+ const doExecute = useCallback<(${paramType}) => Promise<Response>>(async (${paramExpression}) => {
5823
+ let { ${paramExplode} } = p;
5824
+
5825
+ ${requestBody ? `
5826
+ const validationResult = requestBodySchema.safeParse(data);
5827
+ if (!validationResult.success) {
5828
+ return Promise.reject(validationResult.error);
5829
+ }
5830
+ ` : ''}
5831
+
5832
+ return await call({
5833
+ method: '${method}',
5834
+ url: \`${modifiedRequestUrl}\`,
5835
+ headers: {
5836
+ ${contentType ? `"Content-Type": "${contentType}",` : ''}
5837
+ },
5838
+ params,
5839
+ key: \`${"${source}: ${operation}"}\`,
5840
+ source: '${source}'
5841
+ ${requestBody ? finalRequestBodyBlock : ''},
5842
+ ${responseTypePart()}
5843
+ });
5844
+ }, [call]);
5845
+
5846
+ return [doExecute, abort];
5847
+ }
5848
+
5849
+ use${pascalCase(operationId)}AsyncHook.key = \`${"${source}: ${operation}"}\`;
5850
+
5851
+ export const use${pascalCase(operationId)}Async: ${hookShape} = use${pascalCase(operationId)}AsyncHook;
5852
+ `;
5853
+ }
5854
+
5855
+ ;// ../../lib/next-binding/src/lib/templates/type-utils.template.ts
5856
+
5857
+
5858
+ function typeUtilsTemplate(_path) {
5859
+ const ts = typescript(external_path_default().resolve(_path, 'src', 'type-utils.ts'));
5860
+ return ts`import { z } from 'zod';
5861
+
5862
+ export type BinaryData = Blob;
5863
+ export const BinaryDataSchema: z.ZodType<BinaryData> = z.instanceof(Blob);
5864
+
5865
+ // Base64 helpers (browser + Node compatible; no Buffer required)
5866
+ export function base64ToUint8Array(b64: string): Uint8Array {
5867
+ if (typeof atob === 'function') {
5868
+ // Browser
5869
+ const bin = atob(b64);
5870
+ const bytes = new Uint8Array(bin.length);
5871
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
5872
+ return bytes;
5873
+ } else {
5874
+ // Node
5875
+ const buf = Buffer.from(b64, 'base64');
5876
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
5877
+ }
5878
+ }
5879
+
5880
+ `;
5881
+ }
5882
+
5883
+ ;// ../../lib/next-binding/src/lib/templates/docs/sse-hook.ts
5884
+
5885
+ function nextSseHookDocs(descriptor) {
5886
+ const md = mdLiteral('sse-hook.md');
5887
+ const requestBody = descriptor.data.requestBody ? camelCase(descriptor.data.requestBody) : undefined;
5888
+ const params = descriptor.data.variables?.filter((a)=>a.in.toUpperCase() === 'PATH')?.length ? 'params' : undefined;
5889
+ return md`
5890
+ > Intrig-generated hooks are intended for backend data integration. They follow the same tuple-based API pattern as React's built-in state hooks (e.g. useState).
5891
+
5892
+ ## Imports
5893
+
5894
+ #### Import the hook to the component.
5895
+ ${"```ts"}
5896
+ import { use${pascalCase(descriptor.name)} } from '@intrig/next/${descriptor.path}/client';
5897
+ ${"```"}
5898
+
5899
+ #### Import the utility methods.
5900
+ ${"```ts"}
5901
+ import { isSuccess } from '@intrig/next';
5902
+ ${"```"}
5903
+
5904
+ #### Use hook inside the component.
5905
+
5906
+ ${"```tsx"}
5907
+ const [${camelCase(descriptor.name)}Resp, ${camelCase(descriptor.name)}, clear${pascalCase(descriptor.name)}] = use${pascalCase(descriptor.name)}();
5908
+ ${"```"}
5909
+
5910
+ ### Usage
5911
+
5912
+ #### Execute data fetching.
5913
+
5914
+ ${"```tsx"}
5915
+ useEffect(() => {
5916
+ ${camelCase(descriptor.name)}(${[
5917
+ requestBody,
5918
+ params ?? '{}'
5919
+ ].filter(Boolean).join(', ')});
5920
+ }, [${[
5921
+ requestBody,
5922
+ params
5923
+ ].filter(Boolean).join(', ')}/* Dependencies */])
5924
+ ${"```"}
5925
+
5926
+ #### Extract data from the response.
5927
+ \`SSE\` hooks are a special kind of hook that delivers intermediate data updates as they are received.
5928
+
5929
+ ##### Minimal example.
5930
+ ${"```tsx"}
5931
+ const ${camelCase(descriptor.name)} = isPending(${camelCase(descriptor.name)}Resp) ? ${camelCase(descriptor.name)}Resp.data : null;
5932
+ ${"```"}
5933
+
5934
+ ### Full example.
5935
+
5936
+ #### Short format.
5937
+ You can pass the request body and params as props to the hook for initial data binding. This will create the data to be tightly coupled
5938
+ with the component lifecycle.
5939
+
5940
+ > Usually the SSE-hooks are combined with a collector mechanism to get the complete message.
5941
+
5942
+ <details>
5943
+ <summary>Implementation</summary>
5944
+
5945
+ ${"```tsx"}
5946
+ import { use${pascalCase(descriptor.name)} } from '@intrig/next/${descriptor.path}/client';
5947
+ import { isSuccess, isPending, isError } from '@intrig/next';
5948
+ ${requestBody ? `import { ${pascalCase(requestBody)} } from '@intrig/next/${descriptor.source}/components/schemas/${pascalCase(requestBody)}';` : ''}
5949
+ ${params ? `import { ${pascalCase(params)}Params } from '@intrig/next/${descriptor.path}/${pascalCase(descriptor.name)}.params';` : ''}
5950
+ import { useState, useEffect } from 'react';
5951
+ import { LoadingIndicator, ErrorDisplay } from '@/components/ui'; //import from your project.
5952
+ import {flushSync} from "react-dom";
5953
+
5954
+ ${requestBody || params ? `
5955
+ interface MyComponentProps {
5956
+ ${requestBody ? `${camelCase(requestBody)}: ${pascalCase(requestBody)};` : ''}
5957
+ ${params ? `${camelCase(params)}Params: ${pascalCase(params)}Params;` : ''}
5958
+ }
5959
+ ` : ''}
5960
+
5961
+ function MyComponent(${requestBody || params ? 'props: MyComponentProps' : ''}) {
5962
+ const [${camelCase(descriptor.name)}Resp] = use${pascalCase(descriptor.name)}({
5963
+ fetchOnMount: true,
5964
+ clearOnUnmount: true,
5965
+ ${requestBody ? `body: props.${camelCase(requestBody)},` : ''}
5966
+ ${params ? `params: props.${camelCase(params)},` : 'params: {}'}
5967
+ });
5968
+
5969
+ let [data, setData] = useState<${pascalCase(descriptor.name)}[]>([]);
5970
+
5971
+ useEffect(() => {
5972
+ if (isPending(${camelCase(descriptor.name)}Resp)) {
5973
+ flushSync(() => { //Sometimes react tends to skip intermediate renders for the performance. Use flushSync if you need to keep track of messages.
5974
+ setData(data => [...data, ${camelCase(descriptor.name)}Resp.data]);
5975
+ })
5976
+ }
5977
+ }, [${camelCase(descriptor.name)}Resp])
5978
+
5979
+ if (isPending(${camelCase(descriptor.name)}Resp)) {
5980
+ return <>{data.map(a => <>{JSON.stringify(a)}</>)}</>
5981
+ }
5982
+
5983
+ if (isError(${camelCase(descriptor.name)}Resp)) {
5984
+ return <ErrorDisplay error={${camelCase(descriptor.name)}Resp.error}/> //TODO add your error view here.
5985
+ }
5986
+
5987
+ return <>
5988
+ Completed: {JSON.stringify(data)}
5989
+ </>
5990
+ }
5991
+ ${"```"}
5992
+ </details>
5993
+
5994
+ #### Controlled format.
5995
+ If you need more control over the data binding, where the data control is outside of the component lifecycle,
5996
+ you can use the actions provided by the hook.
5997
+
5998
+ <details>
5999
+ <summary>Implementation</summary>
6000
+
6001
+ ${"```tsx"}
6002
+ import { use${pascalCase(descriptor.name)} } from '@intrig/next/${descriptor.path}/client';
6003
+ import { isSuccess } from '@intrig/next';
6004
+ ${requestBody ? `import { ${pascalCase(requestBody)} } from '@intrig/next/${descriptor.source}/components/schemas/${pascalCase(requestBody)}';` : ''}
6005
+ ${params ? `import { ${pascalCase(params)}Params } from '@intrig/next/${descriptor.path}/${pascalCase(descriptor.name)}.params';` : ''}
6006
+ import {flushSync} from "react-dom";
6007
+
6008
+ ${requestBody || params ? `
6009
+ interface MyComponentProps {
6010
+ ${requestBody ? `${camelCase(requestBody)}: ${pascalCase(requestBody)};` : ''}
6011
+ ${params ? `${camelCase(params)}Params: ${pascalCase(params)}Params;` : ''}
6012
+ }
6013
+ ` : ''}
6014
+
6015
+ function MyComponent() {
6016
+ const [${camelCase(descriptor.name)}Resp, ${camelCase(descriptor.name)}, clear${pascalCase(descriptor.name)}] = use${pascalCase(descriptor.name)}();
6017
+
6018
+ useEffect(() => {
6019
+ ${camelCase(descriptor.name)}(${requestBody ? `${camelCase(requestBody)},` : ''} ${params ? `${camelCase(params)},` : '{}'}) //Call the fetch function.
6020
+ return clear${pascalCase(descriptor.name)}; //Clear the data on unmount.
6021
+ }, [])
6022
+
6023
+ let [data, setData] = useState<${pascalCase(descriptor.name)}[]>([]);
6024
+
6025
+ useEffect(() => {
6026
+ if (isPending(${camelCase(descriptor.name)}Resp)) {
6027
+ flushSync(() => { //Sometimes react tends to skip intermediate renders for the performance. Use flushSync if you need to keep track of messages.
6028
+ setData(data => [...data, ${camelCase(descriptor.name)}Resp.data]);
6029
+ })
6030
+ }
6031
+ }, [${camelCase(descriptor.name)}Resp])
6032
+
5532
6033
  if (isPending(${camelCase(descriptor.name)}Resp)) {
5533
- return <LoadingIndicator />
6034
+ return <>{data.map(a => <>{JSON.stringify(a)}</>)}</>
5534
6035
  }
5535
- ${"```"}
5536
- </details>
5537
6036
 
5538
- <details>
5539
- <summary>Inline rendering</summary>
5540
- If you need to show a non-blocking/parallel loading indicator, or change the component behavior based on the loading state, you can inline the loading condition.
5541
-
5542
- ${"```tsx"}
5543
- <>{isPending(${camelCase(descriptor.name)}Resp) ? <LoadingIndicator /> : null}<MyComponent /></>
6037
+ if (isError(${camelCase(descriptor.name)}Resp)) {
6038
+ return <ErrorDisplay error={${camelCase(descriptor.name)}Resp.error}/> //TODO add your error view here.
6039
+ }
6040
+
6041
+ return <>
6042
+ Completed: {JSON.stringify(data)}
6043
+ </>
6044
+ }
5544
6045
  ${"```"}
5545
6046
  </details>
5546
6047
 
5547
- #### Error state
5548
- The error state extraction is done using the \`isError\` utility method. Make sure to import the method into your component.
6048
+ `;
6049
+ }
5549
6050
 
5550
- ${"```tsx"}
5551
- import { isError } from '@intrig/next';
5552
- ${"```"}
6051
+ ;// ../../lib/next-binding/src/lib/templates/docs/async-hook.ts
5553
6052
 
5554
- <details>
5555
- <summary>Early return</summary>
5556
- In most cases, in an error state, you may want to show the error message to the user instead of real component. In that case, you can add a conditional early return.
6053
+ function nextAsyncFunctionHookDocs(descriptor) {
6054
+ const md = mdLiteral('async-hook.md');
6055
+ const requestBody = descriptor.data.requestBody ? camelCase(descriptor.data.requestBody) : undefined;
6056
+ const params = descriptor.data.variables?.filter((a)=>a.in.toUpperCase() === 'PATH')?.length ? 'params' : undefined;
6057
+ return md`
6058
+
6059
+ > Intrig generated async hooks are intended to use for the rapid usecases like validations. This effectively bypasses the network-state management.
6060
+ > The async hooks follow the tuple-based API pattern as React's built-in state hooks (e.g. useState).
5557
6061
 
5558
- ${"```tsx"}
5559
- if (isError(${camelCase(descriptor.name)}Resp)) {
5560
- return <ErrorMessage />
5561
- }
6062
+ ## Imports
6063
+
6064
+ #### Import the hook to the component.
6065
+ ${"```ts"}
6066
+ import { use${pascalCase(descriptor.name)}Async } from '@intrig/next/${descriptor.path}/client';
5562
6067
  ${"```"}
5563
- </details>
5564
6068
 
5565
- <details>
5566
- <summary>Inline rendering</summary>
5567
- If you need to show a non-blocking/parallel error indicator, or change the component behavior based on the error state, you can inline the error condition.
6069
+ #### Use hook inside the component.
5568
6070
 
5569
6071
  ${"```tsx"}
5570
- <>{isError(${camelCase(descriptor.name)}Resp) ? <ErrorMessage /> : null}<MyComponent /></>
6072
+ const [${camelCase(descriptor.name)}, abort${pascalCase(descriptor.name)}] = use${pascalCase(descriptor.name)}Async();
5571
6073
  ${"```"}
5572
- </details>
5573
-
5574
- <details>
5575
- <summary>Side effect upon error</summary>
5576
6074
 
5577
- Usually if the operation is an update, you may need to show an error message to the user. In such cases, you can add a side effect with useEffect, upon error.
6075
+ #### Execute data fetching / calling.
5578
6076
 
5579
- First import \`useEffect\` to your component.
5580
6077
  ${"```tsx"}
5581
- import { useEffect } from 'react'
5582
- ${"```"}
6078
+ const fn = useCallback(async () => {
6079
+ let ${camelCase(descriptor.name)}Data = await ${camelCase(descriptor.name)}(${[
6080
+ requestBody,
6081
+ params ?? '{}'
6082
+ ].filter(Boolean).join(', ')});
6083
+ //TODO do something with the ${camelCase(descriptor.name)}Data.
6084
+ return ${camelCase(descriptor.name)}Data;
6085
+ }, [${[
6086
+ requestBody,
6087
+ params
6088
+ ].filter(Boolean).join(', ')}/* Dependencies */])
5583
6089
 
5584
- Then add the side effect.
5585
- ${"```tsx"}
5586
- useEffect(() => {
5587
- if (isError(${camelCase(descriptor.name)}Resp)) {
5588
- //TODO execute error related operation.
5589
- }
5590
- }, [${camelCase(descriptor.name)}Resp])
5591
6090
  ${"```"}
5592
- </details>
5593
6091
 
5594
- ### Replications
6092
+ ## Full example
5595
6093
 
5596
6094
  <details>
5597
- <summary>Multiple instances for same hook.</summary>
6095
+ <summary>Implementation</summary>
5598
6096
 
5599
- In some case you may need to instanciate multiple instances of the same request. For an example comparision of the products.
5600
- In that cases, you can use \`key\` property to distinguish between these Network states.
6097
+ ${"```tsx"}
6098
+ import { use${pascalCase(descriptor.name)}Async } from '@intrig/next/${descriptor.path}/client';
6099
+ ${requestBody ? `import { ${pascalCase(requestBody)} } from '@intrig/next/${descriptor.source}/components/schemas/${pascalCase(requestBody)}';` : ''}
6100
+ ${params ? `import { ${pascalCase(params)}Params } from '@intrig/next/${descriptor.path}/${pascalCase(descriptor.name)}.params';` : ''}
6101
+ import { useCallback } from 'react';
5601
6102
 
5602
- ${'```tsx'}
5603
- const [${camelCase(descriptor.name)}Resp, ${camelCase(descriptor.name)}, clear${pascalCase(descriptor.name)}] = use${pascalCase(descriptor.name)}({ key: 'id1' });
5604
- ${'```'}
5605
- </details>
6103
+ ${requestBody || params ? `
6104
+ interface MyComponentProps {
6105
+ ${requestBody ? `${camelCase(requestBody)}: ${pascalCase(requestBody)};` : ''}
6106
+ ${params ? `${camelCase(params)}Params: ${pascalCase(params)}Params;` : ''}
6107
+ }
6108
+ ` : ''}
5606
6109
 
6110
+ function MyComponent(${requestBody || params ? 'props: MyComponentProps' : ''}) {
6111
+ const [${camelCase(descriptor.name)}, abort${pascalCase(descriptor.name)}] = use${pascalCase(descriptor.name)}Async();
5607
6112
 
5608
- `;
6113
+ const fn = useCallback(async (${[
6114
+ requestBody,
6115
+ params
6116
+ ].filter(Boolean).join(', ')}) => {
6117
+ return await ${camelCase(descriptor.name)}(${[
6118
+ requestBody,
6119
+ params
6120
+ ].filter(Boolean).join(', ')});
6121
+ }, [/* Dependencies */])
6122
+
6123
+ //use fn where remote callback is needed.
6124
+ return <>
6125
+ <button onClick={() => fn(${[
6126
+ requestBody,
6127
+ params
6128
+ ].filter(Boolean).map((a)=>`props.${a}`).join(', ')})}>Call remote</button>
6129
+ </>
6130
+ }
6131
+
6132
+ ${"```"}
6133
+ </details>
6134
+
6135
+ <hint>
6136
+ //TODO improve with usecases
6137
+ </hint>
6138
+
6139
+ `;
5609
6140
  }
5610
6141
 
5611
6142
  ;// ../../lib/next-binding/src/lib/next-binding.service.ts
@@ -5641,6 +6172,10 @@ function next_binding_service_ts_metadata(k, v) {
5641
6172
 
5642
6173
 
5643
6174
 
6175
+
6176
+
6177
+
6178
+
5644
6179
 
5645
6180
 
5646
6181
 
@@ -5650,11 +6185,12 @@ const nonDownloadMimePatterns = external_picomatch_default()([
5650
6185
  "application/json",
5651
6186
  "application/xml",
5652
6187
  "application/x-www-form-urlencoded",
6188
+ "application/event-stream",
5653
6189
  "text/*"
5654
6190
  ]);
5655
6191
  class IntrigNextBindingService extends GeneratorBinding {
5656
6192
  constructor(sourceManagementService, config){
5657
- super(), this.sourceManagementService = sourceManagementService, this.config = config, this.dump = this.sourceManagementService.dump, this._path = this.config.get("generatedDir") ?? external_path_default().resolve(external_node_process_namespaceObject.cwd(), '.intrig', 'generated');
6193
+ super(), this.sourceManagementService = sourceManagementService, this.config = config, this.logger = new common_namespaceObject.Logger(IntrigNextBindingService.name), this.dump = this.sourceManagementService.dump, this._path = this.config.get("generatedDir") ?? external_path_default().resolve(external_node_process_namespaceObject.cwd(), '.intrig', 'generated');
5658
6194
  }
5659
6195
  getRestOptions() {
5660
6196
  return {
@@ -5686,12 +6222,19 @@ class IntrigNextBindingService extends GeneratorBinding {
5686
6222
  await this.dump(nextExtraTemplate(this._path));
5687
6223
  await this.dump(nextLoggerTemplate(this._path));
5688
6224
  await this.dump(nextSwcrcTemplate(this._path));
6225
+ await this.dump(typeUtilsTemplate(this._path));
5689
6226
  }
5690
6227
  async generateSource(descriptors, source) {
6228
+ //TODO improve this logic to catch potential conflicts.
6229
+ const potentiallyConflictingDescriptors = descriptors.filter(isRestDescriptor).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);
6230
+ const ctx = {
6231
+ potentiallyConflictingDescriptors
6232
+ };
5691
6233
  const groupedByPath = {};
5692
6234
  for (const descriptor of descriptors){
6235
+ this.logger.log(`Generating source: ${descriptor.name}`);
5693
6236
  if (isRestDescriptor(descriptor)) {
5694
- await this.generateRestSource(source, descriptor);
6237
+ await this.generateRestSource(source, descriptor, ctx);
5695
6238
  groupedByPath[descriptor.data.requestUrl] = groupedByPath[descriptor.data.requestUrl] || [];
5696
6239
  groupedByPath[descriptor.data.requestUrl].push(descriptor);
5697
6240
  } else if (isSchemaDescriptor(descriptor)) {
@@ -5702,14 +6245,14 @@ class IntrigNextBindingService extends GeneratorBinding {
5702
6245
  await this.dump(nextRequestRouteTemplate(requestUrl, matchingPaths, this._path));
5703
6246
  }
5704
6247
  }
5705
- async generateRestSource(source, descriptor) {
6248
+ async generateRestSource(source, descriptor, ctx) {
5706
6249
  const clientExports = [];
5707
6250
  const serverExports = [];
5708
6251
  await this.dump(nextParamsTemplate(descriptor, clientExports, serverExports, this._path));
5709
6252
  await this.dump(nextRequestHookTemplate(descriptor, clientExports, serverExports, this._path));
5710
6253
  await this.dump(nextRequestMethodTemplate(descriptor, clientExports, serverExports, this._path));
5711
- //TODO incorporate rest options.
5712
- if (descriptor.data.method.toUpperCase() === 'GET' && !nonDownloadMimePatterns(descriptor.data.responseType)) {
6254
+ await this.dump(nextAsyncFunctionHookTemplate(descriptor, this._path, ctx));
6255
+ if (descriptor.data.method.toUpperCase() === 'GET' && (!nonDownloadMimePatterns(descriptor.data.responseType) || descriptor.data.responseType !== '*/*') || descriptor.data.responseHeaders?.['content-disposition']) {
5713
6256
  await this.dump(nextDownloadHookTemplate(descriptor, clientExports, serverExports, this._path));
5714
6257
  }
5715
6258
  await this.dump(nextClientIndexTemplate([
@@ -5796,16 +6339,20 @@ ${"```"}
5796
6339
  ]));
5797
6340
  const tabs = [];
5798
6341
  if (result.data.responseType === 'text/event-stream') {
5799
- // tabs.push({
5800
- // name: 'SSE Hook',
5801
- // content: (await sseHookDocs(result)).content
5802
- // })
6342
+ tabs.push({
6343
+ name: 'SSE Hook',
6344
+ content: (await nextSseHookDocs(result)).content
6345
+ });
5803
6346
  } else {
5804
6347
  tabs.push({
5805
6348
  name: 'React Hook',
5806
6349
  content: (await nextReactHookDocs(result)).content
5807
6350
  });
5808
6351
  }
6352
+ tabs.push({
6353
+ name: 'Async Hook',
6354
+ content: (await nextAsyncFunctionHookDocs(result)).content
6355
+ });
5809
6356
  return RestDocumentation.from({
5810
6357
  id: result.id,
5811
6358
  name: result.name,
@@ -7114,6 +7661,7 @@ function reactPackageJsonTemplate(_path) {
7114
7661
  "@swc/cli": "^0.7.7",
7115
7662
  "@swc/core": "^1.12.6",
7116
7663
  "@types/node": "^24.0.4",
7664
+ "typescript": "${packageJson.devDependencies.typescript ?? packageJson.dependencies.typescript}",
7117
7665
  "react": "${packageJson.dependencies.react}",
7118
7666
  "react-dom": "${packageJson.dependencies['react-dom']}"
7119
7667
  },
@@ -7913,6 +8461,15 @@ async function reactRequestHookTemplate({ source, data: { paths, operationId, re
7913
8461
  "...params"
7914
8462
  ].join(",");
7915
8463
  const finalRequestBodyBlock = requestBody ? `,data: encode(data, "${contentType}", requestBodySchema)` : '';
8464
+ function responseTypePart() {
8465
+ switch(responseType){
8466
+ case "application/octet-stream":
8467
+ return `responseType: 'blob', adapter: 'fetch',`;
8468
+ case "text/event-stream":
8469
+ return `responseType: 'stream', adapter: 'fetch',`;
8470
+ }
8471
+ return '';
8472
+ }
7916
8473
  return ts`
7917
8474
  ${[
7918
8475
  ...imports
@@ -7957,7 +8514,7 @@ async function reactRequestHookTemplate({ source, data: { paths, operationId, re
7957
8514
  key: \`${"${source}: ${operation}"}\`,
7958
8515
  source: '${source}'
7959
8516
  ${requestBody ? finalRequestBodyBlock : ''},
7960
- ${responseType === "text/event-stream" ? `responseType: 'stream', adapter: 'fetch',` : ''}
8517
+ ${responseTypePart()}
7961
8518
  })
7962
8519
  return successfulDispatch();
7963
8520
  }, [dispatch])
@@ -8097,6 +8654,15 @@ async function reactDownloadHookTemplate({ source, data: { paths, operationId, r
8097
8654
  "...params"
8098
8655
  ].join(",");
8099
8656
  const finalRequestBodyBlock = requestBody ? `,data: encode(data, "${contentType}", requestBodySchema)` : '';
8657
+ function responseTypePart() {
8658
+ switch(responseType){
8659
+ case "application/octet-stream":
8660
+ return `responseType: 'blob', adapter: 'fetch',`;
8661
+ case "text/event-stream":
8662
+ return `responseType: 'stream', adapter: 'fetch',`;
8663
+ }
8664
+ return '';
8665
+ }
8100
8666
  return ts`
8101
8667
  ${[
8102
8668
  ...imports
@@ -8175,7 +8741,7 @@ async function reactDownloadHookTemplate({ source, data: { paths, operationId, r
8175
8741
  key: \`${"${source}: ${operation}"}\`,
8176
8742
  source: '${source}'
8177
8743
  ${requestBody ? finalRequestBodyBlock : ''},
8178
- ${responseType === "text/event-stream" ? `responseType: 'stream', adapter: 'fetch',` : ''}
8744
+ ${responseTypePart()}
8179
8745
  })
8180
8746
  return successfulDispatch();
8181
8747
  }, [dispatch])
@@ -9068,7 +9634,7 @@ ${"```"}
9068
9634
  ;// ../../lib/react-binding/src/lib/templates/source/controller/method/asyncFunctionHook.template.ts
9069
9635
 
9070
9636
 
9071
- function extractAsyncHookShape(response, requestBody, imports) {
9637
+ function asyncFunctionHook_template_extractAsyncHookShape(response, requestBody, imports) {
9072
9638
  if (response) {
9073
9639
  if (requestBody) {
9074
9640
  imports.add(`import { BinaryFunctionAsyncHook } from "@intrig/react"`);
@@ -9087,7 +9653,7 @@ function extractAsyncHookShape(response, requestBody, imports) {
9087
9653
  }
9088
9654
  }
9089
9655
  }
9090
- function asyncFunctionHook_template_extractErrorParams(errorTypes) {
9656
+ function method_asyncFunctionHook_template_extractErrorParams(errorTypes) {
9091
9657
  switch(errorTypes.length){
9092
9658
  case 0:
9093
9659
  return `
@@ -9103,7 +9669,7 @@ function asyncFunctionHook_template_extractErrorParams(errorTypes) {
9103
9669
  const errorSchema = z.union([${errorTypes.map((a)=>`${a}Schema`).join(', ')}])`;
9104
9670
  }
9105
9671
  }
9106
- function asyncFunctionHook_template_extractParamDeconstruction(variables, requestBody) {
9672
+ function method_asyncFunctionHook_template_extractParamDeconstruction(variables, requestBody) {
9107
9673
  const isParamMandatory = variables?.some((a)=>a.in === 'path') || false;
9108
9674
  if (requestBody) {
9109
9675
  if (isParamMandatory) {
@@ -9141,7 +9707,7 @@ async function reactAsyncFunctionHookTemplate({ source, data: { paths, operation
9141
9707
  imports.add(`import { useCallback } from 'react'`);
9142
9708
  imports.add(`import { useTransientCall, encode, isError, isSuccess } from '@intrig/react'`);
9143
9709
  // Hook signature type
9144
- const hookShape = extractAsyncHookShape(response, requestBody, imports);
9710
+ const hookShape = asyncFunctionHook_template_extractAsyncHookShape(response, requestBody, imports);
9145
9711
  // Add body/response param imports
9146
9712
  if (requestBody) {
9147
9713
  imports.add(`import { ${requestBody} as RequestBody, ${requestBody}Schema as requestBodySchema } from "@intrig/react/${source}/components/schemas/${requestBody}"`);
@@ -9156,14 +9722,23 @@ async function reactAsyncFunctionHookTemplate({ source, data: { paths, operation
9156
9722
  ];
9157
9723
  errorTypes.forEach((ref)=>imports.add(`import { ${ref}, ${ref}Schema } from "@intrig/react/${source}/components/schemas/${ref}"`));
9158
9724
  // Error schema block
9159
- const errorSchemaBlock = asyncFunctionHook_template_extractErrorParams(errorTypes.map((a)=>a));
9725
+ const errorSchemaBlock = method_asyncFunctionHook_template_extractErrorParams(errorTypes.map((a)=>a));
9160
9726
  // Param deconstruction
9161
- const { paramExpression, paramType } = asyncFunctionHook_template_extractParamDeconstruction(variables ?? [], requestBody);
9727
+ const { paramExpression, paramType } = method_asyncFunctionHook_template_extractParamDeconstruction(variables ?? [], requestBody);
9162
9728
  const paramExplode = [
9163
9729
  ...variables?.filter((a)=>a.in === 'path').map((a)=>a.name) ?? [],
9164
9730
  '...params'
9165
9731
  ].join(',');
9166
9732
  const finalRequestBodyBlock = requestBody ? `, data: encode(data, "${contentType}", requestBodySchema)` : '';
9733
+ function responseTypePart() {
9734
+ switch(responseType){
9735
+ case "application/octet-stream":
9736
+ return `responseType: 'blob', adapter: 'fetch',`;
9737
+ case "text/event-stream":
9738
+ return `responseType: 'stream', adapter: 'fetch',`;
9739
+ }
9740
+ return '';
9741
+ }
9167
9742
  return ts`
9168
9743
  ${[
9169
9744
  ...imports
@@ -9205,7 +9780,7 @@ function use${pascalCase(operationId)}AsyncHook(): [(${paramType}) => Promise<Re
9205
9780
  key: \`${"${source}: ${operation}"}\`,
9206
9781
  source: '${source}'
9207
9782
  ${requestBody ? finalRequestBodyBlock : ''},
9208
- ${responseType === "text/event-stream" ? `responseType: 'stream', adapter: 'fetch',` : ''}
9783
+ ${responseTypePart()}
9209
9784
  });
9210
9785
  }, [call]);
9211
9786
 
@@ -9312,27 +9887,12 @@ ${"```"}
9312
9887
  ;// ../../lib/react-binding/src/lib/templates/type-utils.template.ts
9313
9888
 
9314
9889
 
9315
- function typeUtilsTemplate(_path) {
9890
+ function type_utils_template_typeUtilsTemplate(_path) {
9316
9891
  const ts = typescript(external_path_default().resolve(_path, 'src', 'type-utils.ts'));
9317
9892
  return ts`import { z } from 'zod';
9318
9893
 
9319
- export type BinaryData = Blob | ArrayBuffer | Uint8Array;
9320
-
9321
- export const BinaryDataSchema = z.union([
9322
- // Blob in browsers
9323
- z.instanceof(Blob).optional(), // optional here so union below still validates if Blob is absent in Node
9324
- // Raw buffers
9325
- z.instanceof(ArrayBuffer),
9326
- z.custom<Uint8Array>((v) => v instanceof Uint8Array, { message: 'Expected Uint8Array' }),
9327
- ]).transform((v) => {
9328
- // Normalize to Blob if possible (nice for downloads in browser)
9329
- if (typeof Blob !== 'undefined') {
9330
- if (v instanceof Blob) return v;
9331
- if (v instanceof ArrayBuffer) return new Blob([v]);
9332
- if (v instanceof Uint8Array) return new Blob([v.buffer]);
9333
- }
9334
- return v;
9335
- });
9894
+ export type BinaryData = Blob;
9895
+ export const BinaryDataSchema: z.ZodType<BinaryData> = z.instanceof(Blob);
9336
9896
 
9337
9897
  // Base64 helpers (browser + Node compatible; no Buffer required)
9338
9898
  export function base64ToUint8Array(b64: string): Uint8Array {
@@ -9412,7 +9972,7 @@ class ReactBindingService extends GeneratorBinding {
9412
9972
  await this.dump(reactPackageJsonTemplate(this._path));
9413
9973
  await this.dump(reactProviderTemplate(this._path, apisToSync));
9414
9974
  await this.dump(reactTsConfigTemplate(this._path));
9415
- await this.dump(typeUtilsTemplate(this._path));
9975
+ await this.dump(type_utils_template_typeUtilsTemplate(this._path));
9416
9976
  }
9417
9977
  async generateSource(descriptors, source) {
9418
9978
  //TODO improve this logic to catch potential conflicts.
@@ -10742,10 +11302,13 @@ function deref(spec) {
10742
11302
  };
10743
11303
  }
10744
11304
 
11305
+ ;// external "node:crypto"
11306
+ const external_node_crypto_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:crypto");
10745
11307
  ;// ../../lib/openapi-source/src/lib/util/normalize.ts
10746
11308
 
10747
11309
 
10748
11310
 
11311
+
10749
11312
  function generateTypeName(operationOb, postFix) {
10750
11313
  return [
10751
11314
  operationOb.tags?.[0],
@@ -10755,7 +11318,7 @@ function generateTypeName(operationOb, postFix) {
10755
11318
  }
10756
11319
  function normalize(spec) {
10757
11320
  const doDeref = deref(spec);
10758
- return (0,external_immer_namespaceObject.produce)(spec, (draft)=>{
11321
+ const normalized = (0,external_immer_namespaceObject.produce)(spec, (draft)=>{
10759
11322
  const paths = draft.paths;
10760
11323
  for (const [path, pathItem] of Object.entries(paths)){
10761
11324
  const pathItemObject = pathItem;
@@ -10860,6 +11423,9 @@ function normalize(spec) {
10860
11423
  }
10861
11424
  //TODO implement fix schema types.
10862
11425
  });
11426
+ return (0,external_immer_namespaceObject.produce)(normalized, (draft)=>{
11427
+ draft.info['x-intrig-hash'] = (0,external_node_crypto_namespaceObject.createHash)('sha256').update(JSON.stringify(draft)).digest('hex');
11428
+ });
10863
11429
  }
10864
11430
 
10865
11431
  ;// ../../lib/openapi-source/src/lib/util/extractRequestsFromSpec.ts
@@ -11003,26 +11569,47 @@ class IntrigOpenapiService {
11003
11569
  }
11004
11570
  async sync(config, id, ctx) {
11005
11571
  this.logger.log('Starting OpenAPI sync process');
11572
+ const errors = [];
11006
11573
  if (!id) {
11574
+ // Process all sources with better error isolation
11007
11575
  for (const source of config.sources){
11008
11576
  this.logger.log(`Processing source: ${source.id}`);
11009
11577
  try {
11010
11578
  await this.doSync(source, config, ctx);
11579
+ this.logger.log(`Successfully synced source: ${source.id}`);
11011
11580
  } catch (e) {
11012
- this.logger.error(`Failed to sync source ${source.id}: ${e.message}`, e);
11581
+ const errorMsg = `Failed to sync source ${source.id}: ${e.message}`;
11582
+ this.logger.error(errorMsg, e);
11583
+ errors.push({
11584
+ sourceId: source.id,
11585
+ error: errorMsg
11586
+ });
11587
+ // Continue with other sources instead of failing completely
11013
11588
  }
11014
11589
  }
11015
11590
  } else {
11016
11591
  const source = config.sources.find((s)=>s.id === id);
11017
11592
  if (!source) {
11018
- this.logger.error(`Source ${id} not found`);
11019
- throw new Error(`Source ${id} not found`);
11593
+ const error = `Source ${id} not found`;
11594
+ this.logger.error(error);
11595
+ throw new Error(error);
11020
11596
  }
11021
11597
  this.logger.log(`Processing specific source: ${id}`);
11022
11598
  try {
11023
11599
  await this.doSync(source, config, ctx);
11600
+ this.logger.log(`Successfully synced source: ${id}`);
11024
11601
  } catch (e) {
11025
- this.logger.error(`Failed to sync source ${id}: ${e.message}`, e);
11602
+ const errorMsg = `Failed to sync source ${id}: ${e.message}`;
11603
+ this.logger.error(errorMsg, e);
11604
+ throw new Error(errorMsg);
11605
+ }
11606
+ }
11607
+ if (errors.length > 0) {
11608
+ const summary = `Sync completed with ${errors.length} errors: ${errors.map((e)=>e.sourceId).join(', ')}`;
11609
+ this.logger.warn(summary);
11610
+ // If all sources failed, throw an error
11611
+ if (errors.length === config.sources.length) {
11612
+ throw new Error(`All sources failed to sync: ${errors.map((e)=>e.error).join('; ')}`);
11026
11613
  }
11027
11614
  }
11028
11615
  this.logger.log('OpenAPI sync process completed');
@@ -11068,7 +11655,8 @@ class IntrigOpenapiService {
11068
11655
  const restData = extractRequestsFromSpec(document);
11069
11656
  const schemas = extractSchemas(document);
11070
11657
  const sha1 = (str)=>external_crypto_namespaceObject.createHash('sha1').update(str).digest('hex');
11071
- return [
11658
+ const hash = document.info['x-intrig-hash'];
11659
+ const descriptors = [
11072
11660
  ...restData.map((restData)=>ResourceDescriptor.from({
11073
11661
  id: sha1(`${id}_${restData.method}_${restData.paths.join('_')}_${restData.operationId}_${restData.contentType}_${restData.responseType}`),
11074
11662
  name: camelCase(restData.operationId),
@@ -11086,6 +11674,17 @@ class IntrigOpenapiService {
11086
11674
  data: schema
11087
11675
  }))
11088
11676
  ];
11677
+ return {
11678
+ descriptors,
11679
+ hash
11680
+ };
11681
+ }
11682
+ async getHash(id) {
11683
+ const document = await this.specManagementService.read(id);
11684
+ if (!document) {
11685
+ throw new Error(`Spec ${id} not found`);
11686
+ }
11687
+ return document.info['x-intrig-hash'];
11089
11688
  }
11090
11689
  validate(normalized, restOptions) {
11091
11690
  function validateForConflictingVariables() {
@@ -11676,7 +12275,7 @@ class SearchService {
11676
12275
  async onModuleInit() {
11677
12276
  try {
11678
12277
  for (const source of this.configService.get().sources){
11679
- const descriptors = await this.openApiService.getResourceDescriptors(source.id);
12278
+ const { descriptors } = await this.openApiService.getResourceDescriptors(source.id);
11680
12279
  descriptors.forEach((descriptor)=>this.addDescriptor(descriptor));
11681
12280
  }
11682
12281
  // Update the CodeAnalyzer with all descriptors
@@ -11968,24 +12567,50 @@ class OperationsService {
11968
12567
  this.config = config;
11969
12568
  this.searchService = searchService;
11970
12569
  this.logger = new common_namespaceObject.Logger(OperationsService.name);
12570
+ // Add sync coordination
12571
+ this.syncInProgress = new Set();
12572
+ this.SYNC_TIMEOUT = 300000; // 5 minutes timeout
11971
12573
  this.generateDir = this.config.get("generatedDir") ?? external_path_namespaceObject.resolve(process.cwd(), ".intrig", "generated");
11972
12574
  }
11973
12575
  async getConfig(ctx) {
11974
12576
  return this.configService.get();
11975
12577
  }
11976
12578
  async sync(ctx, id) {
11977
- const config = await this.getConfig(ctx);
11978
- const prevDescriptors = await this.getPreviousState(ctx, config);
11979
- const restOptions = this.generatorBinding.getRestOptions();
11980
- await this.openApiService.sync({
11981
- ...config,
11982
- restOptions: {
11983
- ...config.restOptions,
11984
- ...restOptions
11985
- }
11986
- }, id, ctx);
11987
- const newDescriptors = await this.getNewState(ctx, config);
11988
- await this.indexDiff(ctx, prevDescriptors, newDescriptors);
12579
+ const syncKey = id || 'all';
12580
+ // Check if sync is already in progress
12581
+ if (this.syncInProgress.has(syncKey)) {
12582
+ const error = `Sync already in progress for: ${syncKey}`;
12583
+ this.logger.warn(error);
12584
+ throw new Error(error);
12585
+ }
12586
+ // Add timeout protection
12587
+ const timeoutId = setTimeout(()=>{
12588
+ this.syncInProgress.delete(syncKey);
12589
+ this.logger.error(`Sync timeout for ${syncKey} after ${this.SYNC_TIMEOUT}ms`);
12590
+ }, this.SYNC_TIMEOUT);
12591
+ this.syncInProgress.add(syncKey);
12592
+ this.logger.log(`Starting sync operation for: ${syncKey}`);
12593
+ try {
12594
+ const config = await this.getConfig(ctx);
12595
+ const prevDescriptors = await this.getPreviousState(ctx, config);
12596
+ const restOptions = this.generatorBinding.getRestOptions();
12597
+ await this.openApiService.sync({
12598
+ ...config,
12599
+ restOptions: {
12600
+ ...config.restOptions,
12601
+ ...restOptions
12602
+ }
12603
+ }, id, ctx);
12604
+ const newDescriptors = await this.getNewState(ctx, config);
12605
+ await this.indexDiff(ctx, prevDescriptors, newDescriptors);
12606
+ this.logger.log(`Sync operation completed successfully for: ${syncKey}`);
12607
+ } catch (error) {
12608
+ this.logger.error(`Sync operation failed for ${syncKey}: ${error.message}`, error);
12609
+ throw error;
12610
+ } finally{
12611
+ clearTimeout(timeoutId);
12612
+ this.syncInProgress.delete(syncKey);
12613
+ }
11989
12614
  }
11990
12615
  async indexDiff(ctx, prevDescriptors, newDescriptors) {
11991
12616
  this.searchService.clearAll();
@@ -11993,21 +12618,36 @@ class OperationsService {
11993
12618
  }
11994
12619
  async getPreviousState(ctx, config) {
11995
12620
  let prevDescriptors = [];
12621
+ const errors = [];
11996
12622
  for (const source of config.sources){
11997
12623
  try {
11998
- const descriptors = await this.openApiService.getResourceDescriptors(source.id);
12624
+ const { descriptors } = await this.openApiService.getResourceDescriptors(source.id);
11999
12625
  prevDescriptors = [
12000
12626
  ...prevDescriptors,
12001
12627
  ...descriptors
12002
12628
  ];
12003
- } catch (e) {}
12629
+ this.logger.debug(`Loaded ${descriptors.length} descriptors from source ${source.id}`);
12630
+ } catch (e) {
12631
+ const errorMsg = `Failed to load previous state for source ${source.id}: ${e.message}`;
12632
+ this.logger.warn(errorMsg);
12633
+ errors.push(errorMsg);
12634
+ // If it's a critical error (not just missing file), we might want to fail
12635
+ if (e.message.includes('Invalid JSON') || e.message.includes('corrupted')) {
12636
+ throw new Error(`Critical error in getPreviousState: ${errorMsg}`);
12637
+ }
12638
+ }
12639
+ }
12640
+ if (errors.length > 0) {
12641
+ this.logger.warn(`getPreviousState completed with ${errors.length} errors: ${errors.join('; ')}`);
12004
12642
  }
12643
+ this.logger.log(`Loaded total of ${prevDescriptors.length} previous descriptors`);
12005
12644
  return prevDescriptors;
12006
12645
  }
12007
12646
  async getNewState(ctx, config) {
12647
+ this.logger.log('Loading new state');
12008
12648
  let prevDescriptors = [];
12009
12649
  for (const source of config.sources){
12010
- const descriptors = await this.openApiService.getResourceDescriptors(source.id);
12650
+ const { descriptors } = await this.openApiService.getResourceDescriptors(source.id);
12011
12651
  prevDescriptors = [
12012
12652
  ...prevDescriptors,
12013
12653
  ...descriptors
@@ -12039,8 +12679,10 @@ class OperationsService {
12039
12679
  async generate(ctx) {
12040
12680
  const config = await this.getConfig(ctx);
12041
12681
  await this.clearGenerateDir(ctx);
12682
+ const hashes = {};
12042
12683
  for (const source of config.sources){
12043
- const descriptors = await this.getDescriptors(ctx, source);
12684
+ const { descriptors, hash } = await this.getDescriptors(ctx, source);
12685
+ hashes[source.id] = hash;
12044
12686
  await this.generateSourceContent(ctx, descriptors, source);
12045
12687
  }
12046
12688
  await this.generateGlobalContent(ctx, config.sources);
@@ -12048,9 +12690,9 @@ class OperationsService {
12048
12690
  await this.buildContent(ctx);
12049
12691
  const tempBuildContext = config.__dangorouslyOverrideBuild;
12050
12692
  if (tempBuildContext) {
12051
- this.copyContentToSource(ctx, tempBuildContext);
12693
+ await this.copyContentToSource(ctx, tempBuildContext);
12052
12694
  } else {
12053
- await this.copyContentToNodeModules(ctx);
12695
+ await this.copyContentToNodeModules(ctx, hashes);
12054
12696
  }
12055
12697
  await this.executePostBuild(ctx);
12056
12698
  }
@@ -12082,7 +12724,7 @@ class OperationsService {
12082
12724
  throw error;
12083
12725
  }
12084
12726
  }
12085
- async copyContentToNodeModules(ctx) {
12727
+ async copyContentToNodeModules(ctx, hashes) {
12086
12728
  const targetLibDir = external_path_namespaceObject.join(this.config.get('rootDir') ?? process.cwd(), 'node_modules', '@intrig', this.generatorBinding.getLibName());
12087
12729
  try {
12088
12730
  if (await external_fs_extra_namespaceObject.pathExists(external_path_namespaceObject.join(targetLibDir, 'src'))) {
@@ -12094,6 +12736,10 @@ class OperationsService {
12094
12736
  this.logger.log(`Copied ${targetLibDir}`);
12095
12737
  await this.mergePackageJson(external_path_namespaceObject.join(this.generateDir, 'package.json'), external_path_namespaceObject.join(targetLibDir, 'package.json'));
12096
12738
  this.logger.log(`Merged package.json files`);
12739
+ await external_fs_extra_namespaceObject.writeJson(external_path_namespaceObject.join(targetLibDir, 'hashes.json'), hashes, {
12740
+ spaces: 2
12741
+ });
12742
+ this.logger.log(`Wrote hashes.json to ${targetLibDir}`);
12097
12743
  } catch (error) {
12098
12744
  this.logger.error(`Failed to copy content to node_modules: ${error}`);
12099
12745
  throw error;
@@ -12123,7 +12769,9 @@ class OperationsService {
12123
12769
  await this.generatorBinding.generateSource(descriptors, source);
12124
12770
  }
12125
12771
  async getDescriptors(ctx, source) {
12126
- return await this.openApiService.getResourceDescriptors(source.id);
12772
+ const descriptors = await this.openApiService.getResourceDescriptors(source.id);
12773
+ // Hash is now available for operations.service to use
12774
+ return descriptors;
12127
12775
  }
12128
12776
  async clearGenerateDir(ctx) {
12129
12777
  if (external_fs_extra_namespaceObject.pathExistsSync(this.generateDir)) {
@@ -12142,6 +12790,74 @@ class OperationsService {
12142
12790
  external_fs_extra_namespaceObject.ensureDirSync(this.generateDir);
12143
12791
  return this.generateDir;
12144
12792
  }
12793
+ async verify(hashes) {
12794
+ this.logger.log('Verifying hashes');
12795
+ try {
12796
+ // 1. Load intrigConfig
12797
+ const intrigConfig = this.configService.get();
12798
+ const sources = intrigConfig.sources || [];
12799
+ // 2. Check that each key in hashes has a matching source id
12800
+ const sourceIds = new Set(sources.map((source)=>source.id));
12801
+ const hashKeys = Object.keys(hashes);
12802
+ for (const hashKey of hashKeys){
12803
+ if (!sourceIds.has(hashKey)) {
12804
+ this.logger.error(`Hash key '${hashKey}' does not have a matching source id`);
12805
+ return {
12806
+ status: 'conflict',
12807
+ message: `Hash key '${hashKey}' does not have a matching source id`,
12808
+ statusCode: 409
12809
+ };
12810
+ }
12811
+ }
12812
+ // 3. Check that there are no more sources than hashes
12813
+ if (sources.length > hashKeys.length) {
12814
+ const extraSources = sources.map((s)=>s.id).filter((id)=>!hashKeys.includes(id));
12815
+ this.logger.error(`There are more sources than hashes. Extra sources: ${extraSources.join(', ')}`);
12816
+ return {
12817
+ status: 'conflict',
12818
+ message: `There are more sources than hashes. Extra sources: ${extraSources.join(', ')}`,
12819
+ statusCode: 409
12820
+ };
12821
+ }
12822
+ // 4. Get hash for each source and validate they match
12823
+ for (const source of sources){
12824
+ try {
12825
+ const actualHash = await this.openApiService.getHash(source.id);
12826
+ const expectedHash = hashes[source.id];
12827
+ if (actualHash !== expectedHash) {
12828
+ this.logger.error(`Hash mismatch for source '${source.id}'. Expected: ${expectedHash}, Actual: ${actualHash}`);
12829
+ return {
12830
+ status: 'conflict',
12831
+ message: `Hash mismatch for source '${source.id}'. Expected: ${expectedHash}, Actual: ${actualHash}`,
12832
+ statusCode: 409
12833
+ };
12834
+ }
12835
+ } catch (error) {
12836
+ this.logger.error(`Failed to get hash for source '${source.id}': ${error.message}`);
12837
+ return {
12838
+ status: 'conflict',
12839
+ message: `Failed to get hash for source '${source.id}': ${error.message}`,
12840
+ statusCode: 409
12841
+ };
12842
+ }
12843
+ }
12844
+ // 5. All validations passed
12845
+ this.logger.log('All hash validations passed successfully');
12846
+ return {
12847
+ status: 'verified',
12848
+ message: 'All hashes match successfully',
12849
+ hashCount: Object.keys(hashes).length,
12850
+ statusCode: 200
12851
+ };
12852
+ } catch (error) {
12853
+ this.logger.error(`Verification failed with error: ${error.message}`);
12854
+ return {
12855
+ status: 'conflict',
12856
+ message: `Verification failed: ${error.message}`,
12857
+ statusCode: 409
12858
+ };
12859
+ }
12860
+ }
12145
12861
  }
12146
12862
  operations_service_ts_decorate([
12147
12863
  WithStatus((event)=>({
@@ -12209,7 +12925,8 @@ operations_service_ts_decorate([
12209
12925
  })),
12210
12926
  operations_service_ts_metadata("design:type", Function),
12211
12927
  operations_service_ts_metadata("design:paramtypes", [
12212
- typeof GenerateEventContext === "undefined" ? Object : GenerateEventContext
12928
+ typeof GenerateEventContext === "undefined" ? Object : GenerateEventContext,
12929
+ typeof Record === "undefined" ? Object : Record
12213
12930
  ]),
12214
12931
  operations_service_ts_metadata("design:returntype", Promise)
12215
12932
  ], OperationsService.prototype, "copyContentToNodeModules", null);
@@ -12328,6 +13045,7 @@ function operations_controller_ts_param(paramIndex, decorator) {
12328
13045
 
12329
13046
 
12330
13047
 
13048
+
12331
13049
  class OperationsController {
12332
13050
  constructor(operationsService){
12333
13051
  this.operationsService = operationsService;
@@ -12352,9 +13070,16 @@ class OperationsController {
12352
13070
  await this.operationsService.generate(generateEventContext);
12353
13071
  generateEventContext.done(new GenerateDoneEventDto(true));
12354
13072
  this.logger.log('Generate operation completed');
12355
- });
13073
+ }, 0);
12356
13074
  return events$;
12357
13075
  }
13076
+ async verify(hashes, res) {
13077
+ this.logger.log('Starting verify operation');
13078
+ const result = await this.operationsService.verify(hashes);
13079
+ this.logger.log('Verify operation completed');
13080
+ const statusCode = result.statusCode || 200;
13081
+ return res.status(statusCode).json(result);
13082
+ }
12358
13083
  }
12359
13084
  operations_controller_ts_decorate([
12360
13085
  (0,common_namespaceObject.Sse)('sync'),
@@ -12407,6 +13132,25 @@ operations_controller_ts_decorate([
12407
13132
  operations_controller_ts_metadata("design:paramtypes", []),
12408
13133
  operations_controller_ts_metadata("design:returntype", Promise)
12409
13134
  ], OperationsController.prototype, "generate", null);
13135
+ operations_controller_ts_decorate([
13136
+ (0,common_namespaceObject.Post)('verify'),
13137
+ (0,swagger_namespaceObject.ApiResponse)({
13138
+ status: 200,
13139
+ description: 'Verify hashes - successful verification'
13140
+ }),
13141
+ (0,swagger_namespaceObject.ApiResponse)({
13142
+ status: 409,
13143
+ description: 'Verify hashes - validation conflicts found'
13144
+ }),
13145
+ operations_controller_ts_param(0, (0,common_namespaceObject.Body)()),
13146
+ operations_controller_ts_param(1, (0,common_namespaceObject.Res)()),
13147
+ operations_controller_ts_metadata("design:type", Function),
13148
+ operations_controller_ts_metadata("design:paramtypes", [
13149
+ typeof Record === "undefined" ? Object : Record,
13150
+ typeof external_express_namespaceObject.Response === "undefined" ? Object : external_express_namespaceObject.Response
13151
+ ]),
13152
+ operations_controller_ts_metadata("design:returntype", Promise)
13153
+ ], OperationsController.prototype, "verify", null);
12410
13154
  OperationsController = operations_controller_ts_decorate([
12411
13155
  (0,swagger_namespaceObject.ApiTags)('Operations'),
12412
13156
  (0,swagger_namespaceObject.ApiExtraModels)(SyncStatusEventDto, SyncDoneEventDto, GenerateStatusEventDto, GenerateDoneEventDto),
@@ -13615,62 +14359,6 @@ DeamonModule = deamon_module_ts_decorate([
13615
14359
  };
13616
14360
  });
13617
14361
 
13618
- ;// external "url"
13619
- const external_url_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("url");
13620
- ;// external "process"
13621
- const external_process_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("process");
13622
- ;// ./src/app/debug/debug.controller.ts
13623
- function debug_controller_ts_decorate(decorators, target, key, desc) {
13624
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
13625
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
13626
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
13627
- return c > 3 && r && Object.defineProperty(target, key, r), r;
13628
- }
13629
- function debug_controller_ts_metadata(k, v) {
13630
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
13631
- }
13632
-
13633
-
13634
-
13635
-
13636
- // Get the directory name in ES modules
13637
- const debug_controller_filename = (0,external_url_namespaceObject.fileURLToPath)("file:///home/tiran-intrigsoft/IdeaProjects/intrig-core/app/intrig/src/app/debug/debug.controller.ts");
13638
- const debug_controller_dirname = (0,external_path_namespaceObject.dirname)(debug_controller_filename);
13639
- class DebugController {
13640
- getDirname() {
13641
- // Calculate paths that might be relevant for debugging
13642
- const assetsPath = (0,external_path_namespaceObject.join)(external_process_namespaceObject.cwd(), 'dist', 'app', 'intrig', 'assets', 'insight');
13643
- const appDirname = (0,external_path_namespaceObject.dirname)(debug_controller_dirname);
13644
- return {
13645
- // ES Module specific values
13646
- dirname: debug_controller_dirname,
13647
- filename: debug_controller_filename,
13648
- meta_url: "file:///home/tiran-intrigsoft/IdeaProjects/intrig-core/app/intrig/src/app/debug/debug.controller.ts",
13649
- // Calculated paths
13650
- assetsPath,
13651
- appDirname,
13652
- // Process information
13653
- cwd: external_process_namespaceObject.cwd(),
13654
- execPath: external_process_namespaceObject.execPath,
13655
- // Environment
13656
- nodeEnv: external_process_namespaceObject.env.NODE_ENV,
13657
- // Module type
13658
- isESM: "function" === 'undefined',
13659
- // Timestamp for cache busting
13660
- timestamp: new Date().toISOString()
13661
- };
13662
- }
13663
- }
13664
- debug_controller_ts_decorate([
13665
- (0,common_namespaceObject.Get)('dirname'),
13666
- debug_controller_ts_metadata("design:type", Function),
13667
- debug_controller_ts_metadata("design:paramtypes", []),
13668
- debug_controller_ts_metadata("design:returntype", void 0)
13669
- ], DebugController.prototype, "getDirname", null);
13670
- DebugController = debug_controller_ts_decorate([
13671
- (0,common_namespaceObject.Controller)('debug')
13672
- ], DebugController);
13673
-
13674
14362
  ;// ./src/app/app.module.ts
13675
14363
  function app_module_ts_decorate(decorators, target, key, desc) {
13676
14364
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -13685,8 +14373,6 @@ function app_module_ts_decorate(decorators, target, key, desc) {
13685
14373
 
13686
14374
 
13687
14375
 
13688
- // import {McpModule} from "./mcp/mcp.module";
13689
-
13690
14376
  class AppModule {
13691
14377
  }
13692
14378
  AppModule = app_module_ts_decorate([
@@ -13703,9 +14389,7 @@ AppModule = app_module_ts_decorate([
13703
14389
  DiscoveryModule,
13704
14390
  DeamonModule
13705
14391
  ],
13706
- controllers: [
13707
- DebugController
13708
- ],
14392
+ controllers: [],
13709
14393
  providers: []
13710
14394
  })
13711
14395
  ], AppModule);
@@ -13723,12 +14407,10 @@ const core_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("
13723
14407
 
13724
14408
 
13725
14409
 
14410
+
13726
14411
  const logger = new common_namespaceObject.Logger('Main');
13727
14412
  async function bootstrapDeamon() {
13728
14413
  const app = await core_namespaceObject.NestFactory.create(AppModule, {
13729
- logger: [
13730
- 'error'
13731
- ]
13732
14414
  });
13733
14415
  app.enableShutdownHooks();
13734
14416
  app.enableCors();
@@ -13766,6 +14448,14 @@ async function bootstrap() {
13766
14448
  await app.init();
13767
14449
  process.stdin.resume();
13768
14450
  } else {
14451
+ if (cmd === 'init') {
14452
+ const configPath = './intrig.config.json';
14453
+ if (external_fs_namespaceObject.existsSync(configPath)) {
14454
+ throw new Error('Intrig is already initialized. Configuration file exists.');
14455
+ }
14456
+ external_fs_namespaceObject.writeFileSync(configPath, JSON.stringify({}, null, 2));
14457
+ logger?.log('Initialized new Intrig project');
14458
+ }
13769
14459
  try {
13770
14460
  await external_nest_commander_namespaceObject.CommandFactory.run(AppModule, {
13771
14461
  logger: false,