Presetting values for a type using Generics - Typescript - typescript-generics

I know I'm way off track here but I'm trying to use generics to "preset" particular values of an "Event" type.
What I'm doing is as follows and what I'm expecting is "my-source" to be the console output.
I can see why this code doesn't work, but I'm putting it here to try and illustrate what I want to try and achieve.
type EventDetail = {
shopId: string;
};
export type Event<
TEventDetailType extends EventDetail,
TType extends string,
TSource extends string
> = {
source?: TSource;
body: TEventDetailType;
type?: TType;
};
export type UpdateShopCustomDomainEventDetail = EventDetail & {
domainName: string;
};
export type UpdateShopCustomDomainEvent = Event<
UpdateShopCustomDomainEventDetail,
"update-shop-custom-domain",
"my-source"
>;
const test:UpdateShopCustomDomainEvent = {body:{shopId:"123123", domainName:"asdf"}}
console.log(test.source);
// undefined
Essentially what I am wanting to do is something like the following. Where I send an event, but I define the type of event that I'm sending with a generic. This UpdateShopCustomDomainEvent will force the user to enter a value for shopId and domainName, and it will also set the type value to "update-shop-custom-domain" and the source to "my-source".
putEvent<UpdateShopCustomDomainEvent>({
body: {
shopId:"1234",
domainName:"mydomain.com"
}
});

Related

Switching feature in Handlebars Templates

So, we have created many templates using handlebars. Out of the many, we have one handlebar where we would like to make some changes that should only go live after a certain date. To do so, we wanted to create sort of a toggle switch, something like:
{{if switch on}}
display new content
{{else}}
display old content
Below is the generic template parser where I am trying to create a switch that I can inject in the if part of my template. Any suggestions?
/**
* Creates HTML output of the specified context for the given templateId and templateVersion combination
* we implicitly assume certain json fields (template specific) to be present in the content
*/
#Timed("handlebarsService.parseTemplateToHtml")
fun parseTemplateToHtml(htmlTemplateLocation: String, model: Map<String, Any>, locale: Locale): String {
val modelWithLanguage = model.toMutableMap()
modelWithLanguage[languageTag] = locale.language
//modelWithLanguage[switch] = "off"
val context = Context.newBuilder(modelWithLanguage)
.resolver(MapValueResolver.INSTANCE)
.build()
val template = try {
handlebars.compile(htmlTemplateLocation)
} catch (e: IOException) {
throw PdfGenerationException(e, "Internal error while compiling template")
}
return try {
template.apply(context)
} catch (e: IOException) {
throw PdfGenerationException(e, "Internal error while applying template")
}
}
}
private const val languageTag = "languageTag"
//private const val switch ="off"

Flow: why does it complain about string being null / undefined?

I am still try to grasp how Flow works, anyone could explain me why this simple example is throwing an error?
function say(text: string) {
console.log(text);
}
say('Hello World!'); // This is alright
const text: ?string = 'Hello World!';
say(text); // Error:(219, 5) Cannot call `say` with `text` bound to `text` because null or undefined [1] is incompatible with string [2].
I know, the text variable can be null, but by the time I call say(text) it is clearly not null.
Flow does not keep track of what you have assigned to what. It only tracks the types of the variables. And you are trying to pass type ?string to string, which isn't a valid assignment since it could be null. You know its not null but flow doesn't because it's not actually executing your code.
It's hard to give you good advice for a workaround because const text: ?string = 'Hello World!'; is a very contrived example, but you can use a refinement to only call say if text has been tested for a non-null value.
const text: ?string = 'Hello World!';
if (text) {
say(text);
}
The only time flow does track what you assign is on variable initialization for implicit typings. But this simply assigns the type of the right hand expression as the type of the variable.
let a: ?string = 'foo'
let b = a; // flow infers the type of b as ?string

Meaning of interrogation

I'm a little bit confuse about the meaning difference of using "?"
I offen saw this:
var foo?: number = "bar"
But also saw this:
function foo(bar: {baz: ?string}) { ... }
And also saw both together.
I've read about invariants and maybe types, but if I understood it right, both signals have the same meaning, which is: "this type is of kind 'X', but it maybe is null or undefined".
Is it right or am I getting it wrong?
Here are answers to most of your questions:
// Don't know what this is, or why you would use it
// Error: undefined is incompatible with string
var foo1?: string = undefined;
// ?string means string, null, or undefined
var foo2: ?string = undefined;
type FooOptional = { foo?: string };
type FooMaybe = { foo: ?string };
// If it's optional it can be completely omitted
var foo3: FooOptional = {};
// It can also be explicitly set to undefined
var foo4: FooOptional = { foo: undefined };
// But not null!
var foo5: FooOptional = { foo: null };
// If it's a maybe type, it must be specified
// Error: property `foo` not found
var foo6: FooMaybe = {};
// But you can set it explicitly to null or undefined
var foo7: FooMaybe = { foo: null };
var foo8: FooMaybe = { foo: undefined };
(tryflow link)
Using both together (e.g. {foo?: ?string} as a type) usually (but not in all cases) indicates that the author doesn't quite know what type they want to use and have just added question marks until it typechecks. Typically I have found that if I think it through, it makes sense to use either an optional property or a maybe type, but not both.

TypeScript - passing a class as an argument, and reflection

I am writing a generic unmarshaller. It converts graph DB data to generated TypeScript (1.8.7) model classes. The input is JSON. The output should be an instance of a model class.
My ultimate goal is to create something like Hibernate OGM, only for Tinkerpop Frames and TypeScript, with REST endpoint in the middle.
What's the right way to pass a class as a parameter and reach it's static members? I want to have something like this:
SomeModel some = <SomeModel> unmarshaller.fromJSON({/*Object from JSON*/}, SomeModel);
I've tried to write a method.
Not sure if I am heading in the right direction, feel free to suggest different approaches.
public fromJSON(input: Object, clazz: typeof FrameModel): FrameModel
{
// This only demonstrates access to Framemodel's metadata
// about original Java model classes.
clazz.graphPropertyMapping;
clazz.graphRelationMapping;
let result = {};
...
return result;
}
...
But when I tried to execute this on Plunker, I got execution errors with unuseful stacktrace.
The model superclass looks like this:
/**
* Things common to all Frames models on the Typescript side.
*/
export class FrameModel
{
// Model metadata
static discriminator: string;
static graphPropertyMapping: { [key:string]:string; };
static graphRelationMapping: { [key:string]:string; };
// Each instance needs a vertex ID
private vertexId: number;
public getVertexId(): number {
return this.vertexId;
}
}
Sample model class:
import {TestPlanetModel} from './TestPlanetModel';
import {TestShipModel} from './TestShipModel';
export class TestGeneratorModel extends FrameModel
{
static discriminator: string = 'TestGenerator';
static graphPropertyMapping: { [key:string]:string; } = {
bar: 'boo',
name: 'name',
rank: 'rank',
};
static graphRelationMapping: { [key:string]:string; } = {
colonizes: 'colonizedPlanet',
commands: 'ship',
};
boo: string;
name: string;
rank: string;
public colonizedPlanet: TestPlanetModel[]; // edge label 'colonizedPlanet'
public ship: TestShipModel; // edge label 'ship'
}
I haven't found much material on reflection and class handling in TypeScript.
I know how I would do this in Java.
I know how I would do this in JavaScript.
I understand that I might achieve similar results with decorators, but having fields or static fields seemed a bit simpler, for generated models.
You've maybe already noticed that class members cannot have const keyword. But you could go with static instead. Also member should be public if you want it to be accessible from outside world.
public static graphPropertyMapping: { [key:string]:string; } = {
bar: 'boo',
name: 'name',
rank: 'rank',
};
As for creating result instance:
let result = new clazz();
//copy properties
return result;
If I understand you correctly then here's something to help you get started:
interface Model {}
interface ModelData {}
interface MyModelConstructor<M extends Model, D extends ModelData> {
new(data: D): M;
// static members
graphPropertyMapping: any;
graphRelationMapping: any;
}
class Unmarshaller {
public fromJSON<T>(input: string | ModelData, ctor: MyModelConstructor<T, ModelData>): T {
let data: ModelData = (typeof input === "string") ? JSON.parse(input) : input;
let propertyMapping = ctor.graphPropertyMapping;
let relationMapping = ctor.graphRelationMapping;
// do whatever with the mappings
return new ctor(input);
}
}
(code in playground)
I don't know how your models look like, so I hope this is close enough.
I recently released an enhanced version of the TypeScript compiler that allows exactly what you are expecting: read all (static or not) fields metadata from a class. For example you can write:
interface MyInterface {
active:boolean;
description: string;
}
class MyClass {
id: number;
name: string;
myComplexField: MyInterface;
}
function printMembers(clazz: Class) {
let fields = clazz.members.filter(m => m.type.kind !== 'function'); //exclude methods.
for(let field of fields) {
let typeName = field.type.kind;
if(typeName === 'class' || typeName === 'interface') {
typeName = (<Class | Interface>field.type).name;
}
console.log(`Field ${field.name} of ${clazz.name} has type: ${typeName}`);
}
}
printMembers(MyClass.getClass());
this is the output:
$ node main.js
Field id of MyClass has type: number
Field name of MyClass has type: string
Field myComplexField of MyClass has type: MyInterface
Of course, if you change the members property access of clazz to statics you will retrieve all static members. These information can be accessed at coding time too, so you can use autocompletion.
You can do the same with Interfaces metadata. Simply write MyInterface for example, and access its members.
You can find the project here.

How do I extend/implement Mongo.Collection in Meteor with Typescript?

It appears that meteor/mongo.Mongo.Collection is an interface (according to these type declarations), and most of the sample code on the web shows how to extend that interface and then write code for methods in a separate block:
export interface List {
_id?: string;
name?: string;
incompleteCount?: number;
}
export interface CollectionLists extends Mongo.Collection<List> {
defaultName(): string;
}
export var Lists = <CollectionLists>(new Mongo.Collection<List>('lists'));
// Calculate a default name for a list in the form of 'List A'
Lists.defaultName = function(): string {
var nextLetter = 'A', nextName = 'List ' + nextLetter;
while (Lists.findOne({name: nextName})) {
// not going to be too smart here, can go past Z
nextLetter = String.fromCharCode(nextLetter.charCodeAt(0) + 1);
nextName = 'List ' + nextLetter;
}
return nextName;
};
Is there a way to rewrite this example with classes instead of this interface with separate code blocks for all the added methods?

Resources