W
Wasp-lang•9mo ago
smilebasti

action calling action not working. Bad programming or issue?

Hi, i am using the latest wasp v12 version and started with a fresh saas template running on ubuntu Desktop 22. I tested the Fileupload page and it works. Now i started developing my own page and action. The page will trigger a action and the action downloads a (for now) hard-coded file that should be uploaded with the createFile action. Meaning 1 action calls a second action. (My backup action calls createFile action). The first part, getting the file works but the calling createFile fails. I copied the code from FileUploadPage.tsx into my backup.ts to trigger the upload. I changed the import in my backup.ts to: import { createFile } from 'wasp/server/operations'; When i run the following code with "wasp start" i get the error during "Building SDK": " Expceted 2 arguments, but got 1 " with const { uploadUrl } = await createFile({ fileType, name } ); . If i add a second argument like: const { uploadUrl } = await createFile({ fileType, name } , name); i get the error: " ERR_MODULE_NOT_FOUND( " This error rises during " validating environment variables ... " on the server. The attached screenshots show the Error. The code shown is from my Backup action getting the file. The File Upload from the frontend still works as expected I am not sure if i am unable to make this action to action in the way i do it? I am not able to figure out why it's not working. Any help would be greatly appreciated. And of course thank you for the otherwise great framework and template.
No description
No description
No description
29 Replies
mindreaderlupoDO
mindreaderlupoDO•9mo ago
There is a thread where I had the same issue. Tl;Dr import { createFile } from 'wasp/client/operations';
smilebasti
smilebastiOP•9mo ago
Thanks for the reply. The error changed to ' Err Unsupported dir import '. See screenshot. I tried removing the File code section and the uploadfile action call. I replaced it with a different working function but its still not working and throughs the same error. Any thoughts on this too?
No description
Filip
Filip•9mo ago
Hey, @smilebasti, thanks for reaching out. And @mindreaderlupoDO, thanks for helping, we appreciate it! @smilebasti, I have a feeling @miho knows what's happening here, so I'm tagging him. If not, I'll help you debug it FYI, when you want to include code in your messages, please enclose it with backticks (`) instead of quotes. It looks much nicer. You can even use three backtics followed by the name of the language to get syntax highlighting:
const { uploadUrl } = await createFile({ fileType, name } , name);
const { uploadUrl } = await createFile({ fileType, name } , name);
Read more about it here: https://gist.github.com/matthewzring/9f7bbfd102003963f9be7dbcf7d40e51
martinsos
martinsos•9mo ago
@smilebasti , let's figure this out! So, the first error you get, that second argument is missing, is due to you calling an action from action. Calling action from an action is still a bit of an awkward case in Wasp -> you can do that, but what you then need to do, is correctly provide it with this second argument, which is context. That context is provided by Wasp then you call the action from client -> wasp defines it and then provides it to your action. But now, when you are calling it directly from another action, it is up to you do provide that second argument, which can be a bit involved / unexpected -> in the future we will provide some nicer ways to do that. So right now, you would have to construct that context object on your own. Good thing is, since you are calling it from your other backup action, you can reuse its context. So, you can do const { uploadUrl } = await createFile({ fileType, name }, context); . The only thing important for this to work is that your backup action needs to declare, in main.wasp file, that it uses File entity, because createFile action expects that entity in its context. If you did that, you can forward that context from backup to createFile. Give that a try and let me know how it goes! That said, I will immediately address two more things: 1. There is another way to share logic between actions -> instead of an action A1 calling action A2, you can extract logic from A2 into function F and then have both A1 and A2 use function F (in which case A1 doesn't use A2). This means that you would create function createFileInTheDb(user, File, { fileType, name }) { ... } that does most of the work in A2, and then both A1 and A2 can call that function. That said, there is no real advantage in doing this over calling A2 from A1, but I wanted to offer it as an alternative in case you get heavily stuck with calling A2 from A1, maybe you can try this then. 2. You tried passing second argument to createFile, which was a good direction, the problem is you gave it name, instead of context. However, the error you then got was a bit weird, mentioning query.js.js -> that looks a bit suspicious to me, and I wonder if that is another error, orthogonal to the problem or wrong second argument. If this problem happens even after you provide context as a second argument, then I would look into it on its own, more closely. Let me know if passing context works! If not, please also share the JS imports you have in your code, for the file where you implemented backup action, those are quite important in this case.
smilebasti
smilebastiOP•9mo ago
Thank you for the extensive reply. Very appreciated 🙂 I tried it with const { uploadUrl } = await createFile({ fileType, name }, context); but no luck: see Image. My import in he backup action is
import { createFile } from 'wasp/client/operations';
import axios from 'axios';
and my modules afterwards...
import { createFile } from 'wasp/client/operations';
import axios from 'axios';
and my modules afterwards...
In the actions.js i call import { getLastImapMail } from './mail-backup/imap-backup.js'; and the action looks like this:
export const createLastMailMessage: CreateLastMailMessage< void,Mail> = async (_args, context) => {
if (!context.user) {
throw new HttpError(401);
}
console.log('createLastMailMessage called befor getLastImapMail gets called');


const { message } = await getLastImapMail();


//Still change to new entitie
return await context.entities.Mail.create({
data: {
message,
user: { connect: { id: context.user.id } },
},
});
};
export const createLastMailMessage: CreateLastMailMessage< void,Mail> = async (_args, context) => {
if (!context.user) {
throw new HttpError(401);
}
console.log('createLastMailMessage called befor getLastImapMail gets called');


const { message } = await getLastImapMail();


//Still change to new entitie
return await context.entities.Mail.create({
data: {
message,
user: { connect: { id: context.user.id } },
},
});
};
The action in the main.wasp has this action:
action createLastMailMessage {
fn: import { createLastMailMessage } from "@src/server/actions.js",
entities: [User, File, Mail]
}
action createLastMailMessage {
fn: import { createLastMailMessage } from "@src/server/actions.js",
entities: [User, File, Mail]
}
And the Page that calls the action with a button press looks like this:
const handleGetBackup = async (e: FormEvent<HTMLFormElement>) => {
try {
e.preventDefault();

const { message } = await createLastMailMessage();
if (!message) {
throw new Error('Failed to latest Mail');
}


} catch (error) {
alert('Getting Mail failed. Please try again');
console.error('Getting Mail failed', error);
}
};
const handleGetBackup = async (e: FormEvent<HTMLFormElement>) => {
try {
e.preventDefault();

const { message } = await createLastMailMessage();
if (!message) {
throw new Error('Failed to latest Mail');
}


} catch (error) {
alert('Getting Mail failed. Please try again');
console.error('Getting Mail failed', error);
}
};
I hope this information helps a little more...
No description
martinsos
martinsos•9mo ago
Yup it certainly does! OK, I think we got this, we just need to figure out a couple more things. Your "backup" action -> what is the name of that action? Is that createLastMailMessage? If so, I don't see you calling createFile from there. Can you also share its code? So that we then have all the code in question, and we can paint the whole picture. Btw when providing code snippets, you can do put "js" after the three backticks, that will color the code correctly, make it easier to read. What I would suggest doing is importing createFile direclty with sometihng like import { createFile } from '../backup.ts' or whatever is correct path for you, instead of doing import { createFile } from 'wasp/client/operations'; , that should make things simpler. We did some changes recenlty here so I am not sure if import { createFile } from 'wasp/client/operations'; works as expected even though we mention it in docs, I will go explore that now @miho @Filip , we have a user here wanting to call an Action from another Action. Now, one way to go about it is importing its JS function directly, ignoring really that it is an action, by doing something like import { secondAction } from '../foo.js', and then they have to make sure to pass it correct context. On the other hand, our docs say that importing actions from client or server is the same, and that it is done with import { createFile } from 'wasp/client/operations', which looks quite suspicious to me, how would that work on server? Did we make a mistake here? If so, is it in the code/API, or in the docs? I am talking about this piece of docs: https://wasp-lang.dev/docs/data-model/operations/actions#using-actions . We should figure this out, and then update our docs/API so that it is clear how to call an action from another action on the server.
Filip
Filip•9mo ago
On the other hand, our docs say that importing actions from client or server is the same, and that it is done with import { createFile } from 'wasp/client/operations', which looks quite suspicious to me, how would that work on server? Did we make a mistake here? If so, is it in the code/API, or in the docs? I am talking about this piece of docs: https://wasp-lang.dev/docs/data-
This is definitely a mistake. I was sure we changed that documentation with 0.12.0 But yes, it looks like we forgot. Definitely a mistake in the docs
martinsos
martinsos•9mo ago
@Filip ok! So, you shouldn't import from wasp/client/operations from the server, right? What should you do then, though? We can do "raw" importing as I described above, which is not terrible -> is there a better way though? Maybe we can import from wasp/server/operations, does that exist?
Filip
Filip•9mo ago
Maybe we can import from wasp/server/operations, does that exist?
It does exist, yes, but still requires context (albeit a shroter one). When calling an action/query imported from wasp/server/operations, you need to pass a context that contains the user info (without the entities). If your query doesn't require auth, you should be able to pass an empty object. Unfortunately, our type support for this is very lacking and we just display the function as (args: any, context: any) => Promise<any>. Still, calling it like this should work (assuming createFile doesn't require auth):
import { createFile } from 'wasp/server/operations'

// ... (
createFile({ fileType, name }, {})
import { createFile } from 'wasp/server/operations'

// ... (
createFile({ fileType, name }, {})
If you need the user inside the query @smilebasti, let me know and I'll dig a little deeper
martinsos
martinsos•9mo ago
@Filip actually if they are calling action A2 from action A1, it should be very easy to get user, they can just get it from the context of the A1. @Filip you said that they don't need to pass entities in the context -> how is that? The context gets enriched with entities after the call? Remind me, we shaped this API like this on purpose -> was this that change you were doing at the very end of 0.12 release? How are we with docs in regards to this?
Filip
Filip•9mo ago
The docs - non-existent, as w concluded above 😄 The API change - I think it's always been like this, but I'll investigate
miho
miho•9mo ago
Here's an issue for the docs change https://github.com/wasp-lang/wasp/issues/1909
GitHub
Fix docs on calling queries and actions on the server · Issue #1909...
Currently we say To use a Query, you can import it from wasp/client/operations and call it directly. As mentioned, the usage doesn't change depending on whether you're on the server or the ...
martinsos
martinsos•9mo ago
Oh sweet thanks @miho ! I added "shouldfix" label on this, I am using this to mark stuff that it is eager to be fixed @smilebasti I hope this convo helps, and I am sorry for our omisions here regarding the docs! We recently updated them, with 0.12, and must have made some mistakes here. This should allow you to fix the issue -> but let us know if you will need any more help!
smilebasti
smilebastiOP•9mo ago
Hi guys, thank you very much for the replies. I tried
import { createFile } from 'wasp/server/operations'

// ... (
createFile({ fileType, name }, {})
import { createFile } from 'wasp/server/operations'

// ... (
createFile({ fileType, name }, {})
but got the same error: Err_module_not_found.
import { createFile } from '../actions.js'

// ... (
createFile({ fileType, name }, context)
import { createFile } from '../actions.js'

// ... (
createFile({ fileType, name }, context)
but got the error: cannot find name 'context'.
MEE6
MEE6•9mo ago
Wohooo @smilebasti, you just became a Waspeteer level 1!
Filip
Filip•9mo ago
Can you give us the entire file?
but got the error: cannot find name 'context'.
To me, this looks like a generic JavaScript reference error, not specific too Wasp But I'd have to see the file to say something useful
smilebasti
smilebastiOP•9mo ago
I then tried
import { createFile } from '../actions.js'

// ... (
createFile({ fileType, name }, {})
import { createFile } from '../actions.js'

// ... (
createFile({ fileType, name }, {})
but got the error: picture. Which looks more promising.
No description
smilebasti
smilebastiOP•9mo ago
Haha you answerd to quick. One sec... The backup.ts file:
import { createFile } from 'wasp/server/operations';
import axios from 'axios';


export const getLastImapMail = async () => {


try {

//const file = new File(content, meta.filename);
const file = new File([''], '/home/smilebasti/Desktop/Mail-SaaS/app/public/banner.png');
if (!file || !file.name || !file.type) {
throw new Error('No file selected');
}

const fileType = file.type;
const name = file.name;

const { uploadUrl } = await createFile({ fileType, name }, {});
if (!uploadUrl) {
throw new Error('Failed to get upload URL');
}
const res = await axios.put(uploadUrl, file, { // to uploadUrl
headers: {
'Content-Type': fileType,
},
});
if (res.status !== 200) {
throw new Error('File upload to S3 failed');
}

} catch (error) {
alert('Error uploading file. Please try again');
console.error('Error uploading file', error);
}

return { message: 'message' };
}
import { createFile } from 'wasp/server/operations';
import axios from 'axios';


export const getLastImapMail = async () => {


try {

//const file = new File(content, meta.filename);
const file = new File([''], '/home/smilebasti/Desktop/Mail-SaaS/app/public/banner.png');
if (!file || !file.name || !file.type) {
throw new Error('No file selected');
}

const fileType = file.type;
const name = file.name;

const { uploadUrl } = await createFile({ fileType, name }, {});
if (!uploadUrl) {
throw new Error('Failed to get upload URL');
}
const res = await axios.put(uploadUrl, file, { // to uploadUrl
headers: {
'Content-Type': fileType,
},
});
if (res.status !== 200) {
throw new Error('File upload to S3 failed');
}

} catch (error) {
alert('Error uploading file. Please try again');
console.error('Error uploading file', error);
}

return { message: 'message' };
}
I removed my logic. I test every iteration with my client side image as file and with the file that gets downloaded. All my code is built upon the opensaas template. I only added on top and did not modify any of the already existing content. I want the same logic as the existing client-side file upload on the server-side with a file that the server pulls from the internet or generates.
Filip
Filip•9mo ago
Ok, I can try and replicate and see why this doesn't work later today. In the meantime, can you send me the variant of the code when you try this:
import { createFile } from '../actions.js'

// ... (
createFile({ fileType, name }, context)
import { createFile } from '../actions.js'

// ... (
createFile({ fileType, name }, context)
That one will probably be easier to fix
smilebasti
smilebastiOP•9mo ago
Error during 'building sdk'
Filip
Filip•9mo ago
No no, not the error, the code 😄
martinsos
martinsos•9mo ago
So from createLastMailMessage action you are calling getLastImapMail. What you need to do is modify getLastImapMail so it takes context as an argument, then pass that context from createLastMailMessage to getLastImapMail and then finally to createFile, as a second argument. So: In actions.js:
//...
export const createLastMailMessage: CreateLastMailMessage< void,Mail> = async (_args, context) => {
//...
const { message } = await getLastImapMail(context);
//...
}
//...
export const createLastMailMessage: CreateLastMailMessage< void,Mail> = async (_args, context) => {
//...
const { message } = await getLastImapMail(context);
//...
}
And in backup.ts:
export const getLastImapMail = async (context) => {
//...
const { uploadUrl } = await createFile({ fileType, name }, context);
//...
}
export const getLastImapMail = async (context) => {
//...
const { uploadUrl } = await createFile({ fileType, name }, context);
//...
}
@Filip I am a bit surprised that import { createFile } from 'wasp/server/operations' didn't work for them though
Filip
Filip•9mo ago
Yes, that is indeed most curious
martinsos
martinsos•9mo ago
How sure are we at all that this works, in general? I haven't seen it in action as far as I can remember.
Filip
Filip•9mo ago
In general, pretty sure. We've tested it when we worked on the new SDK API and I have also been using it during my current task.
Filip
Filip•9mo ago
For example, this is the behavior I have on main in waspc/examples/todo-typescript:
No description
martinsos
martinsos•9mo ago
You are really trying to impress me with these visual effects, and you are succeeding :D. Ok this is reassurring. I wonder if possiblye the fact that operation is importing operation could play a role here (you are importing it from serverSetup, not another operation), although I don't see how.
Filip
Filip•9mo ago
No problem there either
No description
Filip
Filip•9mo ago
You are really trying to impress me with these visual effects, and you are succeeding :D.
This time I did it on purpose, but that's all I got
Want results from more Discord servers?
Add your server