RPC
What is this package?
Inspired on zodern:relay
This package provides functions for building E2E type-safe RPCs. The functions are:
- crateMethod
- createPublication
How to download it?
meteor npm i grubba-rpc meteor npm i zod
or if you prefer using meteor package manager
meteor add grubba:rpc meteor npm i zod
How to use it?
1import { 2 ReturnMethod, // <- Type 3 ReturnSubscription, // <- Type 4 Config, // <- Type 5 SubscriptionCallbacks, // <- Type 6 createMethod, // <- function 7 createPublication // <- function 8} from 'grubba-rpc';
createMethod
1 2const test1 = createMethod('name', z.any(), () => 'str'); 3const result = await test1(); 4// ˆ? is string and their value is 'str'
For semantics uses you can as well use the methods below with the same output as createMethod:
1const joinStr = createMutation( 2 'join', 3 z.object({ foo: z.string(), bar: z.string() }), 4 ({ foo, bar }) => foo + bar); 5const result = await joinStr({ foo: 'foo', bar: 'bar' }); 6// ˆ? is string and their value is 'foobar'
1const query = createQuery( 2 'query', 3 z.object({ _id: z.string() }), 4 async ({ _id }) => { 5 const someData = await DB.findOne(_id); 6 const otherData = await DB.find({ _id: { $ne: someData._id } }).fetchAsync(); 7 return { someData, otherData }; 8 }); 9const result = await query({ _id: 'id' }); 10// ˆ? is string and their value is the item you was querying
example of use
createMethod accepts 4 arguments:
- name: string
- schema: ZodSchema (validator)
- handler (optional): function that receives the arguments of the method and returns the result
- config (optional): object with the following properties:
1type Config<S, T> = { 2 rateLimit?: { 3 interval: number, 4 limit: number 5 }, 6 hooks?: { 7 onBeforeResolve?: Array<(raw: unknown, parsed: S,) => void>; 8 onAfterResolve?: Array<(raw: Maybe<T>, parsed: S, result: T) => void>; 9 onErrorResolve?: Array<(err: Meteor.Error | Error | unknown, raw: Maybe<T>, parsed: S) => void>; 10 } 11}
createPublication
1 const publication = createPublication('findRooms', z.object({ level: z.number() }), ({ level }) => Rooms.find({ level: level })); 2const result = publication({ level: 1 }, (rooms) => console.log(rooms)); 3// ˆ? subscription 4
example of use
createPublication accepts 4 arguments:
- name: string
- schema: ZodSchema (validator)
- handler (optional): function that is being published
- config (optional): object with the following properties:
note that subscription returns the subscription handler the same way as Meteor.publish
1type Config<S, T> = { 2 rateLimit?: { 3 interval: number, 4 limit: number 5 }, 6 hooks?: { 7 onBeforeResolve?: Array<(raw: unknown, parsed: S,) => void>; 8 onAfterResolve?: Array<(raw: Maybe<T>, parsed: S, result: T) => void>; 9 onErrorResolve?: Array<(err: Meteor.Error | Error | unknown, raw: Maybe<T>, parsed: S) => void>; 10 } 11}
Advanced usage
you can take advantage of the hooks to add custom logic to your methods and publications
1 2const fn = createMethod('name', z.any(), () => 'str', { 3 hooks: { 4 onBeforeResolve: [ 5 (raw, parsed) => { 6 console.log('before resolve', raw, parsed); 7 } 8 ], 9 onAfterResolve: [ 10 (raw, parsed, result) => { 11 console.log('after resolve', raw, parsed, result); 12 } 13 ], 14 onErrorResolve: [ 15 (err, raw, parsed) => { 16 console.log('error resolve', err, raw, parsed); 17 } 18 ] 19 } 20}); 21// valid ways as well 22fn.addErrorResolveHook((err, raw, parsed) => { 23 console.log('error resolve', err, raw, parsed); 24}); 25fn.addBeforeResolveHook((raw, parsed) => { 26 console.log('before resolve', raw, parsed); 27}); 28fn.addAfterResolveHook((raw, parsed, result) => { 29 console.log('after resolve', raw, parsed, result); 30}); 31const result = await fn();
Using safe methods
check this example that illustrates this 'secure way' of using safe methods, as it is not bundled in the client
1 2import { createMethod } from 'grubba-rpc' 3import { z } from "zod"; 4 5const DescriptionValidator = z.object({ description: z.string() }); 6 7// tasks.mutations.ts 8// it expects the return type to be a void 9export const insert = createMethod('task.insert', DescriptionValidator).expect<void>(); 10 11// tasks.mutations.js 12// If you are using javascript, you can use the following syntax 13export const insert = createMethod('task.insert', DescriptionValidator).expect(z.void()); 14 15// --------- 16 17// tasks.methods.ts 18import { insert } from './tasks.mutations.ts' 19 20insertTask = ({ description }) => { 21 TasksCollection.insert({ 22 description, 23 userId: Meteor.userId(), 24 createdAt: new Date(), 25 }); 26}; 27 28insert.setResolver(insertTask); 29 30// --------- 31 32 33// client.ts 34import { insert } from './tasks.mutations.ts' 35 36insert({ description: 'test' }); 37//^? it return void and it will run 38// if resolver is not set it will throw an error 39
Experimental
createModule
1const Tasks = createModule('tasks', {insert, remove, setChecked}).build(); 2const foo = createModule('foo') 3 .addMethod('bar', z.string(), () => 'bar' as const) 4 .addMethod('baz', z.string(), () => 'baz') 5 .addQuery('get', z.string(), () => 'get') 6 .build(); 7const k = await foo.bar(); 8// ?^ 'bar'
There is as well the safeBuild method that will return a module and a setter for that module resolvers
1const [TaskModule, setTaskResolver] = createModule('tasks', {insert, remove, setChecked}).safeBuild(); 2setTaskResolver({ insert: ({description}) => 1 }) 3setTaskResolver({ 4 remove: ({taskId}) => { 5 console.log(taskId) 6 }, 7 setChecked: ({taskId}) => { 8 console.log(taskId) 9 } 10}) 11
I'm still solving the problem of how to type the resolvers.
Examples?
in the examples folder you can find a simple example of how to use this package it uses simpletasks as a base
for downloading it you can do the command below or just access this link
git clone https://github.com/Grubba27/meteor-rpc-template.git