How can I transform an object schema's property schemas to derive a new object schema? - joi

Is there a way to introspect and then transform a Joi schema?
I see there is any.describe() which "returns an object that represents the internal configuration of the schema" but then I have to map that internal configuration back to schema builder calls and re-create the schema myself.
e.g. given
const PersonSchema = Joi.object().keys({
firstName: Joi.string().required(),
lastName: Joi.string().required(),
age: Joi.number().required(),
});
I want to define a generic function to take any object schema and make its properties support arrays of the original type equivalent to the following:
const PersonFilterSchema = Joi.object().keys({
firstName: Joi.array().items(Joi.string().required()).optional(),
lastName: Joi.array().items(Joi.string().required()).optional(),
age: Joi.array().items(Joi.number().required()).optional(),
});
i.e. I want to be able to write something like this:
function createFilterSchema(schema) {
return Joi.object().keys(
Object.fromEntries(
schema
.getEntries()
.map(([key, value]) => [key, Joi.array().items(value).optional()])
)
);
}
and then elsewhere use it with whatever raw schemas I have:
const PersonFilterSchema = createFilterSchema(PersonSchema);

Related

Kmongo: how to add unique field

I have a simple user data class that looks like:
#Serializable
data class User(
#SerialName("_id")
val _id: Id<User> = newId(),
val email: String,
var password: String,
var tokens: Array<String> = arrayOf()
)
And I'd like the email value to be unique, i've tried a unique annotation which seemed most appropiate, but with no success.
I've also tried google and the KMongo website but I could not find an answer.
You need to create an index on the field (or combination of fields) that you want to ensure uniqueness on.
db.getCollection<User>().createIndex(User::email,
indexOptions = IndexOptions().unique(true))

Resolving auto-generated typescript-mongodb types for GraphQL output

I'm using the typescript-mongodb plugin to graphql-codegen to generate Typescript types for pulling data from MongoDB and outputting it via GraphQL on Node.
My input GraphQL schema looks like this
type User #entity{
id: ID #id,
firstName: String #column #map(path: "first_name"),
...
The generated output Typescript types look correct
export type User = {
__typename?: 'User',
id?: Maybe<Scalars['ID']>,
firstName?: Maybe<Scalars['String']>,
...
And the corresponding DB object
export type UserDbObject = {
_id?: Maybe<String>,
first_name: Maybe<string>,
...
The problem is when actually sending back the mongo document as a UserDbObject I do not get the fields mapped in the output. I could write a custom resolver that re-maps the fields back to the User type, but that would mean I'm mapping the fields in two different places.
i.e. I do not get mapped fields from a resolver like this
userById: async(_root: any, args: QueryUserByIdArgs, _context: any) : Promise<UserDbObject> => {
const result = await connectDb().then((db) => {
return db.collection<UserDbObject>('users').findOne({'_id': args.id}).then((doc) => {
return doc;
});
})
...
return result as UserDbObject;
}
};
Is there a way to use the typescript-mongodb plugin to only have to map these fields in the schema, then use the auto-generated code to resolve them?
You can use mappers feature of codegen to map between your GraphQL types and your models types.
See:
https://graphql-code-generator.com/docs/plugins/typescript-resolvers#mappers---overwrite-parents-and-resolved-values
https://graphql-code-generator.com/docs/plugins/typescript-resolvers#mappers-object
Since all codegen plugins are independent and not linked together, you should do it manually, something like:
config:
mappers:
User: UserDbObject
This will make typescript-resolvers plugin to use UserDbObject at any time (as parent value, or as return value).
If you wish to automate this, you can either use the codegen programmatically (https://graphql-code-generator.com/docs/getting-started/programmatic-usage), or you can also create a .js file instead of .yaml file that will create the config section according to your needs.

Why mongoose schema type is double, but when insert value, type in mongo is int32?

I'm using mongoose-double to define Double type for mongoose.
My schema contain values property is an array of Double.
In pre-save middleware, init values is an array with 6 items 1000.
Check in mongo, 1000 type is int32
const mongoose = require('mongoose');
require('mongoose-double')(mongoose);
const Double = mongoose.Schema.Types.Double;
const test = new mongoose.Schema({
values: [Double]
})
test.pre('save', function(next) {
this.values = new Array(6).fill(1000),
})
I did what wrong ?
Well, in my opinion, you don't need to use Double.
As far as I know, there is 'Number' type in mongoose schema and it can be double, number and etc.
So use Number instead of Double.
const test = new mongoose.Schema({
values:Number
})
but in your case, you should use like this.
const test = new mongoose.Schema({
values:[Number]
})

Populate a query in Mongoose with Schema First approach and NestJS

First off I want to say this question is similar to this one which references this one. I have the exact same question as the second link except a notable difference. I'm trying to extend a class generated by NestJS which defines a property.
I'm using NestJs with the Schema first approach found here. I'm also generating a classes file based on my GraphQL Schema.
Here is the Schema:
type Location {
name: String!
owner: User!
}
Which generates the class:
export class Location {
name: string;
owner: User;
}
Now, I want to extend this class so I don't have to repeat the data (there are a lot more fields not shown). I also I want to add fields that live on a document but are not in the schema (_id in this example). Here is my LocationDocument and my schema.
export interface LocationDocument extends Location, Document {
_id: Types.ObjectId
}
export const LocationSchema: Schema = new Schema(
{
name: {
type: String,
required: true,
},
owner: {
type: Types.ObjectId,
ref: 'User',
}
);
Now here is my issue. The generated Location class from the GraphQL schema defines the owner property as a User type. But in reality it's a just a mongodb id until it is populated by Mongoose. So it could be a Types.ObjectId or a User on a UserDocument. So I attempted to define it as:
export interface LocationDocument extends Location, Document {
_id: Types.ObjectId
owner: User | Types.ObjectId;
}
But this throws an error in the compiler that LocationDocument incorrectly extends Location. This makes sense. Is there any way to extend the User Class but say that owner property can be a User Type (once populated by Mongoose) or a mongo object ID (as is stored in the database).
I decided that having a property that can be both types, while easy with Mongoose and JS, isn't the typed way. In my schema I have an owner which is a User type. In my database and the document which extends it, I have an OwnerId. So to people accessing the API, they don't care about the ownerId for the relationship. But in my resolver, I use the Id. One is a Mongo ID type, the other is a User type.

Mongoose - how to use model inheritance in a sub document collection

I have this model setup. I want a parent record with an array of sub docs.
The sub docs have a schema, and use inheritance.
//
// Children
function abstractSchema() {
Schema.apply(this, arguments);
this.add({
name: String
});
};
util.inherits(abstractSchema, Schema);
var mySchema = new abstractSchema();
//
// Inherited Types
var textPropertySchema = new abstractSchema({
length: Number
});
var numberPropertySchema = new abstractSchema({
dp: Number
});
//
// Parent Model
var myModelSchema = mongoose.Schema({
name: String,
properties : [mySchema]
});
When i save each an instance of numberPropertySchema or textPropertySchema, the _t (type is written) and is able to deserialise properly.
When however added as a sub doc array, they're all persisted with the base object properties only.
Is there any way round this? any extensions that could be used?
Thanks
sam