@sx3/ultra 0.0.1 → 0.0.2

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/ultra.mjs CHANGED
@@ -1,273 +1 @@
1
- import { t as Procedure } from "./procedure-BN1rLLRX.mjs";
2
- import { n as toRPCResponse, t as toHTTPResponse } from "./response-CNhIkAYG.mjs";
3
- import { t as isRPC } from "./rpc-_rBI0z-9.mjs";
4
- import { serve } from "bun";
5
-
6
- //#region src/ultra.ts
7
- var Ultra = class {
8
- initializers = /* @__PURE__ */ new Set();
9
- handlers = /* @__PURE__ */ new Map();
10
- events = /* @__PURE__ */ new Map();
11
- middlewares = /* @__PURE__ */ new Set();
12
- derived = /* @__PURE__ */ new Set();
13
- derivedWS = /* @__PURE__ */ new Set();
14
- options = { http: true };
15
- server;
16
- constructor(options) {
17
- Object.assign(this.options, options);
18
- }
19
- /** Register procedures */
20
- routes(initializer) {
21
- this.initializers.add(initializer);
22
- return this;
23
- }
24
- /** Register middleware or another Ultra instance */
25
- use(entity) {
26
- if (typeof entity === "function") {
27
- this.middlewares.add(entity);
28
- return this;
29
- }
30
- this.merge(entity);
31
- return this;
32
- }
33
- /** Extends context values for every request with provided values */
34
- derive(derive) {
35
- this.derived.add(derive);
36
- return this;
37
- }
38
- /** Extends WS data for every ws connection */
39
- deriveWS(derive) {
40
- this.derivedWS.add(derive);
41
- return this;
42
- }
43
- start(options) {
44
- if (this.server) {
45
- console.warn("Server is already running");
46
- return this.server;
47
- }
48
- const procedures = this.buildProcedures();
49
- const notFoundHandler = this.wrapHandler(() => new Response("Not Found", { status: 404 }));
50
- this.server = serve({
51
- ...options ?? {},
52
- routes: {
53
- ...this.options.http && {
54
- ...this.buildRoutes(procedures),
55
- "/*": async (request, server) => {
56
- this.emit("http:request", request, server);
57
- return notFoundHandler({
58
- input: null,
59
- context: this.derived.size ? await this.enrichContext({
60
- server,
61
- request
62
- }) : {
63
- server,
64
- request
65
- }
66
- });
67
- }
68
- },
69
- "/ws": async (request, server) => {
70
- if (this.derivedWS.size) {
71
- const data = {};
72
- const context = this.derived.size ? await this.enrichContext({
73
- server,
74
- request
75
- }) : {
76
- server,
77
- request
78
- };
79
- for (const derive of this.derivedWS) Object.assign(data, typeof derive === "function" ? await derive(context) : derive);
80
- server.upgrade(request, { data });
81
- return;
82
- }
83
- server.upgrade(request);
84
- }
85
- },
86
- websocket: {
87
- open: (ws) => {
88
- this.emit("ws:open", ws);
89
- },
90
- close: (ws, code, reason) => {
91
- this.emit("ws:close", ws, code, reason);
92
- },
93
- message: (ws, message) => {
94
- this.emit("ws:message", ws, message);
95
- if (typeof message !== "string") return;
96
- const data = JSON.parse(message);
97
- if (isRPC(data)) this.handleRPC(ws, data);
98
- }
99
- },
100
- error: (error) => {
101
- this.emit("unhandled:error", error);
102
- console.error("Unhandled server error:", error);
103
- return new Response("Internal Server Error", { status: 500 });
104
- }
105
- });
106
- this.emit("server:started", this.server);
107
- return this.server;
108
- }
109
- async stop(closeActiveConnections = false) {
110
- if (!this.server) return console.error("Server is not running");
111
- await this.server.stop(closeActiveConnections);
112
- this.emit("server:stopped", this.server, closeActiveConnections);
113
- }
114
- on(event, listener) {
115
- if (!this.events.has(event)) this.events.set(event, /* @__PURE__ */ new Set());
116
- this.events.get(event).add(listener);
117
- return this;
118
- }
119
- off(event, listener) {
120
- this.events.get(event)?.delete(listener);
121
- return this;
122
- }
123
- emit(event, ...args) {
124
- this.events.get(event)?.forEach((listener) => listener(...args));
125
- return this;
126
- }
127
- async handleRPC(ws, payload) {
128
- const handler = this.handlers.get(payload.method);
129
- if (!handler) {
130
- ws.send(`{"id": "${payload.id}", "error": {"code": 404, "message": "Not found"}}`);
131
- return;
132
- }
133
- try {
134
- ws.send(toRPCResponse(payload.id, await handler({
135
- input: payload.params,
136
- context: await this.enrichContext({
137
- server: this.server,
138
- ws
139
- })
140
- })));
141
- } catch (error) {
142
- this.emit("error", error);
143
- ws.send(toRPCResponse(payload.id, error));
144
- }
145
- }
146
- /** Enrich context with derived values */
147
- async enrichContext(context) {
148
- for (const derive of this.derived) Object.assign(context, typeof derive === "function" ? await derive(context) : derive);
149
- return context;
150
- }
151
- /** Merge other Ultra instance with deduplication */
152
- merge(module) {
153
- module.initializers.forEach((init) => this.initializers.add(init));
154
- module.derived.forEach((derive) => this.derived.add(derive));
155
- module.derivedWS.forEach((derive) => this.derivedWS.add(derive));
156
- module.middlewares.forEach((mw) => this.middlewares.add(mw));
157
- module.events.forEach((listeners, event) => {
158
- if (!this.events.has(event)) this.events.set(event, /* @__PURE__ */ new Set());
159
- const targetListeners = this.events.get(event);
160
- listeners.forEach((listener) => targetListeners.add(listener));
161
- });
162
- }
163
- /** Wrap procedure handler with global middlewares */
164
- wrapHandler(handler) {
165
- if (!this.middlewares.size) return handler;
166
- const middlewares = Array.from(this.middlewares);
167
- return async (options) => {
168
- let idx = 0;
169
- const next = () => {
170
- if (idx === middlewares.length) return handler(options);
171
- return middlewares[idx++]({
172
- ...options,
173
- next
174
- });
175
- };
176
- return next();
177
- };
178
- }
179
- /** Build flat map from procedures tree and write handles map */
180
- buildProcedures() {
181
- const procedures = /* @__PURE__ */ new Map();
182
- const inputFactory = (schema) => {
183
- const procedure = new Procedure();
184
- return schema ? procedure.input(schema) : procedure;
185
- };
186
- for (const initializer of this.initializers) {
187
- const map = initializer(inputFactory);
188
- const stack = [];
189
- for (const [key, value] of Object.entries(map)) stack.push({
190
- path: key,
191
- value
192
- });
193
- while (stack.length) {
194
- const { path, value } = stack.pop();
195
- if (value instanceof Procedure) {
196
- if (procedures.has(path)) throw new Error(`Procedure conflict at path "${path}"`);
197
- procedures.set(path, value);
198
- this.handlers.set(path, this.wrapHandler(value.wrap()));
199
- continue;
200
- }
201
- for (const [childKey, childValue] of Object.entries(value)) {
202
- const nextPath = path ? `${path}/${childKey}` : childKey;
203
- stack.push({
204
- path: nextPath,
205
- value: childValue
206
- });
207
- }
208
- }
209
- }
210
- return procedures;
211
- }
212
- /** Build Bun native HTTP routes */
213
- buildRoutes(procedures) {
214
- const routes = {};
215
- for (const [path, procedure] of procedures) {
216
- const procedureInfo = procedure.getInfo();
217
- if (!procedureInfo.http?.enabled) continue;
218
- const httpPath = `/${path}`;
219
- const handler = this.handlers.get(path);
220
- if (!handler) throw new Error(`Handler for procedure at path "${path}" is not defined`);
221
- const httpHandler = async (request, server) => {
222
- this.emit("http:request", request, server);
223
- let input = request.body;
224
- const baseContext = {
225
- server,
226
- request
227
- };
228
- const context = this.derived.size ? await this.enrichContext(baseContext) : baseContext;
229
- if (input && procedureInfo.hasInput) {
230
- if (request.method === "GET") {
231
- const query = request.url.indexOf("?");
232
- if (query !== -1 && query < request.url.length - 1) input = Object.fromEntries(new URLSearchParams(request.url.slice(query + 1)).entries());
233
- } else if (request.headers.get("Content-Length") !== "0") {
234
- const type = request.headers.get("Content-Type");
235
- if (type) switch (true) {
236
- case type.startsWith("application/json"):
237
- input = await request.json();
238
- break;
239
- case type.startsWith("text"):
240
- input = await request.text();
241
- break;
242
- case type.startsWith("multipart/form-data"):
243
- input = await request.formData();
244
- break;
245
- default:
246
- console.error(`Unsupported Content-Type for procedure ${path}: ${type}`);
247
- break;
248
- }
249
- }
250
- }
251
- try {
252
- return toHTTPResponse(await handler({
253
- input,
254
- context
255
- }));
256
- } catch (error) {
257
- this.emit("error", error);
258
- return toHTTPResponse(error);
259
- }
260
- };
261
- if (!procedureInfo.http.method) {
262
- routes[httpPath] = httpHandler;
263
- continue;
264
- }
265
- if (!routes[httpPath]) routes[httpPath] = {};
266
- routes[httpPath][procedureInfo.http.method] = httpHandler;
267
- }
268
- return routes;
269
- }
270
- };
271
-
272
- //#endregion
273
- export { Ultra };
1
+ import{t as e}from"./procedure-DRzVmxaT.mjs";import{n as t,t as n}from"./response-CnnOFeUL.mjs";import{t as r}from"./rpc-CAwkzxZe.mjs";import{serve as i}from"bun";var a=class{initializers=new Set;handlers=new Map;events=new Map;middlewares=new Set;derived=new Set;derivedWS=new Set;options={http:!0};server;constructor(e){Object.assign(this.options,e)}routes(e){return this.initializers.add(e),this}use(e){return typeof e==`function`?(this.middlewares.add(e),this):(this.merge(e),this)}derive(e){return this.derived.add(e),this}deriveWS(e){return this.derivedWS.add(e),this}start(e){if(this.server)return console.warn(`Server is already running`),this.server;let t=this.buildProcedures(),n=this.wrapHandler(()=>new Response(`Not Found`,{status:404}));return this.server=i({...e??{},routes:{...this.options.http&&{...this.buildRoutes(t),"/*":async(e,t)=>(this.emit(`http:request`,e,t),n({input:null,context:this.derived.size?await this.enrichContext({server:t,request:e}):{server:t,request:e}}))},"/ws":async(e,t)=>{if(this.derivedWS.size){let n={},r=this.derived.size?await this.enrichContext({server:t,request:e}):{server:t,request:e};for(let e of this.derivedWS)Object.assign(n,typeof e==`function`?await e(r):e);t.upgrade(e,{data:n});return}t.upgrade(e)}},websocket:{open:e=>{this.emit(`ws:open`,e)},close:(e,t,n)=>{this.emit(`ws:close`,e,t,n)},message:(e,t)=>{if(this.emit(`ws:message`,e,t),typeof t!=`string`)return;let n=JSON.parse(t);r(n)&&this.handleRPC(e,n)}},error:e=>(this.emit(`unhandled:error`,e),console.error(`Unhandled server error:`,e),new Response(`Internal Server Error`,{status:500}))}),this.emit(`server:started`,this.server),this.server}async stop(e=!1){if(!this.server)return console.error(`Server is not running`);await this.server.stop(e),this.emit(`server:stopped`,this.server,e)}on(e,t){return this.events.has(e)||this.events.set(e,new Set),this.events.get(e).add(t),this}off(e,t){return this.events.get(e)?.delete(t),this}emit(e,...t){return this.events.get(e)?.forEach(e=>e(...t)),this}async handleRPC(e,n){let r=this.handlers.get(n.method);if(!r){e.send(`{"id": "${n.id}", "error": {"code": 404, "message": "Not found"}}`);return}try{e.send(t(n.id,await r({input:n.params,context:await this.enrichContext({server:this.server,ws:e})})))}catch(r){this.emit(`error`,r),e.send(t(n.id,r))}}async enrichContext(e){for(let t of this.derived)Object.assign(e,typeof t==`function`?await t(e):t);return e}merge(e){e.initializers.forEach(e=>this.initializers.add(e)),e.derived.forEach(e=>this.derived.add(e)),e.derivedWS.forEach(e=>this.derivedWS.add(e)),e.middlewares.forEach(e=>this.middlewares.add(e)),e.events.forEach((e,t)=>{this.events.has(t)||this.events.set(t,new Set);let n=this.events.get(t);e.forEach(e=>n.add(e))})}wrapHandler(e){if(!this.middlewares.size)return e;let t=Array.from(this.middlewares);return async n=>{let r=0,i=()=>r===t.length?e(n):t[r++]({...n,next:i});return i()}}buildProcedures(){let t=new Map,n=t=>{let n=new e;return t?n.input(t):n};for(let r of this.initializers){let i=r(n),a=[];for(let[e,t]of Object.entries(i))a.push({path:e,value:t});for(;a.length;){let{path:n,value:r}=a.pop();if(r instanceof e){if(t.has(n))throw Error(`Procedure conflict at path "${n}"`);t.set(n,r),this.handlers.set(n,this.wrapHandler(r.wrap()));continue}for(let[e,t]of Object.entries(r)){let r=n?`${n}/${e}`:e;a.push({path:r,value:t})}}}return t}buildRoutes(e){let t={};for(let[r,i]of e){let e=i.getInfo();if(!e.http?.enabled)continue;let a=`/${r}`,o=this.handlers.get(r);if(!o)throw Error(`Handler for procedure at path "${r}" is not defined`);let s=async(t,i)=>{this.emit(`http:request`,t,i);let a=t.body,s={server:i,request:t},c=this.derived.size?await this.enrichContext(s):s;if(a&&e.hasInput){if(t.method===`GET`){let e=t.url.indexOf(`?`);e!==-1&&e<t.url.length-1&&(a=Object.fromEntries(new URLSearchParams(t.url.slice(e+1)).entries()))}else if(t.headers.get(`Content-Length`)!==`0`){let e=t.headers.get(`Content-Type`);if(e)switch(!0){case e.startsWith(`application/json`):a=await t.json();break;case e.startsWith(`text`):a=await t.text();break;case e.startsWith(`multipart/form-data`):a=await t.formData();break;default:console.error(`Unsupported Content-Type for procedure ${r}: ${e}`);break}}}try{return n(await o({input:a,context:c}))}catch(e){return this.emit(`error`,e),n(e)}};if(!e.http.method){t[a]=s;continue}t[a]||(t[a]={}),t[a][e.http.method]=s}return t}};export{a as Ultra};
@@ -0,0 +1 @@
1
+ import{a as e}from"./error-Dq1biCC8.mjs";async function t(t,n){let r=t[`~standard`].validate(n);if(r instanceof Promise&&(r=await r),r.issues)throw new e(JSON.stringify(r.issues,null,2));return r.value}export{t};
@@ -1,3 +1 @@
1
- import { t as validate } from "./validation-Cop5Wvlr.mjs";
2
-
3
- export { validate };
1
+ import{t as e}from"./validation-BY5LC99k.mjs";export{e as validate};
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@sx3/ultra",
3
3
  "type": "module",
4
- "version": "0.0.1",
4
+ "version": "0.0.2",
5
5
  "private": false,
6
- "description": "Ultra: type-safe RPC over HTTP/WebSocket for Bun",
6
+ "description": "Type-safe RPC over HTTP/WebSocket for Bun",
7
7
  "author": "SX3",
8
8
  "license": "MIT",
9
9
  "homepage": "https://github.com/SX-3/ultra",
@@ -14,7 +14,14 @@
14
14
  "bugs": {
15
15
  "url": "https://github.com/SX-3/ultra/issues"
16
16
  },
17
- "keywords": ["RPC", "HTTP", "WebSocket", "Bun", "Framework", "TypeScript"],
17
+ "keywords": [
18
+ "RPC",
19
+ "HTTP",
20
+ "WebSocket",
21
+ "Bun",
22
+ "Framework",
23
+ "TypeScript"
24
+ ],
18
25
  "sideEffects": false,
19
26
  "exports": {
20
27
  ".": {
@@ -1,45 +0,0 @@
1
- //#region src/error.ts
2
- var UltraError = class extends Error {
3
- status;
4
- constructor(message, name, status) {
5
- super(message);
6
- this.name = name;
7
- this.status = status;
8
- }
9
- toResponse() {
10
- return Response.json({ error: {
11
- name: this.name,
12
- message: this.message
13
- } }, { status: this.status });
14
- }
15
- toJSON() {
16
- return {
17
- name: this.name,
18
- message: this.message,
19
- status: this.status
20
- };
21
- }
22
- };
23
- var ValidationError = class extends UltraError {
24
- constructor(message = "Validation failed") {
25
- super(message, "ValidationError", 422);
26
- }
27
- };
28
- var UnauthorizedError = class extends UltraError {
29
- constructor(message = "Unauthorized") {
30
- super(message, "UnauthorizedError", 401);
31
- }
32
- };
33
- var NotFoundError = class extends UltraError {
34
- constructor(message = "Not Found") {
35
- super(message, "NotFoundError", 404);
36
- }
37
- };
38
- var UnsupportedProtocolError = class extends UltraError {
39
- constructor(message = "Unsupported Protocol") {
40
- super(message, "UnsupportedProtocolError", 400);
41
- }
42
- };
43
-
44
- //#endregion
45
- export { ValidationError as a, UnsupportedProtocolError as i, UltraError as n, UnauthorizedError as r, NotFoundError as t };
@@ -1,86 +0,0 @@
1
- import { t as validate } from "./validation-Cop5Wvlr.mjs";
2
-
3
- //#region src/procedure.ts
4
- var Procedure = class {
5
- inputSchema;
6
- outputSchema;
7
- handlerFunction;
8
- middleware = /* @__PURE__ */ new Set();
9
- httpOptions;
10
- input(schema) {
11
- if (schema) this.inputSchema = schema;
12
- return this;
13
- }
14
- output(schema) {
15
- if (schema) this.outputSchema = schema;
16
- return this;
17
- }
18
- handler(handler) {
19
- this.handlerFunction = handler;
20
- return this;
21
- }
22
- http(options) {
23
- this.httpOptions = {
24
- ...options,
25
- enabled: true
26
- };
27
- return this;
28
- }
29
- use(middleware) {
30
- this.middleware.add(middleware);
31
- return this;
32
- }
33
- wrap() {
34
- if (!this.handlerFunction) throw new Error("Procedure handler is not defined");
35
- if (!this.inputSchema && !this.outputSchema && !this.middleware.size) return this.handlerFunction;
36
- let composed = this.handlerFunction;
37
- switch (true) {
38
- case !this.inputSchema && !this.outputSchema: break;
39
- case !this.inputSchema: {
40
- const previous = composed;
41
- composed = async (options) => validate(this.outputSchema, await previous(options));
42
- break;
43
- }
44
- case !this.outputSchema: {
45
- const previous = composed;
46
- composed = async (options) => previous({
47
- ...options,
48
- input: await validate(this.inputSchema, options.input)
49
- });
50
- break;
51
- }
52
- default: {
53
- const previous = composed;
54
- composed = async (options) => {
55
- const result = await previous({
56
- ...options,
57
- input: await validate(this.inputSchema, options.input)
58
- });
59
- return validate(this.outputSchema, result);
60
- };
61
- }
62
- }
63
- if (this.middleware.size) {
64
- const middleware = Array.from(this.middleware);
65
- for (let i = middleware.length - 1; i >= 0; i--) {
66
- const mw = middleware[i];
67
- const previous = composed;
68
- composed = (options) => mw({
69
- ...options,
70
- next: () => previous(options)
71
- });
72
- }
73
- }
74
- return composed;
75
- }
76
- getInfo() {
77
- return {
78
- http: this.httpOptions,
79
- hasInput: !!this.inputSchema,
80
- hasOutput: !!this.outputSchema
81
- };
82
- }
83
- };
84
-
85
- //#endregion
86
- export { Procedure as t };
@@ -1,59 +0,0 @@
1
- import { n as UltraError } from "./error-CII1zMOR.mjs";
2
-
3
- //#region src/response.ts
4
- function toHTTPResponse(data) {
5
- switch (true) {
6
- case data instanceof Response: return data;
7
- case data instanceof UltraError: return data.toResponse();
8
- case data instanceof Error: return new Response("Internal Server Error", { status: 500 });
9
- case typeof data === "object": return Response.json(data);
10
- case !data: return new Response(null, { status: 204 });
11
- default: return new Response(String(data));
12
- }
13
- }
14
- function toRPCResponse(id, data) {
15
- let result;
16
- switch (true) {
17
- case data instanceof UltraError:
18
- result = {
19
- id,
20
- error: {
21
- code: data.status,
22
- message: data.message
23
- }
24
- };
25
- break;
26
- case data instanceof Error:
27
- result = {
28
- id,
29
- error: {
30
- code: 500,
31
- message: data.message
32
- }
33
- };
34
- break;
35
- case data instanceof Response:
36
- result = {
37
- id,
38
- error: {
39
- code: data.status,
40
- message: data.statusText
41
- }
42
- };
43
- break;
44
- case typeof data === "object" || typeof data === "number" || typeof data === "boolean":
45
- result = {
46
- id,
47
- result: data
48
- };
49
- break;
50
- default: result = {
51
- id,
52
- result: String(data)
53
- };
54
- }
55
- return JSON.stringify(result);
56
- }
57
-
58
- //#endregion
59
- export { toRPCResponse as n, toHTTPResponse as t };
@@ -1,7 +0,0 @@
1
- //#region src/rpc.ts
2
- function isRPC(value) {
3
- return !!value && typeof value === "object" && "id" in value && "method" in value;
4
- }
5
-
6
- //#endregion
7
- export { isRPC as t };
@@ -1,12 +0,0 @@
1
- import { a as ValidationError } from "./error-CII1zMOR.mjs";
2
-
3
- //#region src/validation.ts
4
- async function validate(schema, input) {
5
- let result = schema["~standard"].validate(input);
6
- if (result instanceof Promise) result = await result;
7
- if (result.issues) throw new ValidationError(JSON.stringify(result.issues, null, 2));
8
- return result.value;
9
- }
10
-
11
- //#endregion
12
- export { validate as t };