@intrig/plugin-next 0.0.2-1 ā 0.0.2-3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +293 -69
- package/dist/index.js +293 -69
- package/package.json +3 -4
package/dist/index.cjs
CHANGED
|
@@ -8,7 +8,7 @@ var fsx = require('fs-extra');
|
|
|
8
8
|
var mimeType = require('mime-types');
|
|
9
9
|
var _ = require('lodash');
|
|
10
10
|
var fs = require('fs');
|
|
11
|
-
var
|
|
11
|
+
var chalk = require('chalk');
|
|
12
12
|
|
|
13
13
|
function _interopNamespaceDefault(e) {
|
|
14
14
|
var n = Object.create(null);
|
|
@@ -1651,6 +1651,7 @@ function providerHooksTemplate() {
|
|
|
1651
1651
|
useMemo,
|
|
1652
1652
|
useState,
|
|
1653
1653
|
useRef,
|
|
1654
|
+
useEffect,
|
|
1654
1655
|
} from 'react';
|
|
1655
1656
|
import {
|
|
1656
1657
|
ErrorState,
|
|
@@ -1694,6 +1695,16 @@ export function useNetworkState<T>({
|
|
|
1694
1695
|
const context = useContext(Context);
|
|
1695
1696
|
|
|
1696
1697
|
const [abortController, setAbortController] = useState<AbortController>();
|
|
1698
|
+
|
|
1699
|
+
const contextRef = useRef(context);
|
|
1700
|
+
const schemaRef = useRef(schema);
|
|
1701
|
+
const errorSchemaRef = useRef(errorSchema);
|
|
1702
|
+
|
|
1703
|
+
useEffect(() => {
|
|
1704
|
+
contextRef.current = context;
|
|
1705
|
+
schemaRef.current = schema;
|
|
1706
|
+
errorSchemaRef.current = errorSchema;
|
|
1707
|
+
});
|
|
1697
1708
|
|
|
1698
1709
|
const networkState = useMemo(() => {
|
|
1699
1710
|
logger.info(${"`Updating status ${key} ${operation} ${source}`"});
|
|
@@ -1702,7 +1713,7 @@ export function useNetworkState<T>({
|
|
|
1702
1713
|
(context.state?.[${"`${source}:${operation}:${key}`"}] as NetworkState<T>) ??
|
|
1703
1714
|
init()
|
|
1704
1715
|
);
|
|
1705
|
-
}, [
|
|
1716
|
+
}, [context.state, key, operation, source]);
|
|
1706
1717
|
|
|
1707
1718
|
const dispatch = useCallback(
|
|
1708
1719
|
(state: NetworkState<T>) => {
|
|
@@ -1750,30 +1761,30 @@ export function useNetworkState<T>({
|
|
|
1750
1761
|
signal: abortController.signal,
|
|
1751
1762
|
};
|
|
1752
1763
|
|
|
1753
|
-
await
|
|
1764
|
+
await contextRef.current.execute(
|
|
1754
1765
|
requestConfig,
|
|
1755
1766
|
dispatch,
|
|
1756
|
-
|
|
1757
|
-
|
|
1767
|
+
schemaRef.current,
|
|
1768
|
+
errorSchemaRef.current as any,
|
|
1758
1769
|
);
|
|
1759
1770
|
},
|
|
1760
|
-
[
|
|
1771
|
+
[dispatch, key, operation, source],
|
|
1761
1772
|
);
|
|
1762
1773
|
|
|
1763
1774
|
const deboundedExecute = useMemo(
|
|
1764
|
-
() => debounce(execute, debounceDelay
|
|
1765
|
-
[execute],
|
|
1775
|
+
() => debounce(execute, debounceDelay),
|
|
1776
|
+
[execute, debounceDelay],
|
|
1766
1777
|
);
|
|
1767
1778
|
|
|
1768
1779
|
const clear = useCallback(() => {
|
|
1769
1780
|
logger.info(${"`Clearing request ${key} ${operation} ${source}`"});
|
|
1770
1781
|
dispatch(init());
|
|
1771
|
-
setAbortController((
|
|
1782
|
+
setAbortController((prev) => {
|
|
1772
1783
|
logger.info(${"`Aborting request ${key} ${operation} ${source}`"});
|
|
1773
|
-
|
|
1784
|
+
prev?.abort();
|
|
1774
1785
|
return undefined;
|
|
1775
1786
|
});
|
|
1776
|
-
}, [dispatch,
|
|
1787
|
+
}, [dispatch, key, operation, source]);
|
|
1777
1788
|
|
|
1778
1789
|
return [networkState, deboundedExecute, clear, dispatch];
|
|
1779
1790
|
}
|
|
@@ -1790,6 +1801,14 @@ export function useTransitionCall<T>({
|
|
|
1790
1801
|
}): [(request: RequestType) => Promise<T>, () => void] {
|
|
1791
1802
|
const ctx = useContext(Context);
|
|
1792
1803
|
const controller = useRef<AbortController | undefined>(undefined);
|
|
1804
|
+
|
|
1805
|
+
const schemaRef = useRef(schema);
|
|
1806
|
+
const errorSchemaRef = useRef(errorSchema);
|
|
1807
|
+
|
|
1808
|
+
useEffect(() => {
|
|
1809
|
+
schemaRef.current = schema;
|
|
1810
|
+
errorSchemaRef.current = errorSchema;
|
|
1811
|
+
});
|
|
1793
1812
|
|
|
1794
1813
|
const call = useCallback(
|
|
1795
1814
|
async (request: RequestType) => {
|
|
@@ -1807,12 +1826,12 @@ export function useTransitionCall<T>({
|
|
|
1807
1826
|
reject(state.error);
|
|
1808
1827
|
}
|
|
1809
1828
|
},
|
|
1810
|
-
|
|
1811
|
-
|
|
1829
|
+
schemaRef.current,
|
|
1830
|
+
errorSchemaRef.current,
|
|
1812
1831
|
);
|
|
1813
1832
|
});
|
|
1814
1833
|
},
|
|
1815
|
-
[ctx
|
|
1834
|
+
[ctx],
|
|
1816
1835
|
);
|
|
1817
1836
|
|
|
1818
1837
|
const abort = useCallback(() => {
|
|
@@ -2448,7 +2467,7 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2448
2467
|
const modifiedRequestUrl = `${requestUrl == null ? void 0 : requestUrl.replace(/\{/g, "${")}`;
|
|
2449
2468
|
const imports = new Set();
|
|
2450
2469
|
imports.add(`import { z } from 'zod'`);
|
|
2451
|
-
imports.add(`import { useCallback, useEffect } from 'react'`);
|
|
2470
|
+
imports.add(`import { useCallback, useEffect, useRef } from 'react'`);
|
|
2452
2471
|
imports.add(`import {useNetworkState, NetworkState, DispatchState, error, successfulDispatch, validationError, encode, requestValidationError} from "@intrig/react"`);
|
|
2453
2472
|
const { hookShape, optionsShape } = extractHookShapeAndOptionsShape$1(response, requestBody, imports);
|
|
2454
2473
|
const { paramExpression, paramType } = extractParamDeconstruction$3(variables != null ? variables : [], requestBody);
|
|
@@ -2501,6 +2520,11 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2501
2520
|
schema,
|
|
2502
2521
|
errorSchema
|
|
2503
2522
|
});
|
|
2523
|
+
|
|
2524
|
+
const optionsRef = useRef(options);
|
|
2525
|
+
useEffect(() => {
|
|
2526
|
+
optionsRef.current = options;
|
|
2527
|
+
});
|
|
2504
2528
|
|
|
2505
2529
|
const doExecute = useCallback<(${paramType}) => DispatchState<any>>((${paramExpression}) => {
|
|
2506
2530
|
const { ${paramExplode}} = p
|
|
@@ -2526,22 +2550,22 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2526
2550
|
${responseTypePart()}
|
|
2527
2551
|
})
|
|
2528
2552
|
return successfulDispatch();
|
|
2529
|
-
}, [dispatch])
|
|
2553
|
+
}, [dispatch, dispatchState])
|
|
2530
2554
|
|
|
2531
2555
|
useEffect(() => {
|
|
2532
|
-
if (
|
|
2556
|
+
if (optionsRef.current.fetchOnMount) {
|
|
2533
2557
|
doExecute(${[
|
|
2534
|
-
requestBody ? `
|
|
2535
|
-
"
|
|
2558
|
+
requestBody ? `optionsRef.current.body!` : undefined,
|
|
2559
|
+
"optionsRef.current.params!"
|
|
2536
2560
|
].filter((a)=>a).join(",")});
|
|
2537
2561
|
}
|
|
2538
2562
|
|
|
2539
2563
|
return () => {
|
|
2540
|
-
if (
|
|
2564
|
+
if (optionsRef.current.clearOnUnmount) {
|
|
2541
2565
|
clear();
|
|
2542
2566
|
}
|
|
2543
2567
|
}
|
|
2544
|
-
}, [])
|
|
2568
|
+
}, [doExecute, clear])
|
|
2545
2569
|
|
|
2546
2570
|
return [
|
|
2547
2571
|
state,
|
|
@@ -2838,7 +2862,7 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2838
2862
|
const modifiedRequestUrl = `${requestUrl == null ? void 0 : requestUrl.replace(/\{/g, "${")}`;
|
|
2839
2863
|
const imports = new Set();
|
|
2840
2864
|
imports.add(`import { z } from 'zod'`);
|
|
2841
|
-
imports.add(`import { useCallback, useEffect } from 'react'`);
|
|
2865
|
+
imports.add(`import { useCallback, useEffect, useRef } from 'react'`);
|
|
2842
2866
|
imports.add(`import {useNetworkState, NetworkState, DispatchState, pending, success, error, init, successfulDispatch, validationError, encode, isSuccess, requestValidationError} from "@intrig/react"`);
|
|
2843
2867
|
const { hookShape, optionsShape } = extractHookShapeAndOptionsShape(response, requestBody, imports);
|
|
2844
2868
|
const { paramExpression, paramType } = extractParamDeconstruction$1(variables, requestBody);
|
|
@@ -2884,7 +2908,7 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2884
2908
|
const source = "${source}"
|
|
2885
2909
|
|
|
2886
2910
|
function use${pluginSdk.pascalCase(operationId)}Hook(options: ${optionsShape} = {}): [NetworkState<Response>, (${paramType}) => DispatchState<any>, () => void] {
|
|
2887
|
-
|
|
2911
|
+
const [state, dispatch, clear, dispatchState] = useNetworkState<Response>({
|
|
2888
2912
|
key: options?.key ?? 'default',
|
|
2889
2913
|
operation,
|
|
2890
2914
|
source,
|
|
@@ -2892,6 +2916,11 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2892
2916
|
errorSchema
|
|
2893
2917
|
});
|
|
2894
2918
|
|
|
2919
|
+
const optionsRef = useRef(options);
|
|
2920
|
+
useEffect(() => {
|
|
2921
|
+
optionsRef.current = options;
|
|
2922
|
+
});
|
|
2923
|
+
|
|
2895
2924
|
useEffect(() => {
|
|
2896
2925
|
if (isSuccess(state)) {
|
|
2897
2926
|
let a = document.createElement('a');
|
|
@@ -2924,10 +2953,10 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2924
2953
|
document.body.removeChild(a);
|
|
2925
2954
|
dispatchState(init())
|
|
2926
2955
|
}
|
|
2927
|
-
}, [state])
|
|
2956
|
+
}, [state, dispatchState])
|
|
2928
2957
|
|
|
2929
|
-
|
|
2930
|
-
|
|
2958
|
+
const doExecute = useCallback<(${paramType}) => DispatchState<any>>((${paramExpression}) => {
|
|
2959
|
+
const { ${paramExplode}} = p
|
|
2931
2960
|
|
|
2932
2961
|
${requestBody ? `
|
|
2933
2962
|
const validationResult = requestBodySchema.safeParse(data);
|
|
@@ -2950,22 +2979,22 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2950
2979
|
${responseTypePart()}
|
|
2951
2980
|
})
|
|
2952
2981
|
return successfulDispatch();
|
|
2953
|
-
}, [dispatch])
|
|
2982
|
+
}, [dispatch, dispatchState])
|
|
2954
2983
|
|
|
2955
2984
|
useEffect(() => {
|
|
2956
|
-
if (
|
|
2985
|
+
if (optionsRef.current.fetchOnMount) {
|
|
2957
2986
|
doExecute(${[
|
|
2958
|
-
requestBody ? `
|
|
2959
|
-
"
|
|
2987
|
+
requestBody ? `optionsRef.current.body!` : undefined,
|
|
2988
|
+
"optionsRef.current.params!"
|
|
2960
2989
|
].filter((a)=>a).join(",")});
|
|
2961
2990
|
}
|
|
2962
2991
|
|
|
2963
2992
|
return () => {
|
|
2964
|
-
if (
|
|
2993
|
+
if (optionsRef.current.clearOnUnmount) {
|
|
2965
2994
|
clear();
|
|
2966
2995
|
}
|
|
2967
2996
|
}
|
|
2968
|
-
}, [])
|
|
2997
|
+
}, [doExecute, clear]) // Added proper dependencies
|
|
2969
2998
|
|
|
2970
2999
|
return [
|
|
2971
3000
|
state,
|
|
@@ -3582,6 +3611,217 @@ function handleComplexSchema(schema, imports) {
|
|
|
3582
3611
|
};
|
|
3583
3612
|
}
|
|
3584
3613
|
|
|
3614
|
+
function serverFunctionDocs(descriptor) {
|
|
3615
|
+
const md = pluginSdk.mdLiteral('server-function.md');
|
|
3616
|
+
var _descriptor_data_variables;
|
|
3617
|
+
// ===== Derived names (preserve these) =====
|
|
3618
|
+
const hasPathParams = ((_descriptor_data_variables = descriptor.data.variables) != null ? _descriptor_data_variables : []).some((v)=>{
|
|
3619
|
+
var _v_in;
|
|
3620
|
+
return ((_v_in = v.in) == null ? void 0 : _v_in.toUpperCase()) === 'PATH';
|
|
3621
|
+
});
|
|
3622
|
+
const actionName = pluginSdk.camelCase(descriptor.name) // e.g. getUser
|
|
3623
|
+
;
|
|
3624
|
+
const executeActionName = `execute${pluginSdk.pascalCase(descriptor.name)}` // e.g. executeGetUser
|
|
3625
|
+
;
|
|
3626
|
+
const requestBodyVar = descriptor.data.requestBody ? pluginSdk.camelCase(descriptor.data.requestBody) // e.g. createUser
|
|
3627
|
+
: undefined;
|
|
3628
|
+
const requestBodyType = descriptor.data.requestBody ? pluginSdk.pascalCase(descriptor.data.requestBody) // e.g. CreateUser
|
|
3629
|
+
: undefined;
|
|
3630
|
+
const paramsVar = hasPathParams ? `${actionName}Params` : undefined // e.g. getUserParams
|
|
3631
|
+
;
|
|
3632
|
+
const paramsType = hasPathParams ? `${pluginSdk.pascalCase(descriptor.name)}Params` : undefined // e.g. GetUserParams
|
|
3633
|
+
;
|
|
3634
|
+
const responseTypeName = `${pluginSdk.pascalCase(descriptor.name)}Response` // e.g. GetUserResponse
|
|
3635
|
+
;
|
|
3636
|
+
const callArgs = [
|
|
3637
|
+
requestBodyVar,
|
|
3638
|
+
paramsVar
|
|
3639
|
+
].filter(Boolean).join(', ');
|
|
3640
|
+
return md`
|
|
3641
|
+
# Server-Side Functions ā Quick Guide
|
|
3642
|
+
|
|
3643
|
+
## Copy-paste starter (fast lane)
|
|
3644
|
+
|
|
3645
|
+
### 1) Function import (in your Next.js API route or server component)
|
|
3646
|
+
${"```ts"}
|
|
3647
|
+
import { ${actionName}, ${executeActionName} } from '@intrig/react/${descriptor.path}/server';
|
|
3648
|
+
${"```"}
|
|
3649
|
+
|
|
3650
|
+
### 2) Use the async function
|
|
3651
|
+
${"```ts"}
|
|
3652
|
+
// Simple usage - returns parsed response
|
|
3653
|
+
const result = await ${actionName}(${callArgs});
|
|
3654
|
+
|
|
3655
|
+
// Advanced usage - returns raw data + headers
|
|
3656
|
+
const { data, headers } = await ${executeActionName}(${callArgs});
|
|
3657
|
+
${"```"}
|
|
3658
|
+
|
|
3659
|
+
Server-side functions are for **Next.js API routes** and **server components**. They return **awaitable promises** that directly call your backend API.
|
|
3660
|
+
|
|
3661
|
+
---
|
|
3662
|
+
|
|
3663
|
+
## TL;DR (copyāpaste examples)
|
|
3664
|
+
|
|
3665
|
+
### In API Route (\`pages/api\` or \`app/api\`)
|
|
3666
|
+
${"```ts"}
|
|
3667
|
+
// pages/api/example.ts or app/api/example/route.ts
|
|
3668
|
+
import { ${actionName} } from '@intrig/react/${descriptor.path}/server';
|
|
3669
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
3670
|
+
|
|
3671
|
+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
3672
|
+
try {
|
|
3673
|
+
const result = await ${actionName}(${callArgs});
|
|
3674
|
+
res.status(200).json(result);
|
|
3675
|
+
} catch (error) {
|
|
3676
|
+
console.error('API call failed:', error);
|
|
3677
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
${"```"}
|
|
3681
|
+
|
|
3682
|
+
### In Server Component (App Router)
|
|
3683
|
+
${"```tsx"}
|
|
3684
|
+
// app/example/page.tsx
|
|
3685
|
+
import { ${actionName} } from '@intrig/react/${descriptor.path}/server';
|
|
3686
|
+
|
|
3687
|
+
export default async function ExamplePage() {
|
|
3688
|
+
try {
|
|
3689
|
+
const data = await ${actionName}(${callArgs});
|
|
3690
|
+
|
|
3691
|
+
return (
|
|
3692
|
+
<div>
|
|
3693
|
+
<h1>Server Data</h1>
|
|
3694
|
+
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
3695
|
+
</div>
|
|
3696
|
+
);
|
|
3697
|
+
} catch (error) {
|
|
3698
|
+
return <div>Error loading data: {error.message}</div>;
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
${"```"}
|
|
3702
|
+
|
|
3703
|
+
### In Server Action (App Router)
|
|
3704
|
+
${"```ts"}
|
|
3705
|
+
// app/actions.ts
|
|
3706
|
+
'use server';
|
|
3707
|
+
|
|
3708
|
+
import { ${actionName} } from '@intrig/react/${descriptor.path}/server';
|
|
3709
|
+
|
|
3710
|
+
export async function serverAction(formData: FormData) {
|
|
3711
|
+
try {
|
|
3712
|
+
const result = await ${actionName}(${callArgs});
|
|
3713
|
+
// Process result...
|
|
3714
|
+
return { success: true, data: result };
|
|
3715
|
+
} catch (error) {
|
|
3716
|
+
return { success: false, error: error.message };
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
${"```"}
|
|
3720
|
+
|
|
3721
|
+
${requestBodyType || paramsType ? `### Optional types (if generated by your build)
|
|
3722
|
+
${"```ts"}
|
|
3723
|
+
${requestBodyType ? `import type { ${requestBodyType} } from '@intrig/react/${descriptor.source}/components/schemas/${requestBodyType}';
|
|
3724
|
+
` : ''}${paramsType ? `import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.params';
|
|
3725
|
+
` : ''}import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.response';
|
|
3726
|
+
${"```"}
|
|
3727
|
+
` : ''}
|
|
3728
|
+
|
|
3729
|
+
---
|
|
3730
|
+
|
|
3731
|
+
## Function API
|
|
3732
|
+
|
|
3733
|
+
${"```ts"}
|
|
3734
|
+
// Prefer concrete types if your build emits them:
|
|
3735
|
+
// import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.response';
|
|
3736
|
+
// ${paramsType ? `import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.params';` : ''}
|
|
3737
|
+
|
|
3738
|
+
type ${pluginSdk.pascalCase(descriptor.name)}Data = ${'unknown'}; // replace with ${responseTypeName} if generated
|
|
3739
|
+
type ${pluginSdk.pascalCase(descriptor.name)}Request = {
|
|
3740
|
+
body?: ${requestBodyType != null ? requestBodyType : 'unknown'};
|
|
3741
|
+
params?: ${paramsType != null ? paramsType : 'unknown'};
|
|
3742
|
+
};
|
|
3743
|
+
|
|
3744
|
+
// Main function - returns parsed response
|
|
3745
|
+
declare function ${actionName}(
|
|
3746
|
+
${requestBodyVar ? `body: ${pluginSdk.pascalCase(descriptor.name)}Request['body'], ` : ''}${paramsVar ? `params: ${pluginSdk.pascalCase(descriptor.name)}Request['params'], ` : ''}options?: AsyncRequestOptions
|
|
3747
|
+
): Promise<${pluginSdk.pascalCase(descriptor.name)}Data>;
|
|
3748
|
+
|
|
3749
|
+
// Advanced function - returns raw data + headers
|
|
3750
|
+
declare function ${executeActionName}(
|
|
3751
|
+
${requestBodyVar ? `body: ${pluginSdk.pascalCase(descriptor.name)}Request['body'], ` : ''}${paramsVar ? `params: ${pluginSdk.pascalCase(descriptor.name)}Request['params'], ` : ''}options?: AsyncRequestOptions
|
|
3752
|
+
): Promise<{ data: any, headers: any }>;
|
|
3753
|
+
${"```"}
|
|
3754
|
+
|
|
3755
|
+
### Why server-side functions?
|
|
3756
|
+
- **Server-only execution:** Perfect for Next.js API routes and server components.
|
|
3757
|
+
- **Direct API calls:** No client-side state management needed.
|
|
3758
|
+
- **SSR/SSG friendly:** Use in \`getServerSideProps\`, \`getStaticProps\`, or server components.
|
|
3759
|
+
- **Built-in error handling:** Automatic network and validation error handling.
|
|
3760
|
+
|
|
3761
|
+
### Options
|
|
3762
|
+
Both functions accept an optional \`options\` parameter:
|
|
3763
|
+
|
|
3764
|
+
${"```ts"}
|
|
3765
|
+
type AsyncRequestOptions = {
|
|
3766
|
+
hydrate?: boolean; // Cache response for client-side hydration
|
|
3767
|
+
key?: string; // Custom cache key (when hydrate is true)
|
|
3768
|
+
};
|
|
3769
|
+
${"```"}
|
|
3770
|
+
|
|
3771
|
+
### Error Handling
|
|
3772
|
+
Server-side functions throw errors for:
|
|
3773
|
+
- **Network errors:** Connection issues, HTTP error status codes
|
|
3774
|
+
- **Validation errors:** Response doesn't match expected schema
|
|
3775
|
+
- **Request errors:** Invalid request body or parameters
|
|
3776
|
+
|
|
3777
|
+
Always wrap calls in try-catch blocks for production use.
|
|
3778
|
+
|
|
3779
|
+
---
|
|
3780
|
+
|
|
3781
|
+
## Next.js Integration Patterns
|
|
3782
|
+
|
|
3783
|
+
### 1. API Routes (Pages Router)
|
|
3784
|
+
Use server functions in your API routes to proxy requests or process data server-side.
|
|
3785
|
+
|
|
3786
|
+
### 2. Server Components (App Router)
|
|
3787
|
+
Directly call server functions in your server components for SSR data fetching.
|
|
3788
|
+
|
|
3789
|
+
### 3. Server Actions (App Router)
|
|
3790
|
+
Use server functions within server actions for form submissions and mutations.
|
|
3791
|
+
|
|
3792
|
+
### 4. Middleware
|
|
3793
|
+
Server functions can be used in Next.js middleware for request preprocessing.
|
|
3794
|
+
|
|
3795
|
+
### 5. getServerSideProps / getStaticProps (Pages Router)
|
|
3796
|
+
Perfect for data fetching in traditional Next.js data fetching methods.
|
|
3797
|
+
|
|
3798
|
+
---
|
|
3799
|
+
|
|
3800
|
+
## Best Practices
|
|
3801
|
+
|
|
3802
|
+
1. **Error boundaries:** Always handle errors appropriately for your use case
|
|
3803
|
+
2. **Caching:** Use the \`hydrate\` option for data that should be available client-side
|
|
3804
|
+
3. **Types:** Import and use the generated TypeScript types for better DX
|
|
3805
|
+
4. **Logging:** Server functions include built-in logging for debugging
|
|
3806
|
+
|
|
3807
|
+
${"```ts"}
|
|
3808
|
+
// Example with proper error handling and types
|
|
3809
|
+
import { ${actionName} } from '@intrig/react/${descriptor.path}/server';
|
|
3810
|
+
${paramsType ? `import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.params';` : ''}
|
|
3811
|
+
import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pluginSdk.pascalCase(descriptor.name)}.response';
|
|
3812
|
+
|
|
3813
|
+
export async function fetchData(${paramsVar ? `params: ${paramsType}` : ''}): Promise<${responseTypeName} | null> {
|
|
3814
|
+
try {
|
|
3815
|
+
return await ${actionName}(${callArgs});
|
|
3816
|
+
} catch (error) {
|
|
3817
|
+
console.error('Failed to fetch data:', error);
|
|
3818
|
+
return null;
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
${"```"}
|
|
3822
|
+
`;
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3585
3825
|
async function generateCode(ctx) {
|
|
3586
3826
|
// Root/project files
|
|
3587
3827
|
await ctx.dump(packageJsonTemplate(ctx));
|
|
@@ -3615,6 +3855,7 @@ async function generateCode(ctx) {
|
|
|
3615
3855
|
await ctx.dump(paramsTemplate(restDescriptor));
|
|
3616
3856
|
await ctx.dump(asyncFunctionHookTemplate(restDescriptor, internalGeneratorContext));
|
|
3617
3857
|
await ctx.dump(nextRequestMethodTemplate(restDescriptor, internalGeneratorContext));
|
|
3858
|
+
await ctx.dump(serverFunctionDocs(restDescriptor));
|
|
3618
3859
|
if (restDescriptor.data.isDownloadable) {
|
|
3619
3860
|
await ctx.dump(downloadHookTemplate(restDescriptor, internalGeneratorContext));
|
|
3620
3861
|
}
|
|
@@ -3660,12 +3901,12 @@ Use this TypeScript type anywhere you need static typing for this object shape i
|
|
|
3660
3901
|
|
|
3661
3902
|
## Import
|
|
3662
3903
|
${'```ts'}
|
|
3663
|
-
${importContent}
|
|
3904
|
+
${importContent.content}
|
|
3664
3905
|
${'```'}
|
|
3665
3906
|
|
|
3666
3907
|
## Definition
|
|
3667
3908
|
${'```ts'}
|
|
3668
|
-
${codeContent}
|
|
3909
|
+
${codeContent.content}
|
|
3669
3910
|
${'```'}
|
|
3670
3911
|
`;
|
|
3671
3912
|
}
|
|
@@ -3687,12 +3928,12 @@ Use this JSON Schema with tools that consume JSON Schema: UI form builders (e.g.
|
|
|
3687
3928
|
|
|
3688
3929
|
## Import
|
|
3689
3930
|
${'```ts'}
|
|
3690
|
-
${importContent}
|
|
3931
|
+
${importContent.content}
|
|
3691
3932
|
${'```'}
|
|
3692
3933
|
|
|
3693
3934
|
## Definition
|
|
3694
3935
|
${'```ts'}
|
|
3695
|
-
${codeContent}
|
|
3936
|
+
${codeContent.content}
|
|
3696
3937
|
${'```'}
|
|
3697
3938
|
`;
|
|
3698
3939
|
}
|
|
@@ -3714,12 +3955,12 @@ Use this Zod schema for runtime validation and parsing: form validation, client/
|
|
|
3714
3955
|
|
|
3715
3956
|
## Import
|
|
3716
3957
|
${'```ts'}
|
|
3717
|
-
${importContent}
|
|
3958
|
+
${importContent.content}
|
|
3718
3959
|
${'```'}
|
|
3719
3960
|
|
|
3720
3961
|
## Definition
|
|
3721
3962
|
${'```ts'}
|
|
3722
|
-
${codeContent}
|
|
3963
|
+
${codeContent.content}
|
|
3723
3964
|
${'```'}
|
|
3724
3965
|
`;
|
|
3725
3966
|
}
|
|
@@ -4734,37 +4975,19 @@ function updateGitIgnore(rootDir, entryToAdd) {
|
|
|
4734
4975
|
fs__namespace.writeFileSync(gitIgnorePath, gitIgnoreContent.join('\n'), 'utf-8');
|
|
4735
4976
|
}
|
|
4736
4977
|
}
|
|
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
4978
|
async function initPlugin(ctx) {
|
|
4747
|
-
const { rootDir } = ctx;
|
|
4748
|
-
const
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
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
|
-
}
|
|
4979
|
+
const { rootDir, options } = ctx;
|
|
4980
|
+
const apiRoutesDir = (options == null ? void 0 : options.apiRoutesDir) || 'src/app/api';
|
|
4981
|
+
// Add the directory with (generated) to gitignore
|
|
4982
|
+
updateGitIgnore(rootDir, `${apiRoutesDir}/(generated)`);
|
|
4983
|
+
return {
|
|
4984
|
+
postInit: ()=>{
|
|
4985
|
+
console.log(chalk.blue('\nš Next Steps:'));
|
|
4986
|
+
console.log(chalk.white('To complete your Next.js setup, please refer to the post-initialization instructions at:'));
|
|
4987
|
+
console.log(chalk.cyan('https://intrig.dev/docs/next/initialization#3-post-initialization-steps'));
|
|
4988
|
+
console.log(chalk.gray('\nThis guide will show you how to add IntrigProvider to your Next.js application.\n'));
|
|
4989
|
+
}
|
|
4990
|
+
};
|
|
4768
4991
|
}
|
|
4769
4992
|
|
|
4770
4993
|
async function postBuild(ctx) {
|
|
@@ -4859,7 +5082,8 @@ const $generatorSchema = {
|
|
|
4859
5082
|
properties: {
|
|
4860
5083
|
"apiRoutesDir": {
|
|
4861
5084
|
type: 'string',
|
|
4862
|
-
description: 'The directory where the API routes are stored.'
|
|
5085
|
+
description: 'The directory where the API routes are stored.',
|
|
5086
|
+
default: 'src/app/api'
|
|
4863
5087
|
}
|
|
4864
5088
|
}
|
|
4865
5089
|
};
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as fsx from 'fs-extra';
|
|
|
5
5
|
import * as mimeType from 'mime-types';
|
|
6
6
|
import * as _ from 'lodash';
|
|
7
7
|
import * as fs from 'fs';
|
|
8
|
-
import
|
|
8
|
+
import chalk from 'chalk';
|
|
9
9
|
|
|
10
10
|
class InternalGeneratorContext {
|
|
11
11
|
getCounter(sourceId) {
|
|
@@ -1625,6 +1625,7 @@ function providerHooksTemplate() {
|
|
|
1625
1625
|
useMemo,
|
|
1626
1626
|
useState,
|
|
1627
1627
|
useRef,
|
|
1628
|
+
useEffect,
|
|
1628
1629
|
} from 'react';
|
|
1629
1630
|
import {
|
|
1630
1631
|
ErrorState,
|
|
@@ -1668,6 +1669,16 @@ export function useNetworkState<T>({
|
|
|
1668
1669
|
const context = useContext(Context);
|
|
1669
1670
|
|
|
1670
1671
|
const [abortController, setAbortController] = useState<AbortController>();
|
|
1672
|
+
|
|
1673
|
+
const contextRef = useRef(context);
|
|
1674
|
+
const schemaRef = useRef(schema);
|
|
1675
|
+
const errorSchemaRef = useRef(errorSchema);
|
|
1676
|
+
|
|
1677
|
+
useEffect(() => {
|
|
1678
|
+
contextRef.current = context;
|
|
1679
|
+
schemaRef.current = schema;
|
|
1680
|
+
errorSchemaRef.current = errorSchema;
|
|
1681
|
+
});
|
|
1671
1682
|
|
|
1672
1683
|
const networkState = useMemo(() => {
|
|
1673
1684
|
logger.info(${"`Updating status ${key} ${operation} ${source}`"});
|
|
@@ -1676,7 +1687,7 @@ export function useNetworkState<T>({
|
|
|
1676
1687
|
(context.state?.[${"`${source}:${operation}:${key}`"}] as NetworkState<T>) ??
|
|
1677
1688
|
init()
|
|
1678
1689
|
);
|
|
1679
|
-
}, [
|
|
1690
|
+
}, [context.state, key, operation, source]);
|
|
1680
1691
|
|
|
1681
1692
|
const dispatch = useCallback(
|
|
1682
1693
|
(state: NetworkState<T>) => {
|
|
@@ -1724,30 +1735,30 @@ export function useNetworkState<T>({
|
|
|
1724
1735
|
signal: abortController.signal,
|
|
1725
1736
|
};
|
|
1726
1737
|
|
|
1727
|
-
await
|
|
1738
|
+
await contextRef.current.execute(
|
|
1728
1739
|
requestConfig,
|
|
1729
1740
|
dispatch,
|
|
1730
|
-
|
|
1731
|
-
|
|
1741
|
+
schemaRef.current,
|
|
1742
|
+
errorSchemaRef.current as any,
|
|
1732
1743
|
);
|
|
1733
1744
|
},
|
|
1734
|
-
[
|
|
1745
|
+
[dispatch, key, operation, source],
|
|
1735
1746
|
);
|
|
1736
1747
|
|
|
1737
1748
|
const deboundedExecute = useMemo(
|
|
1738
|
-
() => debounce(execute, debounceDelay
|
|
1739
|
-
[execute],
|
|
1749
|
+
() => debounce(execute, debounceDelay),
|
|
1750
|
+
[execute, debounceDelay],
|
|
1740
1751
|
);
|
|
1741
1752
|
|
|
1742
1753
|
const clear = useCallback(() => {
|
|
1743
1754
|
logger.info(${"`Clearing request ${key} ${operation} ${source}`"});
|
|
1744
1755
|
dispatch(init());
|
|
1745
|
-
setAbortController((
|
|
1756
|
+
setAbortController((prev) => {
|
|
1746
1757
|
logger.info(${"`Aborting request ${key} ${operation} ${source}`"});
|
|
1747
|
-
|
|
1758
|
+
prev?.abort();
|
|
1748
1759
|
return undefined;
|
|
1749
1760
|
});
|
|
1750
|
-
}, [dispatch,
|
|
1761
|
+
}, [dispatch, key, operation, source]);
|
|
1751
1762
|
|
|
1752
1763
|
return [networkState, deboundedExecute, clear, dispatch];
|
|
1753
1764
|
}
|
|
@@ -1764,6 +1775,14 @@ export function useTransitionCall<T>({
|
|
|
1764
1775
|
}): [(request: RequestType) => Promise<T>, () => void] {
|
|
1765
1776
|
const ctx = useContext(Context);
|
|
1766
1777
|
const controller = useRef<AbortController | undefined>(undefined);
|
|
1778
|
+
|
|
1779
|
+
const schemaRef = useRef(schema);
|
|
1780
|
+
const errorSchemaRef = useRef(errorSchema);
|
|
1781
|
+
|
|
1782
|
+
useEffect(() => {
|
|
1783
|
+
schemaRef.current = schema;
|
|
1784
|
+
errorSchemaRef.current = errorSchema;
|
|
1785
|
+
});
|
|
1767
1786
|
|
|
1768
1787
|
const call = useCallback(
|
|
1769
1788
|
async (request: RequestType) => {
|
|
@@ -1781,12 +1800,12 @@ export function useTransitionCall<T>({
|
|
|
1781
1800
|
reject(state.error);
|
|
1782
1801
|
}
|
|
1783
1802
|
},
|
|
1784
|
-
|
|
1785
|
-
|
|
1803
|
+
schemaRef.current,
|
|
1804
|
+
errorSchemaRef.current,
|
|
1786
1805
|
);
|
|
1787
1806
|
});
|
|
1788
1807
|
},
|
|
1789
|
-
[ctx
|
|
1808
|
+
[ctx],
|
|
1790
1809
|
);
|
|
1791
1810
|
|
|
1792
1811
|
const abort = useCallback(() => {
|
|
@@ -2422,7 +2441,7 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2422
2441
|
const modifiedRequestUrl = `${requestUrl == null ? void 0 : requestUrl.replace(/\{/g, "${")}`;
|
|
2423
2442
|
const imports = new Set();
|
|
2424
2443
|
imports.add(`import { z } from 'zod'`);
|
|
2425
|
-
imports.add(`import { useCallback, useEffect } from 'react'`);
|
|
2444
|
+
imports.add(`import { useCallback, useEffect, useRef } from 'react'`);
|
|
2426
2445
|
imports.add(`import {useNetworkState, NetworkState, DispatchState, error, successfulDispatch, validationError, encode, requestValidationError} from "@intrig/react"`);
|
|
2427
2446
|
const { hookShape, optionsShape } = extractHookShapeAndOptionsShape$1(response, requestBody, imports);
|
|
2428
2447
|
const { paramExpression, paramType } = extractParamDeconstruction$3(variables != null ? variables : [], requestBody);
|
|
@@ -2475,6 +2494,11 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2475
2494
|
schema,
|
|
2476
2495
|
errorSchema
|
|
2477
2496
|
});
|
|
2497
|
+
|
|
2498
|
+
const optionsRef = useRef(options);
|
|
2499
|
+
useEffect(() => {
|
|
2500
|
+
optionsRef.current = options;
|
|
2501
|
+
});
|
|
2478
2502
|
|
|
2479
2503
|
const doExecute = useCallback<(${paramType}) => DispatchState<any>>((${paramExpression}) => {
|
|
2480
2504
|
const { ${paramExplode}} = p
|
|
@@ -2500,22 +2524,22 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2500
2524
|
${responseTypePart()}
|
|
2501
2525
|
})
|
|
2502
2526
|
return successfulDispatch();
|
|
2503
|
-
}, [dispatch])
|
|
2527
|
+
}, [dispatch, dispatchState])
|
|
2504
2528
|
|
|
2505
2529
|
useEffect(() => {
|
|
2506
|
-
if (
|
|
2530
|
+
if (optionsRef.current.fetchOnMount) {
|
|
2507
2531
|
doExecute(${[
|
|
2508
|
-
requestBody ? `
|
|
2509
|
-
"
|
|
2532
|
+
requestBody ? `optionsRef.current.body!` : undefined,
|
|
2533
|
+
"optionsRef.current.params!"
|
|
2510
2534
|
].filter((a)=>a).join(",")});
|
|
2511
2535
|
}
|
|
2512
2536
|
|
|
2513
2537
|
return () => {
|
|
2514
|
-
if (
|
|
2538
|
+
if (optionsRef.current.clearOnUnmount) {
|
|
2515
2539
|
clear();
|
|
2516
2540
|
}
|
|
2517
2541
|
}
|
|
2518
|
-
}, [])
|
|
2542
|
+
}, [doExecute, clear])
|
|
2519
2543
|
|
|
2520
2544
|
return [
|
|
2521
2545
|
state,
|
|
@@ -2812,7 +2836,7 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2812
2836
|
const modifiedRequestUrl = `${requestUrl == null ? void 0 : requestUrl.replace(/\{/g, "${")}`;
|
|
2813
2837
|
const imports = new Set();
|
|
2814
2838
|
imports.add(`import { z } from 'zod'`);
|
|
2815
|
-
imports.add(`import { useCallback, useEffect } from 'react'`);
|
|
2839
|
+
imports.add(`import { useCallback, useEffect, useRef } from 'react'`);
|
|
2816
2840
|
imports.add(`import {useNetworkState, NetworkState, DispatchState, pending, success, error, init, successfulDispatch, validationError, encode, isSuccess, requestValidationError} from "@intrig/react"`);
|
|
2817
2841
|
const { hookShape, optionsShape } = extractHookShapeAndOptionsShape(response, requestBody, imports);
|
|
2818
2842
|
const { paramExpression, paramType } = extractParamDeconstruction$1(variables, requestBody);
|
|
@@ -2858,7 +2882,7 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2858
2882
|
const source = "${source}"
|
|
2859
2883
|
|
|
2860
2884
|
function use${pascalCase(operationId)}Hook(options: ${optionsShape} = {}): [NetworkState<Response>, (${paramType}) => DispatchState<any>, () => void] {
|
|
2861
|
-
|
|
2885
|
+
const [state, dispatch, clear, dispatchState] = useNetworkState<Response>({
|
|
2862
2886
|
key: options?.key ?? 'default',
|
|
2863
2887
|
operation,
|
|
2864
2888
|
source,
|
|
@@ -2866,6 +2890,11 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2866
2890
|
errorSchema
|
|
2867
2891
|
});
|
|
2868
2892
|
|
|
2893
|
+
const optionsRef = useRef(options);
|
|
2894
|
+
useEffect(() => {
|
|
2895
|
+
optionsRef.current = options;
|
|
2896
|
+
});
|
|
2897
|
+
|
|
2869
2898
|
useEffect(() => {
|
|
2870
2899
|
if (isSuccess(state)) {
|
|
2871
2900
|
let a = document.createElement('a');
|
|
@@ -2898,10 +2927,10 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2898
2927
|
document.body.removeChild(a);
|
|
2899
2928
|
dispatchState(init())
|
|
2900
2929
|
}
|
|
2901
|
-
}, [state])
|
|
2930
|
+
}, [state, dispatchState])
|
|
2902
2931
|
|
|
2903
|
-
|
|
2904
|
-
|
|
2932
|
+
const doExecute = useCallback<(${paramType}) => DispatchState<any>>((${paramExpression}) => {
|
|
2933
|
+
const { ${paramExplode}} = p
|
|
2905
2934
|
|
|
2906
2935
|
${requestBody ? `
|
|
2907
2936
|
const validationResult = requestBodySchema.safeParse(data);
|
|
@@ -2924,22 +2953,22 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2924
2953
|
${responseTypePart()}
|
|
2925
2954
|
})
|
|
2926
2955
|
return successfulDispatch();
|
|
2927
|
-
}, [dispatch])
|
|
2956
|
+
}, [dispatch, dispatchState])
|
|
2928
2957
|
|
|
2929
2958
|
useEffect(() => {
|
|
2930
|
-
if (
|
|
2959
|
+
if (optionsRef.current.fetchOnMount) {
|
|
2931
2960
|
doExecute(${[
|
|
2932
|
-
requestBody ? `
|
|
2933
|
-
"
|
|
2961
|
+
requestBody ? `optionsRef.current.body!` : undefined,
|
|
2962
|
+
"optionsRef.current.params!"
|
|
2934
2963
|
].filter((a)=>a).join(",")});
|
|
2935
2964
|
}
|
|
2936
2965
|
|
|
2937
2966
|
return () => {
|
|
2938
|
-
if (
|
|
2967
|
+
if (optionsRef.current.clearOnUnmount) {
|
|
2939
2968
|
clear();
|
|
2940
2969
|
}
|
|
2941
2970
|
}
|
|
2942
|
-
}, [])
|
|
2971
|
+
}, [doExecute, clear]) // Added proper dependencies
|
|
2943
2972
|
|
|
2944
2973
|
return [
|
|
2945
2974
|
state,
|
|
@@ -3556,6 +3585,217 @@ function handleComplexSchema(schema, imports) {
|
|
|
3556
3585
|
};
|
|
3557
3586
|
}
|
|
3558
3587
|
|
|
3588
|
+
function serverFunctionDocs(descriptor) {
|
|
3589
|
+
const md = mdLiteral('server-function.md');
|
|
3590
|
+
var _descriptor_data_variables;
|
|
3591
|
+
// ===== Derived names (preserve these) =====
|
|
3592
|
+
const hasPathParams = ((_descriptor_data_variables = descriptor.data.variables) != null ? _descriptor_data_variables : []).some((v)=>{
|
|
3593
|
+
var _v_in;
|
|
3594
|
+
return ((_v_in = v.in) == null ? void 0 : _v_in.toUpperCase()) === 'PATH';
|
|
3595
|
+
});
|
|
3596
|
+
const actionName = camelCase(descriptor.name) // e.g. getUser
|
|
3597
|
+
;
|
|
3598
|
+
const executeActionName = `execute${pascalCase(descriptor.name)}` // e.g. executeGetUser
|
|
3599
|
+
;
|
|
3600
|
+
const requestBodyVar = descriptor.data.requestBody ? camelCase(descriptor.data.requestBody) // e.g. createUser
|
|
3601
|
+
: undefined;
|
|
3602
|
+
const requestBodyType = descriptor.data.requestBody ? pascalCase(descriptor.data.requestBody) // e.g. CreateUser
|
|
3603
|
+
: undefined;
|
|
3604
|
+
const paramsVar = hasPathParams ? `${actionName}Params` : undefined // e.g. getUserParams
|
|
3605
|
+
;
|
|
3606
|
+
const paramsType = hasPathParams ? `${pascalCase(descriptor.name)}Params` : undefined // e.g. GetUserParams
|
|
3607
|
+
;
|
|
3608
|
+
const responseTypeName = `${pascalCase(descriptor.name)}Response` // e.g. GetUserResponse
|
|
3609
|
+
;
|
|
3610
|
+
const callArgs = [
|
|
3611
|
+
requestBodyVar,
|
|
3612
|
+
paramsVar
|
|
3613
|
+
].filter(Boolean).join(', ');
|
|
3614
|
+
return md`
|
|
3615
|
+
# Server-Side Functions ā Quick Guide
|
|
3616
|
+
|
|
3617
|
+
## Copy-paste starter (fast lane)
|
|
3618
|
+
|
|
3619
|
+
### 1) Function import (in your Next.js API route or server component)
|
|
3620
|
+
${"```ts"}
|
|
3621
|
+
import { ${actionName}, ${executeActionName} } from '@intrig/react/${descriptor.path}/server';
|
|
3622
|
+
${"```"}
|
|
3623
|
+
|
|
3624
|
+
### 2) Use the async function
|
|
3625
|
+
${"```ts"}
|
|
3626
|
+
// Simple usage - returns parsed response
|
|
3627
|
+
const result = await ${actionName}(${callArgs});
|
|
3628
|
+
|
|
3629
|
+
// Advanced usage - returns raw data + headers
|
|
3630
|
+
const { data, headers } = await ${executeActionName}(${callArgs});
|
|
3631
|
+
${"```"}
|
|
3632
|
+
|
|
3633
|
+
Server-side functions are for **Next.js API routes** and **server components**. They return **awaitable promises** that directly call your backend API.
|
|
3634
|
+
|
|
3635
|
+
---
|
|
3636
|
+
|
|
3637
|
+
## TL;DR (copyāpaste examples)
|
|
3638
|
+
|
|
3639
|
+
### In API Route (\`pages/api\` or \`app/api\`)
|
|
3640
|
+
${"```ts"}
|
|
3641
|
+
// pages/api/example.ts or app/api/example/route.ts
|
|
3642
|
+
import { ${actionName} } from '@intrig/react/${descriptor.path}/server';
|
|
3643
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
3644
|
+
|
|
3645
|
+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
3646
|
+
try {
|
|
3647
|
+
const result = await ${actionName}(${callArgs});
|
|
3648
|
+
res.status(200).json(result);
|
|
3649
|
+
} catch (error) {
|
|
3650
|
+
console.error('API call failed:', error);
|
|
3651
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
${"```"}
|
|
3655
|
+
|
|
3656
|
+
### In Server Component (App Router)
|
|
3657
|
+
${"```tsx"}
|
|
3658
|
+
// app/example/page.tsx
|
|
3659
|
+
import { ${actionName} } from '@intrig/react/${descriptor.path}/server';
|
|
3660
|
+
|
|
3661
|
+
export default async function ExamplePage() {
|
|
3662
|
+
try {
|
|
3663
|
+
const data = await ${actionName}(${callArgs});
|
|
3664
|
+
|
|
3665
|
+
return (
|
|
3666
|
+
<div>
|
|
3667
|
+
<h1>Server Data</h1>
|
|
3668
|
+
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
3669
|
+
</div>
|
|
3670
|
+
);
|
|
3671
|
+
} catch (error) {
|
|
3672
|
+
return <div>Error loading data: {error.message}</div>;
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
${"```"}
|
|
3676
|
+
|
|
3677
|
+
### In Server Action (App Router)
|
|
3678
|
+
${"```ts"}
|
|
3679
|
+
// app/actions.ts
|
|
3680
|
+
'use server';
|
|
3681
|
+
|
|
3682
|
+
import { ${actionName} } from '@intrig/react/${descriptor.path}/server';
|
|
3683
|
+
|
|
3684
|
+
export async function serverAction(formData: FormData) {
|
|
3685
|
+
try {
|
|
3686
|
+
const result = await ${actionName}(${callArgs});
|
|
3687
|
+
// Process result...
|
|
3688
|
+
return { success: true, data: result };
|
|
3689
|
+
} catch (error) {
|
|
3690
|
+
return { success: false, error: error.message };
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
${"```"}
|
|
3694
|
+
|
|
3695
|
+
${requestBodyType || paramsType ? `### Optional types (if generated by your build)
|
|
3696
|
+
${"```ts"}
|
|
3697
|
+
${requestBodyType ? `import type { ${requestBodyType} } from '@intrig/react/${descriptor.source}/components/schemas/${requestBodyType}';
|
|
3698
|
+
` : ''}${paramsType ? `import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pascalCase(descriptor.name)}.params';
|
|
3699
|
+
` : ''}import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pascalCase(descriptor.name)}.response';
|
|
3700
|
+
${"```"}
|
|
3701
|
+
` : ''}
|
|
3702
|
+
|
|
3703
|
+
---
|
|
3704
|
+
|
|
3705
|
+
## Function API
|
|
3706
|
+
|
|
3707
|
+
${"```ts"}
|
|
3708
|
+
// Prefer concrete types if your build emits them:
|
|
3709
|
+
// import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pascalCase(descriptor.name)}.response';
|
|
3710
|
+
// ${paramsType ? `import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pascalCase(descriptor.name)}.params';` : ''}
|
|
3711
|
+
|
|
3712
|
+
type ${pascalCase(descriptor.name)}Data = ${'unknown'}; // replace with ${responseTypeName} if generated
|
|
3713
|
+
type ${pascalCase(descriptor.name)}Request = {
|
|
3714
|
+
body?: ${requestBodyType != null ? requestBodyType : 'unknown'};
|
|
3715
|
+
params?: ${paramsType != null ? paramsType : 'unknown'};
|
|
3716
|
+
};
|
|
3717
|
+
|
|
3718
|
+
// Main function - returns parsed response
|
|
3719
|
+
declare function ${actionName}(
|
|
3720
|
+
${requestBodyVar ? `body: ${pascalCase(descriptor.name)}Request['body'], ` : ''}${paramsVar ? `params: ${pascalCase(descriptor.name)}Request['params'], ` : ''}options?: AsyncRequestOptions
|
|
3721
|
+
): Promise<${pascalCase(descriptor.name)}Data>;
|
|
3722
|
+
|
|
3723
|
+
// Advanced function - returns raw data + headers
|
|
3724
|
+
declare function ${executeActionName}(
|
|
3725
|
+
${requestBodyVar ? `body: ${pascalCase(descriptor.name)}Request['body'], ` : ''}${paramsVar ? `params: ${pascalCase(descriptor.name)}Request['params'], ` : ''}options?: AsyncRequestOptions
|
|
3726
|
+
): Promise<{ data: any, headers: any }>;
|
|
3727
|
+
${"```"}
|
|
3728
|
+
|
|
3729
|
+
### Why server-side functions?
|
|
3730
|
+
- **Server-only execution:** Perfect for Next.js API routes and server components.
|
|
3731
|
+
- **Direct API calls:** No client-side state management needed.
|
|
3732
|
+
- **SSR/SSG friendly:** Use in \`getServerSideProps\`, \`getStaticProps\`, or server components.
|
|
3733
|
+
- **Built-in error handling:** Automatic network and validation error handling.
|
|
3734
|
+
|
|
3735
|
+
### Options
|
|
3736
|
+
Both functions accept an optional \`options\` parameter:
|
|
3737
|
+
|
|
3738
|
+
${"```ts"}
|
|
3739
|
+
type AsyncRequestOptions = {
|
|
3740
|
+
hydrate?: boolean; // Cache response for client-side hydration
|
|
3741
|
+
key?: string; // Custom cache key (when hydrate is true)
|
|
3742
|
+
};
|
|
3743
|
+
${"```"}
|
|
3744
|
+
|
|
3745
|
+
### Error Handling
|
|
3746
|
+
Server-side functions throw errors for:
|
|
3747
|
+
- **Network errors:** Connection issues, HTTP error status codes
|
|
3748
|
+
- **Validation errors:** Response doesn't match expected schema
|
|
3749
|
+
- **Request errors:** Invalid request body or parameters
|
|
3750
|
+
|
|
3751
|
+
Always wrap calls in try-catch blocks for production use.
|
|
3752
|
+
|
|
3753
|
+
---
|
|
3754
|
+
|
|
3755
|
+
## Next.js Integration Patterns
|
|
3756
|
+
|
|
3757
|
+
### 1. API Routes (Pages Router)
|
|
3758
|
+
Use server functions in your API routes to proxy requests or process data server-side.
|
|
3759
|
+
|
|
3760
|
+
### 2. Server Components (App Router)
|
|
3761
|
+
Directly call server functions in your server components for SSR data fetching.
|
|
3762
|
+
|
|
3763
|
+
### 3. Server Actions (App Router)
|
|
3764
|
+
Use server functions within server actions for form submissions and mutations.
|
|
3765
|
+
|
|
3766
|
+
### 4. Middleware
|
|
3767
|
+
Server functions can be used in Next.js middleware for request preprocessing.
|
|
3768
|
+
|
|
3769
|
+
### 5. getServerSideProps / getStaticProps (Pages Router)
|
|
3770
|
+
Perfect for data fetching in traditional Next.js data fetching methods.
|
|
3771
|
+
|
|
3772
|
+
---
|
|
3773
|
+
|
|
3774
|
+
## Best Practices
|
|
3775
|
+
|
|
3776
|
+
1. **Error boundaries:** Always handle errors appropriately for your use case
|
|
3777
|
+
2. **Caching:** Use the \`hydrate\` option for data that should be available client-side
|
|
3778
|
+
3. **Types:** Import and use the generated TypeScript types for better DX
|
|
3779
|
+
4. **Logging:** Server functions include built-in logging for debugging
|
|
3780
|
+
|
|
3781
|
+
${"```ts"}
|
|
3782
|
+
// Example with proper error handling and types
|
|
3783
|
+
import { ${actionName} } from '@intrig/react/${descriptor.path}/server';
|
|
3784
|
+
${paramsType ? `import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pascalCase(descriptor.name)}.params';` : ''}
|
|
3785
|
+
import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pascalCase(descriptor.name)}.response';
|
|
3786
|
+
|
|
3787
|
+
export async function fetchData(${paramsVar ? `params: ${paramsType}` : ''}): Promise<${responseTypeName} | null> {
|
|
3788
|
+
try {
|
|
3789
|
+
return await ${actionName}(${callArgs});
|
|
3790
|
+
} catch (error) {
|
|
3791
|
+
console.error('Failed to fetch data:', error);
|
|
3792
|
+
return null;
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
${"```"}
|
|
3796
|
+
`;
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3559
3799
|
async function generateCode(ctx) {
|
|
3560
3800
|
// Root/project files
|
|
3561
3801
|
await ctx.dump(packageJsonTemplate(ctx));
|
|
@@ -3589,6 +3829,7 @@ async function generateCode(ctx) {
|
|
|
3589
3829
|
await ctx.dump(paramsTemplate(restDescriptor));
|
|
3590
3830
|
await ctx.dump(asyncFunctionHookTemplate(restDescriptor, internalGeneratorContext));
|
|
3591
3831
|
await ctx.dump(nextRequestMethodTemplate(restDescriptor, internalGeneratorContext));
|
|
3832
|
+
await ctx.dump(serverFunctionDocs(restDescriptor));
|
|
3592
3833
|
if (restDescriptor.data.isDownloadable) {
|
|
3593
3834
|
await ctx.dump(downloadHookTemplate(restDescriptor, internalGeneratorContext));
|
|
3594
3835
|
}
|
|
@@ -3634,12 +3875,12 @@ Use this TypeScript type anywhere you need static typing for this object shape i
|
|
|
3634
3875
|
|
|
3635
3876
|
## Import
|
|
3636
3877
|
${'```ts'}
|
|
3637
|
-
${importContent}
|
|
3878
|
+
${importContent.content}
|
|
3638
3879
|
${'```'}
|
|
3639
3880
|
|
|
3640
3881
|
## Definition
|
|
3641
3882
|
${'```ts'}
|
|
3642
|
-
${codeContent}
|
|
3883
|
+
${codeContent.content}
|
|
3643
3884
|
${'```'}
|
|
3644
3885
|
`;
|
|
3645
3886
|
}
|
|
@@ -3661,12 +3902,12 @@ Use this JSON Schema with tools that consume JSON Schema: UI form builders (e.g.
|
|
|
3661
3902
|
|
|
3662
3903
|
## Import
|
|
3663
3904
|
${'```ts'}
|
|
3664
|
-
${importContent}
|
|
3905
|
+
${importContent.content}
|
|
3665
3906
|
${'```'}
|
|
3666
3907
|
|
|
3667
3908
|
## Definition
|
|
3668
3909
|
${'```ts'}
|
|
3669
|
-
${codeContent}
|
|
3910
|
+
${codeContent.content}
|
|
3670
3911
|
${'```'}
|
|
3671
3912
|
`;
|
|
3672
3913
|
}
|
|
@@ -3688,12 +3929,12 @@ Use this Zod schema for runtime validation and parsing: form validation, client/
|
|
|
3688
3929
|
|
|
3689
3930
|
## Import
|
|
3690
3931
|
${'```ts'}
|
|
3691
|
-
${importContent}
|
|
3932
|
+
${importContent.content}
|
|
3692
3933
|
${'```'}
|
|
3693
3934
|
|
|
3694
3935
|
## Definition
|
|
3695
3936
|
${'```ts'}
|
|
3696
|
-
${codeContent}
|
|
3937
|
+
${codeContent.content}
|
|
3697
3938
|
${'```'}
|
|
3698
3939
|
`;
|
|
3699
3940
|
}
|
|
@@ -4708,37 +4949,19 @@ function updateGitIgnore(rootDir, entryToAdd) {
|
|
|
4708
4949
|
fs.writeFileSync(gitIgnorePath, gitIgnoreContent.join('\n'), 'utf-8');
|
|
4709
4950
|
}
|
|
4710
4951
|
}
|
|
4711
|
-
function updateIntrigConfig(rootDir, apiRoutesDir) {
|
|
4712
|
-
const configPath = path.resolve(rootDir, 'intrig.config.json');
|
|
4713
|
-
if (fs.existsSync(configPath)) {
|
|
4714
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
4715
|
-
config.generatorOptions = config.generatorOptions || {};
|
|
4716
|
-
config.generatorOptions.apiRoutesDir = apiRoutesDir;
|
|
4717
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
4718
|
-
}
|
|
4719
|
-
}
|
|
4720
4952
|
async function initPlugin(ctx) {
|
|
4721
|
-
const { rootDir } = ctx;
|
|
4722
|
-
const
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
default: 'src/app/api',
|
|
4734
|
-
validate: (input)=>input.trim().length > 0 || 'Please enter a valid directory path'
|
|
4735
|
-
}
|
|
4736
|
-
]);
|
|
4737
|
-
// Update intrig.config.json with the apiRoutesDir value
|
|
4738
|
-
updateIntrigConfig(rootDir, apiRoutesDir);
|
|
4739
|
-
// Add the directory with (generated) to gitignore
|
|
4740
|
-
updateGitIgnore(rootDir, `${apiRoutesDir}/(generated)`);
|
|
4741
|
-
}
|
|
4953
|
+
const { rootDir, options } = ctx;
|
|
4954
|
+
const apiRoutesDir = (options == null ? void 0 : options.apiRoutesDir) || 'src/app/api';
|
|
4955
|
+
// Add the directory with (generated) to gitignore
|
|
4956
|
+
updateGitIgnore(rootDir, `${apiRoutesDir}/(generated)`);
|
|
4957
|
+
return {
|
|
4958
|
+
postInit: ()=>{
|
|
4959
|
+
console.log(chalk.blue('\nš Next Steps:'));
|
|
4960
|
+
console.log(chalk.white('To complete your Next.js setup, please refer to the post-initialization instructions at:'));
|
|
4961
|
+
console.log(chalk.cyan('https://intrig.dev/docs/next/initialization#3-post-initialization-steps'));
|
|
4962
|
+
console.log(chalk.gray('\nThis guide will show you how to add IntrigProvider to your Next.js application.\n'));
|
|
4963
|
+
}
|
|
4964
|
+
};
|
|
4742
4965
|
}
|
|
4743
4966
|
|
|
4744
4967
|
async function postBuild(ctx) {
|
|
@@ -4833,7 +5056,8 @@ const $generatorSchema = {
|
|
|
4833
5056
|
properties: {
|
|
4834
5057
|
"apiRoutesDir": {
|
|
4835
5058
|
type: 'string',
|
|
4836
|
-
description: 'The directory where the API routes are stored.'
|
|
5059
|
+
description: 'The directory where the API routes are stored.',
|
|
5060
|
+
default: 'src/app/api'
|
|
4837
5061
|
}
|
|
4838
5062
|
}
|
|
4839
5063
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intrig/plugin-next",
|
|
3
|
-
"version": "0.0.2-
|
|
3
|
+
"version": "0.0.2-3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -15,9 +15,8 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@intrig/plugin-sdk": "^0.0.2-
|
|
19
|
-
"@swc/helpers": "~0.5.11"
|
|
20
|
-
"inquirer": "^9.0.0"
|
|
18
|
+
"@intrig/plugin-sdk": "^0.0.2-6",
|
|
19
|
+
"@swc/helpers": "~0.5.11"
|
|
21
20
|
},
|
|
22
21
|
"files": [
|
|
23
22
|
"dist",
|