@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/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
- /** Optional timeout for RPC calls */
126
- timeout?: number;
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
- type TransportConfig = {
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
- debounce?: number;
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, IRPCCache, IRPCCaches, IRPCContext, IRPCContextProvider, IRPCData, IRPCDataSchema, IRPCError, IRPCHandler, IRPCInit, IRPCInputs, IRPCModule, IRPCObject, IRPCObjectSchema, IRPCOutput, IRPCPackageConfig, IRPCPackageInfo, IRPCParseResult, IRPCPayload, IRPCPrimitive, IRPCPrimitiveSchema, IRPCRequest, IRPCResponse, IRPCSchema, IRPCSpec, IRPCSpecStore, IRPCStubStore, TransportConfig };
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.19",
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
- **Isomorphic Remote Procedure Call** - Call remote functions like local functions.
3
+ **Stop thinking about the network. Just call functions.**
4
4
 
5
- ```typescript
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
- // Just do this:
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
- ## Why IRPC?
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
- **IRPC handled 1 million API calls in 3.6 seconds with 10x fewer HTTP connections.**
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
- - ✅ **6.96x faster** than traditional REST
37
- - ✅ **10x fewer HTTP connections** (automatic batching)
38
- - **Type-safe** (end-to-end TypeScript)
39
- - **Zero boilerplate** (no routes, no endpoints)
40
- - **Transport agnostic** (HTTP, WebSocket, etc.)
41
- - **Built-in caching** (configurable per-call)
42
- - **Retry & timeout** (automatic error handling)
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
- export const hello = irpc.declare<HelloFn>({
97
- name: 'hello'
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'; // Import handlers
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
- ### Timeout
216
+ ### Coalesce
217
+
218
+ Combine multiple calls with identical arguments:
160
219
 
161
220
  ```typescript
162
- export const slowQuery = irpc.declare<SlowQueryFn>({
163
- name: 'slowQuery',
164
- timeout: 30000, // 30 second timeout
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