Skip to content

Grubba27/meteor-rpc

Repository files navigation

RPC

What is this package?

Inspired on zodern:relay

This package provides functions for building E2E type-safe RPCs. The functions are:

  • crateMethod
  • createPublication
  • createModule
  • createClient

How to download it?

meteor npm i grubba-rpc @tanstack/react-query zod

How to use it?

import {
  ReturnMethod, // <- Type
  ReturnSubscription, // <- Type
  Config, // <- Type
  SubscriptionCallbacks, // <- Type
  createMethod, // <- function
  createPublication // <- function
  createModule, // <- function
  createClient, // <- function
} from 'grubba-rpc';

createMethod

const test1 = createMethod('name', z.any(), () => 'str');
const result = await test1();
//    ˆ? is string and their value is 'str'

For semantics uses you can as well use the methods below with the same output as createMethod:

const joinStr = createMutation(
  'join',
  z.object({ foo: z.string(), bar: z.string() }),
  ({ foo, bar }) => foo + bar);
const result = await joinStr({ foo: 'foo', bar: 'bar' });
//    ˆ? is string and their value is 'foobar'
const query = createQuery(
  'query',
  z.object({ _id: z.string() }),
  async ({ _id }) => {
    const someData = await DB.findOne(_id);
    const otherData = await DB.find({ _id: { $ne: someData._id } }).fetchAsync();
    return { someData, otherData };
  });
const result = await query({ _id: 'id' });
//    ˆ? 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:
type Config<S, T> = {
  rateLimit?: {
    interval: number,
    limit: number
  },
  hooks?: {
    onBeforeResolve?: Array<(raw: unknown, parsed: S,) => void>;
    onAfterResolve?: Array<(raw: Maybe<T>, parsed: S, result: T) => void>;
    onErrorResolve?: Array<(err: Meteor.Error | Error | unknown, raw: Maybe<T>, parsed: S) => void>;
  }
}

createPublication

  const publication = createPublication('findRooms', z.object({ level: z.number() }), ({ level }) => Rooms.find({ level: level }));
const result = publication({ level: 1 }, (rooms) => console.log(rooms));
//                                            ˆ? subscription

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

type Config<S, T> = {
  rateLimit?: {
    interval: number,
    limit: number
  },
  hooks?: {
    onBeforeResolve?: Array<(raw: unknown, parsed: S,) => void>;
    onAfterResolve?: Array<(raw: Maybe<T>, parsed: S, result: T) => void>;
    onErrorResolve?: Array<(err: Meteor.Error | Error | unknown, raw: Maybe<T>, parsed: S) => void>;
  }
}

Advanced usage

you can take advantage of the hooks to add custom logic to your methods and publications

const fn = createMethod('name', z.any(), () => 'str', {
  hooks: {
    onBeforeResolve: [
      (raw, parsed) => {
        console.log('before resolve', raw, parsed);
      }
    ],
    onAfterResolve: [
      (raw, parsed, result) => {
        console.log('after resolve', raw, parsed, result);
      }
    ],
    onErrorResolve: [
      (err, raw, parsed) => {
        console.log('error resolve', err, raw, parsed);
      }
    ]
  }
});
// valid ways as well
fn.addErrorResolveHook((err, raw, parsed) => {
  console.log('error resolve', err, raw, parsed);
});
fn.addBeforeResolveHook((raw, parsed) => {
  console.log('before resolve', raw, parsed);
});
fn.addAfterResolveHook((raw, parsed, result) => {
  console.log('after resolve', raw, parsed, result);
});
const 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

import { createMethod } from 'grubba-rpc'
import { z } from "zod";

const DescriptionValidator = z.object({ description: z.string() });

// tasks.mutations.ts
// it expects the return type to be a void
export const insert = createMethod('task.insert', DescriptionValidator).expect<void>();

// tasks.mutations.js
// If you are using javascript, you can use the following syntax
export const insert = createMethod('task.insert', DescriptionValidator).expect(z.void());
// or you can use other name such as:
export const insert = createMethod('task.insert', DescriptionValidator).returns(z.void());

// ---------

// tasks.methods.ts
import { insert } from './tasks.mutations.ts'

insertTask = ({ description }) => {
  TasksCollection.insert({
    description,
    userId: Meteor.userId(),
    createdAt: new Date(),
  });
};

insert.setResolver(insertTask);

// ---------


// client.ts
import { insert } from './tasks.mutations.ts'

insert({ description: 'test' });
//^? it return void and it will run
// if resolver is not set it will throw an error

createModule

const Tasks = createModule('tasks', {insert, remove, setChecked}).build();
const foo = createModule('foo')
  .addMethod('bar', z.string(), () => 'bar' as const)
  .addMethod('baz', z.string(), () => 'baz')
  .addQuery('get', z.string(), () => 'get')
  .addSubmodule('task', Tasks)
  .build();
const k = await foo.bar();
//   ?^ 'bar'

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

React focused API

For now, only works for methods

const test1 = createMethod('name', z.any(), () => 'str');
// in react context / component.tsx


const { data } = test1.useQuery();
// works like useSuspenseQuery from https://tanstack.com/query/latest/docs/react/reference/useSuspenseQuery


// or you can use for mutation
const { mutate } = test1.useMutation();
// uses the https://tanstack.com/query/v4/docs/react/reference/useMutation under the hood

method.useQuery

This uses the same api as useSuspenseQuery

method.useMutation

This uses the same api as useMutation

Using in the client

When using in the client you should use the createModule and build methods to create a module that will be used in the client and be sure that you are exporting the type of the module

You should only create one client in your application

You can have something like api.ts that will export the client and the type of the client

// server.ts

const otherModule = createModule()
  .addMethod('bar', z.string(), () => 'bar')
  .build();
const server = createModule()
  .addMethod('foo', z.string(), () => 'foo')
  .addMethod('bar', z.string(), () => 'bar')
  .addSubmodule('other', otherModule)
  .build();

export type Server = typeof server;


// client.ts


const app = createClient<Server>();

app.foo("str") // <--- This is type safe
app.other.bar("str") // <--- This is type safe

About

Meteor Methods Evolved

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published