@intrig/plugin-next 0.0.2-2 ā 0.0.2-4
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 +287 -37
- package/dist/index.js +287 -37
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -8,6 +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 chalk = require('chalk');
|
|
11
12
|
|
|
12
13
|
function _interopNamespaceDefault(e) {
|
|
13
14
|
var n = Object.create(null);
|
|
@@ -1650,6 +1651,7 @@ function providerHooksTemplate() {
|
|
|
1650
1651
|
useMemo,
|
|
1651
1652
|
useState,
|
|
1652
1653
|
useRef,
|
|
1654
|
+
useEffect,
|
|
1653
1655
|
} from 'react';
|
|
1654
1656
|
import {
|
|
1655
1657
|
ErrorState,
|
|
@@ -1693,6 +1695,16 @@ export function useNetworkState<T>({
|
|
|
1693
1695
|
const context = useContext(Context);
|
|
1694
1696
|
|
|
1695
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
|
+
});
|
|
1696
1708
|
|
|
1697
1709
|
const networkState = useMemo(() => {
|
|
1698
1710
|
logger.info(${"`Updating status ${key} ${operation} ${source}`"});
|
|
@@ -1701,7 +1713,7 @@ export function useNetworkState<T>({
|
|
|
1701
1713
|
(context.state?.[${"`${source}:${operation}:${key}`"}] as NetworkState<T>) ??
|
|
1702
1714
|
init()
|
|
1703
1715
|
);
|
|
1704
|
-
}, [
|
|
1716
|
+
}, [context.state, key, operation, source]);
|
|
1705
1717
|
|
|
1706
1718
|
const dispatch = useCallback(
|
|
1707
1719
|
(state: NetworkState<T>) => {
|
|
@@ -1749,30 +1761,30 @@ export function useNetworkState<T>({
|
|
|
1749
1761
|
signal: abortController.signal,
|
|
1750
1762
|
};
|
|
1751
1763
|
|
|
1752
|
-
await
|
|
1764
|
+
await contextRef.current.execute(
|
|
1753
1765
|
requestConfig,
|
|
1754
1766
|
dispatch,
|
|
1755
|
-
|
|
1756
|
-
|
|
1767
|
+
schemaRef.current,
|
|
1768
|
+
errorSchemaRef.current as any,
|
|
1757
1769
|
);
|
|
1758
1770
|
},
|
|
1759
|
-
[
|
|
1771
|
+
[dispatch, key, operation, source],
|
|
1760
1772
|
);
|
|
1761
1773
|
|
|
1762
1774
|
const deboundedExecute = useMemo(
|
|
1763
|
-
() => debounce(execute, debounceDelay
|
|
1764
|
-
[execute],
|
|
1775
|
+
() => debounce(execute, debounceDelay),
|
|
1776
|
+
[execute, debounceDelay],
|
|
1765
1777
|
);
|
|
1766
1778
|
|
|
1767
1779
|
const clear = useCallback(() => {
|
|
1768
1780
|
logger.info(${"`Clearing request ${key} ${operation} ${source}`"});
|
|
1769
1781
|
dispatch(init());
|
|
1770
|
-
setAbortController((
|
|
1782
|
+
setAbortController((prev) => {
|
|
1771
1783
|
logger.info(${"`Aborting request ${key} ${operation} ${source}`"});
|
|
1772
|
-
|
|
1784
|
+
prev?.abort();
|
|
1773
1785
|
return undefined;
|
|
1774
1786
|
});
|
|
1775
|
-
}, [dispatch,
|
|
1787
|
+
}, [dispatch, key, operation, source]);
|
|
1776
1788
|
|
|
1777
1789
|
return [networkState, deboundedExecute, clear, dispatch];
|
|
1778
1790
|
}
|
|
@@ -1789,6 +1801,14 @@ export function useTransitionCall<T>({
|
|
|
1789
1801
|
}): [(request: RequestType) => Promise<T>, () => void] {
|
|
1790
1802
|
const ctx = useContext(Context);
|
|
1791
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
|
+
});
|
|
1792
1812
|
|
|
1793
1813
|
const call = useCallback(
|
|
1794
1814
|
async (request: RequestType) => {
|
|
@@ -1806,12 +1826,12 @@ export function useTransitionCall<T>({
|
|
|
1806
1826
|
reject(state.error);
|
|
1807
1827
|
}
|
|
1808
1828
|
},
|
|
1809
|
-
|
|
1810
|
-
|
|
1829
|
+
schemaRef.current,
|
|
1830
|
+
errorSchemaRef.current,
|
|
1811
1831
|
);
|
|
1812
1832
|
});
|
|
1813
1833
|
},
|
|
1814
|
-
[ctx
|
|
1834
|
+
[ctx],
|
|
1815
1835
|
);
|
|
1816
1836
|
|
|
1817
1837
|
const abort = useCallback(() => {
|
|
@@ -2447,7 +2467,7 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2447
2467
|
const modifiedRequestUrl = `${requestUrl == null ? void 0 : requestUrl.replace(/\{/g, "${")}`;
|
|
2448
2468
|
const imports = new Set();
|
|
2449
2469
|
imports.add(`import { z } from 'zod'`);
|
|
2450
|
-
imports.add(`import { useCallback, useEffect } from 'react'`);
|
|
2470
|
+
imports.add(`import { useCallback, useEffect, useRef } from 'react'`);
|
|
2451
2471
|
imports.add(`import {useNetworkState, NetworkState, DispatchState, error, successfulDispatch, validationError, encode, requestValidationError} from "@intrig/react"`);
|
|
2452
2472
|
const { hookShape, optionsShape } = extractHookShapeAndOptionsShape$1(response, requestBody, imports);
|
|
2453
2473
|
const { paramExpression, paramType } = extractParamDeconstruction$3(variables != null ? variables : [], requestBody);
|
|
@@ -2500,6 +2520,11 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2500
2520
|
schema,
|
|
2501
2521
|
errorSchema
|
|
2502
2522
|
});
|
|
2523
|
+
|
|
2524
|
+
const optionsRef = useRef(options);
|
|
2525
|
+
useEffect(() => {
|
|
2526
|
+
optionsRef.current = options;
|
|
2527
|
+
});
|
|
2503
2528
|
|
|
2504
2529
|
const doExecute = useCallback<(${paramType}) => DispatchState<any>>((${paramExpression}) => {
|
|
2505
2530
|
const { ${paramExplode}} = p
|
|
@@ -2525,22 +2550,22 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2525
2550
|
${responseTypePart()}
|
|
2526
2551
|
})
|
|
2527
2552
|
return successfulDispatch();
|
|
2528
|
-
}, [dispatch])
|
|
2553
|
+
}, [dispatch, dispatchState])
|
|
2529
2554
|
|
|
2530
2555
|
useEffect(() => {
|
|
2531
|
-
if (
|
|
2556
|
+
if (optionsRef.current.fetchOnMount) {
|
|
2532
2557
|
doExecute(${[
|
|
2533
|
-
requestBody ? `
|
|
2534
|
-
"
|
|
2558
|
+
requestBody ? `optionsRef.current.body!` : undefined,
|
|
2559
|
+
"optionsRef.current.params!"
|
|
2535
2560
|
].filter((a)=>a).join(",")});
|
|
2536
2561
|
}
|
|
2537
2562
|
|
|
2538
2563
|
return () => {
|
|
2539
|
-
if (
|
|
2564
|
+
if (optionsRef.current.clearOnUnmount) {
|
|
2540
2565
|
clear();
|
|
2541
2566
|
}
|
|
2542
2567
|
}
|
|
2543
|
-
}, [])
|
|
2568
|
+
}, [doExecute, clear])
|
|
2544
2569
|
|
|
2545
2570
|
return [
|
|
2546
2571
|
state,
|
|
@@ -2837,7 +2862,7 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2837
2862
|
const modifiedRequestUrl = `${requestUrl == null ? void 0 : requestUrl.replace(/\{/g, "${")}`;
|
|
2838
2863
|
const imports = new Set();
|
|
2839
2864
|
imports.add(`import { z } from 'zod'`);
|
|
2840
|
-
imports.add(`import { useCallback, useEffect } from 'react'`);
|
|
2865
|
+
imports.add(`import { useCallback, useEffect, useRef } from 'react'`);
|
|
2841
2866
|
imports.add(`import {useNetworkState, NetworkState, DispatchState, pending, success, error, init, successfulDispatch, validationError, encode, isSuccess, requestValidationError} from "@intrig/react"`);
|
|
2842
2867
|
const { hookShape, optionsShape } = extractHookShapeAndOptionsShape(response, requestBody, imports);
|
|
2843
2868
|
const { paramExpression, paramType } = extractParamDeconstruction$1(variables, requestBody);
|
|
@@ -2883,7 +2908,7 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2883
2908
|
const source = "${source}"
|
|
2884
2909
|
|
|
2885
2910
|
function use${pluginSdk.pascalCase(operationId)}Hook(options: ${optionsShape} = {}): [NetworkState<Response>, (${paramType}) => DispatchState<any>, () => void] {
|
|
2886
|
-
|
|
2911
|
+
const [state, dispatch, clear, dispatchState] = useNetworkState<Response>({
|
|
2887
2912
|
key: options?.key ?? 'default',
|
|
2888
2913
|
operation,
|
|
2889
2914
|
source,
|
|
@@ -2891,6 +2916,11 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2891
2916
|
errorSchema
|
|
2892
2917
|
});
|
|
2893
2918
|
|
|
2919
|
+
const optionsRef = useRef(options);
|
|
2920
|
+
useEffect(() => {
|
|
2921
|
+
optionsRef.current = options;
|
|
2922
|
+
});
|
|
2923
|
+
|
|
2894
2924
|
useEffect(() => {
|
|
2895
2925
|
if (isSuccess(state)) {
|
|
2896
2926
|
let a = document.createElement('a');
|
|
@@ -2923,10 +2953,10 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2923
2953
|
document.body.removeChild(a);
|
|
2924
2954
|
dispatchState(init())
|
|
2925
2955
|
}
|
|
2926
|
-
}, [state])
|
|
2956
|
+
}, [state, dispatchState])
|
|
2927
2957
|
|
|
2928
|
-
|
|
2929
|
-
|
|
2958
|
+
const doExecute = useCallback<(${paramType}) => DispatchState<any>>((${paramExpression}) => {
|
|
2959
|
+
const { ${paramExplode}} = p
|
|
2930
2960
|
|
|
2931
2961
|
${requestBody ? `
|
|
2932
2962
|
const validationResult = requestBodySchema.safeParse(data);
|
|
@@ -2949,22 +2979,22 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2949
2979
|
${responseTypePart()}
|
|
2950
2980
|
})
|
|
2951
2981
|
return successfulDispatch();
|
|
2952
|
-
}, [dispatch])
|
|
2982
|
+
}, [dispatch, dispatchState])
|
|
2953
2983
|
|
|
2954
2984
|
useEffect(() => {
|
|
2955
|
-
if (
|
|
2985
|
+
if (optionsRef.current.fetchOnMount) {
|
|
2956
2986
|
doExecute(${[
|
|
2957
|
-
requestBody ? `
|
|
2958
|
-
"
|
|
2987
|
+
requestBody ? `optionsRef.current.body!` : undefined,
|
|
2988
|
+
"optionsRef.current.params!"
|
|
2959
2989
|
].filter((a)=>a).join(",")});
|
|
2960
2990
|
}
|
|
2961
2991
|
|
|
2962
2992
|
return () => {
|
|
2963
|
-
if (
|
|
2993
|
+
if (optionsRef.current.clearOnUnmount) {
|
|
2964
2994
|
clear();
|
|
2965
2995
|
}
|
|
2966
2996
|
}
|
|
2967
|
-
}, [])
|
|
2997
|
+
}, [doExecute, clear]) // Added proper dependencies
|
|
2968
2998
|
|
|
2969
2999
|
return [
|
|
2970
3000
|
state,
|
|
@@ -3581,6 +3611,217 @@ function handleComplexSchema(schema, imports) {
|
|
|
3581
3611
|
};
|
|
3582
3612
|
}
|
|
3583
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
|
+
|
|
3584
3825
|
async function generateCode(ctx) {
|
|
3585
3826
|
// Root/project files
|
|
3586
3827
|
await ctx.dump(packageJsonTemplate(ctx));
|
|
@@ -3614,6 +3855,7 @@ async function generateCode(ctx) {
|
|
|
3614
3855
|
await ctx.dump(paramsTemplate(restDescriptor));
|
|
3615
3856
|
await ctx.dump(asyncFunctionHookTemplate(restDescriptor, internalGeneratorContext));
|
|
3616
3857
|
await ctx.dump(nextRequestMethodTemplate(restDescriptor, internalGeneratorContext));
|
|
3858
|
+
await ctx.dump(serverFunctionDocs(restDescriptor));
|
|
3617
3859
|
if (restDescriptor.data.isDownloadable) {
|
|
3618
3860
|
await ctx.dump(downloadHookTemplate(restDescriptor, internalGeneratorContext));
|
|
3619
3861
|
}
|
|
@@ -3659,12 +3901,12 @@ Use this TypeScript type anywhere you need static typing for this object shape i
|
|
|
3659
3901
|
|
|
3660
3902
|
## Import
|
|
3661
3903
|
${'```ts'}
|
|
3662
|
-
${importContent}
|
|
3904
|
+
${importContent.content}
|
|
3663
3905
|
${'```'}
|
|
3664
3906
|
|
|
3665
3907
|
## Definition
|
|
3666
3908
|
${'```ts'}
|
|
3667
|
-
${codeContent}
|
|
3909
|
+
${codeContent.content}
|
|
3668
3910
|
${'```'}
|
|
3669
3911
|
`;
|
|
3670
3912
|
}
|
|
@@ -3686,12 +3928,12 @@ Use this JSON Schema with tools that consume JSON Schema: UI form builders (e.g.
|
|
|
3686
3928
|
|
|
3687
3929
|
## Import
|
|
3688
3930
|
${'```ts'}
|
|
3689
|
-
${importContent}
|
|
3931
|
+
${importContent.content}
|
|
3690
3932
|
${'```'}
|
|
3691
3933
|
|
|
3692
3934
|
## Definition
|
|
3693
3935
|
${'```ts'}
|
|
3694
|
-
${codeContent}
|
|
3936
|
+
${codeContent.content}
|
|
3695
3937
|
${'```'}
|
|
3696
3938
|
`;
|
|
3697
3939
|
}
|
|
@@ -3713,12 +3955,12 @@ Use this Zod schema for runtime validation and parsing: form validation, client/
|
|
|
3713
3955
|
|
|
3714
3956
|
## Import
|
|
3715
3957
|
${'```ts'}
|
|
3716
|
-
${importContent}
|
|
3958
|
+
${importContent.content}
|
|
3717
3959
|
${'```'}
|
|
3718
3960
|
|
|
3719
3961
|
## Definition
|
|
3720
3962
|
${'```ts'}
|
|
3721
|
-
${codeContent}
|
|
3963
|
+
${codeContent.content}
|
|
3722
3964
|
${'```'}
|
|
3723
3965
|
`;
|
|
3724
3966
|
}
|
|
@@ -4738,6 +4980,14 @@ async function initPlugin(ctx) {
|
|
|
4738
4980
|
const apiRoutesDir = (options == null ? void 0 : options.apiRoutesDir) || 'src/app/api';
|
|
4739
4981
|
// Add the directory with (generated) to gitignore
|
|
4740
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
|
+
};
|
|
4741
4991
|
}
|
|
4742
4992
|
|
|
4743
4993
|
async function postBuild(ctx) {
|
package/dist/index.js
CHANGED
|
@@ -5,6 +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 chalk from 'chalk';
|
|
8
9
|
|
|
9
10
|
class InternalGeneratorContext {
|
|
10
11
|
getCounter(sourceId) {
|
|
@@ -1624,6 +1625,7 @@ function providerHooksTemplate() {
|
|
|
1624
1625
|
useMemo,
|
|
1625
1626
|
useState,
|
|
1626
1627
|
useRef,
|
|
1628
|
+
useEffect,
|
|
1627
1629
|
} from 'react';
|
|
1628
1630
|
import {
|
|
1629
1631
|
ErrorState,
|
|
@@ -1667,6 +1669,16 @@ export function useNetworkState<T>({
|
|
|
1667
1669
|
const context = useContext(Context);
|
|
1668
1670
|
|
|
1669
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
|
+
});
|
|
1670
1682
|
|
|
1671
1683
|
const networkState = useMemo(() => {
|
|
1672
1684
|
logger.info(${"`Updating status ${key} ${operation} ${source}`"});
|
|
@@ -1675,7 +1687,7 @@ export function useNetworkState<T>({
|
|
|
1675
1687
|
(context.state?.[${"`${source}:${operation}:${key}`"}] as NetworkState<T>) ??
|
|
1676
1688
|
init()
|
|
1677
1689
|
);
|
|
1678
|
-
}, [
|
|
1690
|
+
}, [context.state, key, operation, source]);
|
|
1679
1691
|
|
|
1680
1692
|
const dispatch = useCallback(
|
|
1681
1693
|
(state: NetworkState<T>) => {
|
|
@@ -1723,30 +1735,30 @@ export function useNetworkState<T>({
|
|
|
1723
1735
|
signal: abortController.signal,
|
|
1724
1736
|
};
|
|
1725
1737
|
|
|
1726
|
-
await
|
|
1738
|
+
await contextRef.current.execute(
|
|
1727
1739
|
requestConfig,
|
|
1728
1740
|
dispatch,
|
|
1729
|
-
|
|
1730
|
-
|
|
1741
|
+
schemaRef.current,
|
|
1742
|
+
errorSchemaRef.current as any,
|
|
1731
1743
|
);
|
|
1732
1744
|
},
|
|
1733
|
-
[
|
|
1745
|
+
[dispatch, key, operation, source],
|
|
1734
1746
|
);
|
|
1735
1747
|
|
|
1736
1748
|
const deboundedExecute = useMemo(
|
|
1737
|
-
() => debounce(execute, debounceDelay
|
|
1738
|
-
[execute],
|
|
1749
|
+
() => debounce(execute, debounceDelay),
|
|
1750
|
+
[execute, debounceDelay],
|
|
1739
1751
|
);
|
|
1740
1752
|
|
|
1741
1753
|
const clear = useCallback(() => {
|
|
1742
1754
|
logger.info(${"`Clearing request ${key} ${operation} ${source}`"});
|
|
1743
1755
|
dispatch(init());
|
|
1744
|
-
setAbortController((
|
|
1756
|
+
setAbortController((prev) => {
|
|
1745
1757
|
logger.info(${"`Aborting request ${key} ${operation} ${source}`"});
|
|
1746
|
-
|
|
1758
|
+
prev?.abort();
|
|
1747
1759
|
return undefined;
|
|
1748
1760
|
});
|
|
1749
|
-
}, [dispatch,
|
|
1761
|
+
}, [dispatch, key, operation, source]);
|
|
1750
1762
|
|
|
1751
1763
|
return [networkState, deboundedExecute, clear, dispatch];
|
|
1752
1764
|
}
|
|
@@ -1763,6 +1775,14 @@ export function useTransitionCall<T>({
|
|
|
1763
1775
|
}): [(request: RequestType) => Promise<T>, () => void] {
|
|
1764
1776
|
const ctx = useContext(Context);
|
|
1765
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
|
+
});
|
|
1766
1786
|
|
|
1767
1787
|
const call = useCallback(
|
|
1768
1788
|
async (request: RequestType) => {
|
|
@@ -1780,12 +1800,12 @@ export function useTransitionCall<T>({
|
|
|
1780
1800
|
reject(state.error);
|
|
1781
1801
|
}
|
|
1782
1802
|
},
|
|
1783
|
-
|
|
1784
|
-
|
|
1803
|
+
schemaRef.current,
|
|
1804
|
+
errorSchemaRef.current,
|
|
1785
1805
|
);
|
|
1786
1806
|
});
|
|
1787
1807
|
},
|
|
1788
|
-
[ctx
|
|
1808
|
+
[ctx],
|
|
1789
1809
|
);
|
|
1790
1810
|
|
|
1791
1811
|
const abort = useCallback(() => {
|
|
@@ -2421,7 +2441,7 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2421
2441
|
const modifiedRequestUrl = `${requestUrl == null ? void 0 : requestUrl.replace(/\{/g, "${")}`;
|
|
2422
2442
|
const imports = new Set();
|
|
2423
2443
|
imports.add(`import { z } from 'zod'`);
|
|
2424
|
-
imports.add(`import { useCallback, useEffect } from 'react'`);
|
|
2444
|
+
imports.add(`import { useCallback, useEffect, useRef } from 'react'`);
|
|
2425
2445
|
imports.add(`import {useNetworkState, NetworkState, DispatchState, error, successfulDispatch, validationError, encode, requestValidationError} from "@intrig/react"`);
|
|
2426
2446
|
const { hookShape, optionsShape } = extractHookShapeAndOptionsShape$1(response, requestBody, imports);
|
|
2427
2447
|
const { paramExpression, paramType } = extractParamDeconstruction$3(variables != null ? variables : [], requestBody);
|
|
@@ -2474,6 +2494,11 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2474
2494
|
schema,
|
|
2475
2495
|
errorSchema
|
|
2476
2496
|
});
|
|
2497
|
+
|
|
2498
|
+
const optionsRef = useRef(options);
|
|
2499
|
+
useEffect(() => {
|
|
2500
|
+
optionsRef.current = options;
|
|
2501
|
+
});
|
|
2477
2502
|
|
|
2478
2503
|
const doExecute = useCallback<(${paramType}) => DispatchState<any>>((${paramExpression}) => {
|
|
2479
2504
|
const { ${paramExplode}} = p
|
|
@@ -2499,22 +2524,22 @@ async function requestHookTemplate({ source, data: { paths, operationId, respons
|
|
|
2499
2524
|
${responseTypePart()}
|
|
2500
2525
|
})
|
|
2501
2526
|
return successfulDispatch();
|
|
2502
|
-
}, [dispatch])
|
|
2527
|
+
}, [dispatch, dispatchState])
|
|
2503
2528
|
|
|
2504
2529
|
useEffect(() => {
|
|
2505
|
-
if (
|
|
2530
|
+
if (optionsRef.current.fetchOnMount) {
|
|
2506
2531
|
doExecute(${[
|
|
2507
|
-
requestBody ? `
|
|
2508
|
-
"
|
|
2532
|
+
requestBody ? `optionsRef.current.body!` : undefined,
|
|
2533
|
+
"optionsRef.current.params!"
|
|
2509
2534
|
].filter((a)=>a).join(",")});
|
|
2510
2535
|
}
|
|
2511
2536
|
|
|
2512
2537
|
return () => {
|
|
2513
|
-
if (
|
|
2538
|
+
if (optionsRef.current.clearOnUnmount) {
|
|
2514
2539
|
clear();
|
|
2515
2540
|
}
|
|
2516
2541
|
}
|
|
2517
|
-
}, [])
|
|
2542
|
+
}, [doExecute, clear])
|
|
2518
2543
|
|
|
2519
2544
|
return [
|
|
2520
2545
|
state,
|
|
@@ -2811,7 +2836,7 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2811
2836
|
const modifiedRequestUrl = `${requestUrl == null ? void 0 : requestUrl.replace(/\{/g, "${")}`;
|
|
2812
2837
|
const imports = new Set();
|
|
2813
2838
|
imports.add(`import { z } from 'zod'`);
|
|
2814
|
-
imports.add(`import { useCallback, useEffect } from 'react'`);
|
|
2839
|
+
imports.add(`import { useCallback, useEffect, useRef } from 'react'`);
|
|
2815
2840
|
imports.add(`import {useNetworkState, NetworkState, DispatchState, pending, success, error, init, successfulDispatch, validationError, encode, isSuccess, requestValidationError} from "@intrig/react"`);
|
|
2816
2841
|
const { hookShape, optionsShape } = extractHookShapeAndOptionsShape(response, requestBody, imports);
|
|
2817
2842
|
const { paramExpression, paramType } = extractParamDeconstruction$1(variables, requestBody);
|
|
@@ -2857,7 +2882,7 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2857
2882
|
const source = "${source}"
|
|
2858
2883
|
|
|
2859
2884
|
function use${pascalCase(operationId)}Hook(options: ${optionsShape} = {}): [NetworkState<Response>, (${paramType}) => DispatchState<any>, () => void] {
|
|
2860
|
-
|
|
2885
|
+
const [state, dispatch, clear, dispatchState] = useNetworkState<Response>({
|
|
2861
2886
|
key: options?.key ?? 'default',
|
|
2862
2887
|
operation,
|
|
2863
2888
|
source,
|
|
@@ -2865,6 +2890,11 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2865
2890
|
errorSchema
|
|
2866
2891
|
});
|
|
2867
2892
|
|
|
2893
|
+
const optionsRef = useRef(options);
|
|
2894
|
+
useEffect(() => {
|
|
2895
|
+
optionsRef.current = options;
|
|
2896
|
+
});
|
|
2897
|
+
|
|
2868
2898
|
useEffect(() => {
|
|
2869
2899
|
if (isSuccess(state)) {
|
|
2870
2900
|
let a = document.createElement('a');
|
|
@@ -2897,10 +2927,10 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2897
2927
|
document.body.removeChild(a);
|
|
2898
2928
|
dispatchState(init())
|
|
2899
2929
|
}
|
|
2900
|
-
}, [state])
|
|
2930
|
+
}, [state, dispatchState])
|
|
2901
2931
|
|
|
2902
|
-
|
|
2903
|
-
|
|
2932
|
+
const doExecute = useCallback<(${paramType}) => DispatchState<any>>((${paramExpression}) => {
|
|
2933
|
+
const { ${paramExplode}} = p
|
|
2904
2934
|
|
|
2905
2935
|
${requestBody ? `
|
|
2906
2936
|
const validationResult = requestBodySchema.safeParse(data);
|
|
@@ -2923,22 +2953,22 @@ async function downloadHookTemplate({ source, data: { paths, operationId, respon
|
|
|
2923
2953
|
${responseTypePart()}
|
|
2924
2954
|
})
|
|
2925
2955
|
return successfulDispatch();
|
|
2926
|
-
}, [dispatch])
|
|
2956
|
+
}, [dispatch, dispatchState])
|
|
2927
2957
|
|
|
2928
2958
|
useEffect(() => {
|
|
2929
|
-
if (
|
|
2959
|
+
if (optionsRef.current.fetchOnMount) {
|
|
2930
2960
|
doExecute(${[
|
|
2931
|
-
requestBody ? `
|
|
2932
|
-
"
|
|
2961
|
+
requestBody ? `optionsRef.current.body!` : undefined,
|
|
2962
|
+
"optionsRef.current.params!"
|
|
2933
2963
|
].filter((a)=>a).join(",")});
|
|
2934
2964
|
}
|
|
2935
2965
|
|
|
2936
2966
|
return () => {
|
|
2937
|
-
if (
|
|
2967
|
+
if (optionsRef.current.clearOnUnmount) {
|
|
2938
2968
|
clear();
|
|
2939
2969
|
}
|
|
2940
2970
|
}
|
|
2941
|
-
}, [])
|
|
2971
|
+
}, [doExecute, clear]) // Added proper dependencies
|
|
2942
2972
|
|
|
2943
2973
|
return [
|
|
2944
2974
|
state,
|
|
@@ -3555,6 +3585,217 @@ function handleComplexSchema(schema, imports) {
|
|
|
3555
3585
|
};
|
|
3556
3586
|
}
|
|
3557
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
|
+
|
|
3558
3799
|
async function generateCode(ctx) {
|
|
3559
3800
|
// Root/project files
|
|
3560
3801
|
await ctx.dump(packageJsonTemplate(ctx));
|
|
@@ -3588,6 +3829,7 @@ async function generateCode(ctx) {
|
|
|
3588
3829
|
await ctx.dump(paramsTemplate(restDescriptor));
|
|
3589
3830
|
await ctx.dump(asyncFunctionHookTemplate(restDescriptor, internalGeneratorContext));
|
|
3590
3831
|
await ctx.dump(nextRequestMethodTemplate(restDescriptor, internalGeneratorContext));
|
|
3832
|
+
await ctx.dump(serverFunctionDocs(restDescriptor));
|
|
3591
3833
|
if (restDescriptor.data.isDownloadable) {
|
|
3592
3834
|
await ctx.dump(downloadHookTemplate(restDescriptor, internalGeneratorContext));
|
|
3593
3835
|
}
|
|
@@ -3633,12 +3875,12 @@ Use this TypeScript type anywhere you need static typing for this object shape i
|
|
|
3633
3875
|
|
|
3634
3876
|
## Import
|
|
3635
3877
|
${'```ts'}
|
|
3636
|
-
${importContent}
|
|
3878
|
+
${importContent.content}
|
|
3637
3879
|
${'```'}
|
|
3638
3880
|
|
|
3639
3881
|
## Definition
|
|
3640
3882
|
${'```ts'}
|
|
3641
|
-
${codeContent}
|
|
3883
|
+
${codeContent.content}
|
|
3642
3884
|
${'```'}
|
|
3643
3885
|
`;
|
|
3644
3886
|
}
|
|
@@ -3660,12 +3902,12 @@ Use this JSON Schema with tools that consume JSON Schema: UI form builders (e.g.
|
|
|
3660
3902
|
|
|
3661
3903
|
## Import
|
|
3662
3904
|
${'```ts'}
|
|
3663
|
-
${importContent}
|
|
3905
|
+
${importContent.content}
|
|
3664
3906
|
${'```'}
|
|
3665
3907
|
|
|
3666
3908
|
## Definition
|
|
3667
3909
|
${'```ts'}
|
|
3668
|
-
${codeContent}
|
|
3910
|
+
${codeContent.content}
|
|
3669
3911
|
${'```'}
|
|
3670
3912
|
`;
|
|
3671
3913
|
}
|
|
@@ -3687,12 +3929,12 @@ Use this Zod schema for runtime validation and parsing: form validation, client/
|
|
|
3687
3929
|
|
|
3688
3930
|
## Import
|
|
3689
3931
|
${'```ts'}
|
|
3690
|
-
${importContent}
|
|
3932
|
+
${importContent.content}
|
|
3691
3933
|
${'```'}
|
|
3692
3934
|
|
|
3693
3935
|
## Definition
|
|
3694
3936
|
${'```ts'}
|
|
3695
|
-
${codeContent}
|
|
3937
|
+
${codeContent.content}
|
|
3696
3938
|
${'```'}
|
|
3697
3939
|
`;
|
|
3698
3940
|
}
|
|
@@ -4712,6 +4954,14 @@ async function initPlugin(ctx) {
|
|
|
4712
4954
|
const apiRoutesDir = (options == null ? void 0 : options.apiRoutesDir) || 'src/app/api';
|
|
4713
4955
|
// Add the directory with (generated) to gitignore
|
|
4714
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
|
+
};
|
|
4715
4965
|
}
|
|
4716
4966
|
|
|
4717
4967
|
async function postBuild(ctx) {
|
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-4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@intrig/plugin-sdk": "^0.0.2-
|
|
18
|
+
"@intrig/plugin-sdk": "^0.0.2-7",
|
|
19
19
|
"@swc/helpers": "~0.5.11"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|