How to return a resolved promise from a module export? - next.js

import { DataTypes } from "sequelize";
import dbConnect from "./dbConnect";
async function UserModel() {
const sequelize = await dbConnect();
const User = sequelize.define(
"User",
{
userKey: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
},
username: {
type: DataTypes.STRING(50),
allowNull: false,
},
},
{
freezeTableName: true, // enforces that table name = model name
}
);
return User;
}
export default UserModel;
The above module returns the UserModel correctly, but I need to resolve it twice --
const users = await (await User()).findAll();
-- in the module that imports it b/c I'm calling a method that also returns a promise.
I'd like to call it like this --
const users = await User().findAll();
-- but everything I've tried has failed.
See usage here:
export async function getServerSideProps() {
const users = await (await User()).findAll();
return {
props: {
users,
},
};
}
Can you shed some light on how to do this?

Related

stitchSchema returns null

I have stitched two schemas together and run on localhost to query it. But the query returns null for the data in the second schema and I am not sure why.
I have the following code to stitch to remote schemas together and run a localhost graphql server to serve it. It should add the linked data from the second schema under cmsMetaData in the main Product data. But cmsMetaData is null.
import { ApolloServer } from 'apollo-server-micro';
import { ApolloServerPluginInlineTraceDisabled, ApolloServerPluginLandingPageLocalDefault } from "apollo-server-core";
import { stitchSchemas } from '#graphql-tools/stitch';
import { delegateToSchema } from '#graphql-tools/delegate';
import { RenameTypes, RenameRootFields } from '#graphql-tools/wrap';
import createRemoteSchema from '../../utils/createRemoteExecutor';
// Configuration for Next.js API Routes
export const config = {
api: {
bodyParser: false,
},
};
// Export as a Next.js API Route
export default async (req, res) => {
// Setup subschema configurations
const productsSubschema = await createRemoteSchema({
url: 'https://schema1.com/graphql/'
});
const cmsSubschema = await createRemoteSchema({
url: 'https://schema2.com/graphql/',
transforms: [
new RenameRootFields(
(operationName, fieldName, fieldConfig) => `strapi_${fieldName}`,
),
new RenameTypes((name) => `Strapi_${name}`),
],
});
// Build the combined schema and set up the extended schema and resolver
const schema = stitchSchemas({
subschemas: [productsSubschema, cmsSubschema],
typeDefs: `
extend type Product {
cmsMetaData: Strapi_Product
}
`,
resolvers: {
Product: {
cmsMetaData: {
selectionSet: `{ id }`,
resolve(product, args, context, info) {
// Get the data for the extended type from the subschema for Strapi
return delegateToSchema({
schema: cmsSubschema,
operation: 'query',
fieldName: 'strapi_product',
args: { where: { SaleorID: product.id } },
context,
info,
});
},
},
},
},
});
// Set up the GraphQL server
const apolloServer = new ApolloServer({
schema,
plugins: [
ApolloServerPluginInlineTraceDisabled(),
ApolloServerPluginLandingPageLocalDefault({ embed: true }),
],
});
await apolloServer.start();
const apolloServerHandler = apolloServer.createHandler({
path: '/api/graphql',
});
// Return the GraphQL endpoint
return apolloServerHandler(req, res);
};
utils/createRemoteExecutor.js is:
import { introspectSchema, wrapSchema } from '#graphql-tools/wrap';
import { print } from 'graphql';
// Builds a remote schema executor function,
// customize any way that you need (auth, headers, etc).
// Expects to recieve an object with "document" and "variable" params,
// and asynchronously returns a JSON response from the remote.
export default async function createRemoteSchema({ url, ...filters }) {
const executor = async ({ document, variables }) => {
const query = print(document);
const fetchResult = await fetch(url, {
method: 'POST',
headers: {
// We can also do Authentication here
'Content-Type': 'application/json',
},
body: JSON.stringify({ query, variables }),
});
return fetchResult.json();
};
return wrapSchema({
schema: await introspectSchema(executor),
executor,
...filters,
});
}
The query is:
products(first: 100, channel: "default-channel")
{
edges
{
node
{
id
name
cmsMetaData
{
Title
SaleorID
}
}
}
}
In my api.tsx, which I generate using codegen.yaml, Product contains cmsMetaData as follows, which is of type Strapi_Product:
export type Product = Node & ObjectWithMetadata & {
__typename?: 'Product';
...
cmsMetaData?: Maybe<Array<Maybe<Strapi_Product>>>;
...
}
Strapi_Product is as follows which contains Title, SaleorID etc.:
export type Strapi_Product = {
__typename?: 'Strapi_Product';
SaleorID?: Maybe<Scalars['String']>;
Title?: Maybe<Scalars['String']>;
createdAt?: Maybe<Scalars['Strapi_DateTime']>;
publishedAt?: Maybe<Scalars['Strapi_DateTime']>;
updatedAt?: Maybe<Scalars['Strapi_DateTime']>;
};
But the date in GraphQL shows null for cmsMetaData as null:
{
"data": {
"products": {
"edges": [
{
"node": {
"id": "UHJvZHVjdDoxMjc=",
"name": "52-00 Base Plate",
"cmsMetaData": null
}
},
{
"node": {
"id": "UHJvZHVjdDoxMjg=",
"name": "52-01HD Weigh Module",
"cmsMetaData": null
}
}
]
}
}
}
Your problem seems related to this github issue. Most of your code looks totally fine, so I guess it fetches the schema correctly. The cmsMetaData field is null because it did not find anything matching objects using the selection criteria. This unwanted behavior is in the transformation and/or the resolver.
A good starting point for debugging would be to remove the RenameRootFields mutation. Furthermore, this example looks like your use case, it is an excellent step-by-step guide.
I also rebuild your example from an example I found on the internet. You most probably have a typo in one of the field names, that caused a null value for me. Make sure A equals B in the code below. I am guessing the initial fieldName is "Product", so after the transformation, this does not equal "strapi_product" and returns a null value.
const cmsSubschema = await createRemoteSchema({
url: 'https://schema2.com/graphql/',
transforms: [
new RenameRootFields(
(_, fieldName) => `strapi_${fieldName}`,), // A
new RenameTypes((name) => `Strapi_${name}`),
],
});
// Build the combined schema and set up the extended schema and resolver
const schema = stitchSchemas({
subschemas: [productsSubschema, cmsSubschema],
typeDefs: `
extend type Product {
cmsMetaData: Strapi_Product
}
`,
resolvers: {
Product: {
cmsMetaData: {
selectionSet: `{ id }`,
resolve(product, args, context, info) {
// Get the data for the extended type from the subschema for Strapi
return delegateToSchema({
schema: cmsSubschema,
operation: 'query',
fieldName: 'strapi_product', // B
args: { where: { SaleorID: product.id } },
context,
info,
});
},
},
},
},
});

Adding update property to mutation function breaks mocked result in MockProvider

I've got the following function that gets triggered on a form submission
const [register, { loading }] = useMutation(RegisterDocument);
const router = useRouter();
const onSubmit = async (values: FormValues) => {
const v = { ...values };
delete v.confirmPassword;
const res = await register({
variables: { options: v },
update: (cache, { data }) => {
cache.writeQuery<MeQuery>({
query: MeDocument,
data: {
__typename: 'Query',
me: data?.register.user,
},
});
},
});
if (res.data?.register.user) {
router.push('/');
}
};
I then have the following test to submit the form
test('it should submit form without error', async () => {
const firstName = faker.name.firstName();
const surname = faker.name.lastName();
const username = faker.internet.userName().replace('#', '');
const email = faker.internet.email();
const password = faker.internet.password(6, false, /^[a-zA-Z0-9_.-]*$/);
const cache = new InMemoryCache().restore({});
const variables = {
options: { email, firstName, password, surname, username },
};
const user = { email, firstName, surname, username, id: 1, activated: false, photo: null };
const mocks = [
{
request: { query: RegisterDocument, variables },
result: { data: { register: { errors: null, user } } },
},
];
const { queryByTestId, container } = renderWithTheme(
<MockedProvider mocks={mocks} cache={cache}>
<Register />
</MockedProvider>,
);
await updateRegisterInputs(container); // util function that updates input values for submission
await submitForm({ queryByTestId, testId: 'register-submit', loadingTestId: 'register-loading' }); // util function that submits form
await waitFor(() => expect(onPush).toBeCalledWith('/'));
});
When I run this test res returns the following
{ data: { register: {} } }
However, once I remove the update property inside the register mutation function, res returns the following.
{ data: { register: { errors: null, user: [Object] } } }
Any ideas why the mocked return value returns an empty object for the register property only when the update property function is added?
Even just instantiating the update property like so;
update: () => {}
still breaks the response from the mutation.
I realised that the graphql doc required the __typename property in the relevant places in my mocks
So I have to update the mock to include the typenames.
const user = { email, firstName, surname, username, id: 1, activated: false, photo: null, __typename: 'User' };
const mocks = [
{
request: { query: RegisterDocument, variables },
result: { data: { register: { errors: null, user, __typename: 'UserResponse' } } },
},
];

How to perform a "where" query using denodb?

I'm trying to register a user and I get an error:
[uncaught application error]: TypeError - Cannot read properties of undefined (reading 'where')
Here is the code:
async register(context: any) {
const body = JSON.parse(await context.request.body().value);
const existing = await Users.where("email", body.email).get();
if (existing.length) {
context.response.status = 400;
return (context.response.body = { message: "User already exists" });
}
const hashedPassword = await Users.hashPassword(body.password);
const user = await Users.create({
email: body.email,
hashedPassword,
});
context.response.body = { message: "User created" };
}
Here is my model:
// import { Model, DataTypes } from "https://deno.land/x/denodb/mod.ts";
import { DataTypes, Model } from "https://deno.land/x/denodb/mod.ts";
import * as bcrypt from "https://deno.land/x/bcrypt/mod.ts";
import {
create,
getNumericDate,
verify,
} from "https://deno.land/x/djwt/mod.ts";
import { JwtConfig } from "../middleware/jwt.ts";
import { db } from "../db.ts";
class Users extends Model {
static table = "users";
static timestamps = true;
static fields = {
id: {
primaryKey: true,
type: DataTypes.STRING,
},
email: {
type: DataTypes.STRING,
unique: true,
},
hashedPassword: {
type: DataTypes.STRING,
},
};
static defaults = {
id: crypto.randomUUID(),
};
// ...
static async hashPassword(password: string) {
const salt = await bcrypt.genSalt(8);
return bcrypt.hash(password, salt);
}
static generateJwt(id: string) {
// Create the payload with the expiration date (token have an expiry date) and the id of current user (you can add that you want)
const payload = {
id,
iat: getNumericDate(new Date()),
};
// return the generated token
return create({ alg: "HS512", typ: "JWT" }, payload, JwtConfig.secretKey);
}
}
//db.link([Users]);
//await db.sync();
export default Users;
Had to uncomment this:
db.link([Users]);

Input not updating on react testing library, thus test failing, however it does update on the actual app

I want to test that when i type a value in an input(inputA), anoter input(inputB) gets updated with a value.
inputA accepts a postal code e.g: "10999", after inputB shows a location: "Berlin"
This works on the actual app, i type in inputA, and inputB gets updated.
When ome types on inputA, an action is dispatched and then inputB gets a new value from the redux state.
This is my test code, any ideas why it doesnt updates the input with placeholder of "Ort" on the test, but it does on the actual app?
import { render, withIntl, withStore, configureStore, withState } from "test-utils-react-testing-library";
import { screen, fireEvent, withHistory, withRoute, within } from "#testing-library/react";
import configureMockStore from 'redux-mock-store';
import ProfileForm from "./ProfileForm";
import PersonalDetails from "../PersonalDetails/PersonalDetails";
const STATE = {
locations: { locations: {} },
streets: { streets: {} },
password: {}
};
const mockStore = configureMockStore();
const STORE = mockStore({
streets: {
isFetching: false,
},
locations: {
locations: {
isFetching: false,
},
},
user: {
session: {
impersonated_access_token: "",
},
updateError: "error",
},
});
const props = {
id: "user1",
user: { email: "max#muster.de" },
locations: {},
onSubmit: jest.fn(),
};
beforeEach(jest.resetAllMocks);
describe("ProfileForm", () => {
describe("on personal details change", () => {
it("auto selects only location when postalcode becomes selected", () => {
const locations = { electricity: { [PLZ_1]: [LOCATION_OBJ_1] } };
const user = { postalcode: null };
render(<ProfileForm {...props} user={user} locations={locations} />, [...decorators, withStore(STORE)]);
const input = screen.getByPlaceholderText("PLZ");
fireEvent.change(input, { target: { value: "10999" } })
screen.debug(screen.getByPlaceholderText("PLZ"))
screen.debug(screen.getByPlaceholderText("Ort"))
expect(screen.getByPlaceholderText("Ort")).toHaveValue("Berlin");
});
});
I guess your input hasn't been updated yet.
Try to use waitfor:
https://testing-library.com/docs/dom-testing-library/api-async#waitfor
import { waitFor } from "#testing-library/react";
const inputNode = screen. getByPlaceholderText("Ort");
// keep in mind that you need to make your test async like this
// it("auto selects only location when postalcode becomes selected", async () => {
await waitFor(() => expect(inputNode).toHaveValue("Berlin"));
If it won't work, try to add timeout:
await waitFor(() => expect(inputNode).toHaveValue("Berlin"), { timeout: 4000 });
I've encountered a similar proplem and found that changes in the microtask queue aren't always flushed, so the changes are not applied/rendered until the test is finished running. What worked for me, was to call jest.useFakeTimers() at the beginning of your testcase, and then await act(async () => { jest.runOnlyPendingTimers() }); after the call to fireEvent.<some-event>(...)
In your case:
it("auto selects only location when postalcode becomes selected", async () => {
jest.useFakeTimers();
const locations = { electricity: { [PLZ_1]: [LOCATION_OBJ_1] } };
const user = { postalcode: null };
render(<ProfileForm {...props} user={user} locations={locations} />, [...decorators, withStore(STORE)]);
const input = screen.getByPlaceholderText("PLZ");
fireEvent.change(input, { target: { value: "10999" } })
await act(async () => {
jest.runOnlyPendingTimers();
});
screen.debug(screen.getByPlaceholderText("PLZ"))
screen.debug(screen.getByPlaceholderText("Ort"))
expect(screen.getByPlaceholderText("Ort")).toHaveValue("Berlin");
});
Tried, but get this error: Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. No idea where that comes from :(
Try to use findBy instead of getBy.
https://testing-library.com/docs/dom-testing-library/api-queries#findby
import { screen, waitFor } from "#testing-library/react";
const inputNode = await screen.findByPlaceholderText("Ort");
// or with timeout: await screen.findByPlaceholderText("Ort", { timeout: 4000 });
await waitFor(() => expect(inputNode).toHaveValue("Berlin"));

Maximum call stack size exceeded( in Nuxt + Firebase Project)

I'm currently creating an authentication feature in Nuxt and Firebase.
The login and logout process itself can be done and the header display changes accordingly, but there is an error in console when I press the login button.
Error content (in console)
Uncaught RangeError: Maximum call stack size exceeded
at Function.keys (<anonymous>)
code
Header.vue(This is the page containing the login button.)↓
googleLogin () {
const provider = new firebase.auth.GoogleAuthProvider()
auth.signInWithPopup(provider)
.then(res => {
this.dialogAuthVisible = false
this.$store.dispatch('auth/setUser',res.user)
}).catch(e => console.log(e))
}
store/auth.js↓
export const strict = false
export const state = () => ({
user: null
})
export const mutations = {
SET_USER (state, payload) {
state.user = payload
}
}
export const actions = {
setUser ({ commit }, user) {
commit('SET_USER',user)
}
}
export const getters = {
isAuthenticated (state) {
return !!state.user
}
}
default.vue↓
mounted () {
auth.onAuthStateChanged(user => {
const { uid, displayName, photoURL} = user
if (user) {
this.$store.dispatch('auth/setUser', { uid, displayName, photoURL})
} else {
this.$store.dispatch('auth/setUser', null)
}
})
}
If there's any information I'm missing, please let me know 🙇️.
Please teach me how to do this 🙇️.
I think the problem is in this code lines :
export const mutations = {
SET_USER (state, payload) {
state.user = payload
}
}
export const actions = {
setUser ({ commit }, user) {
commit('SET_USER',user)
}
}
There is a loop between this mutations and actions
Instead of setting the entire payload into the store object, I just picked the fields I needed, and that resolved the problem for me.
Before:
AUTH_STATUS_CHANGED ({commit}, data: any): any {
if (data && data.authUser) {
commit('SetAuthUser', data.authUser);
} else {
commit('SetAuthUser', null);
}
}
After:
AUTH_STATUS_CHANGED ({commit}, data: any): any {
if (data && data.authUser) {
const user = data.authUser;
commit('SetAuthUser', {
uid: user.uid,
email: user.email,
emailVerified: user.emailVerified,
displayName: user.displayName,
isAnonymous: user.isAnonymous,
photoURL: user.photoURL,
stsTokenManager: user.stsTokenManager,
createdAt: user.createdAt,
lastLoginAt: user.lastLoginAt,
apiKey: user.apiKey,
});
} else {
commit('SetAuthUser', null);
}
}
Inside the mutation, just add the value received from the mutation payload.

Resources