Do Typescript Interfaces allow you to discard attributes not defined on the interface? - interface

I have a function that performs a search. The search can be done a few different ways (by looking for an ID or by querying a few attributes). However, I want to limit what attributes can be passed in. I thought I could do something like:
interface Search {
_id?: string
people?: number
partyName?: string
otherField? string
}
function search(query: Search) {
myDbConnection.find(query).then(... // etc
}
The problem is that any object will conform to this, and query can contain extra attributes. For example, this could be passed:
search({otherField: "foo", aProtectedField: "bar"})
and aProtectedField would be passed along to find.
I am wondering if there is a typescript way of enforcing the attributes passed. Sort of strong-parameters from the Rails world. I know I can do things like pick form lodash or maybe even make a SearchObject class and use the constructor as a means of discarding the extra attributes, but I feel like there is a way to do this within Typescript that I just don't know about.

You could make all the properties required then do an assertion to pass in a subset of the properties.
For example:
interface Search {
_id: string;
people: number;
partyName: string;
otherField: string;
}
function search(query: Search) {
// code here
}
search({ people: 2 } as Search); // ok
search({ otherField: "foo", aProtectedField: "bar" }); // error, good
search({ otherField: "foo", aProtectedField: "bar" } as Search); // error, good

What version of Typescript are you on? Since Typescript 1.6 there has been improved checking for object literals.
On TS 1.8 when I try to run your code I get:
error TS2345: Argument of type '{ otherField: string; aProtectedField: string; }' is not assignable to parameter of type 'Search'.
Object literal may only specify known properties, and 'aProtectedField' does not exist in type 'Search'.
This and noImplicitAny should catch the errors you're worried about.

I want to limit what attributes can be passed in. [...] query can contain extra attributes.
The way I see it, this is controversial. If you limit what attributes a given object might contain, then it is, by definition, limited to that set of attributes, and cannot contain others not allowed by its specs.
Since there is practically nothing you can do about the any type, my recommendation is to resolve this the type-safe way, by defining an option for additional attributes:
interface Search {
_id?: string;
people?: number;
partyName?: string;
additionalFields?: { [key: string]: any };
}
search({ people: 2 }); // ok
search({ _id: "jd", people: 2 }); // ok
search({ _id: "jd", additionalFields: { otherField: "foo" } }); // ok

Related

Typescript with MongoDB findOne method not working with generic

I am writing a class to clean up a lot of my existing database code. The first step for this was writing a class to manage Collections for me, which I attempted to do using a class with a generic.
interface MongodbItem {
_id?: ObjectID
}
class CollectionManager<DataType extends MongodbItem> {
database: Database;
collection: Collection<DataType>;
// ...
async get(id?: ObjectID) {
if (!id) return await this.collection.find({}).toArray();
return await this.collection.findOne({ _id: id });
}
}
However, despite defining the DataType generic as having that _id Typescript gives me the following error (On the line with the .findOne):
Argument of type '{ _id: ObjectID; }' is not assignable to parameter of type 'FilterQuery<DataType>'.
Type '{ _id: ObjectID; }' is not assignable to type '{ [P in keyof DataType]?: Condition<DataType[P]>; }'
reading through the handbook it looks like extending the generic in the way I am should enforce that it has an _id property, so I do not know why this error is still occuring.
Appears to be a but with Typescript itself:
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/40584
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/39358
Solved using workaround
return this.collection.findOne({ _id: id } as FilterQuery<DataType>);

pg-promise ColumnSet with nested object prop?

What is the proper syntax for a object child as a column prop?
const cs = new pgp.helpers.ColumnSet([
{
name: 'uid',
prop: 'id'
}, {
name: 'created_at'
prop: 'member.created_at // <-- error
}
])
Cant seem to get this to work.
While the regular pg-promise query formatting (using Named Parameters) supports nested properties, specifically helpers do not, due to certain templates-related complexity.
However, it is not necessary, because ColumnSet syntax for columns is very flexible (see type Column), and supports dynamic property resolution.
Simply update the column to use init, to get the value dynamically:
{
name: 'created_at',
init: c => c.source.member.created_at
}
For the field, we go to the source object, as per documentation, and take what we need.
Alternative syntax:
{
name: 'created_at',
init(c) {
// with this syntax, this = c.source
return this.member.created_at;
}
}

Comparing two objects in Joi validation (eg. to avoid duplicates)

I'm using Joi to validate a complex form entry. The form asks for two addresses, mainContactAddress and seniorContactAddress. I want to validate them to ensure they aren't the same address.
Each address is an object like this:
{
"line1": "123 Some Street",
"line2": "Some Town",
"county": "Some County",
"postcode": "123 ABC",
"townCity": "City"
}
I initially tried this:
Joi.ukAddress().invalid(Joi.ref('seniorContactAddress'))
(ukAddress() is a custom extension I've created which specifies each of the above fields as a required string.)
This doesn't work, because the equality === comparison between the two objects returns false even when they have the same string values.
I can't see a Joi method to do this. I was hoping to be able to serialise the object (eg. something like Object.values(mainContactAddress).join(',') and then compare the resulting strings) but Joi.ref() only gives, well, a reference to the object, so I can't call functions against it directly.
Any thoughts on how I could achieve this validation/comparison?
I ended up writing a custom rule for my extension:
{
// Enforce a unique address compared to the senior contact
name: 'mainContact',
validate(params, value, state, options) {
// Format addresses into a comparable string,
// making sure we sort them as the stored version
// is in a different order to the form-submitted one.
const serialize = address =>
Object.values(address)
.sort()
.join(',');
const seniorContactAddress = get(
state.parent,
'seniorContactAddress',
[]
);
if (serialize(seniorContactAddress) === serialize(value)) {
return this.createError(
'address.matchesSenior',
{ v: value },
state,
options
);
} else {
return value;
}
}
}
This does feel like an anti-pattern (eg. abusing state to look at other values in the Joi object) but it does what I needed.

must have a selection of subfields. Did you mean \"createEvent { ... }\"?", [graphql] [duplicate]

Hi I am trying to learn GraphQL language. I have below snippet of code.
// Welcome to Launchpad!
// Log in to edit and save pads, run queries in GraphiQL on the right.
// Click "Download" above to get a zip with a standalone Node.js server.
// See docs and examples at https://github.com/apollographql/awesome-launchpad
// graphql-tools combines a schema string with resolvers.
import { makeExecutableSchema } from 'graphql-tools';
// Construct a schema, using GraphQL schema language
const typeDefs = `
type User {
name: String!
age: Int!
}
type Query {
me: User
}
`;
const user = { name: 'Williams', age: 26};
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
me: (root, args, context) => {
return user;
},
},
};
// Required: Export the GraphQL.js schema object as "schema"
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
// Optional: Export a function to get context from the request. It accepts two
// parameters - headers (lowercased http headers) and secrets (secrets defined
// in secrets section). It must return an object (or a promise resolving to it).
export function context(headers, secrets) {
return {
headers,
secrets,
};
};
// Optional: Export a root value to be passed during execution
// export const rootValue = {};
// Optional: Export a root function, that returns root to be passed
// during execution, accepting headers and secrets. It can return a
// promise. rootFunction takes precedence over rootValue.
// export function rootFunction(headers, secrets) {
// return {
// headers,
// secrets,
// };
// };
Request:
{
me
}
Response:
{
"errors": [
{
"message": "Field \"me\" of type \"User\" must have a selection of subfields. Did you mean \"me { ... }\"?",
"locations": [
{
"line": 4,
"column": 3
}
]
}
]
}
Does anyone know what I am doing wrong ? How to fix it ?
From the docs:
A GraphQL object type has a name and fields, but at some point those
fields have to resolve to some concrete data. That's where the scalar
types come in: they represent the leaves of the query.
GraphQL requires that you construct your queries in a way that only returns concrete data. Each field has to ultimately resolve to one or more scalars (or enums). That means you cannot just request a field that resolves to a type without also indicating which fields of that type you want to get back.
That's what the error message you received is telling you -- you requested a User type, but you didn't tell GraphQL at least one field to get back from that type.
To fix it, just change your request to include name like this:
{
me {
name
}
}
... or age. Or both. You cannot, however, request a specific type and expect GraphQL to provide all the fields for it -- you will always have to provide a selection (one or more) of fields for that type.

Mongoose OR operator for schema definitions

Does Mongoose support, or is there an available package that supports multiple "options" for the embedded schemas in an array?
For example, the things property can contain only one of two schemas:
new Schema({
things: [{
requiredProp: String,
otherProp: Number
}, {
otherOption: Number
}]
});
In other words, I do not want to just allow anything (AKA Schema.Types.Mixed) to be stored in this property, but only these two possible definitions.
Or, do schema design recommendations exist to avoid this problem?
You should only define one dict in the array type of the schema, and then set if they are required or not with the mongoose schema types logic. Use pre save if you want to do more logic to assure that either one of the fields have been set, like this:
var MySchema = new Schema({
things: [{
requiredProp: {type: String, required: true},
otherProp: Number,
otherOption: Number,
}]
});
MySchema.pre('save', function(next) {
if (!this.otherProp && !this.otherOption) {
next(new Error('Both otherProp and otherOption can\'t be null'))
} else {
next()
}
})
Opon saving the object it will return an error if neither otherProp nor otherOption has been set.