How to dynamically import module for nitro on nuxt app - javascript

Im trying to dynamically load modules from a nitro server in a nuxt app, but I get the following error:
Cannot find module projectpath/.nuxt/services/listing imported from projectpath/.nuxt/dev/index.mjs
This is the snippet of code Im using for the handler where the dynamic import should take place:
export default defineEventHandler(async (event) => {
const { method, resource, paramValue } = parseRequestResource(event.node.req)
let ServiceInstance = services[resource]
if (ServiceInstance) {
return callResourceMethod(ServiceInstance, method, paramValue, event)
} else {
try {
ServiceInstance = await import(`../services/${resource}`)
} catch (error) {
const Proto = Object.assign({}, Service.prototype, { tableName: resource })
ServiceInstance = Object.create(Proto)
services[resource] = ServiceInstance
}
return callResourceMethod(ServiceInstance, method, paramValue, event)
}
})
How can I this to work? Is there some feature that nitro/nuxt have where I can do this?

I was able to achieve this functionality by using a nitro plugin. However the files being imported need to be *.mjs.
import fs from 'fs'
import { resolve } from 'path'
export default defineNitroPlugin(async (nitroApp) => {
const __dirname = resolve()
const servicesFolderPath = `${__dirname}/server/services`
const serviceFiles = fs.readdirSync(servicesFolderPath)
const services = {}
for (const fileName of serviceFiles) {
if (fileName == '__proto__.mjs') continue
try {
const moduleName = fileName.split('.')[0]
const module = await import(`${servicesFolderPath}/${fileName}`)
services[moduleName] = module.default
} catch (error) {
console.log(error);
}
}
nitroApp.$services = services
})

Related

Rollup Plugin: Using URL of emitted asset file into another emitted asset file

I'm building a simple .gltf plugin for rollup.
The plugin does the following:
Load the .gltf files
Calls emitFile() for any .jpg, .png or .bin dependencies found in the .gltf
Updates the .gltf source with the URLs of the emitted .jpg, .png and .bin
Calls emitFile() for the .gltf itself
The problem is in step 3: emitFile() returns an identifier and there is no way to access the URL using getFileName() until the generation phase has finished. Which means that getFileName() is still not usable in the transform() hook. Besides, I cannot use import.meta.ROLLUP_FILE_URL_${refId} because the file I'm generating is an asset file.
The workaround I've implemented is to patch the .gltf files after the generation but this is not ideal.
What is the proper way to insert emitted file URLs inside assets?
Here's the current code:
import fs from 'node:fs';
import path from 'node:path';
import { createFilter } from '#rollup/pluginutils';
export default function gltf(options = {}) {
const filter = createFilter(options.include, options.exclude);
const exported = [];
return {
name: 'gltf',
load(id) {
if (!id.endsWith('.gltf') || !filter(id)) return null;
const basepath = path.dirname(id);
const source = fs.readFileSync(id);
try {
const model = JSON.parse(source.toString());
const assets = {};
if (model.images) {
model.images.forEach(image => {
const ref = this.emitFile({
type: 'asset',
name: path.basename(image.uri),
source: fs.readFileSync(path.join(basepath, image.uri))
});
assets[image.uri] = ref;
// image.uri = 'import.meta.ROLLUP_FILE_URL_' + ref;
});
}
if (model.buffers) {
model.buffers.forEach(buffer => {
const ref = this.emitFile({
type: 'asset',
name: path.basename(buffer.uri),
source: fs.readFileSync(path.join(basepath, buffer.uri))
});
assets[buffer.uri] = ref;
// buffer.uri = 'import.meta.ROLLUP_FILE_URL_' + ref;
});
}
const ref = this.emitFile({
type: 'asset',
name: path.basename(id),
// source: JSON.stringify(model)
source
});
exported.push({ ref, assets });
return `export default import.meta.ROLLUP_FILE_URL_${ref};`;
} catch (err) {
this.error(err);
}
},
writeBundle(options) {
const done = new Set(); // keep track of the files already patched
exported.forEach(entry => {
const filename = this.getFileName(entry.ref);
if (done.has(filename)) return;
done.add(filename);
const filepath = path.join(options.dir, filename);
const source = fs.readFileSync(filepath);
try {
const model = JSON.parse(source);
if (model.images) {
model.images.forEach(image => {
const ref = entry.assets[image.uri];
if (ref) {
image.uri = path.basename(this.getFileName(ref));
}
});
}
if (model.buffers) {
model.buffers.forEach(buffer => {
const ref = entry.assets[buffer.uri];
if (ref) {
buffer.uri = path.basename(this.getFileName(ref));
}
});
}
fs.writeFileSync(filepath, JSON.stringify(model));
} catch (err) {
this.error(err);
}
});
}
}
}

pg-promise duplicate connection warning on console when set new column set

I'm new to nextjs and I'm creating API on next.js to perform db update using the pg-promise. However, it always hit the WARNING: Creating a duplicate database object for the same connection on console when the app is calling the API.
I tried browsing the docs but couldn't find a solution. I also tried solution (update-2) mentioned on stackoverflow page below, but the warning still exists.
Where should I initialize pg-promise
I think the problem is on the method I used to set the columnset. However I can't find proper way to do it. How should I fix it with pg-promise ?
Db setting code:
import ConfigEnv from 'utils/configuration';
import * as pgLib from 'pg-promise';
const initOptions = {
capSQL: true,
};
const pgp = require('pg-promise')(initOptions);
interface IDatabaseScope {
db: pgLib.IDatabase<any>;
pgp: pgLib.IMain;
}
export function createSingleton<T>(name: string, create: () => T): T {
const s = Symbol.for(name);
let scope = (global as any)[s];
if (!scope) {
scope = {...create()};
(global as any)[s] = scope;
}
return scope;
}
export function getDB(): IDatabaseScope {
return createSingleton<IDatabaseScope>('my-app-db-space', () => {
return {
db: pgp(ConfigEnv.pgp),
pgp
};
});
}
API code:
import {getDB} from 'db/pgpdb';
const {db, pgp} = getDB();
const cs = new pgp.helpers.ColumnSet([
'?detail_id',
'age',
'name'
// 'last_modified_date',
], {
table: 'user_detail',
})
export default async (req, res) => {
try {
// generating the update query where it is needed:
const update = pgp.helpers.update(req.body.content, cs) + ` WHERE v.detail_id = t.detail_id`;
// executing the query
await db
.none(update)
.then(() => {
return res.status(200).end();
})
.catch((error) => {
console.log('error', error);
return res.status(500).send(error);
});
} catch (error) {
console.log(error);
}
};

How to initialize App Data in node js and access it without being undefined in jest test?

i am initializing a node js app with crucial data for the app to work from a database in index.js.
index.ts
import {getInitialData} from 'initData.ts';
export let APP_DATA: AppData;
export const initializeAppData = async () => {
try {
APP_DATA = (await getInitialData()) as AppData;
if (process.env.NODE_ENV !== 'test') {
initializeMongoose();
startServer();
}
} catch (error) {
console.log(error);
}
};
initData.ts
let dbName: string = 'initialData';
if (process.env.NODE_ENV === 'test') {
dbName = 'testDb';
}
const uri = `${process.env.MONGODB_URI}/?maxPoolSize=20&w=majority`;
export async function getInitialData() {
const client = new MongoClient(uri);
try {
await client.connect();
const database = client.db(dbName);
const configCursor = database
.collection('config')
.find({}, { projection: { _id: 0 } });
const config = await configCursor.toArray();
const aaoCursor = database
.collection('aao')
.find({}, { projection: { _id: 0 } });
const aao = await aaoCursor.toArray();
return { config, aao };
} catch {
(err: Error) => console.log(err);
} finally {
await client.close();
}
}
I'm using this array in another file and import it there.
missionCreateHandler
import { APP_DATA } from '../index';
export const addMissionResources = (
alarmKeyword: AlarmKeyword,
newMission: MissionDocument
) => {
const alarmKeywordObject = APP_DATA?.aao.find(
(el) => Object.keys(el)[0] === alarmKeyword
);
const resourceCommand = Object.values(alarmKeywordObject!);
resourceCommand.forEach((el) => {
Object.entries(el).forEach(([key, value]) => {
for (let ii = 1; ii <= value; ii++) {
newMission.resources?.push({
initialType: key,
status: 'unarranged',
});
}
});
});
};
I'm setting up a mongodb-memory-server in globalSetup.ts for Jest and copy the relevant data to the database from json-files.
globalSetup.ts
export = async function globalSetup() {
const instance = await MongoMemoryServer.create({
instance: { dbName: 'testDb' },
});
const uri = instance.getUri();
(global as any).__MONGOINSTANCE = instance;
process.env.MONGODB_URI = uri.slice(0, uri.lastIndexOf('/'));
process.env.JWT_SECRET = 'testSECRET';
const client = new MongoClient(
`${process.env.MONGODB_URI}/?maxPoolSize=20&w=majority`
);
try {
await client.connect();
const database = client.db('testDb');
database.createCollection('aao');
//#ts-ignore
await database.collection('aao').insertMany(aao['default']);
} catch (error) {
console.log(error);
} finally {
await client.close();
}
};
missionCreateHandler.test.ts
test('it adds the correct mission resources to the array', async () => {
const newMission = await Mission.create({
address: {
street: 'test',
houseNr: 23,
},
alarmKeyword: 'R1',
});
const expected = {
initialType: 'rtw',
status: 'unarranged',
};
addMissionResources('R1', newMission);
expect(newMission.resources[0].initialType).toEqual(expected.initialType);
expect(newMission.resources[0].status).toEqual(expected.status);
});
When runing the test, i get an 'TypeError: Cannot convert undefined or null to object at Function.values ()'. So it seems that the APP_DATA object is not set. I checked that the mongodb-memory-server is set up correctly and feed with the needed data.
When i hardcode the content of APP_DATA in index.ts, the test runs without problems.
So my questions are: How is the best practice to set up initial data in a node js app and where to store it (global object, simple variable and import it in the files where needed)? How can the test successfully run, or is my code just untestable?
Thank you!

Error: Fixture "activityLogPage" has unknown parameter "TestRailController"

I have a bit of code here that keeps failing due to Error: Fixture "activityLogPage" has unknown parameter "TestRailController", I am sure this is user error on my end but I cant quite figure it out. Below this is the TestRailController I am trying to call.
activitylogpage.ts --->
import { ActivityLogSelectors } from '../../specs/types/activityLog.type';
import { Page } from '#playwright/test';
import test from '../../common/testrail/fixtures';
export const activityLogSelectors: ActivityLogSelectors = {
datasource_id: 'label[for^=datasource_id]',
categories: 'label:has-text("Access Control: Authentication")',
policy: 'input[id^=downshift]',
};
type ActivityLogFixtures = {
activityLogPage: Page;
};
const activityLogTest = test.extend<ActivityLogFixtures>({
activityLogPage: async ({ page, TestRailController }, use) => {
TestRailController.startNewTest('1');
await page.goto('/');
await page.waitForURL(/risk-posture/i);
await page.click('a:has-text("Activity Log")');
await page.waitForSelector('text=Clear');
await use(page);
},
});
export default activityLogTest;
fixtures.js. --->
const base = require('#playwright/test');
const TestRailController = require('./testrail.interface.js');
module.exports = base.test.extend({
// https://playwright.dev/docs/test-advanced/#testinfo-object
testrailController: async ({ browser }, use, testInfo) => {
const testrailController = new TestRailController();
await use(testrailController);
for (const context of browser.contexts()) {
// eslint-disable-next-line no-await-in-loop
await context.close();
}
// only update testrail when process.env.APP_VERSION is valid string
// testInfo is needed here because this below part will be run before test started while testInfo is null
// use testrail feature for saving test-result
if (appVersion && testInfo) {
await testrailController.updateAllTests(testInfo);
} else {
console.log('Env var APP_VERSION or testInfo is not available, will not update testrail case');
}
},
});

How to access saved json file by AsyncStorage in layoutRedux file?

I'm upldating my app layout by json loaded from api in app.js (saved by reloadLayoutsConfig function). When I want to access my data inside app everything is ok but how can I load layout inside my layoutRedux file?
export const reloadLayoutsConfig = async (newLayoutsConfig) => {
try {
await AsyncStorage.setItem("#LayoutsConfig", JSON.stringify(newLayoutsConfig));
} catch (error) {
console.log("error save user data", error);
}
};
export const getLayoutsConfig = async () => {
try {
const LayoutsConfig = await AsyncStorage.getItem("#LayoutsConfig");
return JSON.parse(LayoutsConfig);
} catch (error) {
console.log(error);
}
};
These two functions work like a charm, whenever I need a value I just do this:
getLayoutsConfig().then((LayoutsConfig) => {
this.setState({ LayoutsConfig : LayoutsConfig });
});
This is my layoutRedux:
import { getLayoutsConfig} from '#config'
const types = {
// LOTS OF CODE
}
const initialState = {
layout: [],
isFetching: false,
}
var layouts = [];
// OLD TYPE FROM STORED JSON FILE IN CODE
var layouts = [...LayoutsConfig];
// NEW WAY FROM LOADED JSON FILE FROM API
getLayoutsConfig().then((LayoutsConfig) => {
layouts = LayoutsConfig;
});
initialState.layout = layouts;
export const actions = {
// LOTS OF CODE
}
export const reducer = (state = initialState, action) => {
// LOTS OF CODE
}
I have mentioned my old and new way to access json file in same code for you to check it. In older version I used to have layout.json file in my code and when I needed to access file i just used it like this:
import { LayoutsConfig } from '#config'
var layouts = [...LayoutsConfig];
in config :
export const LayoutsConfig = AppConfig.LayoutsConfig;
but now i call it like this :
var layouts = [];
getLayoutsConfig().then((LayoutsConfig) => {
layouts = LayoutsConfig;
});
I get error like layout is not loaded. What can I do and how to call my function?

Categories