@arkyn/server 3.0.10 → 3.0.12
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/AGENTS.md +7 -2
- package/README.md +25 -3
- package/dist/http/api/_deleteRequest.d.ts +1 -0
- package/dist/http/api/_deleteRequest.d.ts.map +1 -1
- package/dist/http/api/_getRequest.d.ts +1 -0
- package/dist/http/api/_getRequest.d.ts.map +1 -1
- package/dist/http/api/_makeRequest.d.ts +2 -0
- package/dist/http/api/_makeRequest.d.ts.map +1 -1
- package/dist/http/api/_patchRequest.d.ts +1 -0
- package/dist/http/api/_patchRequest.d.ts.map +1 -1
- package/dist/http/api/_postRequest.d.ts +1 -0
- package/dist/http/api/_postRequest.d.ts.map +1 -1
- package/dist/http/api/_putRequest.d.ts +1 -0
- package/dist/http/api/_putRequest.d.ts.map +1 -1
- package/dist/http/badResponses/_badResponse.d.ts +2 -1
- package/dist/http/badResponses/_badResponse.d.ts.map +1 -1
- package/dist/http/badResponses/badGateway.d.ts +1 -1
- package/dist/http/badResponses/badRequest.d.ts +1 -1
- package/dist/http/badResponses/conflict.d.ts +1 -1
- package/dist/http/badResponses/forbidden.d.ts +1 -1
- package/dist/http/badResponses/notFound.d.ts +1 -1
- package/dist/http/badResponses/notImplemented.d.ts +1 -1
- package/dist/http/badResponses/serverError.d.ts +1 -1
- package/dist/http/badResponses/unauthorized.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +181 -134
- package/dist/index.js.map +1 -1
- package/dist/modules/http/api/_deleteRequest.js +2 -1
- package/dist/modules/http/api/_deleteRequest.js.map +1 -1
- package/dist/modules/http/api/_getRequest.js +2 -1
- package/dist/modules/http/api/_getRequest.js.map +1 -1
- package/dist/modules/http/api/_logRequest.js +1 -1
- package/dist/modules/http/api/_logRequest.js.map +1 -1
- package/dist/modules/http/api/_makeRequest.js +51 -33
- package/dist/modules/http/api/_makeRequest.js.map +1 -1
- package/dist/modules/http/api/_patchRequest.js +2 -1
- package/dist/modules/http/api/_patchRequest.js.map +1 -1
- package/dist/modules/http/api/_postRequest.js +2 -1
- package/dist/modules/http/api/_postRequest.js.map +1 -1
- package/dist/modules/http/api/_putRequest.js +2 -1
- package/dist/modules/http/api/_putRequest.js.map +1 -1
- package/dist/modules/http/badResponses/_badResponse.js +4 -1
- package/dist/modules/http/badResponses/_badResponse.js.map +1 -1
- package/dist/modules/http/badResponses/badGateway.js +1 -1
- package/dist/modules/http/badResponses/badGateway.js.map +1 -1
- package/dist/modules/http/badResponses/badRequest.js +1 -1
- package/dist/modules/http/badResponses/badRequest.js.map +1 -1
- package/dist/modules/http/badResponses/conflict.js +1 -1
- package/dist/modules/http/badResponses/conflict.js.map +1 -1
- package/dist/modules/http/badResponses/forbidden.js +1 -1
- package/dist/modules/http/badResponses/forbidden.js.map +1 -1
- package/dist/modules/http/badResponses/notFound.js +1 -1
- package/dist/modules/http/badResponses/notFound.js.map +1 -1
- package/dist/modules/http/badResponses/notImplemented.js +1 -1
- package/dist/modules/http/badResponses/notImplemented.js.map +1 -1
- package/dist/modules/http/badResponses/serverError.js +1 -1
- package/dist/modules/http/badResponses/serverError.js.map +1 -1
- package/dist/modules/http/badResponses/unauthorized.js +1 -1
- package/dist/modules/http/badResponses/unauthorized.js.map +1 -1
- package/dist/modules/http/badResponses/unprocessableEntity.js +1 -1
- package/dist/modules/http/badResponses/unprocessableEntity.js.map +1 -1
- package/dist/modules/services/apiService.js +12 -6
- package/dist/modules/services/apiService.js.map +1 -1
- package/dist/modules/services/logService.js +21 -8
- package/dist/modules/services/logService.js.map +1 -1
- package/dist/modules/utilities/withSecurityHeaders.js +18 -0
- package/dist/modules/utilities/withSecurityHeaders.js.map +1 -0
- package/dist/modules/validations/validateDate.js +4 -14
- package/dist/modules/validations/validateDate.js.map +1 -1
- package/dist/services/apiService.d.ts +5 -0
- package/dist/services/apiService.d.ts.map +1 -1
- package/dist/services/logService.d.ts +25 -5
- package/dist/services/logService.d.ts.map +1 -1
- package/dist/utilities/withSecurityHeaders.d.ts +45 -0
- package/dist/utilities/withSecurityHeaders.d.ts.map +1 -0
- package/dist/validations/validateDate.d.ts +3 -2
- package/dist/validations/validateDate.d.ts.map +1 -1
- package/package.json +9 -1
|
@@ -12,8 +12,9 @@ var c = class c {
|
|
|
12
12
|
baseHeaders;
|
|
13
13
|
baseToken;
|
|
14
14
|
enableDebug;
|
|
15
|
+
timeoutMs;
|
|
15
16
|
constructor(e) {
|
|
16
|
-
this.baseUrl = e.baseUrl, this.baseHeaders = e.baseHeaders || void 0, this.baseToken = e.baseToken || void 0, this.enableDebug = e.enableDebug || !1;
|
|
17
|
+
this.baseUrl = e.baseUrl, this.baseHeaders = e.baseHeaders || void 0, this.baseToken = e.baseToken || void 0, this.enableDebug = e.enableDebug || !1, this.timeoutMs = e.timeoutMs;
|
|
17
18
|
}
|
|
18
19
|
static toPlainHeaders(e) {
|
|
19
20
|
return e instanceof Headers ? Object.fromEntries(e.entries()) : Object.fromEntries(new Headers(e).entries());
|
|
@@ -52,7 +53,8 @@ var c = class c {
|
|
|
52
53
|
let n = this.generateHeaders(t?.headers || {}, t?.token), i = await r({
|
|
53
54
|
url: this.baseUrl + e,
|
|
54
55
|
urlParams: t?.urlParams || {},
|
|
55
|
-
headers: n
|
|
56
|
+
headers: n,
|
|
57
|
+
timeoutMs: t?.timeoutMs ?? this.timeoutMs
|
|
56
58
|
});
|
|
57
59
|
return this.onDebug(e, "get", {
|
|
58
60
|
headers: n,
|
|
@@ -65,7 +67,8 @@ var c = class c {
|
|
|
65
67
|
url: this.baseUrl + e,
|
|
66
68
|
urlParams: t?.urlParams || {},
|
|
67
69
|
headers: n,
|
|
68
|
-
body: r
|
|
70
|
+
body: r,
|
|
71
|
+
timeoutMs: t?.timeoutMs ?? this.timeoutMs
|
|
69
72
|
});
|
|
70
73
|
return this.onDebug(e, "post", {
|
|
71
74
|
headers: n,
|
|
@@ -79,7 +82,8 @@ var c = class c {
|
|
|
79
82
|
url: this.baseUrl + e,
|
|
80
83
|
urlParams: t?.urlParams || {},
|
|
81
84
|
headers: n,
|
|
82
|
-
body: r
|
|
85
|
+
body: r,
|
|
86
|
+
timeoutMs: t?.timeoutMs ?? this.timeoutMs
|
|
83
87
|
});
|
|
84
88
|
return this.onDebug(e, "put", {
|
|
85
89
|
headers: n,
|
|
@@ -93,7 +97,8 @@ var c = class c {
|
|
|
93
97
|
url: this.baseUrl + e,
|
|
94
98
|
urlParams: t?.urlParams || {},
|
|
95
99
|
headers: n,
|
|
96
|
-
body: r
|
|
100
|
+
body: r,
|
|
101
|
+
timeoutMs: t?.timeoutMs ?? this.timeoutMs
|
|
97
102
|
});
|
|
98
103
|
return this.onDebug(e, "patch", {
|
|
99
104
|
headers: n,
|
|
@@ -107,7 +112,8 @@ var c = class c {
|
|
|
107
112
|
url: this.baseUrl + e,
|
|
108
113
|
urlParams: t?.urlParams || {},
|
|
109
114
|
headers: r,
|
|
110
|
-
body: i
|
|
115
|
+
body: i,
|
|
116
|
+
timeoutMs: t?.timeoutMs ?? this.timeoutMs
|
|
111
117
|
});
|
|
112
118
|
return this.onDebug(e, "delete", {
|
|
113
119
|
headers: r,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiService.js","names":[],"sources":["../../../src/services/apiService.ts"],"sourcesContent":["import { parseSensitiveData } from \"@arkyn/shared\";\nimport { deleteRequest } from \"../http/api/_deleteRequest\";\nimport { getRequest } from \"../http/api/_getRequest\";\nimport { patchRequest } from \"../http/api/_patchRequest\";\nimport { postRequest } from \"../http/api/_postRequest\";\nimport { putRequest } from \"../http/api/_putRequest\";\nimport { flushDebugLogs } from \"../utilities/flushDebugLogs\";\nimport { SENSITIVE_DATA_KEYS } from \"../utilities/sensitiveDataKeys\";\n\ntype ApiServiceConstructorProps = {\n\t/** Base URL prepended to every request path (e.g. `\"https://api.example.com\"`). */\n\tbaseUrl: string;\n\t/** Default headers merged into every request. */\n\tbaseHeaders?: HeadersInit;\n\t/** Default Bearer token; can be overridden per request via `data.token`. */\n\tbaseToken?: string | null;\n\t/** When `true`, logs each request and response to the console in development. */\n\tenableDebug?: boolean;\n};\n\ntype ApiRequestDataWithoutBodyProps = {\n\theaders?: HeadersInit;\n\ttoken?: string;\n\turlParams?: Record<string, string>;\n};\n\ntype ApiRequestDataWithBodyProps = {\n\t// biome-ignore lint/suspicious/noExplicitAny: intentional\n\tbody?: any;\n\theaders?: HeadersInit;\n\ttoken?: string;\n\turlParams?: Record<string, string>;\n};\n\ntype DebugConfig = {\n\theaders?: HeadersInit;\n\t// biome-ignore lint/suspicious/noExplicitAny: intentional\n\tbody?: any;\n\tstatus: number;\n\tmessage: string;\n};\n\n/**\n * HTTP client for external API calls. Wraps fetch with base URL, default headers, optional auth token,\n * and per-request overrides for all standard HTTP methods.\n *\n * @example\n * ```typescript\n * const api = new ApiService({\n * baseUrl: \"https://api.example.com\",\n * baseToken: session.token,\n * enableDebug: true,\n * });\n *\n * const { data } = await api.get(\"/users/me\");\n * const { data: created } = await api.post(\"/orders\", { body: { productId: 1 } });\n * ```\n */\nclass ApiService {\n\tprivate baseUrl: string;\n\tprivate baseHeaders?: HeadersInit;\n\tprivate baseToken?: string;\n\tprivate enableDebug?: boolean;\n\n\tconstructor(props: ApiServiceConstructorProps) {\n\t\tthis.baseUrl = props.baseUrl;\n\t\tthis.baseHeaders = props.baseHeaders || undefined;\n\t\tthis.baseToken = props.baseToken || undefined;\n\t\tthis.enableDebug = props.enableDebug || false;\n\t}\n\n\t/**\n\t * Converts a `HeadersInit` value into a plain object so it can be safely\n\t * JSON-serialized and redacted, regardless of whether it was provided as a\n\t * `Headers` instance, a plain object, or an array of tuples.\n\t */\n\tprivate static toPlainHeaders(headers: HeadersInit): Record<string, string> {\n\t\tif (headers instanceof Headers)\n\t\t\treturn Object.fromEntries(headers.entries());\n\t\treturn Object.fromEntries(new Headers(headers).entries());\n\t}\n\n\t/**\n\t * Serializes a value to JSON and masks sensitive fields (e.g. `Authorization`\n\t * headers, `password`/`token`/`secret` body fields) before it is written to the\n\t * debug output. See `SENSITIVE_DATA_KEYS`.\n\t */\n\tprivate static toRedactedJson(value: unknown): string {\n\t\treturn parseSensitiveData(JSON.stringify(value), [...SENSITIVE_DATA_KEYS]);\n\t}\n\n\tprivate onDebug(endpoint: string, method: string, data: DebugConfig) {\n\t\tif (this.enableDebug) {\n\t\t\tconst debugs: string[] = [];\n\n\t\t\tdebugs.push(`Base URL: ${this.baseUrl}`);\n\t\t\tdebugs.push(`Endpoint: ${endpoint}`);\n\t\t\tdebugs.push(`Status/Method: ${method} => ${data.status}`);\n\t\t\tdebugs.push(`Message: ${data.message}`);\n\n\t\t\tif (data.headers) {\n\t\t\t\tconst plainHeaders = ApiService.toPlainHeaders(data.headers);\n\t\t\t\tdebugs.push(`Headers: ${ApiService.toRedactedJson(plainHeaders)}`);\n\t\t\t}\n\t\t\tif (data.body)\n\t\t\t\tdebugs.push(`Body: ${ApiService.toRedactedJson(data.body)}`);\n\n\t\t\tflushDebugLogs({ debugs, name: \"ApiDebug\", scheme: \"yellow\" });\n\t\t}\n\t}\n\n\tprivate generateHeaders(\n\t\tinitHeaders: HeadersInit,\n\t\ttoken?: string,\n\t): HeadersInit {\n\t\tlet headers: HeadersInit = {};\n\t\tif (this.baseToken) headers = { Authorization: `Bearer ${this.baseToken}` };\n\t\tif (this.baseHeaders) headers = { ...headers, ...this.baseHeaders };\n\n\t\tif (initHeaders) headers = { ...headers, ...initHeaders };\n\t\tif (token) headers = { ...headers, Authorization: `Bearer ${token}` };\n\n\t\treturn headers;\n\t}\n\n\t/**\n\t * Sends a get request to the specified endpoint.\n\t * @param endpoint - The API endpoint to send the get request to.\n\t * @param data - The request data, including optional headers and token.\n\t * @returns The API response data.\n\t */\n\n\tasync get(endpoint: string, data?: ApiRequestDataWithoutBodyProps) {\n\t\tconst headers = this.generateHeaders(data?.headers || {}, data?.token);\n\n\t\tconst response = await getRequest({\n\t\t\turl: this.baseUrl + endpoint,\n\t\t\turlParams: data?.urlParams || {},\n\t\t\theaders,\n\t\t});\n\n\t\tthis.onDebug(endpoint, \"get\", {\n\t\t\theaders,\n\t\t\tmessage: response.message,\n\t\t\tstatus: response.status,\n\t\t});\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Sends a post request to the specified endpoint.\n\t * @param endpoint - The API endpoint to send the post request to.\n\t * @param data - The request data, including body, optional headers, and token.\n\t * @returns The API response data.\n\t */\n\n\tasync post(endpoint: string, data?: ApiRequestDataWithBodyProps) {\n\t\tconst headers = this.generateHeaders(data?.headers || {}, data?.token);\n\t\tconst body = data?.body;\n\n\t\tconst response = await postRequest({\n\t\t\turl: this.baseUrl + endpoint,\n\t\t\turlParams: data?.urlParams || {},\n\t\t\theaders,\n\t\t\tbody,\n\t\t});\n\n\t\tthis.onDebug(endpoint, \"post\", {\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tmessage: response.message,\n\t\t\tstatus: response.status,\n\t\t});\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Sends a put request to the specified endpoint.\n\t * @param endpoint - The API endpoint to send the put request to.\n\t * @param data - The request data, including body, optional headers, and token.\n\t * @returns The API response data.\n\t */\n\n\tasync put(endpoint: string, data?: ApiRequestDataWithBodyProps) {\n\t\tconst headers = this.generateHeaders(data?.headers || {}, data?.token);\n\t\tconst body = data?.body;\n\n\t\tconst response = await putRequest({\n\t\t\turl: this.baseUrl + endpoint,\n\t\t\turlParams: data?.urlParams || {},\n\t\t\theaders,\n\t\t\tbody,\n\t\t});\n\n\t\tthis.onDebug(endpoint, \"put\", {\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tmessage: response.message,\n\t\t\tstatus: response.status,\n\t\t});\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Sends a patch request to the specified endpoint.\n\t * @param endpoint - The API endpoint to send the patch request to.\n\t * @param data - The request data, including body, optional headers, and token.\n\t * @returns The API response data.\n\t */\n\n\tasync patch(endpoint: string, data?: ApiRequestDataWithBodyProps) {\n\t\tconst headers = this.generateHeaders(data?.headers || {}, data?.token);\n\t\tconst body = data?.body;\n\n\t\tconst response = await patchRequest({\n\t\t\turl: this.baseUrl + endpoint,\n\t\t\turlParams: data?.urlParams || {},\n\t\t\theaders,\n\t\t\tbody,\n\t\t});\n\n\t\tthis.onDebug(endpoint, \"patch\", {\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tmessage: response.message,\n\t\t\tstatus: response.status,\n\t\t});\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Sends a delete request to the specified endpoint.\n\t * @param endpoint - The API endpoint to send the delete request to.\n\t * @param data - The request data, including body, optional headers, and token.\n\t * @returns The API response data.\n\t */\n\n\tasync delete(endpoint: string, data?: ApiRequestDataWithBodyProps) {\n\t\tconst headers = this.generateHeaders(data?.headers || {}, data?.token);\n\t\tconst body = data?.body;\n\n\t\tconst response = await deleteRequest({\n\t\t\turl: this.baseUrl + endpoint,\n\t\t\turlParams: data?.urlParams || {},\n\t\t\theaders,\n\t\t\tbody,\n\t\t});\n\n\t\tthis.onDebug(endpoint, \"delete\", {\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tmessage: response.message,\n\t\t\tstatus: response.status,\n\t\t});\n\n\t\treturn response;\n\t}\n}\n\nexport { ApiService };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"apiService.js","names":[],"sources":["../../../src/services/apiService.ts"],"sourcesContent":["import { parseSensitiveData } from \"@arkyn/shared\";\nimport { deleteRequest } from \"../http/api/_deleteRequest\";\nimport { getRequest } from \"../http/api/_getRequest\";\nimport { patchRequest } from \"../http/api/_patchRequest\";\nimport { postRequest } from \"../http/api/_postRequest\";\nimport { putRequest } from \"../http/api/_putRequest\";\nimport { flushDebugLogs } from \"../utilities/flushDebugLogs\";\nimport { SENSITIVE_DATA_KEYS } from \"../utilities/sensitiveDataKeys\";\n\ntype ApiServiceConstructorProps = {\n\t/** Base URL prepended to every request path (e.g. `\"https://api.example.com\"`). */\n\tbaseUrl: string;\n\t/** Default headers merged into every request. */\n\tbaseHeaders?: HeadersInit;\n\t/** Default Bearer token; can be overridden per request via `data.token`. */\n\tbaseToken?: string | null;\n\t/** When `true`, logs each request and response to the console in development. */\n\tenableDebug?: boolean;\n\t/** Default request timeout in ms; can be overridden per request via `data.timeoutMs`. */\n\ttimeoutMs?: number;\n};\n\ntype ApiRequestDataWithoutBodyProps = {\n\theaders?: HeadersInit;\n\ttoken?: string;\n\turlParams?: Record<string, string>;\n\ttimeoutMs?: number;\n};\n\ntype ApiRequestDataWithBodyProps = {\n\t// biome-ignore lint/suspicious/noExplicitAny: intentional\n\tbody?: any;\n\theaders?: HeadersInit;\n\ttoken?: string;\n\turlParams?: Record<string, string>;\n\ttimeoutMs?: number;\n};\n\ntype DebugConfig = {\n\theaders?: HeadersInit;\n\t// biome-ignore lint/suspicious/noExplicitAny: intentional\n\tbody?: any;\n\tstatus: number;\n\tmessage: string;\n};\n\n/**\n * HTTP client for external API calls. Wraps fetch with base URL, default headers, optional auth token,\n * and per-request overrides for all standard HTTP methods.\n *\n * @example\n * ```typescript\n * const api = new ApiService({\n * baseUrl: \"https://api.example.com\",\n * baseToken: session.token,\n * enableDebug: true,\n * });\n *\n * const { data } = await api.get(\"/users/me\");\n * const { data: created } = await api.post(\"/orders\", { body: { productId: 1 } });\n * ```\n */\nclass ApiService {\n\tprivate baseUrl: string;\n\tprivate baseHeaders?: HeadersInit;\n\tprivate baseToken?: string;\n\tprivate enableDebug?: boolean;\n\tprivate timeoutMs?: number;\n\n\tconstructor(props: ApiServiceConstructorProps) {\n\t\tthis.baseUrl = props.baseUrl;\n\t\tthis.baseHeaders = props.baseHeaders || undefined;\n\t\tthis.baseToken = props.baseToken || undefined;\n\t\tthis.enableDebug = props.enableDebug || false;\n\t\tthis.timeoutMs = props.timeoutMs;\n\t}\n\n\t/**\n\t * Converts a `HeadersInit` value into a plain object so it can be safely\n\t * JSON-serialized and redacted, regardless of whether it was provided as a\n\t * `Headers` instance, a plain object, or an array of tuples.\n\t */\n\tprivate static toPlainHeaders(headers: HeadersInit): Record<string, string> {\n\t\tif (headers instanceof Headers)\n\t\t\treturn Object.fromEntries(headers.entries());\n\t\treturn Object.fromEntries(new Headers(headers).entries());\n\t}\n\n\t/**\n\t * Serializes a value to JSON and masks sensitive fields (e.g. `Authorization`\n\t * headers, `password`/`token`/`secret` body fields) before it is written to the\n\t * debug output. See `SENSITIVE_DATA_KEYS`.\n\t */\n\tprivate static toRedactedJson(value: unknown): string {\n\t\treturn parseSensitiveData(JSON.stringify(value), [...SENSITIVE_DATA_KEYS]);\n\t}\n\n\tprivate onDebug(endpoint: string, method: string, data: DebugConfig) {\n\t\tif (this.enableDebug) {\n\t\t\tconst debugs: string[] = [];\n\n\t\t\tdebugs.push(`Base URL: ${this.baseUrl}`);\n\t\t\tdebugs.push(`Endpoint: ${endpoint}`);\n\t\t\tdebugs.push(`Status/Method: ${method} => ${data.status}`);\n\t\t\tdebugs.push(`Message: ${data.message}`);\n\n\t\t\tif (data.headers) {\n\t\t\t\tconst plainHeaders = ApiService.toPlainHeaders(data.headers);\n\t\t\t\tdebugs.push(`Headers: ${ApiService.toRedactedJson(plainHeaders)}`);\n\t\t\t}\n\t\t\tif (data.body)\n\t\t\t\tdebugs.push(`Body: ${ApiService.toRedactedJson(data.body)}`);\n\n\t\t\tflushDebugLogs({ debugs, name: \"ApiDebug\", scheme: \"yellow\" });\n\t\t}\n\t}\n\n\tprivate generateHeaders(\n\t\tinitHeaders: HeadersInit,\n\t\ttoken?: string,\n\t): HeadersInit {\n\t\tlet headers: HeadersInit = {};\n\t\tif (this.baseToken) headers = { Authorization: `Bearer ${this.baseToken}` };\n\t\tif (this.baseHeaders) headers = { ...headers, ...this.baseHeaders };\n\n\t\tif (initHeaders) headers = { ...headers, ...initHeaders };\n\t\tif (token) headers = { ...headers, Authorization: `Bearer ${token}` };\n\n\t\treturn headers;\n\t}\n\n\t/**\n\t * Sends a get request to the specified endpoint.\n\t * @param endpoint - The API endpoint to send the get request to.\n\t * @param data - The request data, including optional headers and token.\n\t * @returns The API response data.\n\t */\n\n\tasync get(endpoint: string, data?: ApiRequestDataWithoutBodyProps) {\n\t\tconst headers = this.generateHeaders(data?.headers || {}, data?.token);\n\n\t\tconst response = await getRequest({\n\t\t\turl: this.baseUrl + endpoint,\n\t\t\turlParams: data?.urlParams || {},\n\t\t\theaders,\n\t\t\ttimeoutMs: data?.timeoutMs ?? this.timeoutMs,\n\t\t});\n\n\t\tthis.onDebug(endpoint, \"get\", {\n\t\t\theaders,\n\t\t\tmessage: response.message,\n\t\t\tstatus: response.status,\n\t\t});\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Sends a post request to the specified endpoint.\n\t * @param endpoint - The API endpoint to send the post request to.\n\t * @param data - The request data, including body, optional headers, and token.\n\t * @returns The API response data.\n\t */\n\n\tasync post(endpoint: string, data?: ApiRequestDataWithBodyProps) {\n\t\tconst headers = this.generateHeaders(data?.headers || {}, data?.token);\n\t\tconst body = data?.body;\n\n\t\tconst response = await postRequest({\n\t\t\turl: this.baseUrl + endpoint,\n\t\t\turlParams: data?.urlParams || {},\n\t\t\theaders,\n\t\t\tbody,\n\t\t\ttimeoutMs: data?.timeoutMs ?? this.timeoutMs,\n\t\t});\n\n\t\tthis.onDebug(endpoint, \"post\", {\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tmessage: response.message,\n\t\t\tstatus: response.status,\n\t\t});\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Sends a put request to the specified endpoint.\n\t * @param endpoint - The API endpoint to send the put request to.\n\t * @param data - The request data, including body, optional headers, and token.\n\t * @returns The API response data.\n\t */\n\n\tasync put(endpoint: string, data?: ApiRequestDataWithBodyProps) {\n\t\tconst headers = this.generateHeaders(data?.headers || {}, data?.token);\n\t\tconst body = data?.body;\n\n\t\tconst response = await putRequest({\n\t\t\turl: this.baseUrl + endpoint,\n\t\t\turlParams: data?.urlParams || {},\n\t\t\theaders,\n\t\t\tbody,\n\t\t\ttimeoutMs: data?.timeoutMs ?? this.timeoutMs,\n\t\t});\n\n\t\tthis.onDebug(endpoint, \"put\", {\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tmessage: response.message,\n\t\t\tstatus: response.status,\n\t\t});\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Sends a patch request to the specified endpoint.\n\t * @param endpoint - The API endpoint to send the patch request to.\n\t * @param data - The request data, including body, optional headers, and token.\n\t * @returns The API response data.\n\t */\n\n\tasync patch(endpoint: string, data?: ApiRequestDataWithBodyProps) {\n\t\tconst headers = this.generateHeaders(data?.headers || {}, data?.token);\n\t\tconst body = data?.body;\n\n\t\tconst response = await patchRequest({\n\t\t\turl: this.baseUrl + endpoint,\n\t\t\turlParams: data?.urlParams || {},\n\t\t\theaders,\n\t\t\tbody,\n\t\t\ttimeoutMs: data?.timeoutMs ?? this.timeoutMs,\n\t\t});\n\n\t\tthis.onDebug(endpoint, \"patch\", {\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tmessage: response.message,\n\t\t\tstatus: response.status,\n\t\t});\n\n\t\treturn response;\n\t}\n\n\t/**\n\t * Sends a delete request to the specified endpoint.\n\t * @param endpoint - The API endpoint to send the delete request to.\n\t * @param data - The request data, including body, optional headers, and token.\n\t * @returns The API response data.\n\t */\n\n\tasync delete(endpoint: string, data?: ApiRequestDataWithBodyProps) {\n\t\tconst headers = this.generateHeaders(data?.headers || {}, data?.token);\n\t\tconst body = data?.body;\n\n\t\tconst response = await deleteRequest({\n\t\t\turl: this.baseUrl + endpoint,\n\t\t\turlParams: data?.urlParams || {},\n\t\t\theaders,\n\t\t\tbody,\n\t\t\ttimeoutMs: data?.timeoutMs ?? this.timeoutMs,\n\t\t});\n\n\t\tthis.onDebug(endpoint, \"delete\", {\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tmessage: response.message,\n\t\t\tstatus: response.status,\n\t\t});\n\n\t\treturn response;\n\t}\n}\n\nexport { ApiService };\n"],"mappings":"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"}
|
|
@@ -6,25 +6,38 @@ var t = class t {
|
|
|
6
6
|
return e.protocol === "https:" ? !0 : e.hostname === "localhost" || e.hostname === "127.0.0.1";
|
|
7
7
|
}
|
|
8
8
|
static setConfig(n) {
|
|
9
|
-
if (t.config)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
if (t.config) {
|
|
10
|
+
e({
|
|
11
|
+
name: "LogServiceError",
|
|
12
|
+
scheme: "yellow",
|
|
13
|
+
debugs: ["LogService.setConfig() was already called and is a singleton — this call was ignored. Call setConfig() once at application boot, never inside a request handler (a per-request value would only ever apply to the first request that reaches it)."]
|
|
14
|
+
});
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
let { trafficSourceId: r, logBaseApiUrl: i } = n, a = n.serviceToken ?? n.userToken;
|
|
18
|
+
if (n.userToken !== void 0 && n.serviceToken === void 0 && e({
|
|
19
|
+
name: "LogServiceError",
|
|
20
|
+
scheme: "yellow",
|
|
21
|
+
debugs: ["LogService.setConfig({ userToken }) is deprecated — use \"serviceToken\" instead. It must be a static, application-level credential, not a per-user/session value."]
|
|
22
|
+
}), !a) throw Error("LogService.setConfig() requires a serviceToken (or the deprecated userToken alias).");
|
|
23
|
+
let o = null;
|
|
24
|
+
if (i) try {
|
|
25
|
+
let n = new URL(i);
|
|
26
|
+
t.isSecureEndpoint(n) ? o = `${i}/ingest-log` : e({
|
|
14
27
|
name: "LogServiceError",
|
|
15
28
|
scheme: "red",
|
|
16
|
-
debugs: [`Insecure logBaseApiUrl "${
|
|
29
|
+
debugs: [`Insecure logBaseApiUrl "${i}" rejected: use https://, or http:// only for localhost/127.0.0.1. Remote logging disabled.`]
|
|
17
30
|
});
|
|
18
31
|
} catch {
|
|
19
32
|
e({
|
|
20
33
|
name: "LogServiceError",
|
|
21
34
|
scheme: "red",
|
|
22
|
-
debugs: [`Invalid logBaseApiUrl "${
|
|
35
|
+
debugs: [`Invalid logBaseApiUrl "${i}". Remote logging disabled.`]
|
|
23
36
|
});
|
|
24
37
|
}
|
|
25
38
|
t.config = {
|
|
26
39
|
trafficSourceId: r,
|
|
27
|
-
|
|
40
|
+
serviceToken: a,
|
|
28
41
|
apiUrl: o
|
|
29
42
|
};
|
|
30
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logService.js","names":[],"sources":["../../../src/services/logService.ts"],"sourcesContent":["import { flushDebugLogs } from \"../utilities/flushDebugLogs\";\n\n/**\n * Static service for log endpoint configuration and access.\n *\n * Stores a singleton configuration containing the traffic source identifier,\n *
|
|
1
|
+
{"version":3,"file":"logService.js","names":[],"sources":["../../../src/services/logService.ts"],"sourcesContent":["import { flushDebugLogs } from \"../utilities/flushDebugLogs\";\n\n/**\n * Static service for log endpoint configuration and access.\n *\n * Stores a singleton configuration containing the traffic source identifier,\n * service token, and log ingestion URL.\n *\n * `serviceToken` authenticates *this application* to the Arkyn log ingestion\n * API — it is a static, application-level credential (e.g. an env var),\n * the same for every request. It is **not** an end-user/session token:\n * because `setConfig` is a singleton that only applies on its first call\n * (see below), passing a per-request value (like `session.token`) here\n * would permanently leak whichever request happened to call `setConfig`\n * first to every other request's outbound telemetry for the lifetime of\n * the process. Call `setConfig` once, at application boot, outside of any\n * request handler.\n */\nclass LogService {\n\tprivate static config?: {\n\t\ttrafficSourceId: string;\n\t\tserviceToken: string;\n\t\tapiUrl: string | null;\n\t};\n\n\t/**\n\t * Checks whether a log ingestion URL is safe to send telemetry to: `https://`\n\t * is always accepted, and plain `http://` is only accepted for local development\n\t * (`localhost` / `127.0.0.1`). Every other `http://` destination is rejected so\n\t * request/response data is never transmitted in plaintext over the network.\n\t */\n\tprivate static isSecureEndpoint(url: URL): boolean {\n\t\tif (url.protocol === \"https:\") return true;\n\t\treturn url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\";\n\t}\n\n\t/**\n\t * Sets the log service configuration once. Subsequent calls are ignored\n\t * (a warning is logged in development/debug mode so accidental\n\t * per-request reconfiguration attempts — e.g. calling this from inside a\n\t * request handler — aren't silently swallowed during development).\n\t *\n\t * There is no hardcoded fallback endpoint: if `logBaseApiUrl` is omitted, invalid,\n\t * or points at an insecure (`http://`) non-local destination, remote log delivery\n\t * is disabled (`getConfig().apiUrl` is `null`) instead of silently sending data to\n\t * a default/insecure host — `logRequest` skips the network call in that case.\n\t *\n\t * @param config.trafficSourceId - Traffic source identifier.\n\t * @param config.serviceToken - Static, application-level credential used to\n\t * authenticate to the Arkyn log ingestion API. Must be the same value for every\n\t * request (e.g. sourced from an env var) — never an end-user/session token.\n\t * @param config.userToken - @deprecated Alias for `serviceToken`, kept for\n\t * backward compatibility. Using this name for a per-request/session value is\n\t * unsafe: `setConfig` is a singleton, so only the first call's value is kept.\n\t * @param config.logBaseApiUrl - Log ingestion base URL. Must be `https://`, or\n\t * `http://localhost`/`http://127.0.0.1` for local development.\n\t */\n\tstatic setConfig(config: {\n\t\ttrafficSourceId: string;\n\t\tserviceToken?: string;\n\t\t/** @deprecated Use `serviceToken` instead. */\n\t\tuserToken?: string;\n\t\tlogBaseApiUrl?: string;\n\t}): void {\n\t\tif (LogService.config) {\n\t\t\tflushDebugLogs({\n\t\t\t\tname: \"LogServiceError\",\n\t\t\t\tscheme: \"yellow\",\n\t\t\t\tdebugs: [\n\t\t\t\t\t\"LogService.setConfig() was already called and is a singleton — this call was ignored. \" +\n\t\t\t\t\t\t\"Call setConfig() once at application boot, never inside a request handler \" +\n\t\t\t\t\t\t\"(a per-request value would only ever apply to the first request that reaches it).\",\n\t\t\t\t],\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst { trafficSourceId, logBaseApiUrl } = config;\n\t\tconst serviceToken = config.serviceToken ?? config.userToken;\n\n\t\tif (config.userToken !== undefined && config.serviceToken === undefined) {\n\t\t\tflushDebugLogs({\n\t\t\t\tname: \"LogServiceError\",\n\t\t\t\tscheme: \"yellow\",\n\t\t\t\tdebugs: [\n\t\t\t\t\t'LogService.setConfig({ userToken }) is deprecated — use \"serviceToken\" instead. ' +\n\t\t\t\t\t\t\"It must be a static, application-level credential, not a per-user/session value.\",\n\t\t\t\t],\n\t\t\t});\n\t\t}\n\n\t\tif (!serviceToken) {\n\t\t\tthrow new Error(\n\t\t\t\t\"LogService.setConfig() requires a serviceToken (or the deprecated userToken alias).\",\n\t\t\t);\n\t\t}\n\n\t\tlet apiUrl: string | null = null;\n\n\t\tif (logBaseApiUrl) {\n\t\t\ttry {\n\t\t\t\tconst parsedUrl = new URL(logBaseApiUrl);\n\n\t\t\t\tif (LogService.isSecureEndpoint(parsedUrl)) {\n\t\t\t\t\tapiUrl = `${logBaseApiUrl}/ingest-log`;\n\t\t\t\t} else {\n\t\t\t\t\tflushDebugLogs({\n\t\t\t\t\t\tname: \"LogServiceError\",\n\t\t\t\t\t\tscheme: \"red\",\n\t\t\t\t\t\tdebugs: [\n\t\t\t\t\t\t\t`Insecure logBaseApiUrl \"${logBaseApiUrl}\" rejected: use https://, or http:// only for localhost/127.0.0.1. Remote logging disabled.`,\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tflushDebugLogs({\n\t\t\t\t\tname: \"LogServiceError\",\n\t\t\t\t\tscheme: \"red\",\n\t\t\t\t\tdebugs: [\n\t\t\t\t\t\t`Invalid logBaseApiUrl \"${logBaseApiUrl}\". Remote logging disabled.`,\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tLogService.config = { trafficSourceId, serviceToken, apiUrl };\n\t}\n\n\t/** Returns the stored configuration, or `undefined` if `setConfig` has not been called. */\n\tstatic getConfig():\n\t\t| { trafficSourceId: string; serviceToken: string; apiUrl: string | null }\n\t\t| undefined {\n\t\treturn LogService.config;\n\t}\n\n\t/**\n\t * Resets the stored configuration, allowing a new initialization.\n\t */\n\tstatic resetConfig() {\n\t\tLogService.config = undefined;\n\t}\n}\n\nexport { LogService };\n"],"mappings":";;AAkBA,IAAM,IAAN,MAAM,EAAW;CAChB,OAAe;CAYf,OAAe,iBAAiB,GAAmB;EAElD,OADI,EAAI,aAAa,WAAiB,KAC/B,EAAI,aAAa,eAAe,EAAI,aAAa;CACzD;CAuBA,OAAO,UAAU,GAMR;EACR,IAAI,EAAW,QAAQ;GACtB,EAAe;IACd,MAAM;IACN,QAAQ;IACR,QAAQ,CACP,mPAGD;GACD,CAAC;GACD;EACD;EAEA,IAAM,EAAE,oBAAiB,qBAAkB,GACrC,IAAe,EAAO,gBAAgB,EAAO;EAanD,IAXI,EAAO,cAAc,KAAA,KAAa,EAAO,iBAAiB,KAAA,KAC7D,EAAe;GACd,MAAM;GACN,QAAQ;GACR,QAAQ,CACP,oKAED;EACD,CAAC,GAGE,CAAC,GACJ,MAAU,MACT,qFACD;EAGD,IAAI,IAAwB;EAE5B,IAAI,GACH,IAAI;GACH,IAAM,IAAY,IAAI,IAAI,CAAa;GAEvC,AAAI,EAAW,iBAAiB,CAAS,IACxC,IAAS,GAAG,EAAc,eAE1B,EAAe;IACd,MAAM;IACN,QAAQ;IACR,QAAQ,CACP,2BAA2B,EAAc,4FAC1C;GACD,CAAC;EAEH,QAAQ;GACP,EAAe;IACd,MAAM;IACN,QAAQ;IACR,QAAQ,CACP,0BAA0B,EAAc,4BACzC;GACD,CAAC;EACF;EAGD,EAAW,SAAS;GAAE;GAAiB;GAAc;EAAO;CAC7D;CAGA,OAAO,YAEM;EACZ,OAAO,EAAW;CACnB;CAKA,OAAO,cAAc;EACpB,EAAW,SAAS,KAAA;CACrB;AACD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/utilities/withSecurityHeaders.ts
|
|
2
|
+
var e = {
|
|
3
|
+
"X-Content-Type-Options": "nosniff",
|
|
4
|
+
"X-Frame-Options": "DENY",
|
|
5
|
+
"Referrer-Policy": "strict-origin-when-cross-origin"
|
|
6
|
+
};
|
|
7
|
+
function t(t, n) {
|
|
8
|
+
let r = {
|
|
9
|
+
...e,
|
|
10
|
+
...n
|
|
11
|
+
};
|
|
12
|
+
for (let [e, n] of Object.entries(r)) n === null ? t.headers.delete(e) : t.headers.set(e, n);
|
|
13
|
+
return t;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { t as withSecurityHeaders };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=withSecurityHeaders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withSecurityHeaders.js","names":[],"sources":["../../../src/utilities/withSecurityHeaders.ts"],"sourcesContent":["const DEFAULT_SECURITY_HEADERS: Record<string, string> = {\n\t\"X-Content-Type-Options\": \"nosniff\",\n\t\"X-Frame-Options\": \"DENY\",\n\t\"Referrer-Policy\": \"strict-origin-when-cross-origin\",\n};\n\ntype SecurityHeaderOverrides = Record<string, string | null>;\n\n/**\n * Adds a small set of standard security headers to a `Response`, mutating and\n * returning the same instance (`Response.headers` is mutable for responses\n * constructed via `new Response()`/`Response.json()`, which is what every\n * `@arkyn/server` response class produces).\n *\n * Opt-in only — nothing in `@arkyn/server` calls this automatically, so it\n * never changes behavior for existing consumers unless they call it\n * themselves, e.g. `return withSecurityHeaders(new Success(...).toResponse())`.\n *\n * Defaults applied (each can be replaced or removed via `overrides` — see below):\n * - `X-Content-Type-Options: nosniff`\n * - `X-Frame-Options: DENY`\n * - `Referrer-Policy: strict-origin-when-cross-origin`\n *\n * `Content-Security-Policy` and `Strict-Transport-Security` are intentionally\n * **not** included by default: a wrong CSP can break an app's own scripts/styles,\n * and HSTS assumes HTTPS is already correctly configured — both are too\n * app-specific to guess safely. Pass them via `overrides` if you want them.\n *\n * @param response - The `Response` to add headers to.\n * @param overrides - Per-header customization: a string sets/replaces that\n * header (works for the defaults above or any other header, e.g. `Content-Security-Policy`);\n * `null` removes a default instead of applying it.\n * @returns The same `Response` instance, for chaining.\n *\n * @example\n * ```typescript\n * import { Success, withSecurityHeaders } from \"@arkyn/server\";\n *\n * export async function loader() {\n * return withSecurityHeaders(new Success(\"OK\", { data }).toResponse());\n * }\n *\n * // Disable one default, add a CSP\n * withSecurityHeaders(response, {\n * \"X-Frame-Options\": null,\n * \"Content-Security-Policy\": \"default-src 'self'\",\n * });\n * ```\n */\nfunction withSecurityHeaders(\n\tresponse: Response,\n\toverrides?: SecurityHeaderOverrides,\n): Response {\n\tconst merged = { ...DEFAULT_SECURITY_HEADERS, ...overrides };\n\n\tfor (const [name, value] of Object.entries(merged)) {\n\t\tif (value === null) response.headers.delete(name);\n\t\telse response.headers.set(name, value);\n\t}\n\n\treturn response;\n}\n\nexport { withSecurityHeaders };\n"],"mappings":";AAAA,IAAM,IAAmD;CACxD,0BAA0B;CAC1B,mBAAmB;CACnB,mBAAmB;AACpB;AA6CA,SAAS,EACR,GACA,GACW;CACX,IAAM,IAAS;EAAE,GAAG;EAA0B,GAAG;CAAU;CAE3D,KAAK,IAAM,CAAC,GAAM,MAAU,OAAO,QAAQ,CAAM,GAChD,AAAI,MAAU,OAAM,EAAS,QAAQ,OAAO,CAAI,IAC3C,EAAS,QAAQ,IAAI,GAAM,CAAK;CAGtC,OAAO;AACR"}
|
|
@@ -3,21 +3,11 @@ import { ValidateDateService as e } from "@arkyn/shared";
|
|
|
3
3
|
function t(t, n) {
|
|
4
4
|
let r = n?.inputFormat || "brazilianDate", i = n?.minYear || 1900, a = n?.maxYear || 3e3, o = new e();
|
|
5
5
|
o.validateInputFormat(r);
|
|
6
|
-
let s
|
|
7
|
-
if (
|
|
6
|
+
let s = t.split(/[-/]/).map(Number);
|
|
7
|
+
if (s.length !== 3) return !1;
|
|
8
8
|
try {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
[s, c, l] = u, o.validateDateParts(l, c, s);
|
|
12
|
-
break;
|
|
13
|
-
case "isoDate":
|
|
14
|
-
[c, s, l] = u, o.validateDateParts(l, c, s);
|
|
15
|
-
break;
|
|
16
|
-
case "timestamp":
|
|
17
|
-
[l, c, s] = u, o.validateDateParts(l, c, s);
|
|
18
|
-
break;
|
|
19
|
-
}
|
|
20
|
-
return !(l < i || l > a);
|
|
9
|
+
let { year: e } = o.parseDateParts(s, r);
|
|
10
|
+
return !(e < i || e > a);
|
|
21
11
|
} catch {
|
|
22
12
|
return !1;
|
|
23
13
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateDate.js","names":[],"sources":["../../../src/validations/validateDate.ts"],"sourcesContent":["import { ValidateDateService } from \"@arkyn/shared\";\n\n/**\n * Validates a date string against a format and optional year bounds.\n *\n * @param date - The date string to validate.\n * @param config.inputFormat - Parsing format: `\"brazilianDate\"` (DD/MM/YYYY, default), `\"
|
|
1
|
+
{"version":3,"file":"validateDate.js","names":[],"sources":["../../../src/validations/validateDate.ts"],"sourcesContent":["import { type DateInputFormat, ValidateDateService } from \"@arkyn/shared\";\n\n/**\n * Validates a date string against a format and optional year bounds.\n *\n * @param date - The date string to validate.\n * @param config.inputFormat - Parsing format: `\"brazilianDate\"` (DD/MM/YYYY, default), `\"usDate\"` (MM-DD-YYYY), `\"isoDate\"` (deprecated misnamed alias for `\"usDate\"` — not actually ISO 8601), or `\"timestamp\"` (YYYY-MM-DD, the actual ISO 8601 date format).\n * @param config.minYear - Minimum allowed year. Defaults to 1900.\n * @param config.maxYear - Maximum allowed year. Defaults to 3000.\n * @returns `true` if the date is valid according to the format and bounds, otherwise `false`.\n *\n * @example\n * ```typescript\n * validateDate(\"31/12/2023\"); // true\n * validateDate(\"2023-12-31\", { inputFormat: \"timestamp\", minYear: 2000, maxYear: 2100 }); // true\n * validateDate(\"29/02/2023\"); // false (not a leap year)\n * ```\n */\n\nfunction validateDate(\n\tdate: string,\n\tconfig?: {\n\t\tinputFormat?: DateInputFormat;\n\t\tminYear?: number;\n\t\tmaxYear?: number;\n\t},\n): boolean {\n\tconst inputFormat = config?.inputFormat || \"brazilianDate\";\n\tconst minYear = config?.minYear || 1900;\n\tconst maxYear = config?.maxYear || 3000;\n\n\tconst validateDateService = new ValidateDateService();\n\tvalidateDateService.validateInputFormat(inputFormat);\n\n\tconst dateParts = date.split(/[-/]/).map(Number);\n\n\tif (dateParts.length !== 3) return false;\n\n\ttry {\n\t\tconst { year } = validateDateService.parseDateParts(dateParts, inputFormat);\n\t\tif (year < minYear || year > maxYear) return false;\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport { validateDate };\n"],"mappings":";;AAmBA,SAAS,EACR,GACA,GAKU;CACV,IAAM,IAAc,GAAQ,eAAe,iBACrC,IAAU,GAAQ,WAAW,MAC7B,IAAU,GAAQ,WAAW,KAE7B,IAAsB,IAAI,EAAoB;CACpD,EAAoB,oBAAoB,CAAW;CAEnD,IAAM,IAAY,EAAK,MAAM,MAAM,CAAC,CAAC,IAAI,MAAM;CAE/C,IAAI,EAAU,WAAW,GAAG,OAAO;CAEnC,IAAI;EACH,IAAM,EAAE,YAAS,EAAoB,eAAe,GAAW,CAAW;EAE1E,OADA,EAAI,IAAO,KAAW,IAAO;CAE9B,QAAQ;EACP,OAAO;CACR;AACD"}
|
|
@@ -7,17 +7,21 @@ type ApiServiceConstructorProps = {
|
|
|
7
7
|
baseToken?: string | null;
|
|
8
8
|
/** When `true`, logs each request and response to the console in development. */
|
|
9
9
|
enableDebug?: boolean;
|
|
10
|
+
/** Default request timeout in ms; can be overridden per request via `data.timeoutMs`. */
|
|
11
|
+
timeoutMs?: number;
|
|
10
12
|
};
|
|
11
13
|
type ApiRequestDataWithoutBodyProps = {
|
|
12
14
|
headers?: HeadersInit;
|
|
13
15
|
token?: string;
|
|
14
16
|
urlParams?: Record<string, string>;
|
|
17
|
+
timeoutMs?: number;
|
|
15
18
|
};
|
|
16
19
|
type ApiRequestDataWithBodyProps = {
|
|
17
20
|
body?: any;
|
|
18
21
|
headers?: HeadersInit;
|
|
19
22
|
token?: string;
|
|
20
23
|
urlParams?: Record<string, string>;
|
|
24
|
+
timeoutMs?: number;
|
|
21
25
|
};
|
|
22
26
|
/**
|
|
23
27
|
* HTTP client for external API calls. Wraps fetch with base URL, default headers, optional auth token,
|
|
@@ -40,6 +44,7 @@ declare class ApiService {
|
|
|
40
44
|
private baseHeaders?;
|
|
41
45
|
private baseToken?;
|
|
42
46
|
private enableDebug?;
|
|
47
|
+
private timeoutMs?;
|
|
43
48
|
constructor(props: ApiServiceConstructorProps);
|
|
44
49
|
/**
|
|
45
50
|
* Converts a `HeadersInit` value into a plain object so it can be safely
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiService.d.ts","sourceRoot":"","sources":["../../src/services/apiService.ts"],"names":[],"mappings":"AASA,KAAK,0BAA0B,GAAG;IACjC,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iFAAiF;IACjF,WAAW,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"apiService.d.ts","sourceRoot":"","sources":["../../src/services/apiService.ts"],"names":[],"mappings":"AASA,KAAK,0BAA0B,GAAG;IACjC,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iFAAiF;IACjF,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,KAAK,8BAA8B,GAAG;IACrC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,KAAK,2BAA2B,GAAG;IAElC,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAUF;;;;;;;;;;;;;;;GAeG;AACH,cAAM,UAAU;IACf,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAC,CAAc;IAClC,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAC,CAAU;IAC9B,OAAO,CAAC,SAAS,CAAC,CAAS;gBAEf,KAAK,EAAE,0BAA0B;IAQ7C;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAM7B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAI7B,OAAO,CAAC,OAAO;IAoBf,OAAO,CAAC,eAAe;IAcvB;;;;;OAKG;IAEG,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,8BAA8B;IAmBjE;;;;;OAKG;IAEG,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,2BAA2B;IAsB/D;;;;;OAKG;IAEG,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,2BAA2B;IAsB9D;;;;;OAKG;IAEG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,2BAA2B;IAsBhE;;;;;OAKG;IAEG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,2BAA2B;CAqBjE;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
* Static service for log endpoint configuration and access.
|
|
3
3
|
*
|
|
4
4
|
* Stores a singleton configuration containing the traffic source identifier,
|
|
5
|
-
*
|
|
5
|
+
* service token, and log ingestion URL.
|
|
6
|
+
*
|
|
7
|
+
* `serviceToken` authenticates *this application* to the Arkyn log ingestion
|
|
8
|
+
* API — it is a static, application-level credential (e.g. an env var),
|
|
9
|
+
* the same for every request. It is **not** an end-user/session token:
|
|
10
|
+
* because `setConfig` is a singleton that only applies on its first call
|
|
11
|
+
* (see below), passing a per-request value (like `session.token`) here
|
|
12
|
+
* would permanently leak whichever request happened to call `setConfig`
|
|
13
|
+
* first to every other request's outbound telemetry for the lifetime of
|
|
14
|
+
* the process. Call `setConfig` once, at application boot, outside of any
|
|
15
|
+
* request handler.
|
|
6
16
|
*/
|
|
7
17
|
declare class LogService {
|
|
8
18
|
private static config?;
|
|
@@ -14,7 +24,10 @@ declare class LogService {
|
|
|
14
24
|
*/
|
|
15
25
|
private static isSecureEndpoint;
|
|
16
26
|
/**
|
|
17
|
-
* Sets the log service configuration once. Subsequent calls are ignored
|
|
27
|
+
* Sets the log service configuration once. Subsequent calls are ignored
|
|
28
|
+
* (a warning is logged in development/debug mode so accidental
|
|
29
|
+
* per-request reconfiguration attempts — e.g. calling this from inside a
|
|
30
|
+
* request handler — aren't silently swallowed during development).
|
|
18
31
|
*
|
|
19
32
|
* There is no hardcoded fallback endpoint: if `logBaseApiUrl` is omitted, invalid,
|
|
20
33
|
* or points at an insecure (`http://`) non-local destination, remote log delivery
|
|
@@ -22,19 +35,26 @@ declare class LogService {
|
|
|
22
35
|
* a default/insecure host — `logRequest` skips the network call in that case.
|
|
23
36
|
*
|
|
24
37
|
* @param config.trafficSourceId - Traffic source identifier.
|
|
25
|
-
* @param config.
|
|
38
|
+
* @param config.serviceToken - Static, application-level credential used to
|
|
39
|
+
* authenticate to the Arkyn log ingestion API. Must be the same value for every
|
|
40
|
+
* request (e.g. sourced from an env var) — never an end-user/session token.
|
|
41
|
+
* @param config.userToken - @deprecated Alias for `serviceToken`, kept for
|
|
42
|
+
* backward compatibility. Using this name for a per-request/session value is
|
|
43
|
+
* unsafe: `setConfig` is a singleton, so only the first call's value is kept.
|
|
26
44
|
* @param config.logBaseApiUrl - Log ingestion base URL. Must be `https://`, or
|
|
27
45
|
* `http://localhost`/`http://127.0.0.1` for local development.
|
|
28
46
|
*/
|
|
29
47
|
static setConfig(config: {
|
|
30
48
|
trafficSourceId: string;
|
|
31
|
-
|
|
49
|
+
serviceToken?: string;
|
|
50
|
+
/** @deprecated Use `serviceToken` instead. */
|
|
51
|
+
userToken?: string;
|
|
32
52
|
logBaseApiUrl?: string;
|
|
33
53
|
}): void;
|
|
34
54
|
/** Returns the stored configuration, or `undefined` if `setConfig` has not been called. */
|
|
35
55
|
static getConfig(): {
|
|
36
56
|
trafficSourceId: string;
|
|
37
|
-
|
|
57
|
+
serviceToken: string;
|
|
38
58
|
apiUrl: string | null;
|
|
39
59
|
} | undefined;
|
|
40
60
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logService.d.ts","sourceRoot":"","sources":["../../src/services/logService.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"logService.d.ts","sourceRoot":"","sources":["../../src/services/logService.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,cAAM,UAAU;IACf,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAIpB;IAEF;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAK/B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE;QACxB,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,8CAA8C;QAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;KACvB,GAAG,IAAI;IAiER,2FAA2F;IAC3F,MAAM,CAAC,SAAS,IACb;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxE,SAAS;IAIZ;;OAEG;IACH,MAAM,CAAC,WAAW;CAGlB;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type SecurityHeaderOverrides = Record<string, string | null>;
|
|
2
|
+
/**
|
|
3
|
+
* Adds a small set of standard security headers to a `Response`, mutating and
|
|
4
|
+
* returning the same instance (`Response.headers` is mutable for responses
|
|
5
|
+
* constructed via `new Response()`/`Response.json()`, which is what every
|
|
6
|
+
* `@arkyn/server` response class produces).
|
|
7
|
+
*
|
|
8
|
+
* Opt-in only — nothing in `@arkyn/server` calls this automatically, so it
|
|
9
|
+
* never changes behavior for existing consumers unless they call it
|
|
10
|
+
* themselves, e.g. `return withSecurityHeaders(new Success(...).toResponse())`.
|
|
11
|
+
*
|
|
12
|
+
* Defaults applied (each can be replaced or removed via `overrides` — see below):
|
|
13
|
+
* - `X-Content-Type-Options: nosniff`
|
|
14
|
+
* - `X-Frame-Options: DENY`
|
|
15
|
+
* - `Referrer-Policy: strict-origin-when-cross-origin`
|
|
16
|
+
*
|
|
17
|
+
* `Content-Security-Policy` and `Strict-Transport-Security` are intentionally
|
|
18
|
+
* **not** included by default: a wrong CSP can break an app's own scripts/styles,
|
|
19
|
+
* and HSTS assumes HTTPS is already correctly configured — both are too
|
|
20
|
+
* app-specific to guess safely. Pass them via `overrides` if you want them.
|
|
21
|
+
*
|
|
22
|
+
* @param response - The `Response` to add headers to.
|
|
23
|
+
* @param overrides - Per-header customization: a string sets/replaces that
|
|
24
|
+
* header (works for the defaults above or any other header, e.g. `Content-Security-Policy`);
|
|
25
|
+
* `null` removes a default instead of applying it.
|
|
26
|
+
* @returns The same `Response` instance, for chaining.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { Success, withSecurityHeaders } from "@arkyn/server";
|
|
31
|
+
*
|
|
32
|
+
* export async function loader() {
|
|
33
|
+
* return withSecurityHeaders(new Success("OK", { data }).toResponse());
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* // Disable one default, add a CSP
|
|
37
|
+
* withSecurityHeaders(response, {
|
|
38
|
+
* "X-Frame-Options": null,
|
|
39
|
+
* "Content-Security-Policy": "default-src 'self'",
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare function withSecurityHeaders(response: Response, overrides?: SecurityHeaderOverrides): Response;
|
|
44
|
+
export { withSecurityHeaders };
|
|
45
|
+
//# sourceMappingURL=withSecurityHeaders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withSecurityHeaders.d.ts","sourceRoot":"","sources":["../../src/utilities/withSecurityHeaders.ts"],"names":[],"mappings":"AAMA,KAAK,uBAAuB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,iBAAS,mBAAmB,CAC3B,QAAQ,EAAE,QAAQ,EAClB,SAAS,CAAC,EAAE,uBAAuB,GACjC,QAAQ,CASV;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { type DateInputFormat } from "@arkyn/shared";
|
|
1
2
|
/**
|
|
2
3
|
* Validates a date string against a format and optional year bounds.
|
|
3
4
|
*
|
|
4
5
|
* @param date - The date string to validate.
|
|
5
|
-
* @param config.inputFormat - Parsing format: `"brazilianDate"` (DD/MM/YYYY, default), `"
|
|
6
|
+
* @param config.inputFormat - Parsing format: `"brazilianDate"` (DD/MM/YYYY, default), `"usDate"` (MM-DD-YYYY), `"isoDate"` (deprecated misnamed alias for `"usDate"` — not actually ISO 8601), or `"timestamp"` (YYYY-MM-DD, the actual ISO 8601 date format).
|
|
6
7
|
* @param config.minYear - Minimum allowed year. Defaults to 1900.
|
|
7
8
|
* @param config.maxYear - Maximum allowed year. Defaults to 3000.
|
|
8
9
|
* @returns `true` if the date is valid according to the format and bounds, otherwise `false`.
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
* ```
|
|
16
17
|
*/
|
|
17
18
|
declare function validateDate(date: string, config?: {
|
|
18
|
-
inputFormat?:
|
|
19
|
+
inputFormat?: DateInputFormat;
|
|
19
20
|
minYear?: number;
|
|
20
21
|
maxYear?: number;
|
|
21
22
|
}): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateDate.d.ts","sourceRoot":"","sources":["../../src/validations/validateDate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validateDate.d.ts","sourceRoot":"","sources":["../../src/validations/validateDate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAuB,MAAM,eAAe,CAAC;AAE1E;;;;;;;;;;;;;;;GAeG;AAEH,iBAAS,YAAY,CACpB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE;IACR,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,GACC,OAAO,CAmBT;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkyn/server",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.12",
|
|
4
4
|
"author": "Arkyn | Lucas Gonçalves",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -240,6 +240,11 @@
|
|
|
240
240
|
"import": "./dist/modules/validations/validateRg.js",
|
|
241
241
|
"types": "./dist/validations/validateRg.d.ts",
|
|
242
242
|
"default": "./dist/modules/validations/validateRg.js"
|
|
243
|
+
},
|
|
244
|
+
"./withSecurityHeaders": {
|
|
245
|
+
"import": "./dist/modules/utilities/withSecurityHeaders.js",
|
|
246
|
+
"types": "./dist/utilities/withSecurityHeaders.d.ts",
|
|
247
|
+
"default": "./dist/modules/utilities/withSecurityHeaders.js"
|
|
243
248
|
}
|
|
244
249
|
},
|
|
245
250
|
"typesVersions": {
|
|
@@ -342,6 +347,9 @@
|
|
|
342
347
|
],
|
|
343
348
|
"validateRg": [
|
|
344
349
|
"./dist/validations/validateRg.d.ts"
|
|
350
|
+
],
|
|
351
|
+
"withSecurityHeaders": [
|
|
352
|
+
"./dist/utilities/withSecurityHeaders.d.ts"
|
|
345
353
|
]
|
|
346
354
|
}
|
|
347
355
|
}
|