@arkyn/server 3.0.1-beta.99 → 3.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/LICENSE.txt +184 -1
- package/README.md +277 -224
- package/dist/http/api/_deleteRequest.d.ts +17 -0
- package/dist/http/api/_deleteRequest.d.ts.map +1 -0
- package/dist/http/api/_getRequest.d.ts +16 -0
- package/dist/http/api/_getRequest.d.ts.map +1 -0
- package/dist/http/api/_logMapperService.d.ts +86 -0
- package/dist/http/api/_logMapperService.d.ts.map +1 -0
- package/dist/{api/arkynLogRequest.d.ts → http/api/_logRequest.d.ts} +22 -19
- package/dist/{api/arkynLogRequest.d.ts.map → http/api/_logRequest.d.ts.map} +1 -1
- package/dist/http/api/_makeRequest.d.ts +61 -0
- package/dist/http/api/_makeRequest.d.ts.map +1 -0
- package/dist/http/api/_patchRequest.d.ts +17 -0
- package/dist/http/api/_patchRequest.d.ts.map +1 -0
- package/dist/http/api/_postRequest.d.ts +17 -0
- package/dist/http/api/_postRequest.d.ts.map +1 -0
- package/dist/http/api/_putRequest.d.ts +17 -0
- package/dist/http/api/_putRequest.d.ts.map +1 -0
- package/dist/http/badResponses/_badResponse.d.ts +21 -7
- package/dist/http/badResponses/_badResponse.d.ts.map +1 -1
- package/dist/http/badResponses/badGateway.d.ts +12 -23
- package/dist/http/badResponses/badGateway.d.ts.map +1 -1
- package/dist/http/badResponses/badRequest.d.ts +10 -23
- package/dist/http/badResponses/badRequest.d.ts.map +1 -1
- package/dist/http/badResponses/conflict.d.ts +10 -23
- package/dist/http/badResponses/conflict.d.ts.map +1 -1
- package/dist/http/badResponses/forbidden.d.ts +10 -23
- package/dist/http/badResponses/forbidden.d.ts.map +1 -1
- package/dist/http/badResponses/notFound.d.ts +10 -23
- package/dist/http/badResponses/notFound.d.ts.map +1 -1
- package/dist/http/badResponses/notImplemented.d.ts +10 -23
- package/dist/http/badResponses/notImplemented.d.ts.map +1 -1
- package/dist/http/badResponses/serverError.d.ts +10 -23
- package/dist/http/badResponses/serverError.d.ts.map +1 -1
- package/dist/http/badResponses/unauthorized.d.ts +10 -23
- package/dist/http/badResponses/unauthorized.d.ts.map +1 -1
- package/dist/http/badResponses/unprocessableEntity.d.ts +23 -33
- package/dist/http/badResponses/unprocessableEntity.d.ts.map +1 -1
- package/dist/http/successResponses/_successResponse.d.ts +21 -7
- package/dist/http/successResponses/_successResponse.d.ts.map +1 -1
- package/dist/http/successResponses/created.d.ts +11 -29
- package/dist/http/successResponses/created.d.ts.map +1 -1
- package/dist/http/successResponses/found.d.ts +11 -32
- package/dist/http/successResponses/found.d.ts.map +1 -1
- package/dist/http/successResponses/noContent.d.ts +10 -16
- package/dist/http/successResponses/noContent.d.ts.map +1 -1
- package/dist/http/successResponses/success.d.ts +11 -29
- package/dist/http/successResponses/success.d.ts.map +1 -1
- package/dist/http/successResponses/updated.d.ts +12 -29
- package/dist/http/successResponses/updated.d.ts.map +1 -1
- package/dist/index.d.ts +9 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1069 -38
- package/dist/index.js.map +1 -0
- package/dist/modules/http/api/_deleteRequest.js +15 -0
- package/dist/modules/http/api/_deleteRequest.js.map +1 -0
- package/dist/modules/http/api/_getRequest.js +14 -0
- package/dist/modules/http/api/_getRequest.js.map +1 -0
- package/dist/modules/http/api/_logMapperService.js +33 -0
- package/dist/modules/http/api/_logMapperService.js.map +1 -0
- package/dist/modules/http/api/_logRequest.js +58 -0
- package/dist/modules/http/api/_logRequest.js.map +1 -0
- package/dist/modules/http/api/_makeRequest.js +72 -0
- package/dist/modules/http/api/_makeRequest.js.map +1 -0
- package/dist/modules/http/api/_patchRequest.js +15 -0
- package/dist/modules/http/api/_patchRequest.js.map +1 -0
- package/dist/modules/http/api/_postRequest.js +15 -0
- package/dist/modules/http/api/_postRequest.js.map +1 -0
- package/dist/modules/http/api/_putRequest.js +15 -0
- package/dist/modules/http/api/_putRequest.js.map +1 -0
- package/dist/modules/http/badResponses/_badResponse.js +65 -0
- package/dist/modules/http/badResponses/_badResponse.js.map +1 -0
- package/dist/modules/http/badResponses/badGateway.js +26 -0
- package/dist/modules/http/badResponses/badGateway.js.map +1 -0
- package/dist/modules/http/badResponses/badRequest.js +26 -0
- package/dist/modules/http/badResponses/badRequest.js.map +1 -0
- package/dist/modules/http/badResponses/conflict.js +26 -0
- package/dist/modules/http/badResponses/conflict.js.map +1 -0
- package/dist/modules/http/badResponses/forbidden.js +26 -0
- package/dist/modules/http/badResponses/forbidden.js.map +1 -0
- package/dist/modules/http/badResponses/notFound.js +26 -0
- package/dist/modules/http/badResponses/notFound.js.map +1 -0
- package/dist/modules/http/badResponses/notImplemented.js +26 -0
- package/dist/modules/http/badResponses/notImplemented.js.map +1 -0
- package/dist/modules/http/badResponses/serverError.js +26 -0
- package/dist/modules/http/badResponses/serverError.js.map +1 -0
- package/dist/modules/http/badResponses/unauthorized.js +26 -0
- package/dist/modules/http/badResponses/unauthorized.js.map +1 -0
- package/dist/modules/http/badResponses/unprocessableEntity.js +30 -0
- package/dist/modules/http/badResponses/unprocessableEntity.js.map +1 -0
- package/dist/modules/http/successResponses/_successResponse.js +64 -0
- package/dist/modules/http/successResponses/_successResponse.js.map +1 -0
- package/dist/modules/http/successResponses/created.js +26 -0
- package/dist/modules/http/successResponses/created.js.map +1 -0
- package/dist/modules/http/successResponses/found.js +26 -0
- package/dist/modules/http/successResponses/found.js.map +1 -0
- package/dist/modules/http/successResponses/noContent.js +19 -0
- package/dist/modules/http/successResponses/noContent.js.map +1 -0
- package/dist/modules/http/successResponses/success.js +26 -0
- package/dist/modules/http/successResponses/success.js.map +1 -0
- package/dist/modules/http/successResponses/updated.js +26 -0
- package/dist/modules/http/successResponses/updated.js.map +1 -0
- package/dist/modules/services/apiService.js +111 -0
- package/dist/modules/services/apiService.js.map +1 -0
- package/dist/modules/services/debugService.js +38 -0
- package/dist/modules/services/debugService.js.map +1 -0
- package/dist/modules/services/logService.js +23 -0
- package/dist/modules/services/logService.js.map +1 -0
- package/dist/modules/utilities/decodeRequestBody.js +22 -0
- package/dist/modules/utilities/decodeRequestBody.js.map +1 -0
- package/dist/modules/utilities/decodeRequestErrorMessage.js +8 -0
- package/dist/modules/utilities/decodeRequestErrorMessage.js.map +1 -0
- package/dist/modules/utilities/errorHandler.js +41 -0
- package/dist/modules/utilities/errorHandler.js.map +1 -0
- package/dist/modules/utilities/flushDebugLogs.js +18 -0
- package/dist/modules/utilities/flushDebugLogs.js.map +1 -0
- package/dist/modules/utilities/formAsyncParse.js +16 -0
- package/dist/modules/utilities/formAsyncParse.js.map +1 -0
- package/dist/modules/utilities/formParse.js +16 -0
- package/dist/modules/utilities/formParse.js.map +1 -0
- package/dist/modules/utilities/getScopedParams.js +11 -0
- package/dist/modules/utilities/getScopedParams.js.map +1 -0
- package/dist/modules/utilities/schemaValidator.js +57 -0
- package/dist/modules/utilities/schemaValidator.js.map +1 -0
- package/dist/modules/validations/validateCep.js +11 -0
- package/dist/modules/validations/validateCep.js.map +1 -0
- package/dist/modules/validations/validateCnpj.js +56 -0
- package/dist/modules/validations/validateCnpj.js.map +1 -0
- package/dist/modules/validations/validateCpf.js +29 -0
- package/dist/modules/validations/validateCpf.js.map +1 -0
- package/dist/modules/validations/validateDate.js +28 -0
- package/dist/modules/validations/validateDate.js.map +1 -0
- package/dist/modules/validations/validateEmail.js +56 -0
- package/dist/modules/validations/validateEmail.js.map +1 -0
- package/dist/modules/validations/validatePassword.js +14 -0
- package/dist/modules/validations/validatePassword.js.map +1 -0
- package/dist/modules/validations/validatePhone.js +12 -0
- package/dist/modules/validations/validatePhone.js.map +1 -0
- package/dist/modules/validations/validateRg.js +10 -0
- package/dist/modules/validations/validateRg.js.map +1 -0
- package/dist/services/apiService.d.ts +25 -26
- package/dist/services/apiService.d.ts.map +1 -1
- package/dist/services/debugService.d.ts +15 -32
- package/dist/services/debugService.d.ts.map +1 -1
- package/dist/services/logService.d.ts +33 -0
- package/dist/services/logService.d.ts.map +1 -0
- package/dist/utilities/decodeRequestBody.d.ts +18 -0
- package/dist/utilities/decodeRequestBody.d.ts.map +1 -0
- package/dist/utilities/decodeRequestErrorMessage.d.ts +19 -0
- package/dist/utilities/decodeRequestErrorMessage.d.ts.map +1 -0
- package/dist/utilities/errorHandler.d.ts +25 -0
- package/dist/utilities/errorHandler.d.ts.map +1 -0
- package/dist/utilities/flushDebugLogs.d.ts +24 -0
- package/dist/utilities/flushDebugLogs.d.ts.map +1 -0
- package/dist/utilities/formAsyncParse.d.ts +38 -0
- package/dist/utilities/formAsyncParse.d.ts.map +1 -0
- package/dist/utilities/formParse.d.ts +38 -0
- package/dist/utilities/formParse.d.ts.map +1 -0
- package/dist/utilities/getScopedParams.d.ts +19 -0
- package/dist/utilities/getScopedParams.d.ts.map +1 -0
- package/dist/utilities/schemaValidator.d.ts +75 -0
- package/dist/utilities/schemaValidator.d.ts.map +1 -0
- package/dist/validations/validateCep.d.ts +6 -11
- package/dist/validations/validateCep.d.ts.map +1 -1
- package/dist/validations/validateCnpj.d.ts +2 -3
- package/dist/validations/validateCnpj.d.ts.map +1 -1
- package/dist/validations/validateCpf.d.ts +4 -12
- package/dist/validations/validateCpf.d.ts.map +1 -1
- package/dist/validations/validateDate.d.ts +13 -24
- package/dist/validations/validateDate.d.ts.map +1 -1
- package/dist/validations/validateEmail.d.ts +3 -4
- package/dist/validations/validateEmail.d.ts.map +1 -1
- package/dist/validations/validatePassword.d.ts +3 -4
- package/dist/validations/validatePassword.d.ts.map +1 -1
- package/dist/validations/validatePhone.d.ts +7 -20
- package/dist/validations/validatePhone.d.ts.map +1 -1
- package/dist/validations/validateRg.d.ts +1 -2
- package/dist/validations/validateRg.d.ts.map +1 -1
- package/package.json +317 -15
- package/dist/api/arkynLogRequest.js +0 -88
- package/dist/api/deleteRequest.d.ts +0 -13
- package/dist/api/deleteRequest.d.ts.map +0 -1
- package/dist/api/deleteRequest.js +0 -14
- package/dist/api/getRequest.d.ts +0 -12
- package/dist/api/getRequest.d.ts.map +0 -1
- package/dist/api/getRequest.js +0 -13
- package/dist/api/makeRequest.d.ts +0 -38
- package/dist/api/makeRequest.d.ts.map +0 -1
- package/dist/api/makeRequest.js +0 -107
- package/dist/api/patchRequest.d.ts +0 -13
- package/dist/api/patchRequest.d.ts.map +0 -1
- package/dist/api/patchRequest.js +0 -14
- package/dist/api/postRequest.d.ts +0 -13
- package/dist/api/postRequest.d.ts.map +0 -1
- package/dist/api/postRequest.js +0 -14
- package/dist/api/putRequest.d.ts +0 -13
- package/dist/api/putRequest.d.ts.map +0 -1
- package/dist/api/putRequest.js +0 -14
- package/dist/bundle.js +0 -1507
- package/dist/bundle.umd.cjs +0 -10
- package/dist/http/badResponses/_badResponse.js +0 -20
- package/dist/http/badResponses/badGateway.js +0 -58
- package/dist/http/badResponses/badRequest.js +0 -58
- package/dist/http/badResponses/conflict.js +0 -58
- package/dist/http/badResponses/forbidden.js +0 -58
- package/dist/http/badResponses/notFound.js +0 -58
- package/dist/http/badResponses/notImplemented.js +0 -58
- package/dist/http/badResponses/serverError.js +0 -58
- package/dist/http/badResponses/unauthorized.js +0 -58
- package/dist/http/badResponses/unprocessableEntity.js +0 -65
- package/dist/http/successResponses/_successResponse.js +0 -37
- package/dist/http/successResponses/created.js +0 -57
- package/dist/http/successResponses/found.js +0 -60
- package/dist/http/successResponses/noContent.js +0 -38
- package/dist/http/successResponses/success.js +0 -57
- package/dist/http/successResponses/updated.js +0 -57
- package/dist/mapper/arkynLogRequestMapper.d.ts +0 -30
- package/dist/mapper/arkynLogRequestMapper.d.ts.map +0 -1
- package/dist/mapper/arkynLogRequestMapper.js +0 -44
- package/dist/services/apiService.js +0 -133
- package/dist/services/arkynLogService.d.ts +0 -44
- package/dist/services/arkynLogService.d.ts.map +0 -1
- package/dist/services/arkynLogService.js +0 -46
- package/dist/services/debugService.js +0 -57
- package/dist/services/decodeErrorMessageFromRequest.d.ts +0 -17
- package/dist/services/decodeErrorMessageFromRequest.d.ts.map +0 -1
- package/dist/services/decodeErrorMessageFromRequest.js +0 -30
- package/dist/services/decodeRequestBody.d.ts +0 -17
- package/dist/services/decodeRequestBody.d.ts.map +0 -1
- package/dist/services/decodeRequestBody.js +0 -38
- package/dist/services/errorHandler.d.ts +0 -44
- package/dist/services/errorHandler.d.ts.map +0 -1
- package/dist/services/errorHandler.js +0 -93
- package/dist/services/flushDebugLogs.d.ts +0 -8
- package/dist/services/flushDebugLogs.d.ts.map +0 -1
- package/dist/services/flushDebugLogs.js +0 -20
- package/dist/services/formAsyncParse.d.ts +0 -59
- package/dist/services/formAsyncParse.d.ts.map +0 -1
- package/dist/services/formAsyncParse.js +0 -58
- package/dist/services/formParse.d.ts +0 -59
- package/dist/services/formParse.d.ts.map +0 -1
- package/dist/services/formParse.js +0 -58
- package/dist/services/getCaller.d.ts +0 -17
- package/dist/services/getCaller.d.ts.map +0 -1
- package/dist/services/getCaller.js +0 -60
- package/dist/services/getScopedParams.d.ts +0 -28
- package/dist/services/getScopedParams.d.ts.map +0 -1
- package/dist/services/getScopedParams.js +0 -34
- package/dist/services/measureRouteExecution.d.ts +0 -3
- package/dist/services/measureRouteExecution.d.ts.map +0 -1
- package/dist/services/measureRouteExecution.js +0 -24
- package/dist/services/schemaValidator.d.ts +0 -158
- package/dist/services/schemaValidator.d.ts.map +0 -1
- package/dist/services/schemaValidator.js +0 -207
- package/dist/types/ApiResponseDTO.d.ts +0 -17
- package/dist/types/ApiResponseDTO.d.ts.map +0 -1
- package/dist/types/ApiResponseDTO.js +0 -1
- package/dist/validations/validateCep.js +0 -33
- package/dist/validations/validateCnpj.js +0 -52
- package/dist/validations/validateCpf.js +0 -54
- package/dist/validations/validateDate.js +0 -73
- package/dist/validations/validateEmail.js +0 -123
- package/dist/validations/validatePassword.js +0 -34
- package/dist/validations/validatePhone.js +0 -44
- package/dist/validations/validateRg.js +0 -31
package/README.md
CHANGED
|
@@ -1,419 +1,472 @@
|
|
|
1
1
|
# @arkyn/server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Comprehensive server-side utilities for building robust backend applications, featuring HTTP response helpers, error handlers, request utilities, and API configurations.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@arkyn/server)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
8
|
|
|
9
|
+
## 🎯 What it solves
|
|
10
|
+
|
|
11
|
+
Backend code — Remix/React Router loaders and actions, or any fetch-based server — tends to reinvent the same plumbing on every project: consistent success/error response shapes, centralized error handling, request body/form parsing, schema validation, and validation of Brazilian documents (CPF/CNPJ/CEP/RG) plus generic fields (email/password/phone/date). `@arkyn/server` packages all of that into small, well-typed primitives so route handlers stay focused on business logic instead of response boilerplate.
|
|
12
|
+
|
|
9
13
|
## ✨ Features
|
|
10
14
|
|
|
11
|
-
- 🌐 **HTTP
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
15
|
+
- 🌐 **HTTP success responses** - `Success`, `Created`, `Updated`, `Found`, `NoContent` with `.toResponse()` / `.toJson()`
|
|
16
|
+
- 🚨 **HTTP error responses** - `BadRequest`, `Unauthorized`, `Forbidden`, `NotFound`, `Conflict`, `UnprocessableEntity`, `ServerError`, `BadGateway`, `NotImplemented`
|
|
17
|
+
- 🧵 **Request utilities** - body decoding, scoped query params, form parsing (sync/async), error-message extraction
|
|
18
|
+
- 🧪 **Schema validation via Zod** - `SchemaValidator` and the underlying `formParse` / `formAsyncParse` helpers
|
|
19
|
+
- 🇧🇷 **Brazilian document validators** - `validateCpf`, `validateCnpj`, `validateCep`, `validateRg`
|
|
20
|
+
- 🔤 **Generic validators** - `validateEmail`, `validatePassword`, `validatePhone`, `validateDate`
|
|
21
|
+
- 🛠️ **Debug & logging services** - `DebugService`, `LogService`, `flushDebugLogs`, plus `ApiService` for outbound HTTP calls
|
|
22
|
+
|
|
23
|
+
## 📋 Prerequisites
|
|
24
|
+
|
|
25
|
+
- **Node.js** `>=18.0.0` or **Bun** `>=1.0.0`
|
|
26
|
+
- Peer dependencies (install alongside `@arkyn/server`):
|
|
27
|
+
- `zod >=4.4.3` — required by `SchemaValidator`, `formParse`, and `formAsyncParse`.
|
|
28
|
+
- `libphonenumber-js >=1.13.7` — required by `validatePhone`.
|
|
17
29
|
|
|
18
30
|
## 📦 Installation
|
|
19
31
|
|
|
32
|
+
> **ESM only.** This package ships as native ES modules with no CommonJS build — use `import`, not `require()`.
|
|
33
|
+
|
|
20
34
|
```bash
|
|
21
|
-
npm install @arkyn/server
|
|
35
|
+
npm install @arkyn/server zod libphonenumber-js
|
|
22
36
|
```
|
|
23
37
|
|
|
24
38
|
## 🚀 Quick Start
|
|
25
39
|
|
|
26
40
|
```typescript
|
|
27
|
-
import { Success, BadRequest, errorHandler } from "@arkyn/server";
|
|
41
|
+
import { Success, BadRequest, errorHandler, decodeRequestBody } from "@arkyn/server";
|
|
28
42
|
|
|
29
|
-
export async function
|
|
43
|
+
export async function action({ request }: ActionFunctionArgs) {
|
|
30
44
|
try {
|
|
31
|
-
const
|
|
32
|
-
|
|
45
|
+
const body = await decodeRequestBody(request);
|
|
46
|
+
|
|
47
|
+
if (!body.email) throw new BadRequest("Email is required");
|
|
48
|
+
|
|
49
|
+
const user = await createUser(body);
|
|
50
|
+
return new Success("User created", { user }).toJson();
|
|
33
51
|
} catch (error) {
|
|
34
52
|
return errorHandler(error);
|
|
35
53
|
}
|
|
36
54
|
}
|
|
55
|
+
```
|
|
37
56
|
|
|
38
|
-
|
|
39
|
-
const body = await decodeRequestBody(request);
|
|
57
|
+
## 📖 API Reference
|
|
40
58
|
|
|
41
|
-
|
|
42
|
-
return BadRequest("Email is required");
|
|
43
|
-
}
|
|
59
|
+
### HTTP Responses
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
return Created(user);
|
|
47
|
-
}
|
|
48
|
-
```
|
|
61
|
+
Every response class extends a base with `.toResponse()` (returns a `Response` with a `Content-Type: application/json` header) and `.toJson()` (built on `Response.json()`). Both produce an equivalent JSON body — pick whichever reads better at the call site. `NoContent` only exposes `.toResponse()`, since it always returns a `null` body.
|
|
49
62
|
|
|
50
|
-
|
|
63
|
+
#### BadGateway
|
|
51
64
|
|
|
52
|
-
|
|
65
|
+
HTTP 502 — the upstream server returned an invalid or unexpected response.
|
|
53
66
|
|
|
54
|
-
|
|
67
|
+
```typescript
|
|
68
|
+
throw new BadGateway("Payment gateway unavailable");
|
|
69
|
+
```
|
|
55
70
|
|
|
56
|
-
|
|
71
|
+
#### BadRequest
|
|
57
72
|
|
|
58
|
-
|
|
59
|
-
import { ApiInstance } from "@arkyn/server";
|
|
73
|
+
HTTP 400 — the request is malformed or contains invalid data.
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
```typescript
|
|
76
|
+
throw new BadRequest("Invalid request body");
|
|
63
77
|
```
|
|
64
78
|
|
|
65
|
-
####
|
|
79
|
+
#### Conflict
|
|
66
80
|
|
|
67
|
-
|
|
81
|
+
HTTP 409 — the request conflicts with the current state of the server (e.g. duplicate record).
|
|
68
82
|
|
|
69
83
|
```typescript
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
ArkynLogInstance.setArkynConfig();
|
|
84
|
+
throw new Conflict("Email already in use");
|
|
73
85
|
```
|
|
74
86
|
|
|
75
|
-
|
|
87
|
+
#### Forbidden
|
|
88
|
+
|
|
89
|
+
HTTP 403 — authenticated but not authorized to access this resource.
|
|
76
90
|
|
|
77
|
-
|
|
91
|
+
```typescript
|
|
92
|
+
throw new Forbidden("You don't have permission to delete this resource");
|
|
93
|
+
```
|
|
78
94
|
|
|
79
|
-
####
|
|
95
|
+
#### NotFound
|
|
80
96
|
|
|
81
|
-
|
|
97
|
+
HTTP 404 — the requested resource does not exist.
|
|
82
98
|
|
|
83
99
|
```typescript
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (!email) {
|
|
87
|
-
return BadRequest("Email is required");
|
|
88
|
-
}
|
|
100
|
+
throw new NotFound("Product not found");
|
|
89
101
|
```
|
|
90
102
|
|
|
91
|
-
####
|
|
103
|
+
#### NotImplemented
|
|
92
104
|
|
|
93
|
-
|
|
105
|
+
HTTP 501 — the server does not support the functionality required to fulfill the request.
|
|
94
106
|
|
|
95
107
|
```typescript
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (!isAuthenticated) {
|
|
99
|
-
return Unauthorized("Authentication required");
|
|
100
|
-
}
|
|
108
|
+
throw new NotImplemented("Webhook delivery is not yet implemented");
|
|
101
109
|
```
|
|
102
110
|
|
|
103
|
-
####
|
|
111
|
+
#### ServerError
|
|
104
112
|
|
|
105
|
-
|
|
113
|
+
HTTP 500 — an unexpected condition prevented the server from fulfilling the request.
|
|
106
114
|
|
|
107
115
|
```typescript
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (!hasPermission) {
|
|
111
|
-
return Forbidden("Insufficient permissions");
|
|
112
|
-
}
|
|
116
|
+
throw new ServerError("Failed to connect to the database");
|
|
113
117
|
```
|
|
114
118
|
|
|
115
|
-
####
|
|
119
|
+
#### Unauthorized
|
|
116
120
|
|
|
117
|
-
|
|
121
|
+
HTTP 401 — the request lacks valid authentication credentials.
|
|
118
122
|
|
|
119
123
|
```typescript
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const user = await findUser(id);
|
|
123
|
-
if (!user) {
|
|
124
|
-
return NotFound("User not found");
|
|
125
|
-
}
|
|
124
|
+
throw new Unauthorized("Invalid or expired token");
|
|
126
125
|
```
|
|
127
126
|
|
|
128
|
-
####
|
|
127
|
+
#### UnprocessableEntity
|
|
129
128
|
|
|
130
|
-
|
|
129
|
+
HTTP 422 — the request is well-formed but contains semantic validation errors. Typically used for form field validation failures.
|
|
131
130
|
|
|
132
131
|
```typescript
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
132
|
+
throw new UnprocessableEntity({
|
|
133
|
+
message: "Validation failed",
|
|
134
|
+
fieldErrors: { email: "Invalid email format", age: "Must be 18 or older" },
|
|
135
|
+
fields: { email: "not-an-email", age: "15" },
|
|
136
|
+
});
|
|
139
137
|
```
|
|
140
138
|
|
|
141
|
-
####
|
|
139
|
+
#### Created
|
|
142
140
|
|
|
143
|
-
|
|
141
|
+
HTTP 201 — the request succeeded and a new resource was created.
|
|
144
142
|
|
|
145
143
|
```typescript
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (!isValidEmail(email)) {
|
|
149
|
-
return UnprocessableEntity("Invalid email format");
|
|
150
|
-
}
|
|
144
|
+
return new Created("User created successfully", { id: user.id }).toJson();
|
|
151
145
|
```
|
|
152
146
|
|
|
153
|
-
####
|
|
147
|
+
#### Found
|
|
148
|
+
|
|
149
|
+
HTTP 302 — the resource was located and the response includes it in the body.
|
|
154
150
|
|
|
155
|
-
|
|
151
|
+
```typescript
|
|
152
|
+
return new Found("Products retrieved", { products }).toJson();
|
|
153
|
+
```
|
|
156
154
|
|
|
157
|
-
####
|
|
155
|
+
#### NoContent
|
|
158
156
|
|
|
159
|
-
|
|
157
|
+
HTTP 204 — the request succeeded but there is no content to return. Typically used for delete or update operations where a body is not needed.
|
|
160
158
|
|
|
161
|
-
|
|
159
|
+
```typescript
|
|
160
|
+
return new NoContent("Record deleted").toResponse();
|
|
161
|
+
```
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
#### Success
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
HTTP 200 — the request succeeded and the response body contains the result.
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
```typescript
|
|
168
|
+
return new Success("Order fetched", { order }).toJson();
|
|
169
|
+
```
|
|
168
170
|
|
|
169
|
-
####
|
|
171
|
+
#### Updated
|
|
170
172
|
|
|
171
|
-
|
|
173
|
+
HTTP 200 — the request succeeded and the resource was updated. Semantically equivalent to `Success` but signals an update operation to consumers.
|
|
172
174
|
|
|
173
175
|
```typescript
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
export async function loader() {
|
|
177
|
-
const users = await getUsers();
|
|
178
|
-
return Success(users);
|
|
179
|
-
}
|
|
176
|
+
return new Updated("Profile updated", { user }).toJson();
|
|
180
177
|
```
|
|
181
178
|
|
|
182
|
-
|
|
179
|
+
### Services
|
|
183
180
|
|
|
184
|
-
|
|
181
|
+
#### ApiService
|
|
182
|
+
|
|
183
|
+
HTTP client for external API calls. Wraps `fetch` with a base URL, default headers, an optional auth token, and per-request overrides for `get`, `post`, `put`, `patch`, and `delete`.
|
|
185
184
|
|
|
186
185
|
```typescript
|
|
187
|
-
|
|
186
|
+
const api = new ApiService({
|
|
187
|
+
baseUrl: "https://api.example.com",
|
|
188
|
+
baseToken: session.token,
|
|
189
|
+
enableDebug: true,
|
|
190
|
+
});
|
|
188
191
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return Created(user);
|
|
192
|
-
}
|
|
192
|
+
const { data } = await api.get("/users/me");
|
|
193
|
+
const { data: created } = await api.post("/orders", { body: { productId: 1 } });
|
|
193
194
|
```
|
|
194
195
|
|
|
195
|
-
####
|
|
196
|
+
#### DebugService
|
|
196
197
|
|
|
197
|
-
|
|
198
|
+
Static service that manages stack-trace configuration for debug output, allowing specific adapter/wrapper files to be skipped so logs show the actual business-logic caller. Used internally by the HTTP response classes; `setIgnoreFile` / `clearIgnoreFiles` let you tune it, and `getCaller()` returns `{ functionName, callerInfo }`.
|
|
198
199
|
|
|
199
200
|
```typescript
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const updatedUser = await updateUser(id, data);
|
|
203
|
-
return Updated(updatedUser);
|
|
201
|
+
// Skip httpAdapter.ts so debug output shows the calling route instead
|
|
202
|
+
DebugService.setIgnoreFile("httpAdapter.ts");
|
|
204
203
|
```
|
|
205
204
|
|
|
206
|
-
####
|
|
205
|
+
#### LogService
|
|
207
206
|
|
|
208
|
-
|
|
207
|
+
Static service for log endpoint configuration. Stores a singleton configuration containing the traffic source identifier, user token, and log ingestion URL; `setConfig` only applies on the first call, `getConfig` reads it back, and `resetConfig` clears it.
|
|
209
208
|
|
|
210
209
|
```typescript
|
|
211
|
-
|
|
210
|
+
LogService.setConfig({
|
|
211
|
+
trafficSourceId: "my-app",
|
|
212
|
+
userToken: session.token,
|
|
213
|
+
});
|
|
212
214
|
|
|
213
|
-
|
|
214
|
-
return NoContent();
|
|
215
|
+
LogService.getConfig(); // { trafficSourceId, userToken, apiUrl }
|
|
215
216
|
```
|
|
216
217
|
|
|
217
|
-
|
|
218
|
+
### Utilities
|
|
218
219
|
|
|
219
|
-
|
|
220
|
+
#### decodeRequestBody
|
|
220
221
|
|
|
221
|
-
|
|
222
|
-
import { Found } from "@arkyn/server";
|
|
222
|
+
Decodes a request body into a plain object, trying JSON first then URL-encoded form data. Throws `BadRequest` if neither format can be parsed.
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
```typescript
|
|
225
|
+
export async function action({ request }: ActionFunctionArgs) {
|
|
226
|
+
const body = await decodeRequestBody(request);
|
|
227
|
+
// body is now a plain JS object
|
|
228
|
+
}
|
|
225
229
|
```
|
|
226
230
|
|
|
227
|
-
|
|
231
|
+
#### decodeRequestErrorMessage
|
|
228
232
|
|
|
229
|
-
|
|
233
|
+
Extracts a human-readable error message from an API response body or a `Response` object. Checks `data.message`, `data.operator_erro_message`, `data.error`, `data.error.message`, and `response.statusText` in that order, falling back to `"Missing error message"`.
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const res = await fetch("/api/orders");
|
|
237
|
+
const data = await res.json().catch(() => null);
|
|
238
|
+
const message = decodeRequestErrorMessage(data, res);
|
|
239
|
+
```
|
|
230
240
|
|
|
231
|
-
#### errorHandler
|
|
241
|
+
#### errorHandler
|
|
232
242
|
|
|
233
|
-
|
|
243
|
+
Converts any thrown value into a `Response`. Recognizes all `@arkyn/server` success and error response classes, native `Response` objects, and falls back to a 500 `ServerError` for anything else. Intended to be used as the catch handler of a route action or loader.
|
|
234
244
|
|
|
235
245
|
```typescript
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
export async function action({ request }) {
|
|
246
|
+
export async function action({ request }: ActionFunctionArgs) {
|
|
239
247
|
try {
|
|
240
|
-
|
|
248
|
+
const user = await findUser(id);
|
|
249
|
+
if (!user) throw new NotFound("User not found");
|
|
250
|
+
return new Success("User retrieved", { user }).toJson();
|
|
241
251
|
} catch (error) {
|
|
242
|
-
return errorHandler(error);
|
|
252
|
+
return errorHandler(error);
|
|
243
253
|
}
|
|
244
254
|
}
|
|
245
255
|
```
|
|
246
256
|
|
|
247
|
-
####
|
|
257
|
+
#### flushDebugLogs
|
|
248
258
|
|
|
249
|
-
|
|
259
|
+
Writes colored `[name] message` lines to the console, but only when `NODE_ENV === "development"` or `DEBUG_MODE === "true"`. No-op in production.
|
|
250
260
|
|
|
251
261
|
```typescript
|
|
252
|
-
|
|
262
|
+
flushDebugLogs({
|
|
263
|
+
name: "API",
|
|
264
|
+
scheme: "cyan",
|
|
265
|
+
debugs: ["POST /api/users", "Status: 201"],
|
|
266
|
+
});
|
|
267
|
+
```
|
|
253
268
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
269
|
+
#### formAsyncParse
|
|
270
|
+
|
|
271
|
+
Async variant of `formParse` — uses `safeParseAsync` to support Zod schemas with async refinements. Returns `{ success: true, data }` on success or `{ success: false, fieldErrors, fields }` on failure.
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
const schema = z.object({ email: z.string().email() });
|
|
275
|
+
const result = await formAsyncParse([{ email: "bad" }, schema]);
|
|
276
|
+
|
|
277
|
+
if (!result.success) {
|
|
278
|
+
console.log(result.fieldErrors); // { email: "Invalid email" }
|
|
257
279
|
}
|
|
258
280
|
```
|
|
259
281
|
|
|
260
|
-
#### formParse
|
|
282
|
+
#### formParse
|
|
261
283
|
|
|
262
|
-
|
|
284
|
+
Validates form data against a Zod schema synchronously. Returns `{ success: true, data }` on success or `{ success: false, fieldErrors, fields }` on failure.
|
|
263
285
|
|
|
264
286
|
```typescript
|
|
265
|
-
|
|
287
|
+
const schema = z.object({ name: z.string().min(1, "Required"), age: z.number().min(18) });
|
|
288
|
+
const result = formParse([{ name: "", age: 15 }, schema]);
|
|
266
289
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const file = formData.get("avatar") as File;
|
|
290
|
+
if (!result.success) {
|
|
291
|
+
console.log(result.fieldErrors); // { name: "Required", age: "..." }
|
|
270
292
|
}
|
|
271
293
|
```
|
|
272
294
|
|
|
273
|
-
#### getScopedParams
|
|
295
|
+
#### getScopedParams
|
|
274
296
|
|
|
275
|
-
Extracts URL parameters from
|
|
297
|
+
Extracts URL search parameters from a request, optionally filtered by a namespace prefix (e.g. `scope:key` → `key`). Without a scope, returns all search params as-is.
|
|
276
298
|
|
|
277
299
|
```typescript
|
|
278
|
-
|
|
300
|
+
// URL: /products?table:page=2&table:sort=asc&other=1
|
|
301
|
+
const params = getScopedParams(request, "table");
|
|
302
|
+
params.get("page"); // "2"
|
|
303
|
+
params.get("sort"); // "asc"
|
|
304
|
+
```
|
|
279
305
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
306
|
+
#### SchemaValidator
|
|
307
|
+
|
|
308
|
+
Wraps a Zod schema with convenience validation methods suited for server-side use: `isValid` (boolean check, never throws), `safeValidate` (raw Zod result, never throws), `validate` (throws `ServerError`, for trusted/internal data), and `formValidate` / `formAsyncValidate` (throw `UnprocessableEntity` with structured field errors, for user-submitted forms).
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
const validator = new SchemaValidator(z.object({ email: z.string().email() }));
|
|
312
|
+
|
|
313
|
+
// Inside a Remix action:
|
|
314
|
+
const body = validator.formValidate(await decodeRequestBody(request));
|
|
284
315
|
```
|
|
285
316
|
|
|
286
|
-
|
|
317
|
+
### Validators
|
|
287
318
|
|
|
288
|
-
|
|
319
|
+
#### validateCep
|
|
320
|
+
|
|
321
|
+
Validates a Brazilian CEP (postal code). A valid CEP must contain exactly 8 numeric digits, optionally formatted as `"12345-678"`.
|
|
289
322
|
|
|
290
323
|
```typescript
|
|
291
|
-
|
|
292
|
-
|
|
324
|
+
validateCep("12345-678"); // true
|
|
325
|
+
validateCep("12345678"); // true
|
|
326
|
+
validateCep("ABCDE-123"); // false
|
|
327
|
+
```
|
|
293
328
|
|
|
294
|
-
|
|
295
|
-
email: z.string().email(),
|
|
296
|
-
name: z.string().min(2),
|
|
297
|
-
});
|
|
329
|
+
#### validateCnpj
|
|
298
330
|
|
|
299
|
-
|
|
300
|
-
const body = await decodeRequestBody(request);
|
|
331
|
+
Validates a Brazilian CNPJ (Cadastro Nacional da Pessoa Jurídica) number: sanitizes non-digit characters, checks length (14 digits), rejects repeating-digit sequences, and verifies both check digits.
|
|
301
332
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
333
|
+
```typescript
|
|
334
|
+
validateCnpj("12.345.678/0001-95"); // false
|
|
335
|
+
validateCnpj("11.444.777/0001-61"); // true
|
|
306
336
|
```
|
|
307
337
|
|
|
308
|
-
####
|
|
338
|
+
#### validateCpf
|
|
309
339
|
|
|
310
|
-
|
|
340
|
+
Validates a Brazilian CPF number. Strips formatting, checks length, rejects repeated-digit sequences, and verifies both check digits with the CPF algorithm.
|
|
311
341
|
|
|
312
342
|
```typescript
|
|
313
|
-
|
|
343
|
+
validateCpf("123.456.789-09"); // false
|
|
344
|
+
validateCpf("111.444.777-35"); // true
|
|
345
|
+
```
|
|
314
346
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
347
|
+
#### validateDate
|
|
348
|
+
|
|
349
|
+
Validates a date string against a format and optional year bounds. `inputFormat` accepts `"brazilianDate"` (`DD/MM/YYYY`, default), `"isoDate"` (`MM-DD-YYYY`), or `"timestamp"` (`YYYY-MM-DD`); `minYear` defaults to `1900` and `maxYear` to `3000`.
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
validateDate("31/12/2023"); // true
|
|
353
|
+
validateDate("2023-12-31", { inputFormat: "timestamp", minYear: 2000, maxYear: 2100 }); // true
|
|
354
|
+
validateDate("29/02/2023"); // false (not a leap year)
|
|
318
355
|
```
|
|
319
356
|
|
|
320
|
-
####
|
|
357
|
+
#### validateEmail
|
|
321
358
|
|
|
322
|
-
|
|
359
|
+
Validates an email address comprehensively: basic format, advanced RFC 5322 syntax rules, and DNS resolution (MX/A/AAAA records) of the domain. Async because of the DNS lookup.
|
|
323
360
|
|
|
324
361
|
```typescript
|
|
325
|
-
|
|
362
|
+
await validateEmail("user@gmail.com"); // true
|
|
363
|
+
await validateEmail("user@gmil.com"); // false (invalid domain)
|
|
364
|
+
await validateEmail("invalid-email"); // false (invalid format)
|
|
365
|
+
```
|
|
326
366
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
367
|
+
#### validatePassword
|
|
368
|
+
|
|
369
|
+
Validates a password: at least 8 characters, at least 1 uppercase letter, at least 1 letter, at least 1 number, and at least 1 special character.
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
validatePassword("Senha@123"); // true
|
|
373
|
+
validatePassword("senha123"); // false (no uppercase, no special char)
|
|
330
374
|
```
|
|
331
375
|
|
|
332
|
-
####
|
|
376
|
+
#### validatePhone
|
|
333
377
|
|
|
334
|
-
|
|
378
|
+
Validates an international phone number using `libphonenumber-js`, then confirms the parsed country code is present in the supported countries list.
|
|
335
379
|
|
|
336
380
|
```typescript
|
|
337
|
-
|
|
381
|
+
validatePhone("+5532912345678"); // true (Brazil)
|
|
382
|
+
validatePhone("+55329123456178"); // false (invalid)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
#### validateRg
|
|
338
386
|
|
|
339
|
-
|
|
387
|
+
Validates a Brazilian RG (Registro Geral) with a generic structural check: removes non-alphanumeric characters, requires a length of 7–9 characters, and optionally allows a trailing letter verifier.
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
validateRg("12.345.678-9"); // true
|
|
391
|
+
validateRg("MG-12.345.678"); // false (not supported)
|
|
392
|
+
validateRg("12345678X"); // true
|
|
340
393
|
```
|
|
341
394
|
|
|
342
395
|
## 🔧 Advanced Usage
|
|
343
396
|
|
|
344
|
-
###
|
|
397
|
+
### Parse → validate → respond pipeline
|
|
345
398
|
|
|
346
399
|
```typescript
|
|
347
|
-
import {
|
|
400
|
+
import {
|
|
401
|
+
decodeRequestBody,
|
|
402
|
+
SchemaValidator,
|
|
403
|
+
Success,
|
|
404
|
+
errorHandler,
|
|
405
|
+
} from "@arkyn/server";
|
|
406
|
+
import { z } from "zod";
|
|
407
|
+
|
|
408
|
+
const createUserSchema = z.object({
|
|
409
|
+
email: z.string().email(),
|
|
410
|
+
name: z.string().min(2),
|
|
411
|
+
});
|
|
348
412
|
|
|
349
|
-
|
|
413
|
+
const userValidator = new SchemaValidator(createUserSchema);
|
|
414
|
+
|
|
415
|
+
export async function action({ request }: ActionFunctionArgs) {
|
|
350
416
|
try {
|
|
351
417
|
const body = await decodeRequestBody(request);
|
|
352
418
|
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
throw new BadRequest("Email is required");
|
|
356
|
-
}
|
|
419
|
+
// Throws UnprocessableEntity with fieldErrors/fields on failure
|
|
420
|
+
const data = userValidator.formValidate(body);
|
|
357
421
|
|
|
358
|
-
const
|
|
359
|
-
return Success(
|
|
422
|
+
const user = await createUser(data);
|
|
423
|
+
return new Success("User created", { user }).toJson();
|
|
360
424
|
} catch (error) {
|
|
361
|
-
|
|
362
|
-
return error; // Return as-is
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Log unexpected errors
|
|
366
|
-
console.error("Unexpected error:", error);
|
|
367
|
-
return ServerError("Internal server error");
|
|
425
|
+
return errorHandler(error);
|
|
368
426
|
}
|
|
369
427
|
}
|
|
370
428
|
```
|
|
371
429
|
|
|
372
|
-
###
|
|
430
|
+
### Brazilian document validation before persisting
|
|
373
431
|
|
|
374
432
|
```typescript
|
|
375
433
|
import {
|
|
376
434
|
decodeRequestBody,
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
435
|
+
validateCpf,
|
|
436
|
+
validateEmail,
|
|
437
|
+
UnprocessableEntity,
|
|
438
|
+
Created,
|
|
439
|
+
errorHandler,
|
|
381
440
|
} from "@arkyn/server";
|
|
382
441
|
|
|
383
|
-
export async function action({ request }) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
442
|
+
export async function action({ request }: ActionFunctionArgs) {
|
|
443
|
+
try {
|
|
444
|
+
const body = await decodeRequestBody(request);
|
|
445
|
+
const fieldErrors: Record<string, string> = {};
|
|
387
446
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return BadRequest("Invalid request data");
|
|
391
|
-
}
|
|
447
|
+
if (!validateCpf(body.cpf)) fieldErrors.cpf = "Invalid CPF";
|
|
448
|
+
if (!(await validateEmail(body.email))) fieldErrors.email = "Invalid email";
|
|
392
449
|
|
|
393
|
-
|
|
394
|
-
|
|
450
|
+
if (Object.keys(fieldErrors).length > 0) {
|
|
451
|
+
throw new UnprocessableEntity({
|
|
452
|
+
message: "Validation failed",
|
|
453
|
+
fieldErrors,
|
|
454
|
+
fields: body,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
395
457
|
|
|
396
|
-
|
|
397
|
-
|
|
458
|
+
const customer = await createCustomer(body);
|
|
459
|
+
return new Created("Customer created", { customer }).toJson();
|
|
460
|
+
} catch (error) {
|
|
461
|
+
return errorHandler(error);
|
|
462
|
+
}
|
|
398
463
|
}
|
|
399
464
|
```
|
|
400
465
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
## 🤝 Contributing
|
|
466
|
+
## 📚 Documentation
|
|
404
467
|
|
|
405
|
-
|
|
468
|
+
Full documentation: [https://docs.arkyn.dev/docs/server/introduction](https://docs.arkyn.dev/docs/server/introduction)
|
|
406
469
|
|
|
407
470
|
## 📄 License
|
|
408
471
|
|
|
409
472
|
This project is licensed under the Apache 2.0 License - see the [LICENSE](./LICENSE.txt) file for details.
|
|
410
|
-
|
|
411
|
-
## 🔗 Links
|
|
412
|
-
|
|
413
|
-
- [GitHub Repository](https://github.com/Lucas-Eduardo-Goncalves/arkyn)
|
|
414
|
-
- [NPM Package](https://www.npmjs.com/package/@arkyn/server)
|
|
415
|
-
- [Full Documentation](https://github.com/Lucas-Eduardo-Goncalves/arkyn#readme)
|
|
416
|
-
|
|
417
|
-
---
|
|
418
|
-
|
|
419
|
-
Made with ❤️ by the Arkyn team
|