@arkstack/common 0.14.18 → 0.14.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/faker.d.ts +9 -0
- package/dist/faker.js +20 -0
- package/dist/index.d.ts +186 -5
- package/dist/index.js +4 -3
- package/dist/system-DUaI4u99.js +475 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils-Df3nH1sG.js +437 -0
- package/package.json +10 -4
- package/dist/utils-DJQAOLbx.js +0 -803
- /package/dist/{helpers-CfQxt_q2.d.ts → helpers-BrQ0B-EX.d.ts} +0 -0
package/dist/utils-DJQAOLbx.js
DELETED
|
@@ -1,803 +0,0 @@
|
|
|
1
|
-
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
|
|
2
|
-
import { Arr, Obj, undot } from "@h3ravel/support";
|
|
3
|
-
import { createJiti } from "jiti";
|
|
4
|
-
import { existsSync, readdirSync } from "fs";
|
|
5
|
-
import { Arkstack } from "@arkstack/contract";
|
|
6
|
-
import { createRequire } from "module";
|
|
7
|
-
import path, { resolve } from "node:path";
|
|
8
|
-
import { pathToFileURL } from "node:url";
|
|
9
|
-
import { rm } from "node:fs/promises";
|
|
10
|
-
import { spawn } from "node:child_process";
|
|
11
|
-
import { Secret, TOTP } from "otpauth";
|
|
12
|
-
import { compare, genSalt, hash } from "bcryptjs";
|
|
13
|
-
import { getUserConfig } from "arkormx";
|
|
14
|
-
//#region src/system.ts
|
|
15
|
-
/**
|
|
16
|
-
* Read the .env file
|
|
17
|
-
*
|
|
18
|
-
* @param env
|
|
19
|
-
* @param def
|
|
20
|
-
* @returns
|
|
21
|
-
*/
|
|
22
|
-
const env = (env, defaultValue) => {
|
|
23
|
-
let val = process.env[env] ?? "";
|
|
24
|
-
if ([
|
|
25
|
-
true,
|
|
26
|
-
"true",
|
|
27
|
-
"on",
|
|
28
|
-
false,
|
|
29
|
-
"false",
|
|
30
|
-
"off"
|
|
31
|
-
].includes(val)) val = [
|
|
32
|
-
true,
|
|
33
|
-
"true",
|
|
34
|
-
"on"
|
|
35
|
-
].includes(val);
|
|
36
|
-
if (!isNaN(Number(val)) && typeof val !== "boolean" && typeof val !== "undefined" && val !== "") val = Number(val);
|
|
37
|
-
if (val === "") val = void 0;
|
|
38
|
-
if (val === "null") val = null;
|
|
39
|
-
val ??= defaultValue;
|
|
40
|
-
return val;
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Build the app url
|
|
44
|
-
*
|
|
45
|
-
* @param link
|
|
46
|
-
* @returns
|
|
47
|
-
*/
|
|
48
|
-
const appUrl = (link) => {
|
|
49
|
-
const port = env("PORT", env("APP_PORT", "3000"));
|
|
50
|
-
const defaultUrl = `http://localhost:${port}`;
|
|
51
|
-
const appUrl = env("APP_URL", `http://localhost:${port}`);
|
|
52
|
-
try {
|
|
53
|
-
const url = new URL(appUrl);
|
|
54
|
-
if (url.port || url.hostname === "localhost") url.port = port;
|
|
55
|
-
const baseUrl = url.toString().replace(/\/$/, "");
|
|
56
|
-
if (link) return `${baseUrl}${`/${link.replace(/^\/+/, "")}`}`;
|
|
57
|
-
return baseUrl;
|
|
58
|
-
} catch {
|
|
59
|
-
return link ? `${defaultUrl}/${link.replace(/^\/+/, "")}` : defaultUrl;
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
const CONFIG_KEY = Symbol("globalConfig");
|
|
63
|
-
globalThis[CONFIG_KEY] = {};
|
|
64
|
-
/**
|
|
65
|
-
* Gets the application configuration.
|
|
66
|
-
*
|
|
67
|
-
* @param key The configuration key to retrieve.
|
|
68
|
-
* @param defaultValue The default value to return if the key is not found.
|
|
69
|
-
* @returns The configuration value.
|
|
70
|
-
*/
|
|
71
|
-
const config = (key, defaultValue) => {
|
|
72
|
-
if (typeof globalThis.env === "undefined") globalThis.env = (key, def) => key ? process.env[key] ?? def : process.env;
|
|
73
|
-
const config = globalThis[CONFIG_KEY];
|
|
74
|
-
if (Object.entries(config).length < 1) {
|
|
75
|
-
let files;
|
|
76
|
-
const dist = path.relative(Arkstack.rootDir(), outputDir());
|
|
77
|
-
const require = createRequire(import.meta.url);
|
|
78
|
-
const configDir = env("CONFIG_PATH", path.join(Arkstack.rootDir(), `${dist}/config`));
|
|
79
|
-
try {
|
|
80
|
-
files = readdirSync(configDir, { withFileTypes: true }).filter((file) => {
|
|
81
|
-
if (file.name.includes("middleware") && globalThis.arkctx?.runtime === "CLI") return false;
|
|
82
|
-
return file.isFile() && (file.name.endsWith(".js") || file.name.endsWith(".ts"));
|
|
83
|
-
});
|
|
84
|
-
} catch {
|
|
85
|
-
files = [];
|
|
86
|
-
}
|
|
87
|
-
Object.assign(config, files.reduce((configs, file) => {
|
|
88
|
-
const configName = path.basename(file.name, path.extname(file.name));
|
|
89
|
-
configs[configName] = require(path.join(file.parentPath, file.name)).default(typeof globalThis.app === "function" ? globalThis.app() : {});
|
|
90
|
-
return configs;
|
|
91
|
-
}, {}));
|
|
92
|
-
globalThis[CONFIG_KEY] = config;
|
|
93
|
-
}
|
|
94
|
-
if (typeof key === "object" && key !== null) {
|
|
95
|
-
const config = Object.assign({}, Arr.dot(globalThis[CONFIG_KEY]), Arr.dot(key));
|
|
96
|
-
globalThis[CONFIG_KEY] = undot(config);
|
|
97
|
-
} else if (typeof key === "string") return Obj.get(globalThis[CONFIG_KEY], key, defaultValue);
|
|
98
|
-
return globalThis[CONFIG_KEY];
|
|
99
|
-
};
|
|
100
|
-
/**
|
|
101
|
-
* Resolve the unified application key.
|
|
102
|
-
*
|
|
103
|
-
* `APP_KEY` (exposed as `config('app.key')`) is the single secret used for
|
|
104
|
-
* signing and encryption across the framework. Resolution order:
|
|
105
|
-
*
|
|
106
|
-
* 1. An explicit `APP_KEY` environment variable.
|
|
107
|
-
* 2. Any legacy environment variable(s) passed in, for backward compatibility
|
|
108
|
-
* with apps that predate `APP_KEY` (e.g. `JWT_SECRET`,
|
|
109
|
-
* `TWO_FACTOR_ENCRYPTION_KEY`).
|
|
110
|
-
* 3. `config('app.key')` — the value from `src/config/app.ts`, which may itself
|
|
111
|
-
* be a placeholder default when no config is loaded.
|
|
112
|
-
*
|
|
113
|
-
* @param legacy Legacy env var name(s) to fall back to.
|
|
114
|
-
* @returns The resolved key, or `undefined` when none is configured.
|
|
115
|
-
*/
|
|
116
|
-
const appKey = (legacy = []) => {
|
|
117
|
-
const explicit = env("APP_KEY");
|
|
118
|
-
if (explicit) return explicit;
|
|
119
|
-
for (const name of Array.isArray(legacy) ? legacy : [legacy]) {
|
|
120
|
-
const value = env(name);
|
|
121
|
-
if (value) return value;
|
|
122
|
-
}
|
|
123
|
-
return config("app.key") || void 0;
|
|
124
|
-
};
|
|
125
|
-
/**
|
|
126
|
-
* Gets the current Node environment (development or production).
|
|
127
|
-
*
|
|
128
|
-
* @returns
|
|
129
|
-
*/
|
|
130
|
-
const nodeEnv = () => {
|
|
131
|
-
let envValue = env("NODE_ENV", "development");
|
|
132
|
-
if (envValue !== "development" && envValue !== "production") envValue = "development";
|
|
133
|
-
return envValue === "production" ? "prod" : "dev";
|
|
134
|
-
};
|
|
135
|
-
/**
|
|
136
|
-
* Gets the output directory for the application based on the current environment.
|
|
137
|
-
*
|
|
138
|
-
* @param cwd The current working directory (optional, defaults to Arkstack.rootDir()).
|
|
139
|
-
* @returns
|
|
140
|
-
*/
|
|
141
|
-
const outputDir = (cwd) => {
|
|
142
|
-
cwd ??= Arkstack.rootDir();
|
|
143
|
-
const NODE_ENV = nodeEnv();
|
|
144
|
-
const output = {
|
|
145
|
-
dev: env("OUTPUT_DIR_DEV", ".arkstack/build"),
|
|
146
|
-
prod: env("OUTPUT_DIR", "dist")
|
|
147
|
-
};
|
|
148
|
-
return path.isAbsolute(output[NODE_ENV] ?? output.dev) ? output[NODE_ENV] ?? output.dev : path.join(cwd, output[NODE_ENV] ?? output.dev);
|
|
149
|
-
};
|
|
150
|
-
const SOURCE_DIR = "src";
|
|
151
|
-
const SOURCE_EXTENSIONS = [
|
|
152
|
-
".ts",
|
|
153
|
-
".tsx",
|
|
154
|
-
".mts",
|
|
155
|
-
".cts",
|
|
156
|
-
".js",
|
|
157
|
-
".mjs",
|
|
158
|
-
".cjs"
|
|
159
|
-
];
|
|
160
|
-
const OUTPUT_EXTENSIONS = [
|
|
161
|
-
".js",
|
|
162
|
-
".mjs",
|
|
163
|
-
".cjs",
|
|
164
|
-
".ts"
|
|
165
|
-
];
|
|
166
|
-
/**
|
|
167
|
-
* Strip a trailing known source/compiled extension from a path.
|
|
168
|
-
*
|
|
169
|
-
* @param value
|
|
170
|
-
* @returns
|
|
171
|
-
*/
|
|
172
|
-
const stripKnownExtension = (value) => value.replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "");
|
|
173
|
-
/**
|
|
174
|
-
* Build the list of concrete file candidates for a base path. Any existing
|
|
175
|
-
* source/compiled extension is dropped first so the correct runtime extension
|
|
176
|
-
* can be tried (e.g. a `.ts` source maps to a `.js` candidate under `dist`).
|
|
177
|
-
*
|
|
178
|
-
* @param base
|
|
179
|
-
* @param extensions
|
|
180
|
-
* @returns
|
|
181
|
-
*/
|
|
182
|
-
const moduleCandidates = (base, extensions) => {
|
|
183
|
-
const bare = stripKnownExtension(base);
|
|
184
|
-
return extensions.map((ext) => bare + ext);
|
|
185
|
-
};
|
|
186
|
-
/**
|
|
187
|
-
* Map an application source path to its build-output counterpart.
|
|
188
|
-
*
|
|
189
|
-
* Application code is authored under `src/` and compiled into {@link outputDir},
|
|
190
|
-
* which strips the leading `src/` segment and emits JavaScript (e.g.
|
|
191
|
-
* `src/app/models/User.ts` -> `dist/app/models/User.js`). A TypeScript source
|
|
192
|
-
* extension is rewritten to `.js`; paths without one (directories) keep their
|
|
193
|
-
* shape. Absolute or root-relative paths outside the app root are returned
|
|
194
|
-
* unchanged.
|
|
195
|
-
*
|
|
196
|
-
* This is a pure path transform — it does not touch the filesystem. Use
|
|
197
|
-
* {@link resolveRuntimeModule} / {@link resolveRuntimeDir} when you need an
|
|
198
|
-
* existing file/dir for the current environment.
|
|
199
|
-
*
|
|
200
|
-
* @param sourcePath Absolute or root-relative source path.
|
|
201
|
-
*/
|
|
202
|
-
const toOutputPath = (sourcePath) => {
|
|
203
|
-
const root = Arkstack.rootDir();
|
|
204
|
-
const abs = path.isAbsolute(sourcePath) ? sourcePath : path.join(root, sourcePath);
|
|
205
|
-
const rel = path.relative(root, abs);
|
|
206
|
-
if (!rel || rel.startsWith("..")) return abs;
|
|
207
|
-
return path.join(outputDir(), rel.replace(new RegExp(`^${SOURCE_DIR}[\\\\/]`), "")).replace(/\.(ts|tsx|mts|cts)$/i, ".js");
|
|
208
|
-
};
|
|
209
|
-
/**
|
|
210
|
-
* Resolve an application module's source path to a file that can be imported at
|
|
211
|
-
* runtime.
|
|
212
|
-
*
|
|
213
|
-
* In development the TypeScript source is loaded directly (jiti compiles on the
|
|
214
|
-
* fly); in production only the build output ships, so the path is remapped into
|
|
215
|
-
* {@link outputDir} with a compiled extension. The first existing candidate
|
|
216
|
-
* wins — production prefers the build output, development prefers source — so a
|
|
217
|
-
* deploy that ships only `dist` never reaches for `src`.
|
|
218
|
-
*
|
|
219
|
-
* @param sourcePath Absolute or root-relative source path, with or without extension.
|
|
220
|
-
* @returns An existing importable path, or `sourcePath` unchanged when none exists.
|
|
221
|
-
*/
|
|
222
|
-
const resolveRuntimeModule = (sourcePath) => {
|
|
223
|
-
const root = Arkstack.rootDir();
|
|
224
|
-
const abs = path.isAbsolute(sourcePath) ? sourcePath : path.join(root, sourcePath);
|
|
225
|
-
const sourceCandidates = moduleCandidates(abs, SOURCE_EXTENSIONS);
|
|
226
|
-
const outputCandidates = moduleCandidates(toOutputPath(abs), OUTPUT_EXTENSIONS);
|
|
227
|
-
return (nodeEnv() === "prod" ? [...outputCandidates, ...sourceCandidates] : [...sourceCandidates, ...outputCandidates]).find((candidate) => existsSync(candidate)) ?? abs;
|
|
228
|
-
};
|
|
229
|
-
/**
|
|
230
|
-
* Resolve an application source directory to the directory that exists at
|
|
231
|
-
* runtime.
|
|
232
|
-
*
|
|
233
|
-
* The directory counterpart of {@link resolveRuntimeModule}: it maps the source
|
|
234
|
-
* directory into {@link outputDir} (stripping the leading `src/` segment) but
|
|
235
|
-
* appends no file extension. Production prefers the build output, development
|
|
236
|
-
* prefers source, and the absolute source path is returned when neither exists.
|
|
237
|
-
*
|
|
238
|
-
* @param sourcePath Absolute or root-relative source directory.
|
|
239
|
-
* @returns An existing directory path, or the absolute source path when none exists.
|
|
240
|
-
*/
|
|
241
|
-
const resolveRuntimeDir = (sourcePath) => {
|
|
242
|
-
const root = Arkstack.rootDir();
|
|
243
|
-
const abs = path.isAbsolute(sourcePath) ? sourcePath : path.join(root, sourcePath);
|
|
244
|
-
const mapped = toOutputPath(abs);
|
|
245
|
-
return (nodeEnv() === "prod" ? [mapped, abs] : [abs, mapped]).find((candidate) => existsSync(candidate)) ?? abs;
|
|
246
|
-
};
|
|
247
|
-
/**
|
|
248
|
-
* Rebuild the application output (tsdown) into {@link outputDir}, wiping it first
|
|
249
|
-
* so no stale emitted modules survive a source change. Standalone — it does NOT
|
|
250
|
-
* boot the app — so the console kernel can call it to self-heal a stale or
|
|
251
|
-
* incomplete build artifact that would otherwise wedge startup. Build-only: it
|
|
252
|
-
* sets `CLI_BUILD` so tsdown emits without starting a watcher/dev server, and
|
|
253
|
-
* inherits the current `NODE_ENV` so it targets the same dir the kernel reads.
|
|
254
|
-
*/
|
|
255
|
-
const rebuildOutput = async () => {
|
|
256
|
-
await rm(outputDir(), {
|
|
257
|
-
recursive: true,
|
|
258
|
-
force: true
|
|
259
|
-
});
|
|
260
|
-
await new Promise((resolveBuild, reject) => {
|
|
261
|
-
const child = spawn(process.platform === "win32" ? "pnpm.cmd" : "pnpm", [
|
|
262
|
-
"exec",
|
|
263
|
-
"tsdown",
|
|
264
|
-
"--log-level",
|
|
265
|
-
"silent"
|
|
266
|
-
], {
|
|
267
|
-
cwd: Arkstack.rootDir(),
|
|
268
|
-
stdio: "inherit",
|
|
269
|
-
env: Object.assign({}, process.env, { CLI_BUILD: "true" })
|
|
270
|
-
});
|
|
271
|
-
child.on("error", reject);
|
|
272
|
-
child.on("exit", (code) => {
|
|
273
|
-
if (code === 0 || code === null) {
|
|
274
|
-
resolveBuild();
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
reject(/* @__PURE__ */ new Error(`tsdown exited with code ${code}`));
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
};
|
|
281
|
-
/**
|
|
282
|
-
*
|
|
283
|
-
* Dynamically imports a file at the given path with full TypeScript support,
|
|
284
|
-
* including `tsconfig.json` path aliases.
|
|
285
|
-
*
|
|
286
|
-
* @param filePath - The path to the file to import.
|
|
287
|
-
* @returns The imported module typed as `T`.
|
|
288
|
-
*
|
|
289
|
-
* @example
|
|
290
|
-
* const config = await importFile<AppConfig>('./config/app.ts')
|
|
291
|
-
*/
|
|
292
|
-
const importFile = async (filePath, userOptions, resolveOptions) => {
|
|
293
|
-
const resolvedPath = resolve(filePath);
|
|
294
|
-
return await createJiti(pathToFileURL(resolvedPath).href, {
|
|
295
|
-
...userOptions,
|
|
296
|
-
interopDefault: false,
|
|
297
|
-
tsconfigPaths: true
|
|
298
|
-
}).import(resolvedPath, resolveOptions);
|
|
299
|
-
};
|
|
300
|
-
/**
|
|
301
|
-
* Picks the command class out of an imported module. Prefers the export named
|
|
302
|
-
* after the file (musket's discovery convention), then a default export, then
|
|
303
|
-
* the first exported constructor it finds.
|
|
304
|
-
*
|
|
305
|
-
* @param mod The imported module namespace.
|
|
306
|
-
* @param basename The file name without extension.
|
|
307
|
-
* @returns The resolved command class, or undefined when none is found.
|
|
308
|
-
*/
|
|
309
|
-
const resolveCommandExport = (mod, basename) => {
|
|
310
|
-
const named = mod[basename];
|
|
311
|
-
if (typeof named === "function") return named;
|
|
312
|
-
if (typeof mod.default === "function") return mod.default;
|
|
313
|
-
return Object.values(mod).find((value) => typeof value === "function");
|
|
314
|
-
};
|
|
315
|
-
/**
|
|
316
|
-
* Discover console command classes from the application's command directory.
|
|
317
|
-
*
|
|
318
|
-
* Commands are loaded straight from TypeScript source through {@link importFile}
|
|
319
|
-
* (jiti), so they are picked up without a build and reflect edits on every run.
|
|
320
|
-
* The built output is only used as a fallback when the source directory is
|
|
321
|
-
* absent — e.g. a production deploy that ships `dist` without `src`.
|
|
322
|
-
*
|
|
323
|
-
* This exists because musket's own glob discovery imports paths with native
|
|
324
|
-
* `import()`, which silently skips `.ts` files — the reason commands previously
|
|
325
|
-
* only appeared after a `build --dev` and never reflected later edits.
|
|
326
|
-
*
|
|
327
|
-
* @param subPath Command directory relative to the app root (src/dist aware).
|
|
328
|
-
* @returns The discovered command classes.
|
|
329
|
-
*/
|
|
330
|
-
const discoverCommands = async (subPath = path.join("app", "console", "commands")) => {
|
|
331
|
-
const root = Arkstack.rootDir();
|
|
332
|
-
const sourceDir = path.join(root, SOURCE_DIR, subPath);
|
|
333
|
-
const outputCommandDir = path.join(outputDir(), subPath);
|
|
334
|
-
const candidateDirs = nodeEnv() === "prod" ? [outputCommandDir, sourceDir] : [sourceDir, outputCommandDir];
|
|
335
|
-
let commandsDir;
|
|
336
|
-
let files = [];
|
|
337
|
-
for (const dir of candidateDirs) try {
|
|
338
|
-
const entries = readdirSync(dir, { withFileTypes: true }).filter((file) => file.isFile() && [
|
|
339
|
-
".ts",
|
|
340
|
-
".js",
|
|
341
|
-
".mjs"
|
|
342
|
-
].includes(path.extname(file.name)));
|
|
343
|
-
if (entries.length > 0) {
|
|
344
|
-
commandsDir = dir;
|
|
345
|
-
files = entries;
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
} catch {}
|
|
349
|
-
if (!commandsDir) return [];
|
|
350
|
-
const commands = [];
|
|
351
|
-
for (const file of files) {
|
|
352
|
-
const basename = path.basename(file.name, path.extname(file.name));
|
|
353
|
-
try {
|
|
354
|
-
const command = resolveCommandExport(await importFile(path.join(commandsDir, file.name)), basename);
|
|
355
|
-
if (command) commands.push(command);
|
|
356
|
-
} catch (error) {
|
|
357
|
-
console.error(`[arkstack] Failed to load command "${file.name}":`, error);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return commands;
|
|
361
|
-
};
|
|
362
|
-
/**
|
|
363
|
-
* Resolves the default export from a module, handling both CJS and ESM interop.
|
|
364
|
-
* In CJS modules, the default export is often the module itself (a function or object),
|
|
365
|
-
* while in ESM the default is nested under the `default` property.
|
|
366
|
-
*
|
|
367
|
-
* @param imp - The imported module
|
|
368
|
-
* @returns The resolved default export
|
|
369
|
-
*/
|
|
370
|
-
const interopDefault = (imp) => {
|
|
371
|
-
return typeof imp === "function" ? imp : imp.default;
|
|
372
|
-
};
|
|
373
|
-
//#endregion
|
|
374
|
-
//#region src/utils/encryption.ts
|
|
375
|
-
var Encryption = class {
|
|
376
|
-
static algorithm = "aes-256-gcm";
|
|
377
|
-
static getKey() {
|
|
378
|
-
const secret = appKey("TWO_FACTOR_ENCRYPTION_KEY");
|
|
379
|
-
if (!secret) throw new Error("APP_KEY is required to use two-factor authentication. Run `ark key:generate`.");
|
|
380
|
-
return createHash("sha256").update(secret).digest();
|
|
381
|
-
}
|
|
382
|
-
static encrypt(value) {
|
|
383
|
-
const iv = randomBytes(12);
|
|
384
|
-
const cipher = createCipheriv(this.algorithm, this.getKey(), iv);
|
|
385
|
-
const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
386
|
-
return [
|
|
387
|
-
iv,
|
|
388
|
-
cipher.getAuthTag(),
|
|
389
|
-
ciphertext
|
|
390
|
-
].map((part) => part.toString("base64url")).join(":");
|
|
391
|
-
}
|
|
392
|
-
static decrypt(payload) {
|
|
393
|
-
const [iv, authTag, ciphertext] = payload.split(":");
|
|
394
|
-
if (!iv || !authTag || !ciphertext) throw new Error("Invalid encrypted payload format");
|
|
395
|
-
const decipher = createDecipheriv(this.algorithm, this.getKey(), Buffer.from(iv, "base64url"));
|
|
396
|
-
decipher.setAuthTag(Buffer.from(authTag, "base64url"));
|
|
397
|
-
return Buffer.concat([decipher.update(Buffer.from(ciphertext, "base64url")), decipher.final()]).toString("utf8");
|
|
398
|
-
}
|
|
399
|
-
};
|
|
400
|
-
//#endregion
|
|
401
|
-
//#region src/utils/hash.ts
|
|
402
|
-
var Hash = class {
|
|
403
|
-
/**
|
|
404
|
-
* Hash a value using bcrypt
|
|
405
|
-
*
|
|
406
|
-
* @param value
|
|
407
|
-
* @returns
|
|
408
|
-
*/
|
|
409
|
-
static async make(value) {
|
|
410
|
-
return await hash(value, await genSalt(10));
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* Verify a value against a hashed value
|
|
414
|
-
*
|
|
415
|
-
* @param value
|
|
416
|
-
* @param hashedValue
|
|
417
|
-
* @returns
|
|
418
|
-
*/
|
|
419
|
-
static async verify(value, hashedValue) {
|
|
420
|
-
return await compare(value, hashedValue);
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* Generate a one-time password (OTP) using TOTP algorithm
|
|
424
|
-
*
|
|
425
|
-
* @param digits The number of digits for the OTP, default is 6.
|
|
426
|
-
* @param label A label to identify the OTP, can be an email or phone number.
|
|
427
|
-
* @param period Interval of time for which a token is valid, in seconds.
|
|
428
|
-
* @returns
|
|
429
|
-
*/
|
|
430
|
-
static otp(digits = 6, label = "Alice", period = 30) {
|
|
431
|
-
return new TOTP({
|
|
432
|
-
label,
|
|
433
|
-
digits,
|
|
434
|
-
issuer: env("APP_NAME", "Roseed"),
|
|
435
|
-
algorithm: "SHA1",
|
|
436
|
-
period,
|
|
437
|
-
secret: "US3WHSG7X5KAPV27VANWKQHF3SH3HULL"
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
static totp(secret, label, issuer = env("APP_NAME", "Roseed"), period = 30) {
|
|
441
|
-
return new TOTP({
|
|
442
|
-
issuer,
|
|
443
|
-
label,
|
|
444
|
-
algorithm: "SHA1",
|
|
445
|
-
digits: 6,
|
|
446
|
-
period,
|
|
447
|
-
secret: Secret.fromBase32(secret)
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
//#endregion
|
|
452
|
-
//#region src/Exceptions/Exception.ts
|
|
453
|
-
var Exception = class extends Error {
|
|
454
|
-
name;
|
|
455
|
-
constructor(message, options) {
|
|
456
|
-
super(message, options);
|
|
457
|
-
this.name = "Exception";
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
//#endregion
|
|
461
|
-
//#region src/Exceptions/AppException.ts
|
|
462
|
-
var AppException = class extends Exception {
|
|
463
|
-
errors = void 0;
|
|
464
|
-
statusCode;
|
|
465
|
-
constructor(message, statusCode = 400, options) {
|
|
466
|
-
super(message, options);
|
|
467
|
-
this.statusCode = statusCode;
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
//#endregion
|
|
471
|
-
//#region src/Exceptions/RequestException.ts
|
|
472
|
-
var RequestException = class RequestException extends AppException {
|
|
473
|
-
statusCode;
|
|
474
|
-
constructor(message, statusCode = 400, options) {
|
|
475
|
-
super(message, statusCode, options);
|
|
476
|
-
this.statusCode = statusCode;
|
|
477
|
-
}
|
|
478
|
-
/**
|
|
479
|
-
* Asserts that a value is not null or undefined.
|
|
480
|
-
*
|
|
481
|
-
* @param value
|
|
482
|
-
* @param message
|
|
483
|
-
* @param code
|
|
484
|
-
* @throws {RequestException} Throws if the value is null or undefined.
|
|
485
|
-
*/
|
|
486
|
-
static assertFound(value, message, code = 404) {
|
|
487
|
-
if (!value) throw new RequestException(message, code);
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Asserts that a value is not null or undefined.
|
|
491
|
-
*
|
|
492
|
-
* @param value
|
|
493
|
-
* @param message
|
|
494
|
-
* @param code
|
|
495
|
-
* @throws {RequestException} Throws if the value is null or undefined.
|
|
496
|
-
* @deprecated Use assertFound instead
|
|
497
|
-
*/
|
|
498
|
-
static assertNotEmpty(value, message, code = 404) {
|
|
499
|
-
return this.assertFound(value, message, code);
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* Asserts that a boolean condition is true.
|
|
503
|
-
*
|
|
504
|
-
* @param boolean
|
|
505
|
-
* @param message
|
|
506
|
-
* @param code
|
|
507
|
-
* @throws {RequestException} Throws if the boolean condition is true.
|
|
508
|
-
*/
|
|
509
|
-
static abortIf(boolean, message, code) {
|
|
510
|
-
if (boolean) throw new RequestException(message, code);
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
//#endregion
|
|
514
|
-
//#region src/utils/helpers.ts
|
|
515
|
-
/**
|
|
516
|
-
* Checks and asserts if target is a class
|
|
517
|
-
*
|
|
518
|
-
* @param target
|
|
519
|
-
* @returns
|
|
520
|
-
*/
|
|
521
|
-
const isClass = (target) => {
|
|
522
|
-
return typeof target === "function" && /^class\s/.test(Function.prototype.toString.call(target));
|
|
523
|
-
};
|
|
524
|
-
/**
|
|
525
|
-
* Determine the number of items to return per page based on the provided query parameters.
|
|
526
|
-
*
|
|
527
|
-
* @param query
|
|
528
|
-
* @returns
|
|
529
|
-
*/
|
|
530
|
-
const perPage = (query) => {
|
|
531
|
-
const requestedPerPage = Number(query.limit ?? query.perPage ?? 15);
|
|
532
|
-
return Number.isFinite(requestedPerPage) && requestedPerPage > 0 ? Math.min(requestedPerPage, 50) : 15;
|
|
533
|
-
};
|
|
534
|
-
async function getModel(modelName) {
|
|
535
|
-
const resolveModelExport = (module, modelName) => {
|
|
536
|
-
if (!isModelModule(module)) return module;
|
|
537
|
-
return module.default ?? module[modelName] ?? module;
|
|
538
|
-
};
|
|
539
|
-
const isModelModule = (value) => typeof value === "object" && value !== null;
|
|
540
|
-
const modelPath = getUserConfig().paths?.models || "./src/models";
|
|
541
|
-
const model = resolveModelExport(await importFile(resolveRuntimeModule(path.join(path.isAbsolute(modelPath) ? modelPath : path.join(Arkstack.rootDir(), modelPath), modelName))), path.basename(modelName, path.extname(modelName)));
|
|
542
|
-
if (typeof model !== "function") throw new Error(`Model "${modelName}" not found`);
|
|
543
|
-
return model;
|
|
544
|
-
}
|
|
545
|
-
const initializeGlobalContext = async ({ Request, Response, Session } = {}) => {
|
|
546
|
-
try {
|
|
547
|
-
const { Request: Req, Response: Res, Session: Ses } = await import("@arkstack/http");
|
|
548
|
-
Session ??= new Ses();
|
|
549
|
-
Request ??= new Req();
|
|
550
|
-
Response ??= new Res();
|
|
551
|
-
} catch {
|
|
552
|
-
Session ??= new class {}();
|
|
553
|
-
Request ??= new class {}();
|
|
554
|
-
Response ??= new class {}();
|
|
555
|
-
}
|
|
556
|
-
globalThis.session ??= () => Session;
|
|
557
|
-
globalThis.request ??= () => Request;
|
|
558
|
-
globalThis.response ??= () => Response;
|
|
559
|
-
};
|
|
560
|
-
/**
|
|
561
|
-
* Thows to abort the current request
|
|
562
|
-
*
|
|
563
|
-
* @param message
|
|
564
|
-
* @param code
|
|
565
|
-
* @throws {RequestException}
|
|
566
|
-
*/
|
|
567
|
-
const abort = (message = "Request Aborted", code = 404) => {
|
|
568
|
-
RequestException.abortIf(true, message, code);
|
|
569
|
-
};
|
|
570
|
-
/**
|
|
571
|
-
* Asserts that a boolean condition is true.
|
|
572
|
-
*
|
|
573
|
-
* @param boolean
|
|
574
|
-
* @param message
|
|
575
|
-
* @param code
|
|
576
|
-
* @throws {RequestException} Throws if the boolean condition is true.
|
|
577
|
-
*/
|
|
578
|
-
const abortIf = (boolean, message = "Request Aborted", code = 404) => {
|
|
579
|
-
RequestException.abortIf(boolean, message, code);
|
|
580
|
-
};
|
|
581
|
-
/**
|
|
582
|
-
* Asserts that a value is not null or undefined.
|
|
583
|
-
*
|
|
584
|
-
* @param value
|
|
585
|
-
* @param message
|
|
586
|
-
* @param code
|
|
587
|
-
* @throws {RequestException} Throws if the value is null or undefined.
|
|
588
|
-
*/
|
|
589
|
-
const assertFound = (value, message, code = 404) => {
|
|
590
|
-
if (!value) throw new RequestException(message, code);
|
|
591
|
-
};
|
|
592
|
-
//#endregion
|
|
593
|
-
//#region src/utils/traits.ts
|
|
594
|
-
/**
|
|
595
|
-
* CRC32 implementation in TypeScript, adapted from https://stackoverflow.com/a/18639999
|
|
596
|
-
* Note: This implementation is not cryptographically secure and is only used for generating
|
|
597
|
-
* unique identifiers for traits based on their factory function's string representation.
|
|
598
|
-
*/
|
|
599
|
-
const crcTable = [];
|
|
600
|
-
for (let n = 0; n < 256; n++) {
|
|
601
|
-
let c = n;
|
|
602
|
-
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
603
|
-
crcTable[n] = c;
|
|
604
|
-
}
|
|
605
|
-
const crc32 = (str) => {
|
|
606
|
-
let crc = -1;
|
|
607
|
-
for (let i = 0; i < str.length; i++) crc = crc >>> 8 ^ crcTable[(crc ^ str.charCodeAt(i)) & 255];
|
|
608
|
-
return (crc ^ -1) >>> 0;
|
|
609
|
-
};
|
|
610
|
-
const isCons = (fn) => typeof fn === "function" && !!fn.prototype && !!fn.prototype.constructor;
|
|
611
|
-
const isArkormModelInstance = (value) => typeof value === "object" && value !== null && typeof value.constructor === "function" && typeof value.getAttribute === "function" && typeof value.setAttribute === "function";
|
|
612
|
-
const isTypeFactory = (fn) => typeof fn === "function" && !fn.prototype && fn.length === 0;
|
|
613
|
-
/**
|
|
614
|
-
* API: generate trait (technical implementation)
|
|
615
|
-
*
|
|
616
|
-
* @param args
|
|
617
|
-
*/
|
|
618
|
-
function trait(...args) {
|
|
619
|
-
const factory = args.length === 2 ? args[1] : args[0];
|
|
620
|
-
const superTraits = args.length === 2 ? args[0] : void 0;
|
|
621
|
-
return {
|
|
622
|
-
id: crc32(factory.toString()),
|
|
623
|
-
symbol: Symbol("trait"),
|
|
624
|
-
factory,
|
|
625
|
-
superTraits
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* utility function: add an additional invisible property to an object
|
|
630
|
-
*
|
|
631
|
-
* @param cons
|
|
632
|
-
* @param field
|
|
633
|
-
* @param value
|
|
634
|
-
* @returns
|
|
635
|
-
*/
|
|
636
|
-
const extendProperties = (cons, field, value) => Object.defineProperty(cons, field, {
|
|
637
|
-
value,
|
|
638
|
-
enumerable: false,
|
|
639
|
-
writable: false
|
|
640
|
-
});
|
|
641
|
-
const traitMethodRegistry = Symbol("trait-method-registry");
|
|
642
|
-
const cloneMethodRegistry = (target) => {
|
|
643
|
-
const registry = target[traitMethodRegistry];
|
|
644
|
-
return new Map([...registry?.entries() ?? []].map(([name, methods]) => [name, [...methods]]));
|
|
645
|
-
};
|
|
646
|
-
const registerMethodScope = (target, base, ignored) => {
|
|
647
|
-
const registry = cloneMethodRegistry(base);
|
|
648
|
-
for (const name of Reflect.ownKeys(target)) {
|
|
649
|
-
if (ignored.has(name)) continue;
|
|
650
|
-
const method = Object.getOwnPropertyDescriptor(target, name)?.value;
|
|
651
|
-
if (typeof method !== "function") continue;
|
|
652
|
-
const methods = registry.get(name);
|
|
653
|
-
const previous = base?.[name];
|
|
654
|
-
if (methods) {
|
|
655
|
-
if (methods.at(-1) !== method) methods.push(method);
|
|
656
|
-
registry.set(name, methods);
|
|
657
|
-
} else if (typeof previous === "function" && previous !== method) registry.set(name, [previous, method]);
|
|
658
|
-
}
|
|
659
|
-
Object.defineProperty(target, traitMethodRegistry, {
|
|
660
|
-
configurable: false,
|
|
661
|
-
enumerable: false,
|
|
662
|
-
value: registry,
|
|
663
|
-
writable: false
|
|
664
|
-
});
|
|
665
|
-
};
|
|
666
|
-
/**
|
|
667
|
-
* Registers conflicting trait methods
|
|
668
|
-
*
|
|
669
|
-
* @param classInstance
|
|
670
|
-
* @param baseClass
|
|
671
|
-
*/
|
|
672
|
-
const registerTraitMethods = (classInstance, baseClass) => {
|
|
673
|
-
registerMethodScope(classInstance.prototype, baseClass.prototype, new Set(["constructor"]));
|
|
674
|
-
registerMethodScope(classInstance, baseClass, new Set([
|
|
675
|
-
"length",
|
|
676
|
-
"name",
|
|
677
|
-
"prototype",
|
|
678
|
-
"arguments",
|
|
679
|
-
"caller"
|
|
680
|
-
]));
|
|
681
|
-
};
|
|
682
|
-
/**
|
|
683
|
-
* Return every trait implementation for a method, bound to the supplied
|
|
684
|
-
* instance or class. Methods are ordered from the base implementation to the
|
|
685
|
-
* currently active trait implementation.
|
|
686
|
-
*
|
|
687
|
-
* @param target
|
|
688
|
-
* @param name
|
|
689
|
-
* @returns
|
|
690
|
-
*/
|
|
691
|
-
const getTraitMethods = (target, name) => {
|
|
692
|
-
return (((typeof target === "function" ? target : Object.getPrototypeOf(target))?.[traitMethodRegistry])?.get(name) ?? []).map((method) => method.bind(target));
|
|
693
|
-
};
|
|
694
|
-
/**
|
|
695
|
-
* Invoke every trait implementation for a method in registration order.
|
|
696
|
-
*
|
|
697
|
-
* @param target
|
|
698
|
-
* @param name
|
|
699
|
-
* @param args
|
|
700
|
-
* @returns
|
|
701
|
-
*/
|
|
702
|
-
const callTraitMethods = (target, name, ...args) => {
|
|
703
|
-
const methods = getTraitMethods(target, name);
|
|
704
|
-
if (methods.length === 0) {
|
|
705
|
-
console.warn(`No conflicting trait methods found for "${String(name)}".`);
|
|
706
|
-
return [];
|
|
707
|
-
}
|
|
708
|
-
return methods.map((method) => method(...args));
|
|
709
|
-
};
|
|
710
|
-
/**
|
|
711
|
-
* utility function: get raw trait
|
|
712
|
-
*
|
|
713
|
-
* @param x
|
|
714
|
-
* @returns
|
|
715
|
-
*/
|
|
716
|
-
const rawTrait = (x) => isTypeFactory(x) ? x() : x;
|
|
717
|
-
/**
|
|
718
|
-
* utility function: derive a trait
|
|
719
|
-
*
|
|
720
|
-
* @param trait$
|
|
721
|
-
* @param baseClass
|
|
722
|
-
* @param derived
|
|
723
|
-
* @returns
|
|
724
|
-
*/
|
|
725
|
-
const deriveTrait = (trait$, baseClass, derived) => {
|
|
726
|
-
const trait = rawTrait(trait$);
|
|
727
|
-
if (trait === void 0 || trait === null || typeof trait.id !== "number") throw new Error("use(): received an undefined or invalid trait. This usually means a circular import — the trait module had not finished initializing when use() ran. Avoid importing models at the top level of trait modules, or break the import cycle.");
|
|
728
|
-
let classInstance = baseClass;
|
|
729
|
-
if (!derived.has(trait.id)) {
|
|
730
|
-
derived.set(trait.id, true);
|
|
731
|
-
if (trait.superTraits !== void 0) for (const superTrait of reverseTraitList(trait.superTraits)) classInstance = deriveTrait(superTrait, classInstance, derived);
|
|
732
|
-
const base = classInstance;
|
|
733
|
-
classInstance = trait.factory(classInstance);
|
|
734
|
-
registerTraitMethods(classInstance, base);
|
|
735
|
-
extendProperties(classInstance, "id", crc32(trait.factory.toString()));
|
|
736
|
-
extendProperties(classInstance, trait.symbol, true);
|
|
737
|
-
}
|
|
738
|
-
return classInstance;
|
|
739
|
-
};
|
|
740
|
-
/**
|
|
741
|
-
* utility function: get reversed trait list
|
|
742
|
-
*
|
|
743
|
-
* @param traits
|
|
744
|
-
* @returns
|
|
745
|
-
*/
|
|
746
|
-
const reverseTraitList = (traits) => traits.slice().reverse();
|
|
747
|
-
function use(...args) {
|
|
748
|
-
const withMethodHelpers = args[0] === true;
|
|
749
|
-
const traits = withMethodHelpers ? args.slice(1) : args;
|
|
750
|
-
if (traits.length === 0) throw new Error("invalid number of parameters (expected one or more traits)");
|
|
751
|
-
let classInstance;
|
|
752
|
-
let lot;
|
|
753
|
-
const last = traits[traits.length - 1];
|
|
754
|
-
if (isCons(last) && !isTypeFactory(last)) {
|
|
755
|
-
classInstance = last;
|
|
756
|
-
lot = traits.slice(0, -1);
|
|
757
|
-
} else if (isArkormModelInstance(last)) {
|
|
758
|
-
classInstance = last.constructor;
|
|
759
|
-
lot = traits.slice(0, -1);
|
|
760
|
-
} else {
|
|
761
|
-
classInstance = class ROOT {};
|
|
762
|
-
lot = traits;
|
|
763
|
-
}
|
|
764
|
-
const derived = /* @__PURE__ */ new Map();
|
|
765
|
-
for (const trait of reverseTraitList(lot)) classInstance = deriveTrait(trait, classInstance, derived);
|
|
766
|
-
if (withMethodHelpers) classInstance = class TraitMethodEnabled extends classInstance {
|
|
767
|
-
getTraitMethods(name) {
|
|
768
|
-
return getTraitMethods(this, name);
|
|
769
|
-
}
|
|
770
|
-
callTraitMethods(name, ...args) {
|
|
771
|
-
return callTraitMethods(this, name, ...args);
|
|
772
|
-
}
|
|
773
|
-
static getTraitMethods(name) {
|
|
774
|
-
return getTraitMethods(this, name);
|
|
775
|
-
}
|
|
776
|
-
static callTraitMethods(name, ...args) {
|
|
777
|
-
return callTraitMethods(this, name, ...args);
|
|
778
|
-
}
|
|
779
|
-
};
|
|
780
|
-
return classInstance;
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* API: type guard for checking whether class instance is derived from a trait
|
|
784
|
-
*
|
|
785
|
-
* @param instance
|
|
786
|
-
* @param trait
|
|
787
|
-
* @returns
|
|
788
|
-
*/
|
|
789
|
-
function uses(instance, trait) {
|
|
790
|
-
if (typeof instance !== "object" || instance === null) return false;
|
|
791
|
-
let obj = instance;
|
|
792
|
-
if (isCons(trait) && !isTypeFactory(trait)) return instance instanceof trait;
|
|
793
|
-
const idTrait = (isTypeFactory(trait) ? trait() : trait)["id"];
|
|
794
|
-
while (obj) {
|
|
795
|
-
if (Object.hasOwn(obj, "constructor")) {
|
|
796
|
-
if ((obj.constructor["id"] ?? 0) === idTrait) return true;
|
|
797
|
-
}
|
|
798
|
-
obj = Object.getPrototypeOf(obj);
|
|
799
|
-
}
|
|
800
|
-
return false;
|
|
801
|
-
}
|
|
802
|
-
//#endregion
|
|
803
|
-
export { resolveRuntimeDir as A, discoverCommands as C, nodeEnv as D, interopDefault as E, toOutputPath as M, outputDir as O, config as S, importFile as T, Hash as _, use as a, appKey as b, abortIf as c, initializeGlobalContext as d, isClass as f, Exception as g, AppException as h, trait as i, resolveRuntimeModule as j, rebuildOutput as k, assertFound as l, RequestException as m, crc32 as n, uses as o, perPage as p, getTraitMethods as r, abort as s, callTraitMethods as t, getModel as u, Encryption as v, env as w, appUrl as x, CONFIG_KEY as y };
|