TypeScript Defining a hash table of functions - handlebars.js

I'm trying to create a definition file for Handlebars, for use with pre-compiled handlebar scripts. Handlebars will put pre-compiled scripts into a string indexed hash table of functions, but I can't figure out or find how this would be defined.
A hypothetical definition would be:
declare module Handlebars {
export var templates: { (model:any) => string; }[index: string];
}
but that's not a valid definition. The definition should work for a call like this:
var myHtml = Handlebars.templates["person-template"]({FNmae: "Eric"});
A definition like this is close:
export var templates: { (model:any) => string; }[];
But that's an array with a numeric index, and it's not the same thing, and VS Intellisense just decides that the functions in the array are any.

What you want to use is an object type with an index signature (see spec section 3.5.3, specifically 3.5.3.3).
declare module Handlebars {
export var templates: {
[s: string]: (model: any) => string;
}
}

Related

zod (or toZod): how to model "type" field in discriminated union

I have this type:
export interface ConnectorForModel {
_type: "connector.for.model",
connectorDefinitionId: string
}
I want to model is as a zod schema. Actually I am using toZod, like this:
export const ConnectorForModelZod: toZod<ConnectorForModel> = z.object({
_type: z.literal("connector.for.model"),
connectorDefinitionId: z.string()
});
And I get this type error:
Type 'ZodLiteral<"connector.for.model">' is not assignable to type 'never'.
Whats the right way to express this?
I think the quickest way to get this working is with ZodType:
import { z } from "zod";
export interface ConnectorForModel {
_type: "connector.for.model";
connectorDefinitionId: string;
}
export const ConnectorForModelZod: z.ZodType<ConnectorForModel> = z.object({
_type: z.literal("connector.for.model"),
connectorDefinitionId: z.string()
});
Aside: I tend to define my types from my zod schemas rather than the other way around. If you don't have control over the type that you're working with then the given approach is the way to go, but you could potentially avoid writing the same code twice using z.TypeOf on the zod schema
type ConnectorForModel = z.TypeOf<typeof ConnectorForModelZod>;
This type would be equivalent to your interface.

What is a good practice for adding flow type annotations on project specific named exports

Please correct me if i am wrong. As far as i understand it up till now; type annotations can be added to a file or in libdefs (for shareable code)
For example in a project specific file helpers.js
// #flow
export function square(value: number): number {
return value * value
}
export function someOtherFunction(arg: string): string {
}
etc...
And in a libdef helpers.js
declare module 'helpers' {
declare export function square(value: number): number;
declare export function someOtherFunction(arg: string): string;
}
What would be a good practice for writing flow annotations on project specific code and especially lots of code. For example helpers exposing 20+ named exports, as this is the point where i am starting to think having a libdef would be more clearer to reason about.
And is it at all possible to use that libdef file as the single entry? I've fooled around a bit and i always had to annotate in the file itself even though i had added the libdef and told flow through the config to include these libdefs.
In our project, we use the following approach:
// #flow
export const square: SquareType = (value) => {
return value * value;
}
So you can declare SquareType in the helpers.js file just above the function or you can move it to a separate file and import it then into helpers.js
Many third-party modules don’t have types or only TypeScript types.
And libdefs need for one reason. To declare types for untyped modules!
More info: https://flow.org/en/docs/libdefs/

How to declare a flowtype library definition for polymorphic functions

What is the proper way to specify the type definitions a polymorphic method that depending on the parameter types has different return types?
index.js:
// #flow
import {func1} from './lib1';
const s: string = func1('string');
const b: boolean = func1(); // should cause type error but does not!
lib1.js:
export function func1(p) {
return (typeof p === 'string') ? p : 0;
}
defs/lib1.js.flow
// #flow
declare module "lib1" {
declare export function func1(p: string): string;
declare export function func1(_: void): number;
}
.flowconfig:
[libs]
defs/
I would have hoped to received an error message in index.js(4) but flow does not complain!
Yes, the example you gave is how to declare an overloaded function. However, you may want to change the second line to:
declare function myFunc(_: void): number;
Since Flow allows a function to be called with too many arguments (though not for much longer), it may select the second overload even if the function is called with a string. The modification I suggest makes it so the argument must be undefined (which is what is implicitly passed if you just leave off an argument).

exported typed functions that return functions should NOT require the returned function to be typed to work in other modules

For example if you are using redux-actions and have created the following module definition for it:
declare module 'redux-actions' {
declare type ActionType = string
declare type Action = {
type: ActionType,
payload?: any,
error?: bool,
meta?: any,
}
declare function createAction<T>(
type: string,
payloadCreator?: (...args: Array<T>) => any,
metaCreator?: Function
): (...args: Array<T>) => Action
}
and then you use that function to return a new function like this:
export const selectProfileTab = createAction('SELECT_PROFILE_TAB', (index: number) => {
playSound()
return { index }
})
and then in another file use it incorrectly like this:
selectorProfileTab('someString')
that won't report an error. It seems to be because flow requires annotations at the "boundaries" of modules. Am I correct?
Because the following does work:
export const selectProfileTab: (index: number) => any = createAction('SELECT_PROFILE_TAB', (index: number) => {
playSound()
return { index }
})
Notice I've annotated the returned function. The following will now produce an error:
selectProfileTab('someString')
I'm just trying to get a hold of this and verify this because it's a lot of extra "boilerplate" to annotate those returned functions, especially when it calling selectProfileTab('someString') would correctly produce an error if used in the same file. It makes you think: what's the point in creating module definitions for the redux-actions package which doesn't have them yet if it doesn't provide any/much value since you have to annotate your returned functions anyway. That's quite disappointing. Am I correct in determining that it's a module "boundaries" limitation/requirement with Flow? Are there any ways to get the desired result of not having to type returned functions that you export?

Where to put helper methods used in events for multiple Meteor templates

I know that I can access methods across files by omitting var, but what is the "best practices" project directory structure for defining helper methods that are used by template events across different templates.
For example, I have:
template1.js:
Template.template1.events({
'event': function () {
helper();
}
});
template2.js:
Template.template2.events({
'event': function () {
helper();
}
});
One problem with Meteor's "share code across files with globals" approach is that when you're looking at the file where a function is used, you don't know where the function is defined. I prefer to define a single global variable in each file which needs to export variables. That variable has the same name as the file, with an initial capital (or some other naming convention which identifies it as an "export object"). Exports are stored as properties of that object. So you might create a file named globalHelpers.js:
GlobalHelpers = {};
GlobalHelpers.helper = function () {
// ...
};
And then use it in other files with GlobalHelpers.helper(). If you look at this code later, you know to look in globalHelpers.js to find the helper function.
If a file exports a single class or collection then it's okay to just use that object as the export object. So instead of things.js with Things = {}; Things.Things = new Mongo.Collection... you can just do Things = new Mongo.Collection....
You might need to place the file in a lib directory so it loads before the other files.
Don't register it with Template.registerHelper unless you want to call it directly from templates with {{ }} calls.
I suggest defining such functions in /client/scripts/globalHelpers.js
Example:
Template.registerHelper('foo',function(arg1,arg2){
return arg1 + arg2;
});
Then you can use this function from anywhere by prefixing it with Blaze._globalHelpers.:
var result = Blaze._globalHelpers.foo(param1, param2);

Resources