@irpclib/irpc 1.0.0-beta.19 → 1.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/call.d.ts +36 -11
- package/dist/call.js +78 -11
- package/dist/enum.d.ts +35 -0
- package/dist/enum.js +36 -0
- package/dist/error.d.ts +5 -0
- package/dist/error.js +7 -2
- package/dist/index.d.ts +7 -3
- package/dist/index.js +7 -3
- package/dist/module.d.ts +5 -4
- package/dist/module.js +32 -20
- package/dist/reader.d.ts +24 -0
- package/dist/reader.js +37 -0
- package/dist/resolver.d.ts +2 -12
- package/dist/resolver.js +20 -2
- package/dist/state.d.ts +82 -0
- package/dist/state.js +126 -0
- package/dist/stream.d.ts +57 -0
- package/dist/stream.js +204 -0
- package/dist/transport.d.ts +5 -4
- package/dist/transport.js +37 -19
- package/dist/types.d.ts +80 -28
- package/package.json +7 -4
- package/readme.md +99 -30
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { IRPC_DATA_TYPE, IRPC_EVENT_TYPE, IRPC_PACKET_TYPE, IRPC_STATUS } from "./enum.js";
|
|
1
2
|
import { ErrorCode } from "./error.js";
|
|
2
3
|
import { IRPCTransport } from "./transport.js";
|
|
4
|
+
import { RemoteState } from "./state.js";
|
|
5
|
+
import { StateChange } from "@anchorlib/core";
|
|
3
6
|
import { ZodArray, ZodBoolean, ZodNull, ZodNumber, ZodObject, ZodSafeParseResult, ZodString, ZodUndefined } from "zod/v4";
|
|
4
7
|
|
|
5
8
|
//#region src/types.d.ts
|
|
@@ -14,6 +17,41 @@ type IRPCStubStore = WeakMap<IRPCHandler, IRPCSpec<IRPCInputs, IRPCOutput>>;
|
|
|
14
17
|
* Used to keep track of available RPC hosts by their names.
|
|
15
18
|
*/
|
|
16
19
|
type IRPCSpecStore = Map<string, IRPCSpec<IRPCInputs, IRPCOutput>>;
|
|
20
|
+
type IRPCStatus = (typeof IRPC_STATUS)[keyof typeof IRPC_STATUS];
|
|
21
|
+
type IRPCDataType = (typeof IRPC_DATA_TYPE)[keyof typeof IRPC_DATA_TYPE];
|
|
22
|
+
type IRPCPacketType = (typeof IRPC_PACKET_TYPE)[keyof typeof IRPC_PACKET_TYPE];
|
|
23
|
+
type IRPCEventType = (typeof IRPC_EVENT_TYPE)[keyof typeof IRPC_EVENT_TYPE];
|
|
24
|
+
type IRPCPacketBase = {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
type: IRPCPacketType;
|
|
28
|
+
status: IRPCStatus;
|
|
29
|
+
createdAt?: number;
|
|
30
|
+
arrivedAt?: number;
|
|
31
|
+
};
|
|
32
|
+
type IRPCPacketData = {
|
|
33
|
+
type: IRPCDataType;
|
|
34
|
+
value: IRPCData;
|
|
35
|
+
};
|
|
36
|
+
type IRPCPacketCall = IRPCPacketBase & {
|
|
37
|
+
args: IRPCData[];
|
|
38
|
+
};
|
|
39
|
+
type IRPCPacketAnswer<T extends IRPCData> = IRPCPacketBase & {
|
|
40
|
+
data?: T;
|
|
41
|
+
error?: IRPCError;
|
|
42
|
+
};
|
|
43
|
+
type IRPCPacketEvent = IRPCPacketBase & {
|
|
44
|
+
data: StateChange;
|
|
45
|
+
};
|
|
46
|
+
type IRPCPacketClose = IRPCPacketBase & {
|
|
47
|
+
error?: IRPCError;
|
|
48
|
+
};
|
|
49
|
+
type IRPCPacketStream<T extends IRPCData> = IRPCPacketAnswer<T> | IRPCPacketEvent | IRPCPacketClose;
|
|
50
|
+
interface IRPCReadable<T> {
|
|
51
|
+
data: T;
|
|
52
|
+
error: Error | undefined;
|
|
53
|
+
status: IRPCStatus;
|
|
54
|
+
}
|
|
17
55
|
/**
|
|
18
56
|
* Represents primitive data types that can be used in IRPC communications.
|
|
19
57
|
* Includes string, number, boolean, null, and undefined.
|
|
@@ -70,17 +108,7 @@ type IRPCPackageInfo = {
|
|
|
70
108
|
/** Optional description of the namespace */
|
|
71
109
|
description?: string;
|
|
72
110
|
};
|
|
73
|
-
type IRPCPackageConfig = IRPCPackageInfo & {
|
|
74
|
-
timeout?: number;
|
|
75
|
-
transport?: IRPCTransport;
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* Defines an RPC module which extends a namespace with execution capabilities.
|
|
79
|
-
*/
|
|
80
|
-
type IRPCModule = IRPCPackageInfo & {
|
|
81
|
-
/** Optional timeout for RPC calls */
|
|
82
|
-
timeout?: number;
|
|
83
|
-
/** Optional transport mechanism for RPC communications */
|
|
111
|
+
type IRPCPackageConfig = IRPCPackageInfo & IRPCCallConfig & {
|
|
84
112
|
transport?: IRPCTransport;
|
|
85
113
|
};
|
|
86
114
|
/**
|
|
@@ -116,15 +144,31 @@ type IRPCHandler = Function;
|
|
|
116
144
|
type IRPCInit<I extends IRPCInputs, O extends IRPCOutput> = {
|
|
117
145
|
/** The name of the RPC function */
|
|
118
146
|
name: string;
|
|
119
|
-
/** Optional schema for input/output validation */
|
|
120
|
-
schema?: IRPCSchema<I, O>;
|
|
121
147
|
/** Optional description of the RPC function */
|
|
122
148
|
description?: string;
|
|
149
|
+
/** Optional schema for input/output validation */
|
|
150
|
+
schema?: IRPCSchema<I, O>;
|
|
123
151
|
/** Optional maximum age of a call in milliseconds */
|
|
124
152
|
maxAge?: number;
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Whether to coalesce multiple calls to the same RPC function within a short time period.
|
|
155
|
+
* If true, multiple calls with the same parameters will be combined into a single call,
|
|
156
|
+
* with subsequent calls waiting for the result of the first call.
|
|
157
|
+
* This can help reduce the number of actual function executions.
|
|
158
|
+
*/
|
|
159
|
+
coalesce?: boolean;
|
|
160
|
+
} & IRPCCallConfig;
|
|
161
|
+
/**
|
|
162
|
+
* Type definition for an RPC declaration.
|
|
163
|
+
* Represents an RPC function with its name, description, and configuration.
|
|
164
|
+
*
|
|
165
|
+
* @template F - The function signature of the RPC
|
|
166
|
+
* @template I - Tuple of input validation schemas
|
|
167
|
+
* @template O - Output validation schema
|
|
168
|
+
*/
|
|
169
|
+
type IRPCDeclareInit<F, I extends IRPCInputs, O extends IRPCOutput> = F extends ((...args: any[]) => RemoteState<infer R>) ? IRPCInit<I, IRPCOutput> & {
|
|
170
|
+
init: () => R;
|
|
171
|
+
} : IRPCInit<I, O>;
|
|
128
172
|
/**
|
|
129
173
|
* Complete specification for an RPC function including its implementation.
|
|
130
174
|
* Extends IRPCInit with the actual handler function.
|
|
@@ -135,6 +179,7 @@ type IRPCInit<I extends IRPCInputs, O extends IRPCOutput> = {
|
|
|
135
179
|
type IRPCSpec<I extends IRPCInputs, O extends IRPCOutput> = IRPCInit<I, O> & {
|
|
136
180
|
/** The actual handler function that implements the RPC */
|
|
137
181
|
handler: IRPCHandler;
|
|
182
|
+
init?: () => unknown;
|
|
138
183
|
};
|
|
139
184
|
/**
|
|
140
185
|
* Represents an incoming RPC request.
|
|
@@ -164,15 +209,6 @@ type IRPCResponse = {
|
|
|
164
209
|
/** Result of the RPC call if successful */
|
|
165
210
|
result?: unknown;
|
|
166
211
|
};
|
|
167
|
-
type IRPCCache = {
|
|
168
|
-
data: IRPCData;
|
|
169
|
-
expiresAt: number;
|
|
170
|
-
timestamp: number;
|
|
171
|
-
};
|
|
172
|
-
/**
|
|
173
|
-
* Represents a cache for RPC responses.
|
|
174
|
-
*/
|
|
175
|
-
type IRPCCaches = Map<string, IRPCCache>;
|
|
176
212
|
/**
|
|
177
213
|
* Context storage mechanism for RPC operations.
|
|
178
214
|
*/
|
|
@@ -190,9 +226,25 @@ type IRPCContextProvider = {
|
|
|
190
226
|
/** Gets the current context store */
|
|
191
227
|
getStore<K, V>(): IRPCContext<K, V>;
|
|
192
228
|
};
|
|
193
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Configuration options for an RPC call.
|
|
231
|
+
*/
|
|
232
|
+
type IRPCCallConfig = {
|
|
233
|
+
/** Timeout for the RPC call in milliseconds */
|
|
194
234
|
timeout?: number;
|
|
195
|
-
|
|
235
|
+
/** Maximum number of retries for the call */
|
|
236
|
+
maxRetries?: number;
|
|
237
|
+
/** Retry strategy mode - either linear or exponential backoff */
|
|
238
|
+
retryMode?: 'linear' | 'exponential';
|
|
239
|
+
/** Base delay between retries in milliseconds */
|
|
240
|
+
retryDelay?: number;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Configuration for transport layer, extending call configuration with debounce settings.
|
|
244
|
+
*/
|
|
245
|
+
type TransportConfig = IRPCCallConfig & {
|
|
246
|
+
/** Debounce setting for transport - can be a boolean to enable/disable or a number for specific delay */
|
|
247
|
+
debounce?: number | boolean;
|
|
196
248
|
};
|
|
197
249
|
//#endregion
|
|
198
|
-
export { IRPCArraySchema,
|
|
250
|
+
export { IRPCArraySchema, IRPCCallConfig, IRPCContext, IRPCContextProvider, IRPCData, IRPCDataSchema, IRPCDataType, IRPCDeclareInit, IRPCError, IRPCEventType, IRPCHandler, IRPCInit, IRPCInputs, IRPCObject, IRPCObjectSchema, IRPCOutput, IRPCPackageConfig, IRPCPackageInfo, IRPCPacketAnswer, IRPCPacketBase, IRPCPacketCall, IRPCPacketClose, IRPCPacketData, IRPCPacketEvent, IRPCPacketStream, IRPCPacketType, IRPCParseResult, IRPCPayload, IRPCPrimitive, IRPCPrimitiveSchema, IRPCReadable, IRPCRequest, IRPCResponse, IRPCSchema, IRPCSpec, IRPCSpecStore, IRPCStatus, IRPCStubStore, TransportConfig };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@irpclib/irpc",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.21",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"module": "./dist/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -36,13 +36,16 @@
|
|
|
36
36
|
"zod": "^4.1.5"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
|
-
"dev": "tsdown --watch",
|
|
39
|
+
"dev": "rimraf dist && tsdown --watch ./src",
|
|
40
40
|
"clean": "rimraf dist",
|
|
41
|
-
"build": "tsdown && publint",
|
|
41
|
+
"build": "rimraf dist && tsdown && publint",
|
|
42
42
|
"format": "biome format --write",
|
|
43
43
|
"test": "rimraf coverage && vitest --run",
|
|
44
44
|
"test:preview": "rimraf coverage && vitest --run && vite preview --outDir coverage",
|
|
45
45
|
"prepublish": "bun run format && bun run clean && tsdown && publint"
|
|
46
46
|
},
|
|
47
|
-
"license": "MIT"
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@anchorlib/core": "1.0.0-beta.20"
|
|
50
|
+
}
|
|
48
51
|
}
|
package/readme.md
CHANGED
|
@@ -1,52 +1,46 @@
|
|
|
1
1
|
# @irpclib/irpc
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Stop thinking about the network. Just call functions.**
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
// Instead of this:
|
|
7
|
-
const response = await fetch('/api/hello');
|
|
8
|
-
const data = await response.json();
|
|
5
|
+
IRPC makes remote function calls and reactive streaming look and feel like local functions. Declare once, implement on the server, call from the client — no routes, no endpoints, no WebSocket configuration.
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
```typescript
|
|
11
8
|
const message = await hello('John');
|
|
9
|
+
|
|
10
|
+
const call = loadDashboard('user-123');
|
|
11
|
+
call.subscribe(state => console.log(state.data));
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
##
|
|
17
|
-
|
|
18
|
-
**Beside the simplicity**, this is what you get:
|
|
19
|
-
|
|
20
|
-
### Benchmark Results
|
|
21
|
-
**Scenario:** 100,000 users, 10 calls each (1,000,000 total calls)
|
|
16
|
+
## Performance
|
|
22
17
|
|
|
23
18
|
| Framework | Total Time | HTTP Requests | Speedup |
|
|
24
19
|
|-----------|------------|---------------|---------|
|
|
25
|
-
| **IRPC** | **3,617ms** | **100,000** | **6.96x**
|
|
20
|
+
| **IRPC** | **3,617ms** | **100,000** | **6.96x** |
|
|
26
21
|
| Bun Native | 25,180ms | 1,000,000 | 1.00x |
|
|
27
22
|
| Hono | 18,004ms | 1,000,000 | 1.40x |
|
|
28
23
|
| Elysia | 36,993ms | 1,000,000 | 0.68x |
|
|
29
24
|
|
|
30
|
-
**
|
|
25
|
+
**1 million API calls in 3.6 seconds with 10x fewer HTTP connections.**
|
|
31
26
|
|
|
32
27
|
---
|
|
33
28
|
|
|
34
29
|
## Features
|
|
35
30
|
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
31
|
+
- 6.96x faster than traditional REST APIs
|
|
32
|
+
- 10x fewer HTTP connections through automatic batching
|
|
33
|
+
- Native continuous reactive streaming via `RemoteState`
|
|
34
|
+
- End-to-end type safety with TypeScript
|
|
35
|
+
- Zero boilerplate — no routes or endpoints
|
|
36
|
+
- Transport agnostic (HTTP, WebSocket, BroadcastChannel)
|
|
37
|
+
- Built-in caching configurable per function
|
|
38
|
+
- Automatic retry and timeout handling
|
|
43
39
|
|
|
44
40
|
---
|
|
45
41
|
|
|
46
42
|
## Quick Start
|
|
47
43
|
|
|
48
|
-
### Create New Project
|
|
49
|
-
|
|
50
44
|
```bash
|
|
51
45
|
npx degit beerush/anchor/templates/irpc-bun-app my-api
|
|
52
46
|
cd my-api
|
|
@@ -92,9 +86,18 @@ irpc.use(transport);
|
|
|
92
86
|
import { irpc } from '../lib/module.js';
|
|
93
87
|
|
|
94
88
|
export type HelloFn = (name: string) => Promise<string>;
|
|
89
|
+
export const hello = irpc.declare<HelloFn>({ name: 'hello' });
|
|
90
|
+
```
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
```typescript
|
|
93
|
+
// rpc/dashboard/index.ts
|
|
94
|
+
import { irpc } from '../lib/module.js';
|
|
95
|
+
import type { RemoteState } from '@irpclib/irpc';
|
|
96
|
+
|
|
97
|
+
export type LoadDashboardFn = (userId: string) => RemoteState<DashboardData>;
|
|
98
|
+
export const loadDashboard = irpc.declare<LoadDashboardFn>({
|
|
99
|
+
name: 'loadDashboard',
|
|
100
|
+
init: () => ({} as DashboardData), // Initial client-side state before server data arrives
|
|
98
101
|
});
|
|
99
102
|
```
|
|
100
103
|
|
|
@@ -110,6 +113,22 @@ irpc.construct(hello, async (name) => {
|
|
|
110
113
|
});
|
|
111
114
|
```
|
|
112
115
|
|
|
116
|
+
```typescript
|
|
117
|
+
// rpc/dashboard/constructor.ts
|
|
118
|
+
import { irpc } from '../lib/module.js';
|
|
119
|
+
import { loadDashboard } from './index.js';
|
|
120
|
+
import { stream } from '@irpclib/irpc';
|
|
121
|
+
|
|
122
|
+
irpc.construct(loadDashboard, (userId) => {
|
|
123
|
+
return stream((data, resolve) => {
|
|
124
|
+
const q1 = db.users.get(userId).then(res => data.user = res);
|
|
125
|
+
const q2 = db.sales.aggregate(userId).then(res => data.sales = res);
|
|
126
|
+
|
|
127
|
+
Promise.all([q1, q2]).then(() => resolve());
|
|
128
|
+
}, {});
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
113
132
|
### 4. Setup Server
|
|
114
133
|
|
|
115
134
|
```typescript
|
|
@@ -118,7 +137,7 @@ import { setContextProvider } from '@irpclib/irpc';
|
|
|
118
137
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
119
138
|
import { HTTPRouter } from '@irpclib/http';
|
|
120
139
|
import { irpc, transport } from './lib/module.js';
|
|
121
|
-
import './rpc/hello/constructor.js';
|
|
140
|
+
import './rpc/hello/constructor.js';
|
|
122
141
|
|
|
123
142
|
setContextProvider(new AsyncLocalStorage());
|
|
124
143
|
|
|
@@ -137,16 +156,54 @@ Bun.serve({
|
|
|
137
156
|
### 5. Use on Client
|
|
138
157
|
|
|
139
158
|
```typescript
|
|
159
|
+
// client.ts
|
|
140
160
|
import { hello } from './rpc/hello/index.js';
|
|
161
|
+
import { loadDashboard } from './rpc/dashboard/index.js';
|
|
141
162
|
|
|
163
|
+
// Standard execution
|
|
142
164
|
const message = await hello('John');
|
|
143
165
|
console.log(message); // "Hello John"
|
|
166
|
+
|
|
167
|
+
// Stream subscription
|
|
168
|
+
const call = loadDashboard('user-123');
|
|
169
|
+
call.subscribe(state => console.log('Hydration state:', state.data));
|
|
144
170
|
```
|
|
145
171
|
|
|
146
172
|
---
|
|
147
173
|
|
|
148
174
|
## Advanced Features
|
|
149
175
|
|
|
176
|
+
### Call Configuration (Available at All Levels)
|
|
177
|
+
|
|
178
|
+
Configure retry, timeout, and other call behaviors at **function**, **package**, or **transport** level:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// Function-level (highest priority)
|
|
182
|
+
const criticalFn = irpc.declare({
|
|
183
|
+
name: 'processPayment',
|
|
184
|
+
timeout: 30000, // 30s timeout
|
|
185
|
+
maxRetries: 5, // 5 retry attempts
|
|
186
|
+
retryMode: 'exponential',
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Package-level (medium priority)
|
|
190
|
+
const irpc = createPackage({
|
|
191
|
+
name: 'my-api',
|
|
192
|
+
timeout: 10000, // 10s default
|
|
193
|
+
maxRetries: 3, // 3 retry attempts
|
|
194
|
+
retryMode: 'linear',
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Transport-level (lowest priority)
|
|
198
|
+
const transport = new HTTPTransport({
|
|
199
|
+
endpoint: '/api',
|
|
200
|
+
timeout: 5000, // 5s fallback
|
|
201
|
+
maxRetries: 1, // 1 retry attempt
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Priority Order:** Function → Package → Transport
|
|
206
|
+
|
|
150
207
|
### Caching
|
|
151
208
|
|
|
152
209
|
```typescript
|
|
@@ -156,15 +213,27 @@ export const getUser = irpc.declare<GetUserFn>({
|
|
|
156
213
|
});
|
|
157
214
|
```
|
|
158
215
|
|
|
159
|
-
###
|
|
216
|
+
### Coalesce
|
|
217
|
+
|
|
218
|
+
Combine multiple calls with identical arguments:
|
|
160
219
|
|
|
161
220
|
```typescript
|
|
162
|
-
export const
|
|
163
|
-
name: '
|
|
164
|
-
|
|
221
|
+
export const expensiveQuery = irpc.declare<ExpensiveQueryFn>({
|
|
222
|
+
name: 'expensiveQuery',
|
|
223
|
+
coalesce: true,
|
|
165
224
|
});
|
|
166
225
|
```
|
|
167
226
|
|
|
227
|
+
### Cache Invalidation
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// Invalidate specific cache entry
|
|
231
|
+
irpc.invalidate(getUser, 'user-123');
|
|
232
|
+
|
|
233
|
+
// Invalidate all cache for a function
|
|
234
|
+
irpc.invalidate(getUser);
|
|
235
|
+
```
|
|
236
|
+
|
|
168
237
|
### Validation (Optional Zod)
|
|
169
238
|
|
|
170
239
|
```typescript
|