[SOLVED] - Typescript MongoDB missing property _id, author

Hi,

I’m in the process of implementing Typescript into my existing project, and I am running into an issue where I have to make a decision for the interface of a collection.

In the interface I have to add _id, author etc as keys for the collection.
All these keys are self generated by either Meteor or Simplschema autoValue/defaultValue. So they are not included in the object before inserting.
Now the issue if I create a const newTodo : Todo = { title: ‘Test todo’ } for example it will give me an error because the _id, author etc are missing on the newTodo object. In order to prevent this I could add a ?_id in the interface, but then in other places of my code it will tell me that this value is possibly undefined (which it will never be because of meteor/simplschema).

So I thought of two options, but I couldn’t get it to work, because of the types expected by Collection.insert(). I thought of adding Partial so it will understand that the _id is missing, but that in other cases it will be aware that _id is always there. But Partial<> is not accepted by insert(). And I found the Mongo.OptionalId<> as a valid input, but there it would still give an issue with the author field and other default fields.

How would it be possible to create a similar approach to the optionalId but with other fields for each different collection that are set by default through Simplschema, but would be missing in the create object and accepted by insert()?

Thank you in advance!
Timo

Hi @tfrionnet, would you be able to provide a code snippet of your implementation on the Collection.insert?

On my side project, I’m using _id as optional when inserting and with _id when updating/deleting, so I have 2 different interfaces

What we do in our projects, are two different types: one for the data from the database (XDocumentType), and one for the transformed object (with methods; XType). If there are any autoValues in the schema, then we handle with one @ts-expect-error in the method that calls insert.

It’s unsafe to use the same type for data before and after schema cleaning, so in your case, the Todo can be either obtained from findOne()/find().fetch() or by actually adding all of the required fields. That’s normal and expected.

2 Likes

Thank you both for helping out!

So if I understand correctly, I could manage two separate interfaces for my documents (one before insert without the _id, author, createdAt) and one that represents the final document that is saved in the database after cleaning and autovalues added.

My current collection initialization looks like this: new Mongo.Collection(“todos”); where for every findOne()/find().fetch() it will return the Todo type, but it will do the same for insert() function where it expects a Todo element or Mongo.OptionalId. Is there a good way how to manage this? Or would this require a declare ts structure on the collections itself?

Thank you in advance!

1 Like

Here’s how we do it:

// Before
const Todos = new Mongo.Collection('todos');

// After
type TodoDocumentType = { _id: string; createdAt: Date; title: string };
type TodoType = TodoDocumentType & { transformedFieldExample: number };
const Todos = new Mongo.Collection<TodoDocumentType, TodoType>('todos', {
  transform: todo => ({ ...todo, transformedFieldExample: Math.random() })
});

Todos.find().fetch(); // TodoType[]
Todos.find({}, { transform: null }).fetch(); // TodoDocumentType[]

And when it comes to inserting:

// Add others in ` | ... | ...` if needed.
type AutogeneratedTodoFields = 'createdAt';
function insertTodo(todo: Omit<TodoDocumentType, AutogeneratedTodoFields>) {
  return Todos.insert(todo as TodoDocumentType);
}

(The latter may require @ts-expect-error.)

You can find a lot of examples in @types/meteor tests.

2 Likes

Hi,

I’ve been quite busy lately, but would like to come back to this post and document how I ended up getting the problems fixed with the help of radekmie!

export interface NewEntity {
    title: string;
}

export interface Entity extends NewEntity {
    _id: string;
    createdAt: Date;
    author: string;
}

export const EntityCollection = new Mongo.Collection<NewEntity, Entity>("entity");

I first create the NewEntity with all the fields that are required when inserting a new entity. If a property is optional, it could be added like this: value?: number.

I then extend this interface into a new Entity interface (the return value from the collection).

I wasn’t aware that the second interface passed to Mongo.Collection<> was to specify input and output values which helped me with the problem.
The insert() function will now expect a NewEntity. Simplschema will add custom values like author and createdAt through autoValue and the _id gets added by mongo itself.
After inserting, the value from findOne or find().fetch() returns the Entity type with all the expected values that have been added in the process.

Thanks again for helping out and happy coding!

1 Like