Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

grubba-rpc

Package Overview
Dependencies
Maintainers
0
Versions
80
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

grubba-rpc

RPC e2e safe

  • 0.13.5
  • latest
  • npm
  • Socket score

Version published
Weekly downloads
29
decreased by-70.71%
Maintainers
0
Weekly downloads
 
Created
Source

Meteor-RPC

What is this package?

Inspired on zodern:relay and on tRPC

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

How to download it?

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

install react query into your project, following their quick start guide

How to use it?

Firstly, you need to create a module, then you can add methods, publications, and subscriptions to it.

Then you need to build the module and use it in the client as a type.

createModule

subModule without a namespace: createModule() is used to create the main server module, the one that will be exported to be used in the client.

subModule with a namespace: createModule("namespace") is used to create a submodule that will be added to the main module.

Remember to use build at the end of module creation to ensure that the module is going to be created.

for example:

// server/main.ts
import { createModule } from "grubba-rpc";
import { ChatCollection } from "/imports/api/chat";
import { z } from "zod";

const Chat = createModule("chat")
  .addMethod("createChat", z.void(), async () => {
    return ChatCollection.insertAsync({ createdAt: new Date(), messages: [] });
  })
  .buildSubmodule();

const server = createModule() // server has no namespace
  .addMethod("bar", z.string(), (arg) => "bar" as const)
  .addSubmodule(Chat)
  .build();

export type Server = typeof server;

// client.ts
import { createClient } from "grubba-rpc";

const api = createClient<Server>();
const bar: "bar" = await api.bar("some string");
//   ?^ 'bar'
const newChatId = await api.chat.createChat(); // with intellisense

module.addMethod

addMethod(name: string, schema: ZodSchema, handler: (args: ZodTypeInput<ZodSchema>) => T, config?: Config<ZodTypeInput<ZodSchema>, T>)

This is the equivalent of Meteor.methods but with types and runtime validation.

// server/main.ts
import { createModule } from "grubba-rpc";
import { z } from "zod";

const server = createModule();

server.addMethod("foo", z.string(), (arg) => "foo" as const);

server.build();

// is the same as

import { Meteor } from "meteor/meteor";
import { z } from "zod";

Meteor.methods({
  foo(arg: string) {
    z.string().parse(arg);
    return "foo";
  },
});

module.addPublication

addPublication(name: string, schema: ZodSchema, handler: (args: ZodTypeInput<ZodSchema>) => Cursor<any, any>)

This is the equivalent of Meteor.publish but with types and runtime validation.

// server/main.ts
import { createModule } from "grubba-rpc";
import { ChatCollection } from "/imports/api/chat";
import { z } from "zod";

const server = createModule();

server.addPublication("chatRooms", z.void(), () => {
  return ChatCollection.find();
});

server.build();

// is the same as
import { Meteor } from "meteor/meteor";
import { ChatCollection } from "/imports/api/chat";

Meteor.publish("chatRooms", function () {
  return ChatCollection.find();
});

module.addSubmodule

This is used to add a submodule to the main module, adding namespaces for your methods and publications and also making it easier to organize your code.

Remember to use submodule.buildSubmodule when creating a submodule

// module/chat.ts
import { ChatCollection } from "/imports/api/chat";
import { createModule } from "grubba-rpc";

export const chatModule = createModule("chat")
  .addMethod("createChat", z.void(), async () => {
    return ChatCollection.insertAsync({ createdAt: new Date(), messages: [] });
  })
  .buildSubmodule(); // <-- this is important so that this module can be added as a submodule

// server/main.ts
import { createModule } from "grubba-rpc";
import { chatModule } from "./module/chat";

const server = createModule()
  .addMethod("bar", z.string(), (arg) => "bar" as const)
  .addSubmodule(chatModule)
  .build();

server.chat; // <-- this is the namespace for the chat module

module.addMiddlewares

addMiddlewares(middlewares: Middleware[])

Type Middleware = (raw: unknown, parsed: unknown) => void;

This is used to add middlewares to the module, it can be used to add side effects logic to the methods and publications, ideal for logging, rate limiting, etc.

The middleware ordering is last in first out. Check the example below:

// module/chat.ts
import { ChatCollection } from "/imports/api/chat";
import { createModule } from "grubba-rpc";

export const chatModule = createModule("chat")
  .addMiddlewares([
    (raw, parsed) => {
      console.log("run first");
    },
  ])
  .addMethod("createChat", z.void(), async () => {
    return ChatCollection.insertAsync({ createdAt: new Date(), messages: [] });
  })
  .buildSubmodule();

// server/main.ts
import { createModule } from "grubba-rpc";
import { chatModule } from "./module/chat";

const server = createModule()
  .addMiddlewares([
    (raw, parsed) => {
      console.log("run second");
    },
  ])
  .addMethod("bar", z.string(), (arg) => "bar" as const)
  .addSubmodule(chatModule)
  .build();

React focused API

Using in the client

When using in the client you have to 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/main.ts
import { createModule } from "grubba-rpc";

const server = createModule()
  .addMethod("bar", z.string(), (arg) => "bar" as const)
  .build();

export type Server = typeof server;

// client.ts
import type { Server } from "/imports/api/server"; // you must import the type
const app = createClient<Server>();

await app.bar("str"); // it will return "bar"

method.useMutation

It uses the useMutation from react-query to create a mutation that will call the method

// server/main.ts
import { createModule } from "grubba-rpc";

const server = createModule()
  .addMethod("bar", z.string(), (arg) => "bar" as const)
  .build();

export type Server = typeof server;

// client.ts
import type { Server } from "/imports/api/server"; // you must import the type
const app = createClient<Server>();

export const Component = () => {
  const { mutate, isLoading, isError, error, data } = app.bar.useMutation();

  return (
    <button
      onClick={() => {
        mutation.mutate("str"); // it has intellisense
      }}
    >
      Click me
    </button>
  );
};

method.useQuery

It uses the useQuery from react-query to create a query that will call the method, it uses suspense to handle loading states

// server/main.ts
import { createModule } from "grubba-rpc";

const server = createModule()
  .addMethod("bar", z.string(), (arg) => "bar" as const)
  .build();

export type Server = typeof server;

// client.ts
import type { Server } from "/imports/api/server"; // you must import the type
const app = createClient<Server>();

export const Component = () => {
  const { data } = app.bar.useQuery("str"); // will trigger suspense

  return <div>{data}</div>;
};

publication.useSubscription

Subscriptions on the client have useSubscription method that can be used as a hook to subscribe to a publication. It uses suspense to handle loading states

// server/main.ts
import { createModule } from "grubba-rpc";
import { ChatCollection } from "/imports/api/chat";
import { z } from "zod";

const server = createModule()
  .addPublication("chatRooms", z.void(), () => {
    return ChatCollection.find();
  })
  .build();

export type Server = typeof server;

// client.ts
import type { Server } from "/imports/api/server"; // you must import the type
const app = createClient<Server>();

export const Component = () => {
  const { data: rooms, collection: chatCollection } =
    api.chatRooms.usePublication(); // it will trigger suspense and rooms is reactive

  return <div>{data}</div>;
};

Examples

Currently we have this chat-app that uses this package to create a chat app

it includes: methods, publications, and subscriptions

Advanced usage

you can take advantage of the hooks to add custom logic to your methods, checking the raw and parsed data, and the result of the method, you can add more complex validations.

server.addMethod("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);
      },
    ],
  },
});

or

// server.ts
server.name.addBeforeResolveHook((raw, parsed) => {
  console.log("before resolve", raw, parsed);
});

server.name.addAfterResolveHook((raw, parsed, result) => {
  console.log("after resolve", raw, parsed, result);
});

server.name.addErrorResolveHook((err, raw, parsed) => {
  console.log("error resolve", err, raw, parsed);
});

server = server.build();

FAQs

Package last updated on 29 Nov 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc