@sx3/ultra 0.0.1 → 0.0.3

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.
Files changed (50) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +512 -512
  3. package/dist/auth.d.mts +16 -16
  4. package/dist/auth.mjs +1 -84
  5. package/dist/client.d.mts +9 -9
  6. package/dist/client.mjs +1 -131
  7. package/dist/context-BAOh_2qI.mjs +1 -0
  8. package/dist/context.d.mts +1 -1
  9. package/dist/context.mjs +1 -10
  10. package/dist/cors.d.mts +3 -3
  11. package/dist/cors.mjs +1 -58
  12. package/dist/crypto-DrpstqPB.mjs +1 -0
  13. package/dist/crypto.mjs +1 -44
  14. package/dist/error-CvEWFOYT.mjs +1 -0
  15. package/dist/error.mjs +1 -3
  16. package/dist/{http-BqWCMASL.d.mts → http-CD8MvQOV.d.mts} +2 -2
  17. package/dist/http.d.mts +2 -2
  18. package/dist/http.mjs +1 -1
  19. package/dist/{middleware-COKreBGP.d.mts → middleware-DVREjjkO.d.mts} +4 -4
  20. package/dist/middleware.d.mts +3 -3
  21. package/dist/middleware.mjs +1 -1
  22. package/dist/procedure-BekIoOQX.mjs +1 -0
  23. package/dist/procedure.d.mts +3 -3
  24. package/dist/procedure.mjs +1 -3
  25. package/dist/response-D9PTLpdq.mjs +1 -0
  26. package/dist/response.mjs +1 -3
  27. package/dist/rpc-D9H6IkRD.mjs +1 -0
  28. package/dist/{rpc-Ch2UXReT.d.mts → rpc-DADpT17P.d.mts} +1 -1
  29. package/dist/rpc.d.mts +1 -1
  30. package/dist/rpc.mjs +1 -3
  31. package/dist/session.d.mts +4 -4
  32. package/dist/session.mjs +1 -181
  33. package/dist/test.d.mts +1 -0
  34. package/dist/test.mjs +1 -0
  35. package/dist/types.d.mts +1 -1
  36. package/dist/types.mjs +1 -1
  37. package/dist/ultra.d.mts +7 -7
  38. package/dist/ultra.mjs +1 -273
  39. package/dist/validation-s1s8P8HO.mjs +1 -0
  40. package/dist/validation.d.mts +1 -1
  41. package/dist/validation.mjs +1 -3
  42. package/package.json +10 -3
  43. package/dist/error-CII1zMOR.mjs +0 -45
  44. package/dist/procedure-BN1rLLRX.mjs +0 -86
  45. package/dist/response-CNhIkAYG.mjs +0 -59
  46. package/dist/rpc-_rBI0z-9.mjs +0 -7
  47. package/dist/validation-Cop5Wvlr.mjs +0 -12
  48. /package/dist/{context-ChCsZh7S.d.mts → context-DNamt_XE.d.mts} +0 -0
  49. /package/dist/{types-Cn69QrjS.d.mts → types-CzIiTHWF.d.mts} +0 -0
  50. /package/dist/{validation-CkRfxQJ_.d.mts → validation-Dfgqsq2f.d.mts} +0 -0
package/dist/session.mjs CHANGED
@@ -1,181 +1 @@
1
- import { i as UnsupportedProtocolError } from "./error-CII1zMOR.mjs";
2
- import { Ultra } from "./ultra.mjs";
3
- import { isHTTP, isWS } from "./context.mjs";
4
- import { sign, unsign } from "./crypto.mjs";
5
- import { randomBytes } from "node:crypto";
6
-
7
- //#region src/session.ts
8
- function defineConfig(config) {
9
- return {
10
- ...config,
11
- cookie: {
12
- path: "/",
13
- httpOnly: true,
14
- secure: true,
15
- sameSite: "lax",
16
- maxAge: config.ttlSec,
17
- ...config?.cookie
18
- }
19
- };
20
- }
21
- /** Extends context and socket data, initiate session instance every request */
22
- function createSessionModule(config) {
23
- return new Ultra().deriveWS((context) => ({ sessionId: Session.getOrCreateId(context.request, config) })).derive((context) => ({ session: new Session(config, context) })).use(async ({ context, next }) => {
24
- await context.session.initiate();
25
- const response = await next();
26
- await context.session.commit();
27
- return response;
28
- });
29
- }
30
- /** Stores the ID in a cookie, then moves it to the socket and uses it for requests */
31
- var Session = class Session {
32
- /** Make random session id */
33
- static makeId() {
34
- return randomBytes(16).toString("base64url");
35
- }
36
- /** Get existing session ID from request cookie or create a new one */
37
- static getOrCreateId(request, config) {
38
- const cookie = request.cookies.get(config.name);
39
- if (cookie) return unsign(cookie, config.secret) || Session.makeId();
40
- return Session.makeId();
41
- }
42
- config;
43
- context;
44
- store;
45
- sessionIdFromClient = null;
46
- sessionId;
47
- sessionState = null;
48
- modified = false;
49
- constructor(config, context) {
50
- this.config = config;
51
- this.context = context;
52
- this.store = config.stores[config.store](config, context);
53
- switch (true) {
54
- case isHTTP(context): {
55
- const cookie = context.request.cookies.get(config.name);
56
- if (cookie) this.sessionIdFromClient = unsign(cookie, config.secret);
57
- break;
58
- }
59
- case isWS(context):
60
- this.sessionIdFromClient = context.ws.data.sessionId || null;
61
- break;
62
- default: throw new UnsupportedProtocolError("Session management is only supported for HTTP and WebSocket protocols.");
63
- }
64
- this.sessionId = this.sessionIdFromClient || Session.makeId();
65
- }
66
- get id() {
67
- return this.sessionId;
68
- }
69
- /** Load data from session store */
70
- async initiate() {
71
- if (this.sessionState) return;
72
- this.sessionState = await this.store.read(this.sessionId) || {};
73
- }
74
- /** Commit data to session store */
75
- async commit() {
76
- this.touch();
77
- if (this.isEmpty && this.sessionIdFromClient) return this.store.destroy(this.sessionIdFromClient);
78
- if (this.sessionIdFromClient && this.sessionIdFromClient !== this.sessionId) {
79
- await this.store.destroy(this.sessionIdFromClient);
80
- await this.store.write(this.sessionId, this.state);
81
- } else if (this.modified) await this.store.write(this.sessionId, this.state);
82
- else await this.store.touch(this.sessionId);
83
- }
84
- /** Change session id */
85
- regenerate() {
86
- this.sessionId = Session.makeId();
87
- }
88
- get(key, defaultValue) {
89
- return this.state[key] ?? defaultValue ?? null;
90
- }
91
- set(key, value) {
92
- this.state[key] = value;
93
- this.modified = true;
94
- }
95
- has(key) {
96
- return Object.hasOwn(this.state, key);
97
- }
98
- all() {
99
- return this.state;
100
- }
101
- delete(key) {
102
- delete this.state[key];
103
- this.modified = true;
104
- }
105
- clear() {
106
- this.sessionState = {};
107
- this.modified = true;
108
- }
109
- get state() {
110
- if (!this.sessionState) throw new Error("Session is not initiated yet.");
111
- return this.sessionState;
112
- }
113
- get isEmpty() {
114
- return Object.keys(this.state).length === 0;
115
- }
116
- touch() {
117
- if ("request" in this.context) this.context.request.cookies.set(this.config.name, sign(this.sessionId, this.config.secret), this.config.cookie);
118
- }
119
- };
120
- var RedisSessionStore = class {
121
- config;
122
- connection;
123
- constructor(config, connection) {
124
- this.config = config;
125
- this.connection = connection;
126
- }
127
- async read(sessionId) {
128
- const value = await this.connection.get(`${this.config.name}:${sessionId}`);
129
- if (!value) return null;
130
- return JSON.parse(value);
131
- }
132
- async write(sessionId, data) {
133
- await this.connection.set(`${this.config.name}:${sessionId}`, JSON.stringify(data), "EX", this.config.ttlSec);
134
- }
135
- async destroy(sessionId) {
136
- await this.connection.del(`${this.config.name}:${sessionId}`);
137
- }
138
- async touch(sessionId) {
139
- await this.connection.expire(`${this.config.name}:${sessionId}`, this.config.ttlSec);
140
- }
141
- };
142
- var MemorySessionStore = class {
143
- config;
144
- sessions = /* @__PURE__ */ new Map();
145
- sweepIntervalMs;
146
- ttlMs;
147
- lastSweepAt = Date.now();
148
- constructor(config, sweepIntervalSec = config.ttlSec) {
149
- this.config = config;
150
- this.sweepIntervalMs = sweepIntervalSec * 1e3;
151
- this.ttlMs = config.ttlSec * 1e3;
152
- }
153
- read(sessionId) {
154
- this.maybeSweep();
155
- return this.sessions.get(sessionId)?.data || null;
156
- }
157
- write(sessionId, data) {
158
- this.maybeSweep();
159
- this.sessions.set(sessionId, {
160
- data,
161
- touched: Date.now()
162
- });
163
- }
164
- destroy(sessionId) {
165
- this.maybeSweep();
166
- this.sessions.delete(sessionId);
167
- }
168
- touch(sessionId) {
169
- this.maybeSweep();
170
- const entry = this.sessions.get(sessionId);
171
- if (entry) entry.touched = Date.now();
172
- }
173
- maybeSweep(now = Date.now()) {
174
- if (now - this.lastSweepAt < this.sweepIntervalMs) return;
175
- this.lastSweepAt = now;
176
- for (const [sessionId, entry] of this.sessions) if (now - entry.touched > this.ttlMs) this.sessions.delete(sessionId);
177
- }
178
- };
179
-
180
- //#endregion
181
- export { MemorySessionStore, RedisSessionStore, Session, createSessionModule, defineConfig };
1
+ import{i as e}from"./error-CvEWFOYT.mjs";import{Ultra as t}from"./ultra.mjs";import{n,t as r}from"./context-BAOh_2qI.mjs";import{i,r as a}from"./crypto-DrpstqPB.mjs";import{randomBytes as o}from"node:crypto";function s(e){return{...e,cookie:{path:`/`,httpOnly:!0,secure:!0,sameSite:`lax`,maxAge:e.ttlSec,...e?.cookie}}}function c(e){return new t().deriveWS(t=>({sessionId:l.getOrCreateId(t.request,e)})).derive(t=>({session:new l(e,t)})).use(async({context:e,next:t})=>{await e.session.initiate();let n=await t();return await e.session.commit(),n})}var l=class t{static makeId(){return o(16).toString(`base64url`)}static getOrCreateId(e,n){let r=e.cookies.get(n.name);return r&&i(r,n.secret)||t.makeId()}config;context;store;sessionIdFromClient=null;sessionId;sessionState=null;modified=!1;constructor(a,o){switch(this.config=a,this.context=o,this.store=a.stores[a.store](a,o),!0){case r(o):{let e=o.request.cookies.get(a.name);e&&(this.sessionIdFromClient=i(e,a.secret));break}case n(o):this.sessionIdFromClient=o.ws.data.sessionId||null;break;default:throw new e(`Session management is only supported for HTTP and WebSocket protocols.`)}this.sessionId=this.sessionIdFromClient||t.makeId()}get id(){return this.sessionId}async initiate(){this.sessionState||=await this.store.read(this.sessionId)||{}}async commit(){if(this.touch(),this.isEmpty&&this.sessionIdFromClient)return this.store.destroy(this.sessionIdFromClient);this.sessionIdFromClient&&this.sessionIdFromClient!==this.sessionId?(await this.store.destroy(this.sessionIdFromClient),await this.store.write(this.sessionId,this.state)):this.modified?await this.store.write(this.sessionId,this.state):await this.store.touch(this.sessionId)}regenerate(){this.sessionId=t.makeId()}get(e,t){return this.state[e]??t??null}set(e,t){this.state[e]=t,this.modified=!0}has(e){return Object.hasOwn(this.state,e)}all(){return this.state}delete(e){delete this.state[e],this.modified=!0}clear(){this.sessionState={},this.modified=!0}get state(){if(!this.sessionState)throw Error(`Session is not initiated yet.`);return this.sessionState}get isEmpty(){return Object.keys(this.state).length===0}touch(){`request`in this.context&&this.context.request.cookies.set(this.config.name,a(this.sessionId,this.config.secret),this.config.cookie)}},u=class{config;connection;constructor(e,t){this.config=e,this.connection=t}async read(e){let t=await this.connection.get(`${this.config.name}:${e}`);return t?JSON.parse(t):null}async write(e,t){await this.connection.set(`${this.config.name}:${e}`,JSON.stringify(t),`EX`,this.config.ttlSec)}async destroy(e){await this.connection.del(`${this.config.name}:${e}`)}async touch(e){await this.connection.expire(`${this.config.name}:${e}`,this.config.ttlSec)}},d=class{config;sessions=new Map;sweepIntervalMs;ttlMs;lastSweepAt=Date.now();constructor(e,t=e.ttlSec){this.config=e,this.sweepIntervalMs=t*1e3,this.ttlMs=e.ttlSec*1e3}read(e){return this.maybeSweep(),this.sessions.get(e)?.data||null}write(e,t){this.maybeSweep(),this.sessions.set(e,{data:t,touched:Date.now()})}destroy(e){this.maybeSweep(),this.sessions.delete(e)}touch(e){this.maybeSweep();let t=this.sessions.get(e);t&&(t.touched=Date.now())}maybeSweep(e=Date.now()){if(!(e-this.lastSweepAt<this.sweepIntervalMs)){this.lastSweepAt=e;for(let[t,n]of this.sessions)e-n.touched>this.ttlMs&&this.sessions.delete(t)}}};export{d as MemorySessionStore,u as RedisSessionStore,l as Session,c as createSessionModule,s as defineConfig};
@@ -0,0 +1 @@
1
+ export { };
package/dist/test.mjs ADDED
@@ -0,0 +1 @@
1
+ import{Ultra as e}from"./ultra.mjs";import{SessionAuthProvider as t,createAuthModule as n,isGuest as r}from"./auth.mjs";import{createHTTPClient as i}from"./client.mjs";import{MemorySessionStore as a,createSessionModule as o,defineConfig as s}from"./session.mjs";const c=o(s({secret:`supersecretkey`,name:``,ttlSec:0,store:`memory`,cookie:{},stores:{memory:e=>new a(e)}})),l=n({provider:`session`,providers:{session:e=>new t(e)}}).use(c);new e().use(l).use(r),i({baseUrl:`dasd`});export{};
package/dist/types.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as JSONValue, i as JSONPrimitive, n as JSONArray, o as Promisable, r as JSONObject, t as DeepReadonly } from "./types-Cn69QrjS.mjs";
1
+ import { a as JSONValue, i as JSONPrimitive, n as JSONArray, o as Promisable, r as JSONObject, t as DeepReadonly } from "./types-CzIiTHWF.mjs";
2
2
  export { DeepReadonly, JSONArray, JSONObject, JSONPrimitive, JSONValue, Promisable };
package/dist/types.mjs CHANGED
@@ -1 +1 @@
1
- export { };
1
+ export{};
package/dist/ultra.d.mts CHANGED
@@ -1,9 +1,9 @@
1
- import { n as DefaultSocketData, t as BaseContext } from "./context-ChCsZh7S.mjs";
2
- import { o as Promisable } from "./types-Cn69QrjS.mjs";
3
- import { n as BunRoutes } from "./http-BqWCMASL.mjs";
4
- import { c as StandardSchemaV1 } from "./validation-CkRfxQJ_.mjs";
5
- import { a as ProcedureHandler, i as Procedure, t as Middleware } from "./middleware-COKreBGP.mjs";
6
- import { n as Payload } from "./rpc-Ch2UXReT.mjs";
1
+ import { n as DefaultSocketData, t as BaseContext } from "./context-DNamt_XE.mjs";
2
+ import { o as Promisable } from "./types-CzIiTHWF.mjs";
3
+ import { n as BunRoutes } from "./http-CD8MvQOV.mjs";
4
+ import { c as StandardSchemaV1 } from "./validation-Dfgqsq2f.mjs";
5
+ import { a as ProcedureHandler, i as Procedure, t as Middleware } from "./middleware-DVREjjkO.mjs";
6
+ import { n as Payload } from "./rpc-DADpT17P.mjs";
7
7
  import { BunRequest, ErrorLike, Server, ServerWebSocket } from "bun";
8
8
 
9
9
  //#region src/ultra.d.ts
@@ -43,7 +43,7 @@ declare class Ultra<Procedures extends ProceduresMap = ProceduresMap, Context ex
43
43
  /** Register procedures */
44
44
  routes<const P extends ProceduresMap>(initializer: ProcedureMapInitializer<P, Context>): Ultra<Procedures & P, Context, SocketData>;
45
45
  /** Register middleware or another Ultra instance */
46
- use<const PluginProcedures extends ProceduresMap, const PluginContext extends BaseContext, const PluginSocketData extends DefaultSocketData>(entity: Middleware<unknown, unknown, Context> | Ultra<PluginProcedures, PluginContext, PluginSocketData>): Ultra<Procedures & PluginProcedures, Context & PluginContext, SocketData & PluginSocketData>;
46
+ use<const PluginProcedures extends ProceduresMap, const PluginContext extends BaseContext, const PluginSocketData extends DefaultSocketData>(entity: Middleware<any, any, Context> | Ultra<PluginProcedures, PluginContext, PluginSocketData>): Ultra<Procedures & PluginProcedures, Context & PluginContext, SocketData & PluginSocketData>;
47
47
  /** Extends context values for every request with provided values */
48
48
  derive<const D extends DeriveValue<Context>>(derive: D): Ultra<Procedures, Context & ExtractDerive<Context, D>, SocketData>;
49
49
  /** Extends WS data for every ws connection */
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-BekIoOQX.mjs";import{n as t,t as n}from"./response-D9PTLpdq.mjs";import{t as r}from"./rpc-D9H6IkRD.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-CvEWFOYT.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,2 +1,2 @@
1
- import { a as PathSegment, c as StandardSchemaV1, d as validate, i as Issue, l as SuccessResult, n as InferInput, o as Props, r as InferOutput, s as Result, t as FailureResult, u as Types } from "./validation-CkRfxQJ_.mjs";
1
+ import { a as PathSegment, c as StandardSchemaV1, d as validate, i as Issue, l as SuccessResult, n as InferInput, o as Props, r as InferOutput, s as Result, t as FailureResult, u as Types } from "./validation-Dfgqsq2f.mjs";
2
2
  export { FailureResult, InferInput, InferOutput, Issue, PathSegment, Props, Result, StandardSchemaV1, SuccessResult, Types, validate };
@@ -1,3 +1 @@
1
- import { t as validate } from "./validation-Cop5Wvlr.mjs";
2
-
3
- export { validate };
1
+ import{t as e}from"./validation-s1s8P8HO.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.3",
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 };