Is there a way to create one array properties with many mongoDB objectiID inside - arrays

I use Loopback4.
When i want to add a mongoDb ObjectId property inside my model i do that :
#property({
type: 'string',
mongodb: {dataType: 'ObjectID'},
})
organizationId?: string;
Now i want to make an array with MongoDB ObjectId properties inside, so i tried to do :
#property({
type: 'array',
itemType: 'string',
mongodb: {dataType: 'ObjectID'},
})
tagsId?: string[];
but it seems like all the array is converted to one ObjectID inside the mongoDb.
What I want to do is to simply get an array with many ObjectId inside. I tried everything in my knowledgme: that was not enough.

i found a solution :
Step 1 :
create a model with just one id.
Step 2 :
Make an array with your new model
Step 1 :
In your future model (in my case : tagReference) :
#model()
export class TagReference extends Entity {
#property({
type: 'string',
mongodb: {dataType: 'ObjectID'},
})
id?: string;
constructor(data?: Partial<TagReference>) {
super(data);
}
}
Step 2:
Where you want your array :
import {TagReference} from './tag-reference.model';
#model()
export class Resource extends BaseEntity {
// ...
#property({
type: 'array',
itemType: TagReference,
})
tagIds?: string[];
// ...
}

Related

TypeScript - How to infer a type from array of objects

I am building a React form component with TypeScript and want to type the data returned by my form.
My component accepts a prop called "fields" with the following structure:
const fields = [
{
name: "title",
default: "",
data: undefined,
size: 2
},
{
name: "users",
default: [],
data: [{id: 1, ...}]
size: 8
},
]
I would like to find a way to retrieve the type of the data returned by component based on the "field" variable. So, basically, I want to get something like that:
type FormData = {
title: string;
users: Array<{id: number, ...}>
}
The best solution would be to infer a different type depending on the "data" key. If "data" exists, then the field will have the same type as data, otherwise it is the type of the "default" key. All other keys should be ignored.
I believe I should be using generics to achieve this but I am not even sure this is something possible with TS and I have to admit that I can't figure it out...
Has anyone already faced this situation and found a solution to a similar issue ?
Thank you very much!
Based on the given array fields, we can create the type FormData with the following generic type as long as the fields variable was initialized with as const to stop TypeScript from widening the string literals.
const fields = [
{
name: "title",
default: "",
data: undefined,
size: 2
},
{
name: "users",
default: [],
data: [{id: 1}],
size: 8
},
] as const
type FormData<T extends readonly { name: st
[E in T[number] as E["name"]]: E["data"]
? E["default"]
: E["data"]
}
type Result = FormData<typeof fields>
// type Result = {
// title: "";
// users: readonly [{
// readonly id: 1;
// }];
// }
This might or might not work in the context of your component. But you did not show us the component itself. This is a solution based on the information given in your question.
Playground

How to infer types of an object created from a schema?

I'm trying to implement something similar to storybook's "Controls" feature where you can define a bunch of properties, and they become controls in the UI.
I defined a schema type and a schema of how to create those controls:
// Example schema
var newSchema: BlockSchema = {
title: "New Schema",
controls: {
name: {
type: 'string',
placeholder: 'Please insert your name'
},
size: {
type: 'select',
options: ['quarter', 'half', 'full']
},
hasInfo: {
type: 'bool'
},
amount: {
type: 'number'
}
}
}
But now I need a type that is the result of what the user has selected. A type for the final values, something like:
type MapControlTypes = {
bool: boolean;
string: string;
select: string;
number: number;
};
type InferType<T extends BlockSchema> = { /* MapControlTypes<?????????> */ }
type NewSchemaControls = InferType<typeof newSchema>;
/* Expected result:
NewSchemaControls = {
name: string;
size: string;
hasInfo: boolean;
amount: number;
}
*/
I need to infer the types from the controls property of my schema, but how could I implement this inference? Here's a playground with complete example code
I tried implementing this, and this solution. But they don't work well and also only support two types.
Titian Cernicova-Dragomir's solution didn't work too. Playground, but it has a very similar problem that happened when I tried other solutions. Maybe is it because I'm not using MapControlTypes on my ControlSchema?
Solved!
You can do this, using a mapped type, but first you need to preserve the original type of the schema. If you add a type annotation to it, then information about specific fields and types will be lost. It will just be a BlockSchema
The easiest way to do this is to omit the annotation, and use an as const assertion to make the compiler infer literal types for type.
With this extra info in hand, we can then use a mapped type to transform the schema into an object type:
type InferType<T extends BlockSchema> = {
-readonly [P in keyof T['controls']]: MapControlTypes[T['controls'][P]['type']]
}
Playground Link
You can also use a function to create the schema, and be more selective about what gets the readonly treatment:
function buildBlockSchema<B extends BlockSchema>(b: B) {
return b
}
Playground Link

How do you write flexible typescript types/interfaces for client-side documents?

Let's say we have the following models:
const catSchema = new Schema({
name: String,
favoriteFood: { type: Schema.Types.ObjectId, ref: 'FoodType' },
});
const foodType = new Schema({
name: String,
});
Here we can see that favoriteFood on the catSchema is a reference to another collection in our database.
Now, let's say that the getAllCats api does not populate the favoriteFood field because it isn't necessary and therefor just returns the reference id for foodType. The response from the api might look like this:
[
{name: 'Fluffy', favoriteFood: '621001113833bd74d6f1fc8c'},
{name: 'Meowzer', favoriteFood: '621001113833bd74d6f1fc4b'}
]
However, the getOneCat api DOES populate the favoriteFood field with the corresponding document. It might look like this:
{
name: 'Fluffy',
favoriteFood: {
name: 'pizza'
}
}
My question, how does one write a client side interface/type for my cat document?
Do we do this?
interface IFavoriteFood {
name: string
}
interface ICat {
name: string,
favoriteFood: IFavoriteFood | string
}
Say we have a React functional component like this:
const Cat = (cat: ICat) => {
return (
<div>
${cat.favoriteFood.name}
</div>
)
}
We will get the following typescript error :
"Property 'name' does not exist on type 'string | IFavoriteFood'.
Property 'name' does not exist on type 'string'."
So, I have to do something like this to make typescript happy:
const Cat = (cat: ICat) => {
return (
<div>
${typeof cat.favoriteFood === 'string' ? 'favoriteFood is not populated': cat.favoriteFood.name}
</div>
)
}
Do I write two separate interfaces? One for the cat object with favoriteFood as a string for the objectId and one for cat with favoriteFood as the populated object?
interface IFavoriteFood {
name: string
}
interface ICat {
name: string,
favoriteFood: string
}
interface ICatWithFavoriteFood {
name: string,
favoriteFood: IFavoriteFood
}
const Cat = (cat: ICatWithFavoriteFood) => {
return (
<div>
${cat.favoriteFood.name}
</div>
)
}
Would love to hear how people approach this in their codebase. Also open to being pointed to any articles/resources that address this issue.
This:
favoriteFood: IFavoriteFood | string
Is a bad idea and will lead to a lot of ugly code trying to sort out when it's one data type versus the other.
I think a better approach (and one I personally use a lot) would be:
favoriteFoodId: string
favoriteFood?: IFavoriteFood
So favoriteFoodId is always there, and is always a string. And a full favoriteFood object is sometimes there.
Now to use that value is a very simple and standard null check.
const foodName = cat.favoriteFood?.name ?? '- ice cream, probably -'
Note, this does mean changing you schema a bit so that foreign keys are suffixed with Id to not clash with the keys that will contain the actual full association data.
You could extend this to the two interface approach as well, if you wanted to lock things down a bit tighter:
interface ICat {
name: string,
favoriteFoodId: string
favoriteFood?: null // must be omitted, undefined, or null
}
interface ICatWithFavoriteFood extends ICat {
favoriteFood: IFavoriteFood // required
}
But that's probably not necessary since handling nulls in your react component is usually cheap and easy.

how to represent state object as typescript interface

I thought I am comfortable with Javascript and React, but currently suffering through typescript learning curve. I have my react state defined as:
state = {
fields: { // list of fields
symbol: '',
qty: '',
side: ''
},
fieldErrors: {}
};
I want to be able to use it as following (dictionary):
onInputChange = (name :string, value :string, error :string) => {
const fields = this.state.fields;
const fieldErrors = this.state.fieldErrors;
fields[name] = value;
fieldErrors[name] = error;
this.setState({fields, fieldErrors});
}
How do I represent my state in terms of Typescript? I am trying something like:
interface IFields {
name: string
}
interface IOrderEntryState {
fields: IFields,
fieldErrors: IFields
}
Pardon if my question sounds illiterate, totally new at this. Thanks
Based on your snippet, it looks like fields[name] is assigning an arbitrary key to that object. So you probably want to use an index signature to represent that instead of the hardcoded key name as you have now.
So your interfaces probably should look more like this:
interface IFields {
// This is an index signature. It means this object can hold
// a key of any name, and they can be accessed and set using
// bracket notation: `this.state.fields["somekey"]`
[name: string]: string
}
interface IOrderEntryState {
fields: IFields,
fieldErrors: IFields
}
If you want a dictionary you can declare one using generic type with index property. It would look like this:
interface Dictionary<TKey, TVal> {
[key: TKey]: TVal;
}
interface IOrderEntryState {
fields: Dictionary<string, string>,
fieldErrors: Dictionary<string, string>
}
This makes IOrderEntryState.fields have arbitrary string attribute names with string values.

Mongoose - Remove several objects from an array (not exact match)

I have a collection Playlist that contains an array of items
{
userId: {
type : String,
required : true,
index : true,
unique : true
},
items: [
{
id: { // do not mix up with _id, which is the autogenerated id of the pair {id,type}. ID is itemId
type : Schema.Types.ObjectId
},
type: {
type : String
}
}
]
}
Mongo automatically adds the _id field to the items when I push a pair {id,type} to items (but I don't care about it).
Now I would like to remove several "pairs" at once from the items array.
I have tried using $pullAll but it requires an exact match, and I do not know the _id, so it does not remove anything from items
playlistModel.update({userId:userId},{$pullAll:{items:[{id:"123",type:"video"},{id:"456",type:"video"}]}},null,function(err){
I have tried using $pull with different variants, but it removed ALL objects from items
playlistModel.update({userId:userId},{$pull:{items:{"items.id":{$in:["123","456"]}}}},null,function(err){
playlistModel.update({userId:userId},{$pull:{items:{$in:[{id:"123",type:"video"},{id:"456",type:"video"}]}}},null,function(err){
Am I missing something or am I asking something that isn't implemented?
If the latter, is there a way I can go around that _id issue?
OK I found a way that works using $pull:
playlistModel.update({userId:userId},{$pull:{items:{id:{$in:["123","456"]}}}},null,function(err){
It doesn't take the type into account but I can't see any issue with that since the id is unique across all types anyway.
Although I will wait a bit to see if someone has a better solution to offer
EDIT
With Veeram's help I got to this other solution, which IMO is more elegant because I don't have _ids that I don't need in the database, and the $pullAll option seems more correct here
var playlistItemSchema = mongoose.Schema({
id: { // do not mix up with autogenerated _id. id is itemId
type : Schema.Types.ObjectId
},
type: {
type : String
}
},{ _id : false });
var schema = new Schema({
userId: {
type : String,
required : true,
index : true,
unique : true
},
items: [playlistItemSchema]
});
playlistModel.update({userId:userId},{$pullAll:{items:[{id:"123",type:"video"},{id:"456",type:"video"}]}},null,function(err){
tips:
you can use _id field to handle your playlistModel data.
mongoose api : new mongoose.Types.ObjectId to generate an Object_id
let _id=new mongoose.Types.ObjectId;
playlistModel.updateMany({_id:_id},{ $set: { name: 'bob' }}).exec(data=>{console.log('exec OK')});

Resources