Convincing TS that an object coming through a proxy is never null - javascript

I have the following fn that creates a proxy:
const getStorageProxy = (store?: Storage) => {
const proxyObj: { store?: DataStorage } = {};
return new Proxy(proxyObj, {
get(obj, prop) {
if (!obj.store) {
obj.store = new DataStorage(store);
}
return obj.store[prop];
},
});
};
And then I export several instances of this proxy:
const storageA = getStorageProxy(storeA);
const storageB = getStorageProxy(storeB);
export { storageA, storageB };
The reason for this is that I don't want to actually instantiate the DataStorage class unless it's used, and for convenience I want to be able to type:
import { storageA } from 'storage';
This all works. The problem is that according to TS both of my exported storage instances are of type DataStorage | void because of:
const proxyObj: { store?: DataStorage } = {};
They're actually not because of the getter in the proxy, but how do I tell this to TS without actually assigning something to proxyObj.store on instantiation?

Related

Prevent copy of class instance when importing in two different files - JavaScript?

I have a class like this: Code Sandbox
(In the sandbox, I have imported file1 so that that part of code gets executed first and then trying to access the same using file2 however it returns undefined)
import crypto from 'crypto';
const deleteMillis = 3600 * 1000;
class Security {
activeSessions: Record<string, { ua: string }>;
constructor() {
this.activeSessions = {};
}
getSession(session: string, ua: string): { ua: string } | undefined {
const currentSession = this.activeSessions[session];
console.log(this.activeSessions, this.activeSessions[session], session);
if (!currentSession) return;
if (ua !== currentSession.ua) return;
return currentSession;
}
addSession(ua: string): string {
const session = crypto.randomUUID();
this.activeSessions[session] = {
ua
};
setTimeout(() => {
this.removeSession(session);
}, deleteMillis);
return session;
}
removeSession(session: string) {
delete this.activeSessions[session];
}
}
const security = new Security();
export default security;
I want to use security instance of this class into multiple es module files. Let's say file1.js and file2.js.
Now the problem is that when i do an import:
import security from "#/security"; // file-1
import security from "#/security"; // file-2
The problem with the above is that the data doesn't remain synced in both the files and they operate independently.
these two files create two different instances however I wanted to use one instance in both the files. How can I achieve the same? Do I need to create a third file or what exactly should be the approach?
As suggested in the answer, I tried this:
class Security {
activeSessions: Record<string, { ua: string }>;
private static instance: Security;
constructor() {
this.activeSessions = {};
}
getSession(session: string, ua: string): { ua: string } | undefined {
const currentSession = this.activeSessions[session];
console.log(this.activeSessions, this.activeSessions[session], session);
if (!currentSession) return;
if (ua !== currentSession.ua) return;
return currentSession;
}
addSession(ua: string): string {
const session = crypto.randomUUID();
this.activeSessions[session] = {
ua
};
setTimeout(() => {
this.removeSession(session);
}, deleteMillis);
return session;
}
removeSession(session: string) {
delete this.activeSessions[session];
}
static getInstance(): Security {
if (!Security.instance) {
Security.instance = new Security();
}
console.log('Security instance', Security.instance);
return Security.instance;
}
}
const security = Security.getInstance();
export default security;
However this also fails to preserve the instance.
Here's an approach, we define a static method getInstance in the Security class and call it to get Security instance in other files
class Security {
private static instance: Security;
// other methods and variables
static getInstance(): Security {
if (!Security.instance) {
Security.instance = new Security();
}
return Security.instance;
}
}
export default Security;
Usage example
let securityInstance = Security.getInstance();
securityInstance.getSession();
This is called the Singleton design pattern, you can read more about it here Singleton design pattern
The shown code works for me (the session object is the same for both files). It is, however, not clear what you want to achieve with the code.
In file1.js you have security.addSession("abc", "deg") in file2.js you have security.getSession("abc", "def"). (deg vs def).
After calling security.addSession("abc", "deg") the contents of this.activeSessions['abc'] is: {ua : "deg"}.
When you call security.getSession("abc", "def") you have the test if (ua !== currentSession.ua) return; which compares ua which is def with currentSession.ua which is deg and that is not equal, so you return undefined.

Lit-translate shows code or key instead of translation

I am trying to use lit-translate to translate my "Elmish" typescript website into different languages. I use webpack and dotnet.
Inside my index.ts I register the translate config:
registerTranslateConfig({
lookup: (key, config) => config.strings != null ? config.strings[key] : key,
empty: key => key,
loader: lang => {
return new Promise((resolve, reject) => {
resolve({"title": "Der Titel"});
})}
});
use("en");
(The loader is hardcoded because getting the localization file also didn't work, but that's not important for now).
Inside html I use get("title")or translate("title") to get the translation.
Instead of the translation, I either read [title] or
(part) => { partCache.set(part, cb); updatePart(part, cb); }
If I assign the result of translate() to a variable, I get the following result inside the Object:
TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
at Function.r (<anonymous>:1:83)
at Module../src/index.ts (http://localhost:8080/app.bundle.js:9800:10)
at __webpack_require__ (http://localhost:8080/app.bundle.js:12476:33)
at http://localhost:8080/app.bundle.js:13494:11
at http://localhost:8080/app.bundle.js:13497:12
I already tried disabling webpack strict mode.
The full class looks like the following:
export class App extends Application<Model, Message, {}> {
#property({type: Boolean})
hasLoadedStrings = false;
init(): [Model, Cmd] {
const initModel: Model = {
openPage: [{title: "Home"}, home]
}
return [initModel, Cmd.none]
}
constructor() {
super();
this.hasLoadedStrings = false;
}
shouldUpdate (changedProperties: Map<string | number | symbol, unknown>) {
return this.hasLoadedStrings && super.shouldUpdate(changedProperties);
}
async connectedCallback () {
super.connectedCallback();
await use("en");
this.hasLoadedStrings = true;
}
}
customElements.define('my-element', App);
I solved the problem by writing my own little translation library.
The lit-translate package contains errors if you use it as suggested in the documentation so feel free to use my solution:
translate.ts:
const de_config =
{
"Category": {
"Home": "Start",
},
"Home": {
"Welcome back,": "Willkommen zurück,"
};
export function translate(key: string) {
const keyParts = key.split(".");
const keyCategory = keyParts.shift();
const keyWord = keyParts.join('.');
let translationIndexes = typedKeys(de_config);
for(let translationIndex of translationIndexes)
{
if(translationIndex == keyCategory) {
let translationKeys = typedKeys(de_config[translationIndex]);
for(let translationKey of translationKeys) {
if(translationKey == keyWord) {
return de_config[translationIndex][translationKey];
}
}
}
}
return key;
}
function typedKeys<T>(o: T): (keyof T)[] {
return Object.keys(o) as (keyof T)[];
}
Access translations with:
import { translate } from './translate';
translate("Category.Home");
One could also store translation object in a different file, write a function to change language dynamically, etc...

I can't access modified window.location object jest in another module

I want to mock window.location.search.
config.test.js
import config from './config'
import { MULTIPLE_VIDEOS } from './Constants/flagKeys'
describe('', () => {
const flag = { [MULTIPLE_VIDEOS]: true }
global.window = Object.create(window)
Object.defineProperty(window, 'location', {
value: {}
})
afterAll(() => {
global.window = null
})
it('Mark query string flag as true', () => {
global.window.location.search = `?${MULTIPLE_VIDEOS}=true`
expect(config.flags).toEqual(flag)
})
})
config.js
export default { flags: getFlagsFromQueryString() }
function getFlagsFromQueryString () {
const queryString = qs.parse(window.location.search.slice(1))
const flags = {}
Object.entries(queryString).forEach(([name, value]) => {
flags[name] = value.toLowerCase() === 'true'
})
return flags
}
Though I set search value in location object before calling config.flags, I can't access it inside the function, It always returns empty string.
I want window.location.search.slice(1) to return ?multipleVideos=true inside getFlagsFromQueryString function instead of empty string, since I changed the value in the test file.
One thing I noticed, when I export the function and call in the test file it works.
For this type of complex uses. I would recommend you to create a Window service/util class and expose methods from there. easy to test and mock.
Sample:
// Window.js
class Window {
constructor(configs) {}
navigate(href) {
window.location.href = href;
}
}
export default new Window({});
Now you can easily mock Window.js. It is similar to DI pattern.

How to create Class dynamically in a proper way in Typescript?

I am trying to migrate my app to typescript. I have kind of a base class that is my base library object. My classes created dependent on the base class. Below is a glimpse of my problem.
Below code works but autocomplete doesn't work. Couldn't figure out what should be defined for the type of Model.
const map = new WeakMap();
function weakRef<T>(context: T): T {
// #ts-ignore
if (!map.has(context)) { map.set(context, {}); }
// #ts-ignore
return map.get(context);
}
function getModel(provider: Provider) {
return class Model {
getSomething(key: string) {
return weakRef(provider).configuration(key);
}
};
}
class Provider {
configuration: (key: string) => string;
constructor() {
weakRef(this).configuration = (key: string) => {
return key;
};
weakRef(this).Model = getModel(this);
}
get Model(): any {
return weakRef(this).Model;
}
set Model(model: any) {
weakRef(this).Model = model;
}
}
const provider = new Provider();
const obj = new (provider.Model)();
console.log(obj.getSomething('test')); // This works, but autocomplete doesn't
I don't want to pass provider to the constructor in base model.
Any help is appreciated, thanks.
You can get the return type of a function type with ReturnType<T>.
In this case, ReturnType<typeof getModel> should do the trick.
You can also fix the //#ts-ignore lines by constraining T to be an object type.
const map = new WeakMap();
function weakRef<T extends object>(context: T): T {
if (!map.has(context)) { map.set(context, {}); }
return map.get(context);
}
That said, I think this architecture is unnecessarily complicated. Why not just use this instead of a value tied to this?

Convert Namespaced Javascript Functions to Typescript

I know that all javascript is valid typescript but I'd like to start converting my javascript to typescript conventions. I'm battling this one snippet of JS:
My standard Javascript that works
if (MyCompany === undefined) {
var MyCompany = {};
}
MyCompany.Uploader = MyCompany.Uploader || {};
MyCompany.Uploader.Core = function (config) {
'use strict';
function build() {
console.log("building");
}
return {
build: build
};
};
var config = {this: "that};
MyCompany.Uploader.Core(config).build(); // outputs building to console
I've been messing with multiple approaches and I feel like I not close enough.
My failed attempt at converting to Typescript
namespace MyCompany.Uploader {
export var Core = (config:any) => {
function build() {
console.log("building");
}
};
}
let configobj = {here:"there"};
MyCompany.Uploader.Core(configobj).build();
This simply doesn't work. I can't seem to access the build function. I'm sure this is a rookie mistake.
The error I get: Property build does not exist on type void
That's because you did not add an important part of your javascript code into the typescript version, and that's the return object which contains the reference for the build function, it should be:
namespace MyCompany.Uploader {
export var Core = (config: any) {
function build() {
console.log("building");
}
return {
build: build
}
};
}
let configobj = { here: "there" };
MyCompany.Uploader.Core(configobj).build();
You can also define interfaces for the config and the return object:
namespace MyCompany.Uploader {
export interface Config {
here: string;
}
export interface Builder {
build: () => void;
}
export var Core = (config: Config): Builder => {
function build() {
console.log(config.here);
}
return {
build: build
}
};
}
let configobj = { here: "there" };
MyCompany.Uploader.Core(configobj).build();

Categories