enders-sync-client 0.1.0
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/README.md +534 -0
- package/dist/src/client.d.ts +11 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +47 -0
- package/dist/src/client.js.map +1 -0
- package/package.json +35 -0
- package/src/client.ts +70 -0
- package/tsconfig.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
# Enders-Sync
|
|
2
|
+
|
|
3
|
+
A zero-boilerplate RPC (Remote Procedure Call) Fullstack library for Express.js that makes calling server functions from the client feel like calling local functions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ð **Fullstack**: both server-side and client-side libraries
|
|
8
|
+
- ð **Zero Boilerplate**: Call server functions directly without writing fetch code
|
|
9
|
+
- ð **Built-in Authentication**: Cookie-based auth with customizable validators
|
|
10
|
+
- ðĄ **Auto-Discovery**: Client automatically discovers available server functions
|
|
11
|
+
- ðŊ **Type-Safe**: Full TypeScript support
|
|
12
|
+
- ðŠķ **Lightweight**: No dependencies except Express
|
|
13
|
+
- ð **Promise-Based**: Works seamlessly with async/await
|
|
14
|
+
|
|
15
|
+
## Table of Content
|
|
16
|
+
|
|
17
|
+
- [Enders-Sync](#enders-sync)
|
|
18
|
+
- [Features](#features)
|
|
19
|
+
- [Table of Content](#table-of-content)
|
|
20
|
+
- [Installation](#installation)
|
|
21
|
+
- [Quick Start](#quick-start)
|
|
22
|
+
- [Server Setup](#server-setup)
|
|
23
|
+
- [Client Setup](#client-setup)
|
|
24
|
+
- [React Example](#react-example)
|
|
25
|
+
- [Authentication](#authentication)
|
|
26
|
+
- [Custom Validator](#custom-validator)
|
|
27
|
+
- [Security Considerations](#security-considerations)
|
|
28
|
+
- [Multiple RPC Endpoints](#multiple-rpc-endpoints)
|
|
29
|
+
- [API Reference](#api-reference)
|
|
30
|
+
- [Server API](#server-api)
|
|
31
|
+
- [`useExpressRPC(app, path, validator, cookieKey?)`](#useexpressrpcapp-path-validator-cookiekey)
|
|
32
|
+
- [`RPC.add(functionHandler)`](#rpcaddfunctionhandler)
|
|
33
|
+
- [`RPC.dump()`](#rpcdump)
|
|
34
|
+
- [Client API](#client-api)
|
|
35
|
+
- [`new RPC(url)`](#new-rpcurl)
|
|
36
|
+
- [`await rpc.load()`](#await-rpcload)
|
|
37
|
+
- [`await rpc.call(name, params)`](#await-rpccallname-params)
|
|
38
|
+
- [Endpoints](#endpoints)
|
|
39
|
+
- [Error Handling](#error-handling)
|
|
40
|
+
- [Server-Side Errors](#server-side-errors)
|
|
41
|
+
- [Client-Side Error Handling](#client-side-error-handling)
|
|
42
|
+
- [TypeScript Support](#typescript-support)
|
|
43
|
+
- [Server-Side Types](#server-side-types)
|
|
44
|
+
- [Client-Side Types](#client-side-types)
|
|
45
|
+
- [Best Practices](#best-practices)
|
|
46
|
+
- [Example: Complete App](#example-complete-app)
|
|
47
|
+
- [License](#license)
|
|
48
|
+
- [Contributing](#contributing)
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
[Go Back](#table-of-content)
|
|
53
|
+
|
|
54
|
+
on the server:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install enders-sync
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
on the client:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install enders-sync-client
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
[Go Back](#table-of-content)
|
|
69
|
+
|
|
70
|
+
### Server Setup
|
|
71
|
+
|
|
72
|
+
[Go Back](#table-of-content)
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
import express from 'express';
|
|
76
|
+
import { useExpressRPC } from 'glasswire';
|
|
77
|
+
|
|
78
|
+
const app = express();
|
|
79
|
+
app.use(express.json());
|
|
80
|
+
|
|
81
|
+
// Create a public RPC endpoint (no authentication required)
|
|
82
|
+
const publicRPC = useExpressRPC(app, '/api/public', () => ({
|
|
83
|
+
success: true,
|
|
84
|
+
metadata: { role: 'public' }
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
// Register your functions
|
|
88
|
+
publicRPC.add(function getUser(auth_metadata, userId) {
|
|
89
|
+
return { id: userId, name: 'John Doe', email: 'john@example.com' };
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
publicRPC.add(function calculateSum(auth_metadata, a, b) {
|
|
93
|
+
return a + b;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
app.listen(3000, () => {
|
|
97
|
+
console.log('Server running on http://localhost:3000');
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Client Setup
|
|
102
|
+
|
|
103
|
+
[Go Back](#table-of-content)
|
|
104
|
+
|
|
105
|
+
**api.js**:
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
import { RPC } from 'glasswire-client';
|
|
109
|
+
|
|
110
|
+
// Create RPC client instance
|
|
111
|
+
export const api = new RPC('/api/public');
|
|
112
|
+
|
|
113
|
+
// Load available functions (call once on app initialization)
|
|
114
|
+
await api.load();
|
|
115
|
+
|
|
116
|
+
// Now call server functions as if they were local!
|
|
117
|
+
const user = await api.getUser(123);
|
|
118
|
+
console.log(user); // { id: 123, name: 'John Doe', email: 'john@example.com' }
|
|
119
|
+
|
|
120
|
+
const sum = await api.calculateSum(5, 10);
|
|
121
|
+
console.log(sum); // 15
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**App.jsx**:
|
|
125
|
+
|
|
126
|
+
### React Example
|
|
127
|
+
|
|
128
|
+
[Go Back](#table-of-content)
|
|
129
|
+
|
|
130
|
+
```jsx
|
|
131
|
+
import { useEffect, useState } from 'react';
|
|
132
|
+
import { api } from './api';
|
|
133
|
+
|
|
134
|
+
function UserProfile({ userId }) {
|
|
135
|
+
const [user, setUser] = useState(null);
|
|
136
|
+
const [loading, setLoading] = useState(true);
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
api.getUser(userId)
|
|
140
|
+
.then(setUser)
|
|
141
|
+
.catch(console.error)
|
|
142
|
+
.finally(() => setLoading(false));
|
|
143
|
+
}, [userId]);
|
|
144
|
+
|
|
145
|
+
if (loading) return <div>Loading...</div>;
|
|
146
|
+
return <div>{user.name}</div>;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Authentication
|
|
151
|
+
|
|
152
|
+
[Go Back](#table-of-content)
|
|
153
|
+
|
|
154
|
+
### Custom Validator
|
|
155
|
+
|
|
156
|
+
[Go Back](#table-of-content)
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
import express from 'express';
|
|
160
|
+
import jwt from 'jsonwebtoken';
|
|
161
|
+
import { useExpressRPC } from 'glasswire';
|
|
162
|
+
|
|
163
|
+
const app = express();
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
// create a validator for your Auth and access control
|
|
167
|
+
function authUser(cookie) {
|
|
168
|
+
try {
|
|
169
|
+
const token = cookie; // or parse from cookie string
|
|
170
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
success: true,
|
|
174
|
+
metadata: {
|
|
175
|
+
userId: decoded.userId,
|
|
176
|
+
role: decoded.role
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return { success: false };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
const authenticatedRPC = useExpressRPC (
|
|
186
|
+
app,
|
|
187
|
+
'/api/user',
|
|
188
|
+
authUser,
|
|
189
|
+
'auth_token' // custom cookie key (default: 'token')
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
// Access auth metadata in your functions
|
|
194
|
+
authenticatedRPC.add( function getMyProfile(auth_metadata) {
|
|
195
|
+
const userId = auth_metadata.userId;
|
|
196
|
+
// Fetch user profile using authenticated userId
|
|
197
|
+
return { id: userId, name: 'Current User' };
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Security Considerations
|
|
202
|
+
|
|
203
|
+
[Go Back](#table-of-content)
|
|
204
|
+
|
|
205
|
+
- Always validate and sanitize RPC function inputs
|
|
206
|
+
- Use different validators for different permission levels
|
|
207
|
+
- Consider rate limiting for public endpoints
|
|
208
|
+
- The auth metadata is trusted - ensure your validator is secure
|
|
209
|
+
|
|
210
|
+
### Multiple RPC Endpoints
|
|
211
|
+
|
|
212
|
+
[Go Back](#table-of-content)
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
// Public API (no auth)
|
|
216
|
+
const publicRPC = useExpressRPC(app, '/api/public', () => ({
|
|
217
|
+
success: true,
|
|
218
|
+
metadata: {}
|
|
219
|
+
}));
|
|
220
|
+
|
|
221
|
+
// User API (requires authentication)
|
|
222
|
+
const userRPC = useExpressRPC(app, '/api/user', validateUserToken);
|
|
223
|
+
|
|
224
|
+
// Admin API (requires admin role)
|
|
225
|
+
const adminRPC = useExpressRPC(app, '/api/admin', validateAdminToken);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Client:**
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
import { RPC } from "glasswire-client"
|
|
232
|
+
|
|
233
|
+
export const publicAPI = new RPC('/api/public');
|
|
234
|
+
export const userAPI = new RPC('/api/user');
|
|
235
|
+
export const adminAPI = new RPC('/api/admin');
|
|
236
|
+
|
|
237
|
+
// Load all APIs
|
|
238
|
+
await Promise.all([
|
|
239
|
+
publicAPI.load(),
|
|
240
|
+
userAPI.load(),
|
|
241
|
+
adminAPI.load()
|
|
242
|
+
]);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## API Reference
|
|
246
|
+
|
|
247
|
+
[Go Back](#table-of-content)
|
|
248
|
+
|
|
249
|
+
### Server API
|
|
250
|
+
|
|
251
|
+
[Go Back](#table-of-content)
|
|
252
|
+
|
|
253
|
+
#### `useExpressRPC(app, path, validator, cookieKey?)`
|
|
254
|
+
|
|
255
|
+
[Go Back](#table-of-content)
|
|
256
|
+
|
|
257
|
+
Creates an RPC endpoint on your Express app.
|
|
258
|
+
|
|
259
|
+
**Parameters:**
|
|
260
|
+
|
|
261
|
+
- `app` (Express): Your Express application instance
|
|
262
|
+
- `path` (string): Base path for the RPC endpoint (e.g., `/api/public`)
|
|
263
|
+
- `validator` (Function): Authentication validator function
|
|
264
|
+
- `cookieKey` (string, optional): Cookie key to extract auth token from (default: `'token'`)
|
|
265
|
+
|
|
266
|
+
**Returns:** `RPC` instance
|
|
267
|
+
|
|
268
|
+
**Validator Function:**
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
type Validator = (cookie: string) => {
|
|
272
|
+
success: boolean;
|
|
273
|
+
metadata?: Record<string, string | number>;
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### `RPC.add(functionHandler)`
|
|
278
|
+
|
|
279
|
+
[Go Back](#table-of-content)
|
|
280
|
+
|
|
281
|
+
Registers a function to be callable via RPC.
|
|
282
|
+
|
|
283
|
+
**Requirements:**
|
|
284
|
+
|
|
285
|
+
- Function must be a named function (not arrow function)
|
|
286
|
+
- First parameter must be `auth_metadata`
|
|
287
|
+
- Remaining parameters are the RPC call arguments
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
rpc.add(function myFunction(auth_metadata, param1, param2) {
|
|
291
|
+
// Your logic here
|
|
292
|
+
return result;
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
#### `RPC.dump()`
|
|
297
|
+
|
|
298
|
+
[Go Back](#table-of-content)
|
|
299
|
+
|
|
300
|
+
Returns an array of all registered function names.
|
|
301
|
+
|
|
302
|
+
```javascript
|
|
303
|
+
const functions = rpc.dump();
|
|
304
|
+
console.log(functions); // ['getUser', 'calculateSum', ...]
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Client API
|
|
308
|
+
|
|
309
|
+
[Go Back](#table-of-content)
|
|
310
|
+
|
|
311
|
+
#### `new RPC(url)`
|
|
312
|
+
|
|
313
|
+
[Go Back](#table-of-content)
|
|
314
|
+
|
|
315
|
+
Creates a new RPC client instance.
|
|
316
|
+
|
|
317
|
+
**Parameters:**
|
|
318
|
+
|
|
319
|
+
- `url` (string): Base URL of the RPC endpoint (e.g., `/api/public`)
|
|
320
|
+
|
|
321
|
+
#### `await rpc.load()`
|
|
322
|
+
|
|
323
|
+
[Go Back](#table-of-content)
|
|
324
|
+
|
|
325
|
+
Discovers and loads all available RPC functions from the server. Must be called before using any remote functions.
|
|
326
|
+
|
|
327
|
+
**Returns:** `Promise<void>`
|
|
328
|
+
|
|
329
|
+
#### `await rpc.call(name, params)`
|
|
330
|
+
|
|
331
|
+
[Go Back](#table-of-content)
|
|
332
|
+
|
|
333
|
+
Manually call an RPC function (usually not needed - use auto-generated methods instead).
|
|
334
|
+
|
|
335
|
+
**Parameters:**
|
|
336
|
+
|
|
337
|
+
- `name` (string): Function name
|
|
338
|
+
- `params` (Array): Function parameters
|
|
339
|
+
|
|
340
|
+
**Returns:** `Promise<any>`
|
|
341
|
+
|
|
342
|
+
## Endpoints
|
|
343
|
+
|
|
344
|
+
[Go Back](#table-of-content)
|
|
345
|
+
|
|
346
|
+
[Go Back](#table-of-content)
|
|
347
|
+
|
|
348
|
+
When you create an RPC endpoint at `/api/public`, two routes are automatically created:
|
|
349
|
+
|
|
350
|
+
- `GET /api/public/discover` - Returns list of available functions
|
|
351
|
+
- `POST /api/public/call` - Executes RPC calls
|
|
352
|
+
|
|
353
|
+
## Error Handling
|
|
354
|
+
|
|
355
|
+
[Go Back](#table-of-content)
|
|
356
|
+
|
|
357
|
+
### Server-Side Errors
|
|
358
|
+
|
|
359
|
+
[Go Back](#table-of-content)
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
publicRPC.add(function riskyOperation(auth_metadata, data) {
|
|
363
|
+
if (!data) {
|
|
364
|
+
throw new Error('Data is required');
|
|
365
|
+
}
|
|
366
|
+
// Process data
|
|
367
|
+
return result;
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Client-Side Error Handling
|
|
372
|
+
|
|
373
|
+
[Go Back](#table-of-content)
|
|
374
|
+
|
|
375
|
+
```javascript
|
|
376
|
+
try {
|
|
377
|
+
const result = await api.riskyOperation(null);
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.error('RPC Error:', error.message);
|
|
380
|
+
// Handle error appropriately
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Or with promises
|
|
384
|
+
api.riskyOperation(data)
|
|
385
|
+
.then(result => console.log('Success:', result))
|
|
386
|
+
.catch(error => console.error('Error:', error));
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## TypeScript Support
|
|
390
|
+
|
|
391
|
+
[Go Back](#table-of-content)
|
|
392
|
+
|
|
393
|
+
### Server-Side Types
|
|
394
|
+
|
|
395
|
+
[Go Back](#table-of-content)
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { type AuthMetadata } from 'glasswire';
|
|
399
|
+
|
|
400
|
+
interface User{
|
|
401
|
+
id:
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function getUser(
|
|
405
|
+
auth_metadata: AuthMetadata
|
|
406
|
+
): User {
|
|
407
|
+
return {
|
|
408
|
+
id: auth_metadata.id! , // members are validated by the validator
|
|
409
|
+
name: 'John Doe',
|
|
410
|
+
email: 'john@example.com'
|
|
411
|
+
};
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
// ...
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
// then you register the backend function
|
|
419
|
+
publicRPC.add(getUser);
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Client-Side Types
|
|
423
|
+
|
|
424
|
+
[Go Back](#table-of-content)
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
import { RPC } from 'glasswire-client';
|
|
428
|
+
|
|
429
|
+
export interface PublicAPI {
|
|
430
|
+
getUser(userId: number): Promise<{ id: number, name: string , email: string }>;
|
|
431
|
+
calculateSum(a: number, b: number): Promise<number>;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export const public_api = new RPC('/api/public') as PublicAPI;
|
|
435
|
+
await public_api.load();
|
|
436
|
+
|
|
437
|
+
// Now you get full type safety!
|
|
438
|
+
const user: User = await public_api.getUser(123);
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## Best Practices
|
|
442
|
+
|
|
443
|
+
[Go Back](#table-of-content)
|
|
444
|
+
|
|
445
|
+
1. **Initialize once**: Call `api.load()` once when your app starts, not on every component mount
|
|
446
|
+
2. **Error handling**: Always handle errors from RPC calls
|
|
447
|
+
3. **Named functions**: Use named functions (not arrow functions) for RPC handlers
|
|
448
|
+
4. **Validation**: Validate input parameters in your RPC functions
|
|
449
|
+
5. **Authentication**: Use different RPC endpoints for different permission levels
|
|
450
|
+
6. **Async operations**: RPC handlers can be async functions
|
|
451
|
+
|
|
452
|
+
## Example: Complete App
|
|
453
|
+
|
|
454
|
+
[Go Back](#table-of-content)
|
|
455
|
+
|
|
456
|
+
**server.js**:
|
|
457
|
+
|
|
458
|
+
```javascript
|
|
459
|
+
import express from 'express';
|
|
460
|
+
import { useExpressRPC } from 'glasswire/server';
|
|
461
|
+
|
|
462
|
+
const app = express();
|
|
463
|
+
app.use(express.json());
|
|
464
|
+
|
|
465
|
+
const publicRPC = useExpressRPC(app, '/api/public', () => ({
|
|
466
|
+
success: true,
|
|
467
|
+
metadata: {}
|
|
468
|
+
}));
|
|
469
|
+
|
|
470
|
+
// Database mock
|
|
471
|
+
const users = [
|
|
472
|
+
{ id: 1, name: 'Alice', email: 'alice@example.com' },
|
|
473
|
+
{ id: 2, name: 'Bob', email: 'bob@example.com' }
|
|
474
|
+
];
|
|
475
|
+
|
|
476
|
+
publicRPC.add(function getUsers(auth_metadata) {
|
|
477
|
+
return users;
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
publicRPC.add(function getUserById(auth_metadata, id) {
|
|
481
|
+
const user = users.find(u => u.id === id);
|
|
482
|
+
if (!user) throw new Error('User not found');
|
|
483
|
+
return user;
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
publicRPC.add(async function searchUsers(auth_metadata, query) {
|
|
487
|
+
// Simulate async database query
|
|
488
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
489
|
+
return users.filter(u =>
|
|
490
|
+
u.name.toLowerCase().includes(query.toLowerCase())
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
app.listen(3000);
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**api.js**:
|
|
498
|
+
|
|
499
|
+
```javascript
|
|
500
|
+
import { RPC } from 'glasswire-client';
|
|
501
|
+
|
|
502
|
+
export const api = new RPC('/api/public');
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**app.js**:
|
|
506
|
+
|
|
507
|
+
```javascript
|
|
508
|
+
import { api } from './api.js';
|
|
509
|
+
|
|
510
|
+
// Initialize
|
|
511
|
+
await api.load();
|
|
512
|
+
|
|
513
|
+
// Use anywhere in your app
|
|
514
|
+
const users = await api.getUsers();
|
|
515
|
+
console.log(users);
|
|
516
|
+
|
|
517
|
+
const alice = await api.getUserById(1);
|
|
518
|
+
console.log(alice);
|
|
519
|
+
|
|
520
|
+
const results = await api.searchUsers('bob');
|
|
521
|
+
console.log(results);
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## License
|
|
525
|
+
|
|
526
|
+
[Go Back](#table-of-content)
|
|
527
|
+
|
|
528
|
+
MIT ÂĐ Hussein Layth Al-Madhachi
|
|
529
|
+
|
|
530
|
+
## Contributing
|
|
531
|
+
|
|
532
|
+
[Go Back](#table-of-content)
|
|
533
|
+
|
|
534
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface RPCConfig {
|
|
2
|
+
url: string;
|
|
3
|
+
}
|
|
4
|
+
export declare class RPC {
|
|
5
|
+
url: string;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
constructor(url: string | RPCConfig);
|
|
8
|
+
load(): Promise<void>;
|
|
9
|
+
call<T = any>(name: string, params: any[]): Promise<T>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,SAAS;IACtB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,GAAG;IACL,GAAG,EAAE,MAAM,CAAC;IAGnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;gBAEP,GAAG,EAAE,MAAM,GAAG,SAAS;IAK7B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBrB,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;CAuB/D"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export class RPC {
|
|
2
|
+
url;
|
|
3
|
+
constructor(url) {
|
|
4
|
+
const urlString = typeof url === 'string' ? url : url.url;
|
|
5
|
+
this.url = urlString.endsWith("/") ? urlString.slice(0, -1) : urlString;
|
|
6
|
+
}
|
|
7
|
+
async load() {
|
|
8
|
+
let response;
|
|
9
|
+
try {
|
|
10
|
+
response = await fetch(`${this.url}/discover`);
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
console.error("Failed to fetch RPC list to load all remote functions");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
console.error("Failed to load RPC list due to a backend RPC endpoint misconfiguration");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const methods = await response.json();
|
|
21
|
+
for (const name of methods) {
|
|
22
|
+
this[name] = (...args) => this.call(name, args);
|
|
23
|
+
}
|
|
24
|
+
console.log("RPC functions have been loaded successfully");
|
|
25
|
+
}
|
|
26
|
+
async call(name, params) {
|
|
27
|
+
let response;
|
|
28
|
+
try {
|
|
29
|
+
response = await fetch(`${this.url}/call`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify({ method: name, params }),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
throw new Error(`RPC Network Error while calling ${name} from ${this.url}: ${e}`);
|
|
39
|
+
}
|
|
40
|
+
const data = await response.json();
|
|
41
|
+
if (!data.success) {
|
|
42
|
+
throw new Error(`RPC function error from backend: ${name}() \n\n\n${data.error?.toString()}`);
|
|
43
|
+
}
|
|
44
|
+
return data.data;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,GAAG;IACL,GAAG,CAAS;IAKnB,YAAY,GAAuB;QAC/B,MAAM,SAAS,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;QAC1D,IAAI,CAAC,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5E,CAAC;IAED,KAAK,CAAC,IAAI;QACN,IAAI,QAAkB,CAAC;QAEvB,IAAI,CAAC;YACD,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;YACvE,OAAO;QACX,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wEAAwE,CAAC,CAAC;YACxF,OAAO;QACX,CAAC;QAED,MAAM,OAAO,GAAa,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEhD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAW,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,IAAI,CAAU,IAAY,EAAE,MAAa;QAC3C,IAAI,QAAkB,CAAC;QAEvB,IAAI,CAAC;YACD,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,OAAO,EAAE;gBACvC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACL,cAAc,EAAE,kBAAkB;iBACrC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;aACjD,CAAC,CAAC;QACP,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,mCAAmC,IAAI,SAAS,IAAI,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;QAED,MAAM,IAAI,GAAmB,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEnD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,YAAY,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClG,CAAC;QAED,OAAO,IAAI,CAAC,IAAS,CAAC;IAC1B,CAAC;CACJ"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "enders-sync-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Fullstack RPC library connecting your backend to your frontend seemlessly over a REST API ",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Hussein Layth Al-Madhachi",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/src/client.js",
|
|
9
|
+
"module": "./dist/src/client.js",
|
|
10
|
+
"types": "./dist/src/client.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/src/client.js",
|
|
14
|
+
"types": "./dist/src/client.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"rpc",
|
|
19
|
+
"remote-procedure-call",
|
|
20
|
+
"express",
|
|
21
|
+
"api",
|
|
22
|
+
"microservices",
|
|
23
|
+
"backend",
|
|
24
|
+
"typescript",
|
|
25
|
+
"nodejs",
|
|
26
|
+
"middleware",
|
|
27
|
+
"cookie-auth",
|
|
28
|
+
"json-rpc",
|
|
29
|
+
"lightweight",
|
|
30
|
+
"glasswire"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
interface RPCResponse<T = any> {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data?: T;
|
|
4
|
+
error?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface RPCConfig {
|
|
8
|
+
url: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class RPC {
|
|
12
|
+
public url: string;
|
|
13
|
+
|
|
14
|
+
// Index signature to allow dynamic method names
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
|
|
17
|
+
constructor(url: string | RPCConfig) {
|
|
18
|
+
const urlString = typeof url === 'string' ? url : url.url;
|
|
19
|
+
this.url = urlString.endsWith("/") ? urlString.slice(0, -1) : urlString;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async load(): Promise<void> {
|
|
23
|
+
let response: Response;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
response = await fetch(`${this.url}/discover`);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error("Failed to fetch RPC list to load all remote functions");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
console.error("Failed to load RPC list due to a backend RPC endpoint misconfiguration");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const methods: string[] = await response.json();
|
|
38
|
+
|
|
39
|
+
for (const name of methods) {
|
|
40
|
+
this[name] = (...args: any[]) => this.call(name, args);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log("RPC functions have been loaded successfully");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async call<T = any>(name: string, params: any[]): Promise<T> {
|
|
47
|
+
let response: Response;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
response = await fetch(`${this.url}/call`, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: {
|
|
53
|
+
'Content-Type': 'application/json',
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({ method: name, params }),
|
|
56
|
+
});
|
|
57
|
+
} catch (e) {
|
|
58
|
+
throw new Error(`RPC Network Error while calling ${name} from ${this.url}: ${e}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const data: RPCResponse<T> = await response.json();
|
|
62
|
+
|
|
63
|
+
if (!data.success) {
|
|
64
|
+
throw new Error(`RPC function error from backend: ${name}() \n\n\n${data.error?.toString()}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return data.data as T;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
// File Layout
|
|
5
|
+
"rootDir": "./",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
|
|
8
|
+
// Environment Settings
|
|
9
|
+
// See also https://aka.ms/tsconfig/module
|
|
10
|
+
"module": "nodenext",
|
|
11
|
+
"target": "esnext",
|
|
12
|
+
// For nodejs:
|
|
13
|
+
// "lib": ["esnext"],
|
|
14
|
+
// "types": ["node"],
|
|
15
|
+
// and npm install -D @types/node
|
|
16
|
+
|
|
17
|
+
// Other Outputs
|
|
18
|
+
"sourceMap": true,
|
|
19
|
+
"declaration": true,
|
|
20
|
+
"declarationMap": true,
|
|
21
|
+
|
|
22
|
+
// Stricter Typechecking Options
|
|
23
|
+
"noUncheckedIndexedAccess": true,
|
|
24
|
+
"exactOptionalPropertyTypes": true,
|
|
25
|
+
|
|
26
|
+
// Style Options
|
|
27
|
+
// "noImplicitReturns": true,
|
|
28
|
+
// "noImplicitOverride": true,
|
|
29
|
+
// "noUnusedLocals": true,
|
|
30
|
+
// "noUnusedParameters": true,
|
|
31
|
+
// "noFallthroughCasesInSwitch": true,
|
|
32
|
+
// "noPropertyAccessFromIndexSignature": true,
|
|
33
|
+
|
|
34
|
+
// Recommended Options
|
|
35
|
+
"strict": true,
|
|
36
|
+
"jsx": "react-jsx",
|
|
37
|
+
"verbatimModuleSyntax": true,
|
|
38
|
+
"isolatedModules": true,
|
|
39
|
+
"noUncheckedSideEffectImports": true,
|
|
40
|
+
"moduleDetection": "force",
|
|
41
|
+
"skipLibCheck": true,
|
|
42
|
+
}
|
|
43
|
+
}
|