arri 0.0.38 → 0.0.40

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/index.mjs CHANGED
@@ -1,8 +1,641 @@
1
- export { A as Arri, D as DEV_DEFINITION_ENDPOINT, q as DEV_ENDPOINT_ROOT, K as ErrorResponse, H as HttpMethodValues, x as createRpcDefinition, L as defineError, G as defineMiddleware, F as defineRoute, w as defineRpc, M as errorResponseFromValidationErrors, B as getRpcParamDefinition, z as getRpcParamName, y as getRpcPath, C as getRpcResponseName, N as handleH3Error, t as isHttpMethod, v as isRpc, I as registerRoute, E as registerRpc } from './shared/arri.f1c922a6.mjs';
2
- import '@sinclair/typebox';
3
- import 'h3';
4
- import '@sinclair/typebox/errors';
5
- import '@sinclair/typebox/value';
1
+ import { Type, TypeGuard } from '@sinclair/typebox';
2
+ import { createError, isError, setResponseStatus, sendError, eventHandler, isPreflightRequest, readValidatedBody, getValidatedQuery, setResponseHeader, send, createRouter, createApp, getQuery, readBody } from 'h3';
3
+ import { ValueErrorType } from '@sinclair/typebox/errors';
4
+ import { e as isHttpMethod, r as removeDisallowedChars, k as kebabCase, p as pascalCase } from './shared/arri.ebf86912.mjs';
5
+ import { Value } from '@sinclair/typebox/value';
6
+
7
+ const ErrorResponse = Type.Object(
8
+ {
9
+ statusCode: Type.Integer(),
10
+ statusMessage: Type.String(),
11
+ stack: Type.Array(Type.Any()),
12
+ data: Type.Optional(Type.Object({}, { additionalProperties: true }))
13
+ },
14
+ { $id: "ErrorResponse" }
15
+ );
16
+ function defineError(statusCode, input = {}) {
17
+ const defaultVals = errorResponseDefaults[statusCode];
18
+ return createError({
19
+ statusCode,
20
+ statusMessage: input.statusMessage ?? defaultVals.message ?? "An unknown error occurred",
21
+ stack: input.stack ?? "",
22
+ data: input.data
23
+ });
24
+ }
25
+ function errorResponseFromValidationErrors(errors, prefixText = `Missing or invalid parameters`) {
26
+ const errorParts = [];
27
+ for (const err of errors) {
28
+ const propName = err.path.split("/");
29
+ propName.shift();
30
+ if (!errorParts.includes(propName.join("."))) {
31
+ errorParts.push(propName.join("."));
32
+ }
33
+ }
34
+ throw defineError(400, {
35
+ statusMessage: `${prefixText}: [${errorParts.join(",")}]`,
36
+ data: errors
37
+ });
38
+ }
39
+ const errorResponseDefaults = {
40
+ 400: {
41
+ name: "BAD_REQUEST",
42
+ message: "Bad Request"
43
+ },
44
+ 401: {
45
+ name: "UNAUTHORIZED",
46
+ message: "Unauthorized"
47
+ },
48
+ 402: {
49
+ name: "PAYMENT_REQUIRED",
50
+ message: "Payment Required"
51
+ },
52
+ 403: {
53
+ name: "FORBIDDEN",
54
+ message: "Forbidden"
55
+ },
56
+ 404: {
57
+ name: "NOT_FOUND",
58
+ message: "Not Found"
59
+ },
60
+ 405: {
61
+ name: "METHOD_NOT_ALLOWED",
62
+ message: "Method Not Allowed"
63
+ },
64
+ 406: {
65
+ name: "NOT_ACCEPTABLE",
66
+ message: "Not Acceptable"
67
+ },
68
+ 407: {
69
+ name: "PROXY_AUTHENTICATION_REQUIRED",
70
+ message: "Proxy Authentication Required"
71
+ },
72
+ 408: {
73
+ name: "REQUEST_TIMEOUT",
74
+ message: "Request Timeout"
75
+ },
76
+ 409: {
77
+ name: "CONFLICT",
78
+ message: "Conflict"
79
+ },
80
+ 410: {
81
+ name: "GONE",
82
+ message: "Gone"
83
+ },
84
+ 411: {
85
+ name: "LENGTH_REQUIRED",
86
+ message: "Length Required"
87
+ },
88
+ 412: {
89
+ name: "PRECONDITION_FAILED",
90
+ message: "Precondition Failed"
91
+ },
92
+ 413: {
93
+ name: "REQUEST_TOO_LONG",
94
+ message: "Request Entity Too Large"
95
+ },
96
+ 414: {
97
+ name: "REQUEST_URI_TOO_LONG",
98
+ message: "Request-URI Too Long"
99
+ },
100
+ 415: {
101
+ name: "UNSUPPORTED_MEDIA_TYPE",
102
+ message: "Unsupported Media Type"
103
+ },
104
+ 416: {
105
+ name: "REQUESTED_RANGE_NOT_SATISFIABLE",
106
+ message: "Requested Range Not Satisfiable"
107
+ },
108
+ 417: {
109
+ name: "EXPECTATION_FAILED",
110
+ message: "Expectation Failed"
111
+ },
112
+ 418: {
113
+ name: "IM_A_TEAPOT",
114
+ message: "I'm a teapot"
115
+ },
116
+ 419: {
117
+ name: "INSUFFICIENT_SPACE_ON_RESOURCE",
118
+ message: "Insufficient Space on Resource"
119
+ },
120
+ 420: {
121
+ name: "METHOD_FAILURE",
122
+ message: "Method Failure"
123
+ },
124
+ 421: {
125
+ name: "MISDIRECTED_REQUEST",
126
+ message: "Misdirected Request"
127
+ },
128
+ 422: {
129
+ name: "UNPROCESSABLE_ENTITY",
130
+ message: "Unprocessable Entity"
131
+ },
132
+ 423: {
133
+ name: "LOCKED",
134
+ message: "Locked"
135
+ },
136
+ 424: {
137
+ name: "FAILED_DEPENDENCY",
138
+ message: "Failed Dependency"
139
+ },
140
+ 428: {
141
+ name: "PRECONDITION_REQUIRED",
142
+ message: "Precondition Required"
143
+ },
144
+ 429: {
145
+ name: "TOO_MANY_REQUESTS",
146
+ message: "Too Many Requests"
147
+ },
148
+ 431: {
149
+ name: "REQUEST_HEADER_FIELDS_TOO_LARGE",
150
+ message: "Request Header Fields Too Large"
151
+ },
152
+ 451: {
153
+ name: "UNAVAILABLE_FOR_LEGAL_REASONS",
154
+ message: "Unavailable For Legal Reasons"
155
+ },
156
+ 500: {
157
+ name: "INTERNAL_SERVER_ERROR",
158
+ message: "Internal Server Error"
159
+ },
160
+ 501: {
161
+ name: "NOT_IMPLEMENTED",
162
+ message: "Not Implemented"
163
+ },
164
+ 502: {
165
+ name: "BAD_GATEWAY",
166
+ message: "Bad Gateway"
167
+ },
168
+ 503: {
169
+ name: "SERVICE_UNAVAILABLE",
170
+ message: "Service Unavailable"
171
+ },
172
+ 504: {
173
+ name: "GATEWAY_TIMEOUT",
174
+ message: "Gateway Timeout"
175
+ },
176
+ 505: {
177
+ name: "HTTP_VERSION_NOT_SUPPORTED",
178
+ message: "HTTP Version Not Supported"
179
+ },
180
+ 507: {
181
+ name: "INSUFFICIENT_STORAGE",
182
+ message: "Insufficient Storage"
183
+ },
184
+ 511: {
185
+ name: "NETWORK_AUTHENTICATION_REQUIRED",
186
+ message: "Network Authentication Required"
187
+ }
188
+ };
189
+ async function handleH3Error(err, context, event, onError) {
190
+ const error = isError(err) ? err : defineError(500, { data: err, stack: `${err}` });
191
+ setResponseStatus(event, error.statusCode);
192
+ if (onError) {
193
+ await onError(error, context, event);
194
+ }
195
+ if (event.handled) {
196
+ return;
197
+ }
198
+ sendError(event, error);
199
+ }
200
+
201
+ function typeboxSafeValidate(schema, coerce = false) {
202
+ const fn = (input) => {
203
+ const finalInput = coerce ? Value.Convert(schema, input) : input;
204
+ if (Value.Check(schema, finalInput)) {
205
+ return { success: true, value: finalInput };
206
+ }
207
+ const errs = [...Value.Errors(schema, finalInput)];
208
+ return {
209
+ success: false,
210
+ errors: errs
211
+ };
212
+ };
213
+ return fn;
214
+ }
215
+
216
+ function isRpc(input) {
217
+ if (typeof input !== "object") {
218
+ return false;
219
+ }
220
+ const anyInput = input;
221
+ if (!isHttpMethod(anyInput.method)) {
222
+ return false;
223
+ }
224
+ if (typeof anyInput.handler !== "function") {
225
+ return false;
226
+ }
227
+ return true;
228
+ }
229
+ function defineRpc(config) {
230
+ return config;
231
+ }
232
+ function createRpcDefinition(rpcName, httpPath, procedure) {
233
+ return {
234
+ description: procedure.description,
235
+ path: httpPath,
236
+ method: procedure.method ?? "post",
237
+ params: getRpcParamDefinition(rpcName, procedure),
238
+ response: getRpcResponseDefinition(rpcName, procedure)
239
+ };
240
+ }
241
+ function getRpcPath(rpcName, prefix = "") {
242
+ const path = rpcName.split(".").map(
243
+ (part) => removeDisallowedChars(
244
+ kebabCase(part),
245
+ `!@#$%^&*()+=[]{}|\\;:'"<>,./?`
246
+ )
247
+ ).join("/");
248
+ const finalPath = prefix ? `/${prefix}/${path}` : `/${path}`;
249
+ return finalPath;
250
+ }
251
+ function getRpcParamName(rpcName, procedure) {
252
+ if (!procedure.params) {
253
+ return null;
254
+ }
255
+ const nameParts = rpcName.split(".").map(
256
+ (part) => removeDisallowedChars(part, `!@#$%^&*()+=[]{}|\\;:'"<>,./?`)
257
+ );
258
+ const paramName = procedure.params.$id ?? pascalCase(`${nameParts.join(`_`)}_params`);
259
+ return paramName;
260
+ }
261
+ function getRpcParamDefinition(rpcName, procedure) {
262
+ if (!procedure.params) {
263
+ return void 0;
264
+ }
265
+ const name = getRpcParamName(rpcName, procedure);
266
+ if (!name) {
267
+ return void 0;
268
+ }
269
+ return name;
270
+ }
271
+ function getRpcResponseName(rpcName, procedure) {
272
+ if (!procedure.response) {
273
+ return null;
274
+ }
275
+ if (TypeGuard.TObject(procedure.response)) {
276
+ const nameParts = rpcName.split(".").map(
277
+ (part) => removeDisallowedChars(part, `!@#$%^&*()+=[]{}|\\;:'"<>,./?`)
278
+ );
279
+ const responseName = procedure.response.$id ?? pascalCase(`${nameParts.join("_")}_response`);
280
+ return responseName;
281
+ }
282
+ return null;
283
+ }
284
+ function getRpcResponseDefinition(rpcName, procedure) {
285
+ if (!procedure.response) {
286
+ return void 0;
287
+ }
288
+ const name = getRpcResponseName(rpcName, procedure);
289
+ if (!name) {
290
+ return void 0;
291
+ }
292
+ return name;
293
+ }
294
+ function registerRpc(router, path, procedure, middleware, opts) {
295
+ const httpMethod = procedure.method ?? "post";
296
+ const handler = eventHandler(async (event) => {
297
+ const context = {
298
+ type: "procedure",
299
+ params: void 0
300
+ };
301
+ if (isPreflightRequest(event)) {
302
+ return "ok";
303
+ }
304
+ try {
305
+ if (middleware.length) {
306
+ await Promise.all(middleware.map((m) => m(context, event)));
307
+ }
308
+ if (procedure.params) {
309
+ switch (httpMethod) {
310
+ case "get":
311
+ case "head": {
312
+ const parsedParams = await getValidatedQuery(
313
+ event,
314
+ typeboxSafeValidate(procedure.params, true)
315
+ );
316
+ if (!parsedParams.success) {
317
+ const errorParts = [];
318
+ for (const err of parsedParams.errors) {
319
+ const propName = err.path.split("/");
320
+ propName.shift();
321
+ if (!errorParts.includes(propName.join("."))) {
322
+ errorParts.push(propName.join("."));
323
+ }
324
+ }
325
+ throw defineError(400, {
326
+ statusMessage: `Missing or invalid url query parameters: [${errorParts.join(
327
+ ","
328
+ )}]`,
329
+ data: parsedParams.errors
330
+ });
331
+ }
332
+ context.params = parsedParams.value;
333
+ break;
334
+ }
335
+ case "delete":
336
+ case "patch":
337
+ case "post":
338
+ case "put": {
339
+ const parsedParams = await readValidatedBody(
340
+ event,
341
+ typeboxSafeValidate(procedure.params)
342
+ );
343
+ if (!parsedParams.success) {
344
+ let isObjectError = false;
345
+ const errorParts = [];
346
+ for (const err of parsedParams.errors) {
347
+ if (err.type === ValueErrorType.Object) {
348
+ isObjectError = true;
349
+ }
350
+ const propName = err.path.split("/");
351
+ propName.shift();
352
+ if (!errorParts.includes(propName.join("."))) {
353
+ errorParts.push(propName.join("."));
354
+ }
355
+ }
356
+ if (isObjectError) {
357
+ throw defineError(400, {
358
+ statusMessage: `Invalid request body. Expected object.`
359
+ });
360
+ }
361
+ throw defineError(400, {
362
+ statusMessage: `Invalid request body. Affected properties [${errorParts.join(
363
+ ", "
364
+ )}]`,
365
+ data: parsedParams.errors
366
+ });
367
+ }
368
+ context.params = parsedParams.value;
369
+ break;
370
+ }
371
+ default:
372
+ break;
373
+ }
374
+ }
375
+ const response = await procedure.handler(context, event);
376
+ context.response = response;
377
+ if (opts.onBeforeResponse) {
378
+ await opts.onBeforeResponse(context, event);
379
+ }
380
+ if (typeof response === "object") {
381
+ setResponseHeader(event, "Content-Type", "application/json");
382
+ await send(event, JSON.stringify(response));
383
+ } else {
384
+ await send(event, response ?? "");
385
+ }
386
+ if (opts.onAfterResponse) {
387
+ await opts.onAfterResponse(context, event);
388
+ }
389
+ if (procedure.postHandler) {
390
+ await procedure.postHandler(context, event);
391
+ }
392
+ } catch (err) {
393
+ await handleH3Error(err, context, event, opts.onError);
394
+ }
395
+ return "";
396
+ });
397
+ switch (httpMethod) {
398
+ case "get":
399
+ router.get(path, handler);
400
+ break;
401
+ case "head":
402
+ router.head(path, handler);
403
+ break;
404
+ case "delete":
405
+ router.delete(path, handler);
406
+ break;
407
+ case "patch":
408
+ router.patch(path, handler);
409
+ break;
410
+ case "put":
411
+ router.put(path, handler);
412
+ break;
413
+ case "post":
414
+ default:
415
+ router.post(path, handler);
416
+ break;
417
+ }
418
+ }
419
+
420
+ function defineRoute(config) {
421
+ return config;
422
+ }
423
+ const defineMiddleware = (middleware) => middleware;
424
+ function registerRoute(router, route, middleware, prefix, opts) {
425
+ const handler = eventHandler(async (event) => {
426
+ const context = {
427
+ type: "route",
428
+ params: event.context.params,
429
+ query: void 0,
430
+ body: void 0
431
+ };
432
+ try {
433
+ if (middleware.length) {
434
+ for (const m of middleware) {
435
+ await m(context, event);
436
+ }
437
+ }
438
+ if (route.query) {
439
+ const result = await getValidatedQuery(
440
+ event,
441
+ typeboxSafeValidate(route.query, true)
442
+ );
443
+ if (!result.success) {
444
+ throw errorResponseFromValidationErrors(
445
+ result.errors,
446
+ "Missing or invalid query parameters"
447
+ );
448
+ }
449
+ context.query = result.value;
450
+ }
451
+ if (route.body && route.method !== "get" && route.method !== "head") {
452
+ const result = await readValidatedBody(
453
+ event,
454
+ typeboxSafeValidate(route.body)
455
+ );
456
+ if (!result.success) {
457
+ throw errorResponseFromValidationErrors(
458
+ result.errors,
459
+ "Missing or invalid body parameters"
460
+ );
461
+ }
462
+ context.body = result.value;
463
+ }
464
+ const response = await route.handler(context, event);
465
+ context.response = response;
466
+ if (opts?.onBeforeResponse) {
467
+ await opts.onBeforeResponse(context, event);
468
+ }
469
+ if (typeof response === "object") {
470
+ setResponseHeader(event, "Content-Type", "application/json");
471
+ await send(event, JSON.stringify(response));
472
+ } else {
473
+ await send(event, response ?? "");
474
+ }
475
+ if (opts?.onAfterResponse) {
476
+ await opts.onAfterResponse(context, event);
477
+ }
478
+ if (route.postHandler) {
479
+ await route.postHandler(context, event);
480
+ }
481
+ } catch (err) {
482
+ await handleH3Error(err, context, event, opts?.onError);
483
+ }
484
+ if (!event.handled) {
485
+ await send(event, "");
486
+ }
487
+ return "";
488
+ });
489
+ const finalPath = (prefix ? `/${prefix}${route.path}` : route.path).split("//").join("/");
490
+ switch (route.method) {
491
+ case "get":
492
+ router.get(finalPath, handler);
493
+ break;
494
+ case "head":
495
+ router.head(finalPath, handler);
496
+ break;
497
+ case "delete":
498
+ router.delete(finalPath, handler);
499
+ break;
500
+ case "patch":
501
+ router.patch(finalPath, handler);
502
+ break;
503
+ case "post":
504
+ router.post(finalPath, handler);
505
+ break;
506
+ case "put":
507
+ router.put(finalPath, handler);
508
+ break;
509
+ }
510
+ }
511
+
512
+ var __defProp = Object.defineProperty;
513
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
514
+ var __publicField = (obj, key, value) => {
515
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
516
+ return value;
517
+ };
518
+ const DEV_ENDPOINT_ROOT = `/__arri_dev__`;
519
+ const DEV_DEFINITION_ENDPOINT = `${DEV_ENDPOINT_ROOT}/definition`;
520
+ class Arri {
521
+ constructor(opts = {}) {
522
+ __publicField(this, "__isArri__", true);
523
+ __publicField(this, "h3App");
524
+ __publicField(this, "h3Router", createRouter());
525
+ __publicField(this, "rpcDefinitionPath");
526
+ __publicField(this, "rpcRoutePrefix");
527
+ __publicField(this, "appInfo");
528
+ __publicField(this, "procedures", {});
529
+ __publicField(this, "models", {});
530
+ __publicField(this, "middlewares", []);
531
+ __publicField(this, "onAfterResponse");
532
+ __publicField(this, "onBeforeResponse");
533
+ __publicField(this, "onError");
534
+ this.appInfo = opts?.appInfo;
535
+ this.h3App = createApp({
536
+ debug: opts?.debug,
537
+ onRequest: opts?.onRequest
538
+ });
539
+ this.onError = opts.onError;
540
+ this.onAfterResponse = opts.onAfterResponse;
541
+ this.onBeforeResponse = opts.onBeforeResponse;
542
+ this.rpcRoutePrefix = opts?.rpcRoutePrefix ?? "";
543
+ this.rpcDefinitionPath = opts?.rpcDefinitionPath ?? "__definition";
544
+ this.h3App.use(this.h3Router);
545
+ this.registerRoute({
546
+ path: this.rpcRoutePrefix ? `/${this.rpcRoutePrefix}/${this.rpcDefinitionPath}`.split("//").join("/") : `/${this.rpcDefinitionPath}`,
547
+ method: "get",
548
+ handler: () => this.getAppDefinition()
549
+ });
550
+ if (process.env.ARRI_DEV_MODE === "true") {
551
+ this.registerRoute({
552
+ path: DEV_DEFINITION_ENDPOINT,
553
+ method: "get",
554
+ handler: () => this.getAppDefinition()
555
+ });
556
+ }
557
+ this.h3Router.use(
558
+ "/**",
559
+ eventHandler(async (event) => {
560
+ setResponseStatus(event, 404);
561
+ const error = defineError(404);
562
+ const query = getQuery(event);
563
+ const disallowedBodyMethods = ["GET", "HEAD", "OPTION"];
564
+ const canBody = !disallowedBodyMethods.includes(event.method);
565
+ const context = {
566
+ type: "route",
567
+ params: event.context.params,
568
+ query,
569
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
570
+ body: canBody ? await readBody(event).catch((_) => void 0) : void 0
571
+ };
572
+ try {
573
+ if (this.middlewares.length) {
574
+ for (const m of this.middlewares) {
575
+ await m(context, event);
576
+ }
577
+ }
578
+ } catch (err) {
579
+ await handleH3Error(err, context, event, this.onError);
580
+ }
581
+ if (event.handled) {
582
+ return;
583
+ }
584
+ if (this.onError) {
585
+ await this.onError(error, context, event);
586
+ }
587
+ if (event.handled) {
588
+ return;
589
+ }
590
+ sendError(event, error);
591
+ })
592
+ );
593
+ }
594
+ registerMiddleware(middleware) {
595
+ this.middlewares.push(middleware);
596
+ }
597
+ registerRpc(name, procedure) {
598
+ const path = getRpcPath(name, this.rpcRoutePrefix);
599
+ this.procedures[name] = createRpcDefinition(name, path, procedure);
600
+ if (TypeGuard.TObject(procedure.params)) {
601
+ const paramName = getRpcParamName(name, procedure);
602
+ if (paramName) {
603
+ this.models[paramName] = procedure.params;
604
+ }
605
+ }
606
+ if (TypeGuard.TObject(procedure.response)) {
607
+ const responseName = getRpcResponseName(name, procedure);
608
+ if (responseName) {
609
+ this.models[responseName] = procedure.response;
610
+ }
611
+ }
612
+ registerRpc(this.h3Router, path, procedure, this.middlewares, {
613
+ onError: this.onError,
614
+ onAfterResponse: this.onAfterResponse,
615
+ onBeforeResponse: this.onBeforeResponse
616
+ });
617
+ }
618
+ registerRoute(route) {
619
+ registerRoute(this.h3Router, route, this.middlewares);
620
+ }
621
+ getAppDefinition() {
622
+ const appDef = {
623
+ arriSchemaVersion: "0.0.1",
624
+ info: this.appInfo,
625
+ procedures: {},
626
+ models: this.models,
627
+ errors: ErrorResponse
628
+ };
629
+ Object.keys(this.procedures).forEach((key) => {
630
+ const rpc = this.procedures[key];
631
+ appDef.procedures[key] = rpc;
632
+ });
633
+ return appDef;
634
+ }
635
+ getH3Instance() {
636
+ return this.h3App;
637
+ }
638
+ }
6
639
 
7
640
  const defaultConfig = {
8
641
  port: 3e3,
@@ -29,4 +662,4 @@ function defineConfig(config) {
29
662
  };
30
663
  }
31
664
 
32
- export { defaultConfig, defineConfig };
665
+ export { Arri, DEV_DEFINITION_ENDPOINT, DEV_ENDPOINT_ROOT, ErrorResponse, createRpcDefinition, defaultConfig, defineConfig, defineError, defineMiddleware, defineRoute, defineRpc, errorResponseFromValidationErrors, getRpcParamDefinition, getRpcParamName, getRpcPath, getRpcResponseName, handleH3Error, isRpc, registerRoute, registerRpc };