elysia 0.2.1 → 0.2.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.
@@ -0,0 +1 @@
1
+ export {};
package/dist/error.js ADDED
@@ -0,0 +1,7 @@
1
+ const errorCodeToStatus = new Map();
2
+ errorCodeToStatus.set('INTERNAL_SERVER_ERROR', 500);
3
+ errorCodeToStatus.set('NOT_FOUND', 404);
4
+ errorCodeToStatus.set('VALIDATION', 400);
5
+ const knownErrors = new Set(errorCodeToStatus.keys());
6
+ export const mapErrorCode = (error) => knownErrors.has(error) ? error : 'UNKNOWN';
7
+ export const mapErrorStatus = (error) => errorCodeToStatus.get(error) ?? 500;
@@ -0,0 +1,173 @@
1
+ const isNotEmpty = (obj) => {
2
+ for (const x in obj)
3
+ return true;
4
+ return false;
5
+ };
6
+ export const mapEarlyResponse = (response, set) => {
7
+ if (set.redirect)
8
+ return Response.redirect(set.redirect, {
9
+ headers: set.headers
10
+ });
11
+ if (isNotEmpty(set.headers) || set.status !== 200)
12
+ switch (typeof response) {
13
+ case 'string':
14
+ return new Response(response, {
15
+ status: set.status,
16
+ headers: set.headers
17
+ });
18
+ case 'object':
19
+ if (response instanceof Error)
20
+ return errorToResponse(response, set.headers);
21
+ if (response instanceof Response) {
22
+ for (const key in set.headers)
23
+ response.headers.append(key, set.headers[key]);
24
+ return response;
25
+ }
26
+ if (response instanceof Blob)
27
+ return new Response(response, {
28
+ status: set.status,
29
+ headers: set.headers
30
+ });
31
+ if (!set.headers['Content-Type'])
32
+ set.headers['Content-Type'] = 'application/json';
33
+ return new Response(JSON.stringify(response), {
34
+ status: set.status,
35
+ headers: set.headers
36
+ });
37
+ case 'function':
38
+ if (response instanceof Blob)
39
+ return new Response(response, {
40
+ status: set.status,
41
+ headers: set.headers
42
+ });
43
+ for (const key in set.headers)
44
+ response.headers.append(key, set.headers[key]);
45
+ return response;
46
+ case 'number':
47
+ case 'boolean':
48
+ return new Response(response.toString(), {
49
+ status: set.status,
50
+ headers: set.headers
51
+ });
52
+ default:
53
+ break;
54
+ }
55
+ else
56
+ switch (typeof response) {
57
+ case 'string':
58
+ return new Response(response);
59
+ case 'object':
60
+ if (response instanceof Response)
61
+ return response;
62
+ if (response instanceof Error)
63
+ return errorToResponse(response, set.headers);
64
+ if (response instanceof Blob)
65
+ return new Response(response);
66
+ return new Response(JSON.stringify(response), {
67
+ headers: {
68
+ 'content-type': 'application/json'
69
+ }
70
+ });
71
+ case 'function':
72
+ if (response instanceof Blob)
73
+ return new Response(response);
74
+ return response;
75
+ case 'number':
76
+ case 'boolean':
77
+ return new Response(response.toString());
78
+ default:
79
+ break;
80
+ }
81
+ };
82
+ export const mapResponse = (response, set) => {
83
+ if (set.redirect)
84
+ return Response.redirect(set.redirect, {
85
+ headers: set.headers
86
+ });
87
+ if (isNotEmpty(set.headers) || set.status !== 200)
88
+ switch (typeof response) {
89
+ case 'string':
90
+ return new Response(response, {
91
+ status: set.status,
92
+ headers: set.headers
93
+ });
94
+ case 'object':
95
+ if (response instanceof Error)
96
+ return errorToResponse(response, set.headers);
97
+ if (response instanceof Response) {
98
+ for (const key in set.headers)
99
+ response.headers.append(key, set.headers[key]);
100
+ return response;
101
+ }
102
+ if (response instanceof Blob)
103
+ return new Response(response, {
104
+ status: set.status,
105
+ headers: set.headers
106
+ });
107
+ if (!set.headers['Content-Type'])
108
+ set.headers['Content-Type'] = 'application/json';
109
+ return new Response(JSON.stringify(response), {
110
+ status: set.status,
111
+ headers: set.headers
112
+ });
113
+ case 'function':
114
+ if (response instanceof Blob)
115
+ return new Response(response, {
116
+ status: set.status,
117
+ headers: set.headers
118
+ });
119
+ return response();
120
+ case 'number':
121
+ case 'boolean':
122
+ return new Response(response.toString(), {
123
+ status: set.status,
124
+ headers: set.headers
125
+ });
126
+ case 'undefined':
127
+ return new Response('', {
128
+ status: set.status,
129
+ headers: set.headers
130
+ });
131
+ default:
132
+ return new Response(response, {
133
+ status: set.status,
134
+ headers: set.headers
135
+ });
136
+ }
137
+ else
138
+ switch (typeof response) {
139
+ case 'string':
140
+ return new Response(response);
141
+ case 'object':
142
+ if (response instanceof Response)
143
+ return response;
144
+ if (response instanceof Error)
145
+ return errorToResponse(response, set.headers);
146
+ if (response instanceof Blob)
147
+ return new Response(response);
148
+ return new Response(JSON.stringify(response), {
149
+ headers: {
150
+ 'content-type': 'application/json'
151
+ }
152
+ });
153
+ case 'function':
154
+ if (response instanceof Blob)
155
+ return new Response(response);
156
+ return response();
157
+ case 'number':
158
+ case 'boolean':
159
+ return new Response(response.toString());
160
+ case 'undefined':
161
+ return new Response('');
162
+ default:
163
+ return new Response(response);
164
+ }
165
+ };
166
+ export const errorToResponse = (error, headers) => new Response(JSON.stringify({
167
+ name: error?.name,
168
+ message: error?.message,
169
+ cause: error?.cause
170
+ }), {
171
+ status: 500,
172
+ headers
173
+ });
package/dist/index.d.ts CHANGED
@@ -108,6 +108,6 @@ export default class Elysia<Instance extends ElysiaInstance = ElysiaInstance> {
108
108
  }
109
109
  export { Elysia };
110
110
  export { Type as t } from '@sinclair/typebox';
111
- export { SCHEMA, DEFS, createValidationError, getSchemaValidator } from './utils';
111
+ export { SCHEMA, DEFS, createValidationError, getSchemaValidator, mergeDeep, mergeHook, mergeObjectArray, mapPathnameAndQueryRegEx, mapQuery } from './utils';
112
112
  export type { Context, PreContext } from './context';
113
113
  export type { Handler, RegisteredHook, BeforeRequestHandler, TypedRoute, OverwritableTypeRoute, ElysiaInstance, ElysiaConfig, HTTPMethod, ComposedHandler, InternalRoute, BodyParser, ErrorHandler, ErrorCode, TypedSchema, LocalHook, LocalHandler, LifeCycle, LifeCycleEvent, AfterRequestHandler, HookHandler, TypedSchemaToRoute, UnwrapSchema, LifeCycleStore, VoidLifeCycle, SchemaValidator, ElysiaRoute, ExtractPath, IsPathParameter, IsAny, IsNever, UnknownFallback, WithArray, ObjectValues, PickInOrder, MaybePromise, MergeIfNotNull } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,488 @@
1
+ import { Raikiri } from 'raikiri';
2
+ import { mapResponse, mapEarlyResponse } from './handler';
3
+ import { mapQuery, clone, mergeHook, mergeDeep, createValidationError, getSchemaValidator, SCHEMA, DEFS, getResponseSchemaValidator, mapPathnameAndQueryRegEx } from './utils';
4
+ import { registerSchemaPath } from './schema';
5
+ import { mapErrorCode, mapErrorStatus } from './error';
6
+ const ASYNC_FN = 'AsyncFunction';
7
+ export default class Elysia {
8
+ constructor(config = {}) {
9
+ this.store = {
10
+ [SCHEMA]: {},
11
+ [DEFS]: {}
12
+ };
13
+ this.decorators = null;
14
+ this.event = {
15
+ start: [],
16
+ request: [],
17
+ parse: [],
18
+ transform: [],
19
+ beforeHandle: [],
20
+ afterHandle: [],
21
+ error: [],
22
+ stop: []
23
+ };
24
+ this.server = null;
25
+ this.$schema = null;
26
+ this.router = new Raikiri();
27
+ this.fallbackRoute = {};
28
+ this.routes = [];
29
+ this.lazyLoadModules = [];
30
+ this.stop = async () => {
31
+ if (!this.server)
32
+ throw new Error("Elysia isn't running. Call `app.listen` to start the server.");
33
+ this.server.stop();
34
+ for (let i = 0; i < this.event.stop.length; i++)
35
+ await this.event.stop[i](this);
36
+ };
37
+ this.config = {
38
+ strictPath: false,
39
+ ...config
40
+ };
41
+ }
42
+ _addHandler(method, path, handler, hook) {
43
+ path = path.startsWith('/') ? path : `/${path}`;
44
+ this.routes.push({
45
+ method,
46
+ path,
47
+ handler,
48
+ hooks: mergeHook(clone(this.event), hook)
49
+ });
50
+ const defs = this.store[DEFS];
51
+ const body = getSchemaValidator(hook?.schema?.body ?? this.$schema?.body, defs);
52
+ const header = getSchemaValidator(hook?.schema?.headers ?? this.$schema?.headers, defs, true);
53
+ const params = getSchemaValidator(hook?.schema?.params ?? this.$schema?.params, defs);
54
+ const query = getSchemaValidator(hook?.schema?.query ?? this.$schema?.query, defs);
55
+ const response = getResponseSchemaValidator(hook?.schema?.response ?? this.$schema?.response, defs);
56
+ registerSchemaPath({
57
+ schema: this.store[SCHEMA],
58
+ hook,
59
+ method,
60
+ path,
61
+ models: this.store[DEFS]
62
+ });
63
+ const validator = body || header || params || query || response
64
+ ? {
65
+ body,
66
+ header,
67
+ params,
68
+ query,
69
+ response
70
+ }
71
+ : undefined;
72
+ if (path === '/*')
73
+ this.fallbackRoute[method] = {
74
+ handle: handler,
75
+ hooks: mergeHook(clone(this.event), hook),
76
+ validator
77
+ };
78
+ const mainHandler = {
79
+ handle: handler,
80
+ hooks: mergeHook(clone(this.event), hook),
81
+ validator
82
+ };
83
+ this.router.add(method, path, mainHandler);
84
+ if (!this.config.strictPath && path !== '/')
85
+ if (path.endsWith('/'))
86
+ this.router.add(method, path.substring(0, path.length - 1), mainHandler);
87
+ else
88
+ this.router.add(method, `${path}/`, mainHandler);
89
+ }
90
+ onStart(handler) {
91
+ this.event.start.push(handler);
92
+ return this;
93
+ }
94
+ onRequest(handler) {
95
+ this.event.request.push(handler);
96
+ return this;
97
+ }
98
+ onParse(parser) {
99
+ this.event.parse.splice(this.event.parse.length - 1, 0, parser);
100
+ return this;
101
+ }
102
+ onTransform(handler) {
103
+ this.event.transform.push(handler);
104
+ return this;
105
+ }
106
+ onBeforeHandle(handler) {
107
+ this.event.beforeHandle.push(handler);
108
+ return this;
109
+ }
110
+ onAfterHandle(handler) {
111
+ this.event.afterHandle.push(handler);
112
+ return this;
113
+ }
114
+ onError(errorHandler) {
115
+ this.event.error.push(errorHandler);
116
+ return this;
117
+ }
118
+ onStop(handler) {
119
+ this.event.stop.push(handler);
120
+ return this;
121
+ }
122
+ on(type, handler) {
123
+ switch (type) {
124
+ case 'start':
125
+ this.event.start.push(handler);
126
+ break;
127
+ case 'request':
128
+ this.event.request.push(handler);
129
+ break;
130
+ case 'parse':
131
+ this.event.parse.push(handler);
132
+ break;
133
+ case 'transform':
134
+ this.event.transform.push(handler);
135
+ break;
136
+ case 'beforeHandle':
137
+ this.event.beforeHandle.push(handler);
138
+ break;
139
+ case 'afterHandle':
140
+ this.event.afterHandle.push(handler);
141
+ break;
142
+ case 'error':
143
+ this.event.error.push(handler);
144
+ break;
145
+ case 'stop':
146
+ this.event.stop.push(handler);
147
+ break;
148
+ }
149
+ return this;
150
+ }
151
+ group(prefix, run) {
152
+ const instance = new Elysia();
153
+ instance.store = this.store;
154
+ const sandbox = run(instance);
155
+ if (sandbox.event.request.length)
156
+ this.event.request = [
157
+ ...this.event.request,
158
+ ...sandbox.event.request
159
+ ];
160
+ Object.values(instance.routes).forEach(({ method, path, handler, hooks }) => {
161
+ this._addHandler(method, `${prefix}${path}`, handler, hooks);
162
+ });
163
+ return this;
164
+ }
165
+ guard(hook, run) {
166
+ const instance = new Elysia();
167
+ instance.store = this.store;
168
+ const sandbox = run(instance);
169
+ if (sandbox.event.request.length)
170
+ this.event.request = [
171
+ ...this.event.request,
172
+ ...sandbox.event.request
173
+ ];
174
+ Object.values(instance.routes).forEach(({ method, path, handler, hooks: localHook }) => {
175
+ this._addHandler(method, path, handler, mergeHook(hook, localHook));
176
+ });
177
+ return this;
178
+ }
179
+ use(plugin) {
180
+ if (plugin instanceof Promise) {
181
+ this.lazyLoadModules.push(plugin.then((plugin) => {
182
+ if (typeof plugin === 'function')
183
+ return plugin(this);
184
+ return plugin.default(this);
185
+ }));
186
+ return this;
187
+ }
188
+ const instance = plugin(this);
189
+ if (instance instanceof Promise) {
190
+ this.lazyLoadModules.push(instance);
191
+ return this;
192
+ }
193
+ return instance;
194
+ }
195
+ get(path, handler, hook) {
196
+ this._addHandler('GET', path, handler, hook);
197
+ return this;
198
+ }
199
+ post(path, handler, hook) {
200
+ this._addHandler('POST', path, handler, hook);
201
+ return this;
202
+ }
203
+ put(path, handler, hook) {
204
+ this._addHandler('PUT', path, handler, hook);
205
+ return this;
206
+ }
207
+ patch(path, handler, hook) {
208
+ this._addHandler('PATCH', path, handler, hook);
209
+ return this;
210
+ }
211
+ delete(path, handler, hook) {
212
+ this._addHandler('DELETE', path, handler, hook);
213
+ return this;
214
+ }
215
+ options(path, handler, hook) {
216
+ this._addHandler('OPTIONS', path, handler, hook);
217
+ return this;
218
+ }
219
+ all(path, handler, hook) {
220
+ this._addHandler('ALL', path, handler, hook);
221
+ return this;
222
+ }
223
+ head(path, handler, hook) {
224
+ this._addHandler('HEAD', path, handler, hook);
225
+ return this;
226
+ }
227
+ trace(path, handler, hook) {
228
+ this._addHandler('TRACE', path, handler, hook);
229
+ return this;
230
+ }
231
+ connect(path, handler, hook) {
232
+ this._addHandler('CONNECT', path, handler, hook);
233
+ return this;
234
+ }
235
+ route(method, path, handler, hook) {
236
+ this._addHandler(method, path, handler, hook);
237
+ return this;
238
+ }
239
+ state(name, value) {
240
+ if (!(name in this.store)) {
241
+ ;
242
+ this.store[name] = value;
243
+ }
244
+ return this;
245
+ }
246
+ decorate(name, value) {
247
+ if (!this.decorators)
248
+ this.decorators = {};
249
+ if (!(name in this.decorators))
250
+ this.decorators[name] = value;
251
+ return this;
252
+ }
253
+ derive(transform) {
254
+ this.store = mergeDeep(this.store, transform(() => this.store));
255
+ return this;
256
+ }
257
+ inject(transform) {
258
+ return this.onTransform((context) => {
259
+ Object.assign(context, transform(context));
260
+ });
261
+ }
262
+ schema(schema) {
263
+ const defs = this.store[DEFS];
264
+ this.$schema = {
265
+ body: getSchemaValidator(schema.body, defs),
266
+ headers: getSchemaValidator(schema?.headers, defs, true),
267
+ params: getSchemaValidator(schema?.params, defs),
268
+ query: getSchemaValidator(schema?.query, defs),
269
+ response: getSchemaValidator(schema?.response, defs)
270
+ };
271
+ return this;
272
+ }
273
+ async handle(request) {
274
+ const set = {
275
+ status: 200,
276
+ headers: {}
277
+ };
278
+ let context;
279
+ if (this.decorators) {
280
+ context = clone(this.decorators);
281
+ context.request = request;
282
+ context.set = set;
283
+ context.store = this.store;
284
+ context.query = {};
285
+ }
286
+ else {
287
+ context = {
288
+ set,
289
+ store: this.store,
290
+ request,
291
+ query: {}
292
+ };
293
+ }
294
+ try {
295
+ const event = this.event;
296
+ if (event.request.length)
297
+ for (let i = 0; i < event.request.length; i++) {
298
+ const onRequest = event.request[i];
299
+ let response = onRequest(context);
300
+ if (onRequest.constructor.name === ASYNC_FN)
301
+ response = await response;
302
+ response = mapEarlyResponse(response, set);
303
+ if (response)
304
+ return response;
305
+ }
306
+ const fracture = request.url.match(mapPathnameAndQueryRegEx);
307
+ if (!fracture)
308
+ throw new Error('NOT_FOUND');
309
+ const route = this.router.match(request.method, fracture[1]) ||
310
+ this.router.match('ALL', fracture[1]);
311
+ if (!route)
312
+ throw new Error('NOT_FOUND');
313
+ const handler = route.store || this.fallbackRoute[request.method];
314
+ if (!handler)
315
+ throw new Error('NOT_FOUND');
316
+ let body;
317
+ if (request.method !== 'GET') {
318
+ let contentType = request.headers.get('content-type');
319
+ if (contentType) {
320
+ const index = contentType.indexOf(';');
321
+ if (index !== -1)
322
+ contentType = contentType.slice(0, index);
323
+ if (event.parse.length)
324
+ for (let i = 0; i < event.parse.length; i++) {
325
+ const fn = event.parse[i];
326
+ let temp = fn(context, contentType);
327
+ if (fn.constructor.name === ASYNC_FN)
328
+ temp = await temp;
329
+ if (temp) {
330
+ body = temp;
331
+ break;
332
+ }
333
+ }
334
+ if (body === undefined) {
335
+ switch (contentType) {
336
+ case 'application/json':
337
+ body = await request.json();
338
+ break;
339
+ case 'text/plain':
340
+ body = await request.text();
341
+ break;
342
+ case 'application/x-www-form-urlencoded':
343
+ body = mapQuery(await request.text());
344
+ break;
345
+ }
346
+ }
347
+ }
348
+ }
349
+ context.body = body;
350
+ context.params = route?.params || {};
351
+ if (fracture[2])
352
+ context.query = mapQuery(fracture[2]);
353
+ const hooks = handler.hooks;
354
+ if (hooks.transform.length)
355
+ for (let i = 0; i < hooks.transform.length; i++) {
356
+ const fn = hooks.transform[i];
357
+ const operation = fn(context);
358
+ if (fn.constructor.name === ASYNC_FN)
359
+ await operation;
360
+ }
361
+ if (handler.validator) {
362
+ const validator = handler.validator;
363
+ if (validator.headers) {
364
+ const _header = {};
365
+ for (const key in request.headers)
366
+ _header[key] = request.headers.get(key);
367
+ if (validator.headers.Check(_header) === false)
368
+ throw createValidationError('header', validator.headers, _header);
369
+ }
370
+ if (validator.params?.Check(context.params) === false)
371
+ throw createValidationError('params', validator.params, context.params);
372
+ if (validator.query?.Check(context.query) === false)
373
+ throw createValidationError('query', validator.query, context.query);
374
+ if (validator.body?.Check(body) === false)
375
+ throw createValidationError('body', validator.body, body);
376
+ }
377
+ if (hooks.beforeHandle.length)
378
+ for (let i = 0; i < hooks.beforeHandle.length; i++) {
379
+ const fn = hooks.beforeHandle[i];
380
+ let response = fn(context);
381
+ if (fn.constructor.name === ASYNC_FN)
382
+ response = await response;
383
+ if (response !== null && response !== undefined) {
384
+ for (let i = 0; i < hooks.afterHandle.length; i++) {
385
+ const fn = hooks.afterHandle[i];
386
+ let newResponse = fn(context, response);
387
+ if (fn.constructor.name === ASYNC_FN)
388
+ newResponse = await newResponse;
389
+ if (newResponse)
390
+ response = newResponse;
391
+ }
392
+ const result = mapEarlyResponse(response, context.set);
393
+ if (result)
394
+ return result;
395
+ }
396
+ }
397
+ let response = handler.handle(context);
398
+ if (response instanceof Promise)
399
+ response = await response;
400
+ if (handler.validator?.response?.Check(response) === false)
401
+ throw createValidationError('response', handler.validator.response, response);
402
+ if (hooks.afterHandle.length)
403
+ for (let i = 0; i < hooks.afterHandle.length; i++) {
404
+ const afterHandle = hooks.afterHandle[i];
405
+ let newResponse = afterHandle(context, response);
406
+ if (afterHandle.constructor.name === ASYNC_FN)
407
+ newResponse = await newResponse;
408
+ const result = mapEarlyResponse(newResponse, context.set);
409
+ if (result)
410
+ return result;
411
+ }
412
+ return mapResponse(response, context.set);
413
+ }
414
+ catch (error) {
415
+ return this.handleError(error, set);
416
+ }
417
+ }
418
+ async handleError(error, set = {
419
+ headers: {}
420
+ }) {
421
+ for (let i = 0; i < this.event.error.length; i++) {
422
+ let response = this.event.error[i]({
423
+ code: mapErrorCode(error.message),
424
+ error,
425
+ set
426
+ });
427
+ if (response instanceof Promise)
428
+ response = await response;
429
+ if (response !== undefined && response !== null)
430
+ return mapResponse(response, set);
431
+ }
432
+ return new Response(typeof error.cause === 'string' ? error.cause : error.message, {
433
+ headers: set.headers,
434
+ status: mapErrorStatus(mapErrorCode(error.message))
435
+ });
436
+ }
437
+ listen(options, callback) {
438
+ if (!Bun)
439
+ throw new Error('Bun to run');
440
+ if (typeof options === 'string') {
441
+ options = +options;
442
+ if (Number.isNaN(options))
443
+ throw new Error('Port must be a numeric value');
444
+ }
445
+ const fetch = this.handle.bind(this);
446
+ const serve = typeof options === 'object'
447
+ ? {
448
+ ...this.config.serve,
449
+ ...options,
450
+ fetch
451
+ }
452
+ : {
453
+ ...this.config.serve,
454
+ port: options,
455
+ fetch
456
+ };
457
+ const key = `$$Elysia:${serve.port}`;
458
+ if (globalThis[key]) {
459
+ this.server = globalThis[key];
460
+ this.server.reload(serve);
461
+ }
462
+ else {
463
+ globalThis[key] = this.server = Bun.serve(serve);
464
+ }
465
+ for (let i = 0; i < this.event.start.length; i++)
466
+ this.event.start[i](this);
467
+ if (callback)
468
+ callback(this.server);
469
+ Promise.all(this.lazyLoadModules).then(() => {
470
+ if (!this.server.pendingRequests)
471
+ Bun.gc(true);
472
+ });
473
+ return this;
474
+ }
475
+ get modules() {
476
+ return Promise.all(this.lazyLoadModules);
477
+ }
478
+ setModel(record) {
479
+ Object.entries(record).forEach(([key, value]) => {
480
+ if (!(key in this.store[DEFS]))
481
+ this.store[DEFS][key] = value;
482
+ });
483
+ return this;
484
+ }
485
+ }
486
+ export { Elysia };
487
+ export { Type as t } from '@sinclair/typebox';
488
+ export { SCHEMA, DEFS, createValidationError, getSchemaValidator, mergeDeep, mergeHook, mergeObjectArray, mapPathnameAndQueryRegEx, mapQuery } from './utils';
package/dist/schema.js ADDED
@@ -0,0 +1,109 @@
1
+ import { Kind } from '@sinclair/typebox';
2
+ export const toOpenAPIPath = (path) => path
3
+ .split('/')
4
+ .map((x) => (x.startsWith(':') ? `{${x.slice(1, x.length)}}` : x))
5
+ .join('/');
6
+ export const mapProperties = (name, schema, models) => {
7
+ if (schema === undefined)
8
+ return [];
9
+ if (typeof schema === 'string')
10
+ if (schema in models)
11
+ schema = models[schema];
12
+ else
13
+ throw new Error(`Can't find model ${schema}`);
14
+ return Object.entries(schema?.properties ?? []).map(([key, value]) => ({
15
+ ...value,
16
+ in: name,
17
+ name: key,
18
+ type: value?.type,
19
+ required: schema.required?.includes(key) ?? false
20
+ }));
21
+ };
22
+ export const registerSchemaPath = ({ schema, path, method, hook, models }) => {
23
+ path = toOpenAPIPath(path);
24
+ const bodySchema = hook?.schema?.body;
25
+ const paramsSchema = hook?.schema?.params;
26
+ const headerSchema = hook?.schema?.headers;
27
+ const querySchema = hook?.schema?.query;
28
+ let responseSchema = hook?.schema?.response;
29
+ if (typeof responseSchema === 'object') {
30
+ if (Kind in responseSchema) {
31
+ const { type, properties, required, ...rest } = responseSchema;
32
+ responseSchema = {
33
+ '200': {
34
+ ...rest,
35
+ schema: {
36
+ type,
37
+ properties,
38
+ required
39
+ }
40
+ }
41
+ };
42
+ }
43
+ else {
44
+ Object.entries(responseSchema).forEach(([key, value]) => {
45
+ if (typeof value === 'string') {
46
+ const { type, properties, required, ...rest } = models[value];
47
+ responseSchema[key] = {
48
+ ...rest,
49
+ schema: {
50
+ $ref: `#/definitions/${value}`
51
+ }
52
+ };
53
+ }
54
+ else {
55
+ const { type, properties, required, ...rest } = value;
56
+ responseSchema[key] = {
57
+ ...rest,
58
+ schema: {
59
+ type,
60
+ properties,
61
+ required
62
+ }
63
+ };
64
+ }
65
+ });
66
+ }
67
+ }
68
+ else if (typeof responseSchema === 'string') {
69
+ const { type, properties, required, ...rest } = models[responseSchema];
70
+ responseSchema = {
71
+ '200': {
72
+ ...rest,
73
+ schema: {
74
+ $ref: `#/definitions/${responseSchema}`
75
+ }
76
+ }
77
+ };
78
+ }
79
+ const parameters = [
80
+ ...mapProperties('header', headerSchema, models),
81
+ ...mapProperties('path', paramsSchema, models),
82
+ ...mapProperties('query', querySchema, models)
83
+ ];
84
+ if (bodySchema)
85
+ parameters.push({
86
+ in: 'body',
87
+ name: 'body',
88
+ required: true,
89
+ schema: typeof bodySchema === 'string'
90
+ ? {
91
+ $ref: `#/definitions/${bodySchema}`
92
+ }
93
+ : bodySchema
94
+ });
95
+ schema[path] = {
96
+ ...(schema[path] ? schema[path] : {}),
97
+ [method.toLowerCase()]: {
98
+ ...(headerSchema || paramsSchema || querySchema || bodySchema
99
+ ? { parameters }
100
+ : {}),
101
+ ...(responseSchema
102
+ ? {
103
+ responses: responseSchema
104
+ }
105
+ : {}),
106
+ ...hook?.schema?.detail
107
+ }
108
+ };
109
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/utils.js ADDED
@@ -0,0 +1,101 @@
1
+ import { Kind, Type } from '@sinclair/typebox';
2
+ import { TypeCompiler } from '@sinclair/typebox/compiler';
3
+ export const SCHEMA = Symbol('schema');
4
+ export const DEFS = Symbol('definitions');
5
+ export const mergeObjectArray = (a, b) => [
6
+ ...(Array.isArray(a) ? a : [a]),
7
+ ...(Array.isArray(b) ? b : [b])
8
+ ];
9
+ export const mergeHook = (a, b) => {
10
+ const aSchema = 'schema' in a ? a.schema : null;
11
+ const bSchema = b && 'schema' in b ? b.schema : null;
12
+ return {
13
+ schema: aSchema || bSchema
14
+ ? {
15
+ body: bSchema?.body ?? aSchema?.body,
16
+ header: bSchema?.headers ?? aSchema?.headers,
17
+ params: bSchema?.params ?? aSchema?.params,
18
+ query: bSchema?.query ?? aSchema?.query,
19
+ response: bSchema?.response ?? aSchema?.response
20
+ }
21
+ : undefined,
22
+ transform: mergeObjectArray(a.transform ?? [], b?.transform ?? []),
23
+ beforeHandle: mergeObjectArray(a.beforeHandle ?? [], b?.beforeHandle ?? []),
24
+ afterHandle: mergeObjectArray(a.afterHandle ?? [], b?.afterHandle ?? []),
25
+ error: mergeObjectArray(a.error ?? [], b?.error ?? [])
26
+ };
27
+ };
28
+ export const clone = (value) => [value][0];
29
+ export const mapPathnameAndQueryRegEx = /:\/\/[^/]+([^#?]+)(?:\?([^#]+))?/;
30
+ export const mapQuery = (url) => {
31
+ const mapped = {};
32
+ const paths = url.split('&');
33
+ for (let i = 0; i < paths.length; i++) {
34
+ const part = paths[i];
35
+ const index = part.indexOf('=');
36
+ let value = part.slice(index + 1);
37
+ if (value.includes('%'))
38
+ value = decodeURIComponent(value);
39
+ mapped[part.slice(0, index)] = value;
40
+ }
41
+ return mapped;
42
+ };
43
+ const isObject = (item) => item && typeof item === 'object' && !Array.isArray(item);
44
+ export const mergeDeep = (target, source) => {
45
+ const output = Object.assign({}, target);
46
+ if (isObject(target) && isObject(source)) {
47
+ Object.keys(source).forEach((key) => {
48
+ if (isObject(source[key])) {
49
+ if (!(key in target))
50
+ Object.assign(output, { [key]: source[key] });
51
+ else
52
+ output[key] = mergeDeep(target[key], source[key]);
53
+ }
54
+ else {
55
+ Object.assign(output, { [key]: source[key] });
56
+ }
57
+ });
58
+ }
59
+ return output;
60
+ };
61
+ export const createValidationError = (type, validator, value) => {
62
+ const error = validator.Errors(value).next().value;
63
+ return new Error('VALIDATION', {
64
+ cause: `Invalid ${type}: '${error?.path?.slice(1) || 'root'}'. ${error.message}`
65
+ });
66
+ };
67
+ export const getSchemaValidator = (s, models, additionalProperties = false) => {
68
+ if (!s)
69
+ return;
70
+ if (typeof s === 'string' && !(s in models))
71
+ return;
72
+ const schema = typeof s === 'string' ? models[s] : s;
73
+ if (schema.type === 'object' && 'additionalProperties' in schema === false)
74
+ schema.additionalProperties = additionalProperties;
75
+ return TypeCompiler.Compile(schema);
76
+ };
77
+ export const getResponseSchemaValidator = (s, models, additionalProperties = false) => {
78
+ if (!s)
79
+ return;
80
+ if (typeof s === 'string' && !(s in models))
81
+ return;
82
+ const maybeSchemaOrRecord = typeof s === 'string' ? models[s] : s;
83
+ const schema = Kind in maybeSchemaOrRecord
84
+ ? maybeSchemaOrRecord
85
+ : Type.Union(Object.keys(maybeSchemaOrRecord)
86
+ .map((key) => {
87
+ const maybeNameOrSchema = maybeSchemaOrRecord[key];
88
+ if (typeof maybeNameOrSchema === 'string') {
89
+ if (maybeNameOrSchema in models) {
90
+ const schema = models[maybeNameOrSchema];
91
+ return schema;
92
+ }
93
+ return undefined;
94
+ }
95
+ return maybeNameOrSchema;
96
+ })
97
+ .filter((a) => a));
98
+ if (schema.type === 'object' && 'additionalProperties' in schema === false)
99
+ schema.additionalProperties = additionalProperties;
100
+ return TypeCompiler.Compile(schema);
101
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elysia",
3
3
  "description": "Fast, and friendly Bun web framework",
4
- "version": "0.2.1",
4
+ "version": "0.2.2",
5
5
  "author": {
6
6
  "name": "saltyAom",
7
7
  "url": "https://github.com/SaltyAom",