
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
High-performance TypeScript library for type-safe data validation and client-server communication with bidirectional RPC, streaming support, and multiple transport options
Type-safe bidirectional RPC with automatic validation and full TypeScript inference.
ggtype is a high-performance TypeScript library for building type-safe communication between client and server. Define your API once, get automatic runtime validation, full type inference, and bidirectional RPC—all with zero code generation.
✅ Use ggtype when:
bun add ggtype
# or
npm install ggtype
# or
yarn add ggtype
// server.ts
import { action, createRouter, m } from 'ggtype'
// Define an action with validation
const getUser = action(
m.object({ id: m.string() }),
async ({ params }) => {
return {
id: params.id,
name: 'John Doe',
email: 'john@example.com'
}
}
)
// Create router
const router = createRouter({
serverActions: { getUser },
})
export type Router = typeof router
// Use with any server
Bun.serve({
port: 3000,
async fetch(request) {
return router.onRequest({ request, ctx: {} })
},
})
// client.ts
import { createRouterClient, isSuccess } from 'ggtype'
import type { Router } from './server' // Type-only import
const client = createRouterClient<Router>({
httpURL: 'http://localhost:3000',
})
// Call with full type safety
const result = await client.fetchActions.getUser({ id: '1' })
if (isSuccess(result)) {
console.log('User:', result.data) // Fully typed!
} else {
console.error('Error:', result.error?.message)
}
That's it! You get automatic validation, full TypeScript types, and a simple API.
For complete, runnable examples, see the examples/ folder:
Actions are type-safe functions with automatic validation:
const createUser = action(
m.object({
name: m.string(),
email: m.string().isEmail(),
age: m.number().min(18),
}),
async ({ params }) => {
// params is fully typed and validated
return { id: '123', ...params }
}
)
Routers organize your actions and enable bidirectional RPC:
const router = createRouter({
serverActions: { getUser, createUser },
clientActions: { /* optional */ },
})
Clients provide type-safe access to your server actions:
const client = createRouterClient<Router>({
httpURL: 'http://localhost:3000',
})
// Use proxy methods for automatic type narrowing
const result = await client.fetchActions.getUser({ id: '1' })
Return streams from actions for real-time data:
// server.ts
const searchUsers = action(
m.object({ query: m.string() }),
async function* ({ params }) {
// Yield results as they become available
yield { id: '1', name: 'John', query: params.query }
yield { id: '2', name: 'Jane', query: params.query }
}
)
// client.ts
const stream = client.streamActions.searchUsers({ query: 'john' })
for await (const result of stream) {
if (isSuccess(result)) {
console.log('User:', result.data)
}
}
The killer feature: Server can call client actions with full type safety and validation.
// server.ts
const clientActions = defineClientActionsSchema({
showNotification: {
params: m.object({ message: m.string() }),
return: m.object({ acknowledged: m.boolean() }),
},
})
type ClientActions = typeof clientActions
const updateUser = action(
m.object({ id: m.string(), name: m.string() }),
async ({ params, clientActions }) => {
// Call client action with type safety
const { showNotification } = clientActions<ClientActions>()
const result = await showNotification?.({
message: `User ${params.id} updated!`,
})
// Client response is validated automatically!
if (result?.status === 'ok') {
console.log('Acknowledged:', result.data.acknowledged)
}
return { success: true }
}
)
const router = createRouter({
serverActions: { updateUser },
clientActions, // Enable bidirectional RPC
})
// client.ts
const client = createRouterClient<Router>({
streamURL: 'http://localhost:3000', // Use stream/websocket for bidirectional
defineClientActions: {
showNotification: async (params) => {
// params is validated automatically
alert(params.message)
// Return value is validated against schema
return { acknowledged: true }
},
},
})
Key benefits:
ggtype supports multiple transports. Choose what fits your needs:
| Transport | Use Case | Bidirectional RPC |
|---|---|---|
httpURL | Simple request/response (REST-like) | ❌ |
streamURL | HTTP streaming, real-time updates | ✅ |
websocketURL | Persistent connections, chat, games | ✅ |
halfDuplexUrl | Interactive bidirectional streaming | ✅ |
Transport selection: When multiple URLs are provided, the client uses the first available in priority order (stream → websocket → http). No automatic downgrade.
const client = createRouterClient<Router>({
streamURL: 'http://localhost:3000/stream', // Tried first
websocketURL: 'ws://localhost:3000/ws', // Fallback
httpURL: 'http://localhost:3000/http', // Last resort
})
Works with any server framework. Just plug in onRequest or onMessage:
// Bun
Bun.serve({
async fetch(request) {
return router.onRequest({ request, ctx: {} })
},
})
// WebSocket
Bun.serve({
websocket: {
message(ws, message) {
router.onWebSocketMessage({
ws,
message: message as Uint8Array,
ctx: {},
})
},
},
})
// Elysia, Express, etc.
app.post('/api', async (req, res) => {
const response = await router.onRequest({
request: req,
ctx: {},
})
return response
})
ggtype provides type-safe error handling:
import { isSuccess, isValidationError } from 'ggtype'
const result = await client.fetchActions.getUser({ id: '123' })
if (isSuccess(result)) {
// TypeScript knows result.data exists
console.log('User:', result.data)
} else {
// TypeScript knows result.error exists
if (isValidationError(result.error)) {
console.error('Validation errors:', result.error.errors)
} else {
console.error('Error:', result.error.message)
}
}
Built-in file support across all transports:
// Server: Receive files
const uploadImage = action(
m.object({ title: m.string() }),
async ({ params, files }) => {
const imageFile = files?.get('file')
// Process file...
return { success: true }
}
)
// Client: Upload files
const result = await client.fetchActions.uploadImage(
{ title: 'My Image' },
{ files: [imageFile] }
)
// Server: Return files
const getFile = action(
m.object({ id: m.string() }),
async ({ params }) => {
return new File([content], 'document.pdf', {
type: 'application/pdf',
})
}
)
// Client: Receive files
const result = await client.fetchActions.getFile({ id: '123' })
if (isSuccess(result)) {
const file = result.data // File object
const url = URL.createObjectURL(file)
}
Rich validation system with TypeScript inference:
// Primitives
m.string(), m.number(), m.boolean(), m.date(), m.null()
// Files
m.file(), m.blob()
// Collections
m.array(model), m.object({ ... }), m.record(model)
// Unions
m.or(model1, model2), m.and(model1, model2), m.enums('a', 'b', 'c')
// Constraints
m.string().min(5).max(100).pattern(/^[A-Z]/)
m.number().min(0).max(100)
m.string().isEmail().isOptional()
// Custom validation
m.string().validate((value) => {
if (value.length < 5) {
return 'Must be at least 5 characters'
}
})
All models are required by default. Use .isOptional() to make them optional.
createRouter(options)
- serverActions: Record<string, Action>
- clientActions?: ClientActionsSchema
- responseTimeout?: number
router.onRequest(options) // HTTP requests
router.onStream(options) // HTTP streaming
router.onWebSocketMessage(options) // WebSocket messages
createRouterClient<Router>(options)
- httpURL?: string
- streamURL?: string
- websocketURL?: string
- halfDuplexUrl?: string
- defineClientActions?: Record<string, Function>
- defaultHeaders?: Headers
client.fetch(params, options?) // Multiple actions
client.stream(params, options?) // Stream multiple actions
client.fetchActions.actionName(...) // Single action (proxy)
client.streamActions.actionName(...) // Stream single action (proxy)
client.startWebsocket() // WebSocket connection
client.startDuplex() // Duplex connection
action(model, callback)
- callback receives: { params, ctx, clientActions, files }
- action.run(params) - Test actions directly (clientActions is optional)
defineClientActionsSchema(schema)
isSuccess(result) // Type guard for success
isError(result) // Type guard for error
isValidationError(error) // Type guard for validation errors
ValidationError // Error class
📚 Full API Documentation — Complete reference with detailed examples and type definitions.
const getUser = action(
m.object({
id: m.string(),
includeEmail: m.boolean().isOptional(),
}),
async ({ params }) => {
// params.includeEmail is optional
}
)
Pass context to actions:
const router = createRouter({
serverActions: { getUser },
})
router.onRequest({
request,
ctx: { user: currentUser }, // Pass context
})
// In action
const getUser = action(
m.object({ id: m.string() }),
async ({ params, ctx }) => {
const user = ctx?.user
// Use context
}
)
const router = createRouter({
serverActions: { getUser },
responseTimeout: 30000, // 30 seconds
})
You can test actions directly using action.run(). The clientActions parameter is optional—if not provided, it defaults to an empty object:
import { action, m } from 'ggtype'
const getUser = action(
m.object({ id: m.string() }),
async ({ params, ctx, clientActions }) => {
// clientActions() is always available, even when not provided in tests
const actions = clientActions()
return { id: params.id, name: 'John' }
}
)
// Test without clientActions (optional)
const result = await getUser.run({
params: { id: '123' },
ctx: { userId: 'user-1' },
// clientActions is optional - defaults to empty object
})
// Test with custom clientActions
const resultWithClientActions = await getUser.run({
params: { id: '123' },
ctx: { userId: 'user-1' },
clientActions: () => ({
showNotification: async () => ({ status: 'ok', data: { acknowledged: true } }),
}),
})
MIT License — see LICENSE file for details.
FAQs
High-performance TypeScript library for type-safe data validation and client-server communication with bidirectional RPC, streaming support, and multiple transport options
The npm package ggtype receives a total of 10 weekly downloads. As such, ggtype popularity was classified as not popular.
We found that ggtype demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.