grubba
September 15, 2022, 12:24pm
1
TL;DR;
Typed Meteor Methods.
Still in discussion / Alfa.
Based on what we have seen and how well the community received the idea of evolving meteor methods, we have started developing an opt-in package into the core that is similar to what zodern:relay has shown us.
We are open to ideas that can make the developer experience on meteor even better than it is today. You can send them there in the pull request or here! Feedbacks are much appreciated
meteor:devel
← meteor:feature-rpc
opened 03:08PM - 14 Sep 22 UTC
# RPC Package
Minimal opt-in package for calling meteor methods with e2e type-s… afety.
## Demo
PoC is [here](https://github.com/Grubba27/charm-meteor-app)
## Explanation
As we have seen in this [PR/Discussion](https://github.com/meteor/meteor/pull/11039), Meteor and its users would benefit a lot from a modern implementation of Validated Methods(evolved meteor methods).
I'm willing to bring back this discussion with a new view and with another implementation. Most of this code and ideas have come from @zodern [zodern:relay](https://packosphere.com/zodern/relay). Also, I've made some design decisions that we should all try and decide on the best approach.
### Key decisions
- Convention over configuration (should work out of the box as intuitive as possible);
- Incremental(It should be easy to move from ``Meteor.call`` to this solution);
- All types defined
- Functional over Class based solution
### Non-decisions(can change)
- Zod (made development easier and is a good practice, I think, but we could be Schema-validator agnostic)
### How to use it?
```js
// consider that we have these methods below:
/**
* Insert a task for the logged user.
* @param {{ description: String }}
* @throws Will throw an error if user is not logged in.
*/
const insertTask = ({ description }) => {
check(description, String);
checkLoggedIn();
TasksCollection.insert({
description,
userId: Meteor.userId(),
createdAt: new Date(),
});
};
/**
* Insert a task for the logged user.
* @param {{ newDescripiton: String , taskId: String}}
* @throws Will throw an error if user is not logged in.
*/
const updateTask = ({ newDescripiton, taskId }) => {
check(newDescripiton, String);
checkLoggedIn();
TasksCollection.update(
{ _id: taskId },
{ $set: { description: newDescripiton } }
);
};
/**
* Check if user is logged in and is the task owner.
* @param {{ taskId: String }}
* @throws Will throw an error if user is not logged in or is not the task owner.
*/
const checkTaskOwner = ({ taskId }) => {
check(taskId, String);
checkLoggedIn();
const task = TasksCollection.findOne({
_id: taskId,
userId: Meteor.userId(),
});
if (!task) {
throw new Meteor.Error('Error', 'Access denied.');
}
};
/**
* Remove a task.
* @param {{ taskId: String }}
* @throws Will throw an error if user is not logged in or is not the task owner.
*/
export const removeTask = ({ taskId }) => {
checkTaskOwner({ taskId });
TasksCollection.remove(taskId);
};
/**
* Toggle task as done or not.
* @param {{ taskId: String }}
* @throws Will throw an error if user is not logged in or is not the task owner.
*/
const toggleTaskDone = ({ taskId }) => {
checkTaskOwner({ taskId });
const task = TasksCollection.findOne(taskId);
TasksCollection.update({ _id: taskId }, { $set: { done: !task.done } });
};
function publishTasks() {
return TasksCollection.find({ userId: this.userId });
}
// in the old way:
Meteor.methods({
insertTask,
removeTask,
toggleTaskDone,
});
Meteor.publish('tasksByLoggedUser', function publishTasks() {
return TasksCollection.find({ userId: this.userId });
});
// in the front end we would call like this:
Meteor.call('insertTask', {description: 'some'})
// or like this:
const isLoading = useSubscribe('tasksByLoggedUser');
// using RPC
const Tasks = createRouter('tasks')
.addMethod('insert', z.object({ description: z.string() }), insertTask)
.addMethod('update', z.object({ newDescripiton: z.string(), taskId: z.string() }), updateTask)
.addMethod('remove', z.object({ taskId: z.string() }), removeTask)
.addMethod('toggleDone', z.object({ taskId: z.string() }), toggleTaskDone)
.addPublication('byUser', z.any(), publishTasks)
.build();
// Tasks schema is like this: {
// "insert": ({description: string}) => void;
// "update": ({newDescripiton: string, taskId: string)} => void;
// "remove": ({taskId: string}) => void;
// "toggleDone": ({taskId: string}) => void;
// "byUser": () => Mongo.Cursor;
// }
// in the front end we would call like this:
Tasks.insert({ description })
// ˆ? fully typed
// Subscriptions can be made like this
const isLoading = useSubscribe(Tasks.byUser.config.name);
```
### What do we have in this Alfa?
- Minimal opt-in package;
- Fully typed;
- hooks
4 Likes
Interesting, looking forward to see how it develops.
grubba
September 27, 2022, 1:01pm
6
Hello there folks. I have great news! meteor-RPC is ready for testing. The package repo is currently here and if you want a quickstart with it you can use this starter template that is SimpleTasks with meteor-rpc included.
Any feedback is very welcomed, doubts you can ping me!
3 Likes