@arkstack/common 0.7.8 → 0.7.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,215 @@
1
+ import { Obj } from "@h3ravel/support";
2
+ import { createJiti } from "jiti";
3
+ import { createRequire } from "module";
4
+ import path, { resolve } from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import { readdirSync } from "fs";
7
+ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
8
+ import { Secret, TOTP } from "otpauth";
9
+ import { compare, genSalt, hash } from "bcryptjs";
10
+ import { getUserConfig } from "arkormx";
11
+ //#region src/system.ts
12
+ /**
13
+ * Read the .env file
14
+ *
15
+ * @param env
16
+ * @param def
17
+ * @returns
18
+ */
19
+ const env = (env, defaultValue) => {
20
+ let val = process.env[env] ?? "";
21
+ if ([
22
+ true,
23
+ "true",
24
+ "on",
25
+ false,
26
+ "false",
27
+ "off"
28
+ ].includes(val)) val = [
29
+ true,
30
+ "true",
31
+ "on"
32
+ ].includes(val);
33
+ if (!isNaN(Number(val)) && typeof val !== "boolean" && typeof val !== "undefined" && val !== "") val = Number(val);
34
+ if (val === "") val = void 0;
35
+ if (val === "null") val = null;
36
+ val ??= defaultValue;
37
+ return val;
38
+ };
39
+ /**
40
+ * Build the app url
41
+ *
42
+ * @param link
43
+ * @returns
44
+ */
45
+ const appUrl = (link) => {
46
+ const port = env("PORT") || "3000";
47
+ const defaultUrl = `http://localhost:${port}`;
48
+ const appUrl = env("APP_URL") ?? defaultUrl;
49
+ try {
50
+ const url = new URL(appUrl);
51
+ if (url.port || url.hostname === "localhost") url.port = port;
52
+ const baseUrl = url.toString().replace(/\/$/, "");
53
+ if (link) return `${baseUrl}${`/${link.replace(/^\/+/, "")}`}`;
54
+ return baseUrl;
55
+ } catch {
56
+ return link ? `${defaultUrl}/${link.replace(/^\/+/, "")}` : defaultUrl;
57
+ }
58
+ };
59
+ /**
60
+ * Gets the application configuration.
61
+ *
62
+ * @param key The configuration key to retrieve.
63
+ * @param defaultValue The default value to return if the key is not found.
64
+ * @returns The configuration value.
65
+ */
66
+ const config = (key, defaultValue) => {
67
+ const dist = path.relative(process.cwd(), outputDir());
68
+ const require = createRequire(import.meta.url);
69
+ const config = readdirSync(path.join(process.cwd(), `${dist}/config`), { withFileTypes: true }).filter((file) => {
70
+ if (file.name.includes("middleware") && globalThis.arkctx.runtime === "CLI") return false;
71
+ return file.isFile() && (file.name.endsWith(".js") || file.name.endsWith(".ts"));
72
+ }).reduce((configs, file) => {
73
+ const configName = path.basename(file.name, path.extname(file.name));
74
+ configs[configName] = require(path.join(file.parentPath, file.name)).default(globalThis.app());
75
+ return configs;
76
+ }, {});
77
+ if (key) return Obj.get(config, key, defaultValue);
78
+ return config;
79
+ };
80
+ /**
81
+ * Gets the current Node environment (development or production).
82
+ *
83
+ * @returns
84
+ */
85
+ const nodeEnv = () => {
86
+ let envValue = env("NODE_ENV", "development");
87
+ if (envValue !== "development" && envValue !== "production") envValue = "development";
88
+ return envValue === "production" ? "prod" : "dev";
89
+ };
90
+ /**
91
+ * Gets the output directory for the application based on the current environment.
92
+ *
93
+ * @param cwd The current working directory (optional, defaults to process.cwd()).
94
+ * @returns
95
+ */
96
+ const outputDir = (cwd = process.cwd()) => {
97
+ const NODE_ENV = nodeEnv();
98
+ const output = {
99
+ dev: env("OUTPUT_DIR_DEV", ".arkstack/build"),
100
+ prod: env("OUTPUT_DIR", "dist")
101
+ };
102
+ return path.isAbsolute(output[NODE_ENV] ?? output.dev) ? output[NODE_ENV] ?? output.dev : path.join(cwd, output[NODE_ENV] ?? output.dev);
103
+ };
104
+ const importFile = async (filePath) => {
105
+ const resolvedPath = resolve(filePath);
106
+ return await createJiti(pathToFileURL(resolvedPath).href, {
107
+ interopDefault: false,
108
+ tsconfigPaths: true
109
+ }).import(resolvedPath);
110
+ };
111
+ //#endregion
112
+ //#region src/utils/encryption.ts
113
+ var Encryption = class {
114
+ static algorithm = "aes-256-gcm";
115
+ static getKey() {
116
+ const secret = env("TWO_FACTOR_ENCRYPTION_KEY");
117
+ if (!secret) throw new Error("TWO_FACTOR_ENCRYPTION_KEY is required to use two-factor authentication");
118
+ return createHash("sha256").update(secret).digest();
119
+ }
120
+ static encrypt(value) {
121
+ const iv = randomBytes(12);
122
+ const cipher = createCipheriv(this.algorithm, this.getKey(), iv);
123
+ const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
124
+ return [
125
+ iv,
126
+ cipher.getAuthTag(),
127
+ ciphertext
128
+ ].map((part) => part.toString("base64url")).join(":");
129
+ }
130
+ static decrypt(payload) {
131
+ const [iv, authTag, ciphertext] = payload.split(":");
132
+ if (!iv || !authTag || !ciphertext) throw new Error("Invalid encrypted payload format");
133
+ const decipher = createDecipheriv(this.algorithm, this.getKey(), Buffer.from(iv, "base64url"));
134
+ decipher.setAuthTag(Buffer.from(authTag, "base64url"));
135
+ return Buffer.concat([decipher.update(Buffer.from(ciphertext, "base64url")), decipher.final()]).toString("utf8");
136
+ }
137
+ };
138
+ //#endregion
139
+ //#region src/utils/hash.ts
140
+ var Hash = class {
141
+ /**
142
+ * Hash a value using bcrypt
143
+ *
144
+ * @param value
145
+ * @returns
146
+ */
147
+ static async make(value) {
148
+ return await hash(value, await genSalt(10));
149
+ }
150
+ /**
151
+ * Verify a value against a hashed value
152
+ *
153
+ * @param value
154
+ * @param hashedValue
155
+ * @returns
156
+ */
157
+ static async verify(value, hashedValue) {
158
+ return await compare(value, hashedValue);
159
+ }
160
+ /**
161
+ * Generate a one-time password (OTP) using TOTP algorithm
162
+ *
163
+ * @param digits The number of digits for the OTP, default is 6.
164
+ * @param label A label to identify the OTP, can be an email or phone number.
165
+ * @param period Interval of time for which a token is valid, in seconds.
166
+ * @returns
167
+ */
168
+ static otp(digits = 6, label = "Alice", period = 30) {
169
+ return new TOTP({
170
+ label,
171
+ digits,
172
+ issuer: env("APP_NAME", "Roseed"),
173
+ algorithm: "SHA1",
174
+ period,
175
+ secret: "US3WHSG7X5KAPV27VANWKQHF3SH3HULL"
176
+ });
177
+ }
178
+ static totp(secret, label, issuer = env("APP_NAME", "Roseed"), period = 30) {
179
+ return new TOTP({
180
+ issuer,
181
+ label,
182
+ algorithm: "SHA1",
183
+ digits: 6,
184
+ period,
185
+ secret: Secret.fromBase32(secret)
186
+ });
187
+ }
188
+ };
189
+ //#endregion
190
+ //#region src/utils/helpers.ts
191
+ /**
192
+ * Determine the number of items to return per page based on the provided query parameters.
193
+ *
194
+ * @param query
195
+ * @returns
196
+ */
197
+ const perPage = (query) => {
198
+ const requestedPerPage = Number(query.limit ?? query.perPage ?? 15);
199
+ return Number.isFinite(requestedPerPage) && requestedPerPage > 0 ? Math.min(requestedPerPage, 50) : 15;
200
+ };
201
+ async function getModel(modelName) {
202
+ const resolveModelExport = (module, modelName) => {
203
+ if (!isModelModule(module)) return module;
204
+ return module.default ?? module[modelName] ?? module;
205
+ };
206
+ const isModelModule = (value) => typeof value === "object" && value !== null;
207
+ const modelPath = getUserConfig().paths?.models || "./src/models";
208
+ const model = resolveModelExport(await importFile(path.join(path.isAbsolute(modelPath) ? modelPath : path.join(process.cwd(), modelPath), modelName)), path.basename(modelName, path.extname(modelName)));
209
+ if (typeof model !== "function") throw new Error(`Model "${modelName}" not found`);
210
+ return model;
211
+ }
212
+ //#endregion
213
+ export { appUrl as a, importFile as c, Encryption as i, nodeEnv as l, perPage as n, config as o, Hash as r, env as s, getModel as t, outputDir as u };
214
+
215
+ //# sourceMappingURL=helpers-DfQxjBEv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers-DfQxjBEv.js","names":[],"sources":["../src/system.ts","../src/utils/encryption.ts","../src/utils/hash.ts","../src/utils/helpers.ts"],"sourcesContent":["import { DotPath, Obj } from '@h3ravel/support'\nimport { GlobalConfig, GlobalEnv } from './types'\n\nimport { createJiti } from 'jiti'\nimport { createRequire } from 'module'\nimport path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { readdirSync } from 'fs'\nimport { resolve } from 'node:path'\n\n/**\n * Read the .env file\n *\n * @param env\n * @param def\n * @returns\n */\nexport const env: GlobalEnv = <X = string, Y = undefined | X> (\n env: string,\n defaultValue?: Y,\n) => {\n let val: string | number | boolean | undefined | null = process.env[env] ?? ''\n\n if ([true, 'true', 'on', false, 'false', 'off'].includes(val)) {\n val = [true, 'true', 'on'].includes(val)\n }\n\n if (!isNaN(Number(val)) && typeof val !== 'boolean' && typeof val !== 'undefined' && val !== '') {\n val = Number(val)\n }\n\n if (val === '') {\n val = undefined\n }\n\n if (val === 'null') {\n val = null\n }\n\n val ??= defaultValue as typeof val\n\n return val as Y extends undefined ? X : Y\n}\n\n/**\n * Build the app url\n *\n * @param link\n * @returns\n */\nexport const appUrl = (link?: string): string => {\n const port = env('PORT') || '3000'\n const defaultUrl = `http://localhost:${port}`\n const appUrl = env('APP_URL') ?? defaultUrl\n\n try {\n const url = new URL(appUrl)\n // Append port only if APP_URL has a port or is localhost\n if (url.port || url.hostname === 'localhost') {\n url.port = port\n }\n // Remove trailing slash from base URL\n const baseUrl = url.toString().replace(/\\/$/, '')\n // Append link with proper path separator\n if (link) {\n // Ensure link starts with '/' and remove duplicate slashes\n const normalizedLink = `/${link.replace(/^\\/+/, '')}`\n\n return `${baseUrl}${normalizedLink}`\n }\n\n return baseUrl\n } catch {\n // Return default URL with link if provided\n return link ? `${defaultUrl}/${link.replace(/^\\/+/, '')}` : defaultUrl\n }\n}\n\n/**\n * Gets the application configuration.\n * \n * @param key The configuration key to retrieve.\n * @param defaultValue The default value to return if the key is not found.\n * @returns The configuration value.\n */\nexport const config: GlobalConfig = <X extends Record<string, any>, P extends DotPath<X> | undefined = undefined> (\n key?: P,\n defaultValue?: any\n) => {\n const dist = path.relative(process.cwd(), outputDir())\n const require = createRequire(import.meta.url)\n\n const files = readdirSync(path.join(process.cwd(), `${dist}/config`), { withFileTypes: true })\n .filter(file => {\n if (file.name.includes('middleware') && globalThis.arkctx.runtime === 'CLI') return false\n\n return file.isFile() && (file.name.endsWith('.js') || file.name.endsWith('.ts'))\n })\n\n const config = files.reduce((configs, file) => {\n const configName = path.basename(file.name, path.extname(file.name))\n\n configs[configName] = require(path.join(file.parentPath, file.name))\n .default((globalThis as any).app())\n\n return configs\n }, {} as Record<string, any>) as X\n\n\n if (key) {\n return Obj.get(config, key, defaultValue)\n }\n\n return config\n}\n\n/**\n * Gets the current Node environment (development or production).\n * \n * @returns \n */\nexport const nodeEnv = () => {\n let envValue = env<'development' | 'production'>('NODE_ENV', 'development')\n\n if (envValue !== 'development' && envValue !== 'production') {\n envValue = 'development'\n }\n\n return envValue === 'production' ? 'prod' : 'dev'\n}\n\n/**\n * Gets the output directory for the application based on the current environment.\n * \n * @param cwd The current working directory (optional, defaults to process.cwd()).\n * @returns \n */\nexport const outputDir = (cwd = process.cwd()) => {\n const NODE_ENV = nodeEnv()\n\n const output = {\n dev: env('OUTPUT_DIR_DEV', '.arkstack/build'),\n prod: env('OUTPUT_DIR', 'dist'),\n }\n\n return path.isAbsolute(output[NODE_ENV] ?? output.dev)\n ? (output[NODE_ENV] ?? output.dev)\n : path.join(cwd, output[NODE_ENV] ?? output.dev)\n}\n\n\nexport const importFile = async <T = unknown> (filePath: string): Promise<T> => {\n const resolvedPath = resolve(filePath)\n const jiti = createJiti(pathToFileURL(resolvedPath).href, {\n interopDefault: false,\n tsconfigPaths: true,\n })\n\n return await jiti.import<T>(resolvedPath)\n}","import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto'\n\nimport { env } from '../system'\n\nexport class Encryption {\n private static readonly algorithm = 'aes-256-gcm'\n\n private static getKey () {\n const secret = env('TWO_FACTOR_ENCRYPTION_KEY')\n\n if (!secret) {\n throw new Error('TWO_FACTOR_ENCRYPTION_KEY is required to use two-factor authentication')\n }\n\n return createHash('sha256').update(secret).digest()\n }\n\n static encrypt (value: string) {\n const iv = randomBytes(12)\n const cipher = createCipheriv(this.algorithm, this.getKey(), iv)\n const ciphertext = Buffer.concat([\n cipher.update(value, 'utf8'),\n cipher.final(),\n ])\n const authTag = cipher.getAuthTag()\n\n return [iv, authTag, ciphertext].map((part) => part.toString('base64url')).join(':')\n }\n\n static decrypt (payload: string) {\n const [iv, authTag, ciphertext] = payload.split(':')\n\n if (!iv || !authTag || !ciphertext) {\n throw new Error('Invalid encrypted payload format')\n }\n\n const decipher = createDecipheriv(\n this.algorithm,\n this.getKey(),\n Buffer.from(iv, 'base64url'),\n )\n\n decipher.setAuthTag(Buffer.from(authTag, 'base64url'))\n\n const plaintext = Buffer.concat([\n decipher.update(Buffer.from(ciphertext, 'base64url')),\n decipher.final(),\n ])\n\n return plaintext.toString('utf8')\n }\n}","import { Secret, TOTP } from 'otpauth'\nimport { compare, genSalt, hash } from 'bcryptjs'\n\nimport { env } from '../system'\n\nexport class Hash {\n /**\n * Hash a value using bcrypt\n * \n * @param value \n * @returns \n */\n static async make (value: string): Promise<string> {\n const salt = await genSalt(10)\n\n return await hash(value, salt)\n }\n\n /**\n * Verify a value against a hashed value\n * \n * @param value \n * @param hashedValue \n * @returns \n */\n static async verify (value: string, hashedValue: string): Promise<boolean> {\n return await compare(value, hashedValue)\n }\n\n /**\n * Generate a one-time password (OTP) using TOTP algorithm\n * \n * @param digits The number of digits for the OTP, default is 6.\n * @param label A label to identify the OTP, can be an email or phone number.\n * @param period Interval of time for which a token is valid, in seconds.\n * @returns \n */\n static otp (digits: number = 6, label: string = 'Alice', period: number = 30) {\n return new TOTP({\n label,\n digits,\n issuer: env('APP_NAME', 'Roseed'),\n algorithm: 'SHA1',\n period, // in seconds.\n secret: 'US3WHSG7X5KAPV27VANWKQHF3SH3HULL',\n })\n }\n\n static totp (secret: string, label: string, issuer: string = env('APP_NAME', 'Roseed'), period: number = 30) {\n return new TOTP({\n issuer,\n label,\n algorithm: 'SHA1',\n digits: 6,\n period,\n secret: Secret.fromBase32(secret),\n })\n }\n}","import { getUserConfig, type Model, type ModelStatic } from 'arkormx'\nimport { importFile } from '../system'\nimport path from 'node:path'\n\nexport type AbstractModelConstructor<TModel = unknown> =\n abstract new (attributes?: Record<string, unknown>) => TModel\n\nexport type ModelConstructor<TModel extends Model = Model> =\n AbstractModelConstructor<TModel> &\n Pick<ModelStatic<TModel>, keyof ModelStatic<TModel>>\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ModelRegistry { }\n\ntype ModelName = Extract<keyof ModelRegistry, string>\ntype ModelModule = Record<string, unknown> & {\n default?: unknown;\n}\n\n/**\n * Determine the number of items to return per page based on the provided query parameters.\n * \n * @param query \n * @returns \n */\nexport const perPage = (query: { limit?: number; perPage?: number }) => {\n\n const requestedPerPage = Number(query.limit ?? query.perPage ?? 15)\n\n return Number.isFinite(requestedPerPage) && requestedPerPage > 0\n ? Math.min(requestedPerPage, 50)\n : 15\n}\n\n/**\n * Import an application model by name.\n *\n * Apps can augment `ModelRegistry` to make `getModel('User')` return `typeof User`.\n * Without a registry entry, pass the class type explicitly: `getModel<typeof User>('User')`.\n * \n * @param modelName \n */\nexport async function getModel<TName extends ModelName> (\n modelName: TName\n): Promise<ModelRegistry[TName]>\nexport async function getModel<TModel extends AbstractModelConstructor = ModelConstructor> (\n modelName: string\n): Promise<TModel>\nexport async function getModel (modelName: string) {\n const resolveModelExport = (module: ModelModule | unknown, modelName: string) => {\n if (!isModelModule(module)) {\n return module\n }\n\n return module.default ?? module[modelName] ?? module\n }\n\n const isModelModule = (value: unknown): value is ModelModule => (\n typeof value === 'object' && value !== null\n )\n\n const modelPath = getUserConfig().paths?.models || './src/models'\n const modulePath = path.join(\n path.isAbsolute(modelPath) ? modelPath : path.join(process.cwd(), modelPath),\n modelName\n )\n const module = await importFile<ModelModule | unknown>(modulePath)\n const exportName = path.basename(modelName, path.extname(modelName))\n const model = resolveModelExport(module, exportName)\n\n if (typeof model !== 'function') {\n throw new Error(`Model \"${modelName}\" not found`)\n }\n\n return model\n}\n"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { TOTP } from "otpauth";
2
+ import { Model, ModelStatic } from "arkormx";
3
+
4
+ //#region src/utils/encryption.d.ts
5
+ declare class Encryption {
6
+ private static readonly algorithm;
7
+ private static getKey;
8
+ static encrypt(value: string): string;
9
+ static decrypt(payload: string): string;
10
+ }
11
+ //#endregion
12
+ //#region src/utils/hash.d.ts
13
+ declare class Hash {
14
+ static make(value: string): Promise<string>;
15
+ static verify(value: string, hashedValue: string): Promise<boolean>;
16
+ static otp(digits?: number, label?: string, period?: number): TOTP;
17
+ static totp(secret: string, label: string, issuer?: string, period?: number): TOTP;
18
+ }
19
+ //#endregion
20
+ //#region src/utils/helpers.d.ts
21
+ type AbstractModelConstructor<TModel = unknown> = abstract new (attributes?: Record<string, unknown>) => TModel;
22
+ type ModelConstructor<TModel extends Model = Model> = AbstractModelConstructor<TModel> & Pick<ModelStatic<TModel>, keyof ModelStatic<TModel>>;
23
+ interface ModelRegistry {}
24
+ type ModelName = Extract<keyof ModelRegistry, string>;
25
+ declare const perPage: (query: {
26
+ limit?: number;
27
+ perPage?: number;
28
+ }) => number;
29
+ declare function getModel<TName extends ModelName>(modelName: TName): Promise<ModelRegistry[TName]>;
30
+ declare function getModel<TModel extends AbstractModelConstructor = ModelConstructor>(modelName: string): Promise<TModel>;
31
+ //#endregion
32
+ export { perPage as a, getModel as i, ModelConstructor as n, Hash as o, ModelRegistry as r, Encryption as s, AbstractModelConstructor as t };
33
+ //# sourceMappingURL=helpers-P7NkVB6R.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  /// <reference path="./app.d.ts" />
2
+ import { a as perPage, i as getModel, n as ModelConstructor, o as Hash, r as ModelRegistry, s as Encryption, t as AbstractModelConstructor } from "./helpers-P7NkVB6R.js";
2
3
  import { DotPath } from "@h3ravel/support";
3
4
  import pino from "pino";
4
5
  import { ChalkInstance } from "chalk";
5
- import { TOTP } from "otpauth";
6
- import { Model, ModelStatic } from "arkormx";
7
6
 
8
7
  //#region src/lifecycle.d.ts
9
8
  declare const bindGracefulShutdown: (shutdown: () => Promise<void> | void) => void;
@@ -171,33 +170,5 @@ declare class Hook {
171
170
  static clear: () => void;
172
171
  }
173
172
  //#endregion
174
- //#region src/utils/encryption.d.ts
175
- declare class Encryption {
176
- private static readonly algorithm;
177
- private static getKey;
178
- static encrypt(value: string): string;
179
- static decrypt(payload: string): string;
180
- }
181
- //#endregion
182
- //#region src/utils/hash.d.ts
183
- declare class Hash {
184
- static make(value: string): Promise<string>;
185
- static verify(value: string, hashedValue: string): Promise<boolean>;
186
- static otp(digits?: number, label?: string, period?: number): TOTP;
187
- static totp(secret: string, label: string, issuer?: string, period?: number): TOTP;
188
- }
189
- //#endregion
190
- //#region src/utils/helpers.d.ts
191
- type AbstractModelConstructor<TModel = unknown> = abstract new (attributes?: Record<string, unknown>) => TModel;
192
- type ModelConstructor<TModel extends Model = Model> = AbstractModelConstructor<TModel> & Pick<ModelStatic<TModel>, keyof ModelStatic<TModel>>;
193
- interface ModelRegistry {}
194
- type ModelName = Extract<keyof ModelRegistry, string>;
195
- declare const perPage: (query: {
196
- limit?: number;
197
- perPage?: number;
198
- }) => number;
199
- declare function getModel<TName extends ModelName>(modelName: TName): Promise<ModelRegistry[TName]>;
200
- declare function getModel<TModel extends AbstractModelConstructor = ModelConstructor>(modelName: string): Promise<TModel>;
201
- //#endregion
202
173
  export { AbstractModelConstructor, AppException, ArkstackErrorPayload, ArkstackErrorShape, DotPathValue, Encryption, ErrorHandler, Exception, GlobalConfig, GlobalEnv, Hash, Hook, IHook, Logger, LoggerChalk, LoggerLog, LoggerParseSignature, ModelConstructor, ModelRegistry, RequestException, appUrl, bindGracefulShutdown, bootWithDetectedPort, config, createErrorPayload, env, getErrorLogger, getModel, getPrimaryError, getValidationErrors, importFile, isModelNotFoundError, isValidationError, loadPrototypes, logUnhandledError, nodeEnv, normalizeStatusCode, outputDir, perPage, renderError, serializeError, shouldHideStack, shouldLogError, toErrorShape };
203
174
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,16 +1,9 @@
1
- import { Obj, str } from "@h3ravel/support";
2
- import { createJiti } from "jiti";
3
- import { createRequire } from "module";
4
- import path, { resolve } from "node:path";
5
- import { pathToFileURL } from "node:url";
6
- import { readdirSync } from "fs";
1
+ import { a as appUrl, c as importFile, i as Encryption, l as nodeEnv, n as perPage, o as config, r as Hash, s as env, t as getModel, u as outputDir } from "./helpers-DfQxjBEv.js";
2
+ import { str } from "@h3ravel/support";
3
+ import path from "node:path";
7
4
  import { detect } from "detect-port";
8
5
  import pino from "pino";
9
6
  import chalk from "chalk";
10
- import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
11
- import { Secret, TOTP } from "otpauth";
12
- import { compare, genSalt, hash } from "bcryptjs";
13
- import { getUserConfig } from "arkormx";
14
7
  //#region src/lifecycle.ts
15
8
  const bindGracefulShutdown = (shutdown) => {
16
9
  [
@@ -24,107 +17,6 @@ const bindGracefulShutdown = (shutdown) => {
24
17
  });
25
18
  };
26
19
  //#endregion
27
- //#region src/system.ts
28
- /**
29
- * Read the .env file
30
- *
31
- * @param env
32
- * @param def
33
- * @returns
34
- */
35
- const env = (env, defaultValue) => {
36
- let val = process.env[env] ?? "";
37
- if ([
38
- true,
39
- "true",
40
- "on",
41
- false,
42
- "false",
43
- "off"
44
- ].includes(val)) val = [
45
- true,
46
- "true",
47
- "on"
48
- ].includes(val);
49
- if (!isNaN(Number(val)) && typeof val !== "boolean" && typeof val !== "undefined" && val !== "") val = Number(val);
50
- if (val === "") val = void 0;
51
- if (val === "null") val = null;
52
- val ??= defaultValue;
53
- return val;
54
- };
55
- /**
56
- * Build the app url
57
- *
58
- * @param link
59
- * @returns
60
- */
61
- const appUrl = (link) => {
62
- const port = env("PORT") || "3000";
63
- const defaultUrl = `http://localhost:${port}`;
64
- const appUrl = env("APP_URL") ?? defaultUrl;
65
- try {
66
- const url = new URL(appUrl);
67
- if (url.port || url.hostname === "localhost") url.port = port;
68
- const baseUrl = url.toString().replace(/\/$/, "");
69
- if (link) return `${baseUrl}${`/${link.replace(/^\/+/, "")}`}`;
70
- return baseUrl;
71
- } catch {
72
- return link ? `${defaultUrl}/${link.replace(/^\/+/, "")}` : defaultUrl;
73
- }
74
- };
75
- /**
76
- * Gets the application configuration.
77
- *
78
- * @param key The configuration key to retrieve.
79
- * @param defaultValue The default value to return if the key is not found.
80
- * @returns The configuration value.
81
- */
82
- const config = (key, defaultValue) => {
83
- const dist = path.relative(process.cwd(), outputDir());
84
- const require = createRequire(import.meta.url);
85
- const config = readdirSync(path.join(process.cwd(), `${dist}/config`), { withFileTypes: true }).filter((file) => {
86
- if (file.name.includes("middleware") && globalThis.arkctx.runtime === "CLI") return false;
87
- return file.isFile() && (file.name.endsWith(".js") || file.name.endsWith(".ts"));
88
- }).reduce((configs, file) => {
89
- const configName = path.basename(file.name, path.extname(file.name));
90
- configs[configName] = require(path.join(file.parentPath, file.name)).default(globalThis.app());
91
- return configs;
92
- }, {});
93
- if (key) return Obj.get(config, key, defaultValue);
94
- return config;
95
- };
96
- /**
97
- * Gets the current Node environment (development or production).
98
- *
99
- * @returns
100
- */
101
- const nodeEnv = () => {
102
- let envValue = env("NODE_ENV", "development");
103
- if (envValue !== "development" && envValue !== "production") envValue = "development";
104
- return envValue === "production" ? "prod" : "dev";
105
- };
106
- /**
107
- * Gets the output directory for the application based on the current environment.
108
- *
109
- * @param cwd The current working directory (optional, defaults to process.cwd()).
110
- * @returns
111
- */
112
- const outputDir = (cwd = process.cwd()) => {
113
- const NODE_ENV = nodeEnv();
114
- const output = {
115
- dev: env("OUTPUT_DIR_DEV", ".arkstack/build"),
116
- prod: env("OUTPUT_DIR", "dist")
117
- };
118
- return path.isAbsolute(output[NODE_ENV] ?? output.dev) ? output[NODE_ENV] ?? output.dev : path.join(cwd, output[NODE_ENV] ?? output.dev);
119
- };
120
- const importFile = async (filePath) => {
121
- const resolvedPath = resolve(filePath);
122
- return await createJiti(pathToFileURL(resolvedPath).href, {
123
- interopDefault: false,
124
- tsconfigPaths: true
125
- }).import(resolvedPath);
126
- };
127
- //#endregion
128
20
  //#region src/network.ts
129
21
  const bootWithDetectedPort = async (boot, preferredPort = 3e3, app) => {
130
22
  if (app && !globalThis.app) globalThis.app = () => app;
@@ -612,107 +504,6 @@ var Hook = class {
612
504
  };
613
505
  };
614
506
  //#endregion
615
- //#region src/utils/encryption.ts
616
- var Encryption = class {
617
- static algorithm = "aes-256-gcm";
618
- static getKey() {
619
- const secret = env("TWO_FACTOR_ENCRYPTION_KEY");
620
- if (!secret) throw new Error("TWO_FACTOR_ENCRYPTION_KEY is required to use two-factor authentication");
621
- return createHash("sha256").update(secret).digest();
622
- }
623
- static encrypt(value) {
624
- const iv = randomBytes(12);
625
- const cipher = createCipheriv(this.algorithm, this.getKey(), iv);
626
- const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
627
- return [
628
- iv,
629
- cipher.getAuthTag(),
630
- ciphertext
631
- ].map((part) => part.toString("base64url")).join(":");
632
- }
633
- static decrypt(payload) {
634
- const [iv, authTag, ciphertext] = payload.split(":");
635
- if (!iv || !authTag || !ciphertext) throw new Error("Invalid encrypted payload format");
636
- const decipher = createDecipheriv(this.algorithm, this.getKey(), Buffer.from(iv, "base64url"));
637
- decipher.setAuthTag(Buffer.from(authTag, "base64url"));
638
- return Buffer.concat([decipher.update(Buffer.from(ciphertext, "base64url")), decipher.final()]).toString("utf8");
639
- }
640
- };
641
- //#endregion
642
- //#region src/utils/hash.ts
643
- var Hash = class {
644
- /**
645
- * Hash a value using bcrypt
646
- *
647
- * @param value
648
- * @returns
649
- */
650
- static async make(value) {
651
- return await hash(value, await genSalt(10));
652
- }
653
- /**
654
- * Verify a value against a hashed value
655
- *
656
- * @param value
657
- * @param hashedValue
658
- * @returns
659
- */
660
- static async verify(value, hashedValue) {
661
- return await compare(value, hashedValue);
662
- }
663
- /**
664
- * Generate a one-time password (OTP) using TOTP algorithm
665
- *
666
- * @param digits The number of digits for the OTP, default is 6.
667
- * @param label A label to identify the OTP, can be an email or phone number.
668
- * @param period Interval of time for which a token is valid, in seconds.
669
- * @returns
670
- */
671
- static otp(digits = 6, label = "Alice", period = 30) {
672
- return new TOTP({
673
- label,
674
- digits,
675
- issuer: env("APP_NAME", "Roseed"),
676
- algorithm: "SHA1",
677
- period,
678
- secret: "US3WHSG7X5KAPV27VANWKQHF3SH3HULL"
679
- });
680
- }
681
- static totp(secret, label, issuer = env("APP_NAME", "Roseed"), period = 30) {
682
- return new TOTP({
683
- issuer,
684
- label,
685
- algorithm: "SHA1",
686
- digits: 6,
687
- period,
688
- secret: Secret.fromBase32(secret)
689
- });
690
- }
691
- };
692
- //#endregion
693
- //#region src/utils/helpers.ts
694
- /**
695
- * Determine the number of items to return per page based on the provided query parameters.
696
- *
697
- * @param query
698
- * @returns
699
- */
700
- const perPage = (query) => {
701
- const requestedPerPage = Number(query.limit ?? query.perPage ?? 15);
702
- return Number.isFinite(requestedPerPage) && requestedPerPage > 0 ? Math.min(requestedPerPage, 50) : 15;
703
- };
704
- async function getModel(modelName) {
705
- const resolveModelExport = (module, modelName) => {
706
- if (!isModelModule(module)) return module;
707
- return module.default ?? module[modelName] ?? module;
708
- };
709
- const isModelModule = (value) => typeof value === "object" && value !== null;
710
- const modelPath = getUserConfig().paths?.models || "./src/models";
711
- const model = resolveModelExport(await importFile(path.join(path.isAbsolute(modelPath) ? modelPath : path.join(process.cwd(), modelPath), modelName)), path.basename(modelName, path.extname(modelName)));
712
- if (typeof model !== "function") throw new Error(`Model "${modelName}" not found`);
713
- return model;
714
- }
715
- //#endregion
716
507
  export { AppException, Encryption, ErrorHandler, Exception, Hash, Hook, Logger, RequestException, appUrl, bindGracefulShutdown, bootWithDetectedPort, config, createErrorPayload, env, getErrorLogger, getModel, getPrimaryError, getValidationErrors, importFile, isModelNotFoundError, isValidationError, loadPrototypes, logUnhandledError, nodeEnv, normalizeStatusCode, outputDir, perPage, renderError, serializeError, shouldHideStack, shouldLogError, toErrorShape };
717
508
 
718
509
  //# sourceMappingURL=index.js.map