@arkstack/common 0.19.2 → 0.19.3
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/README.md +120 -124
- package/dist/app.d.ts +29 -40
- package/dist/{dist-DwGScrDq.js → dist-kNVDZ1C_.js} +3 -3
- package/dist/faker.js +1 -1
- package/dist/{helpers-O3ZUac1k.d.ts → helpers-DpnBcrO9.d.ts} +9 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +60 -60
- package/dist/{system-BH1phDBR.js → system-DzAzwH__.js} +18 -18
- package/dist/utils/index.d.ts +16 -16
- package/dist/utils/index.js +1 -1
- package/dist/{utils-CvlK2sYM.js → utils-DwfM0lt4.js} +86 -86
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -46,11 +46,11 @@ Provides core application-level utilities for environment variable access, confi
|
|
|
46
46
|
Reads a value from `process.env` with automatic type coercion. Booleans (`true`, `false`, `on`, `off`), numbers, `null`, and empty strings are all handled gracefully.
|
|
47
47
|
|
|
48
48
|
```ts
|
|
49
|
-
import { env } from '@arkstack/common'
|
|
49
|
+
import { env } from '@arkstack/common'
|
|
50
50
|
|
|
51
|
-
const port = env('PORT', 3000)
|
|
52
|
-
const debug = env('DEBUG', false)
|
|
53
|
-
const name = env('APP_NAME', 'App')
|
|
51
|
+
const port = env('PORT', 3000) // number
|
|
52
|
+
const debug = env('DEBUG', false) // boolean
|
|
53
|
+
const name = env('APP_NAME', 'App') // string
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
**Type coercion rules:**
|
|
@@ -70,13 +70,13 @@ const name = env('APP_NAME', 'App'); // string
|
|
|
70
70
|
Loads and merges all configuration files from the build output's `config/` directory. Supports dot-path key access with full TypeScript inference.
|
|
71
71
|
|
|
72
72
|
```ts
|
|
73
|
-
import { config } from '@arkstack/common'
|
|
73
|
+
import { config } from '@arkstack/common'
|
|
74
74
|
|
|
75
75
|
// Get the full config object
|
|
76
|
-
const allConfig = config()
|
|
76
|
+
const allConfig = config()
|
|
77
77
|
|
|
78
78
|
// Access a nested key
|
|
79
|
-
const dbHost = config('database.host', 'localhost')
|
|
79
|
+
const dbHost = config('database.host', 'localhost')
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
Config files are loaded from the resolved `outputDir()` using `createRequire`. Middleware config files are skipped when running in CLI context.
|
|
@@ -88,10 +88,10 @@ Config files are loaded from the resolved `outputDir()` using `createRequire`. M
|
|
|
88
88
|
Builds a fully-qualified application URL from `APP_URL` and `PORT` environment variables.
|
|
89
89
|
|
|
90
90
|
```ts
|
|
91
|
-
import { appUrl } from '@arkstack/common'
|
|
91
|
+
import { appUrl } from '@arkstack/common'
|
|
92
92
|
|
|
93
|
-
appUrl()
|
|
94
|
-
appUrl('/api/health')
|
|
93
|
+
appUrl() // "http://localhost:3000"
|
|
94
|
+
appUrl('/api/health') // "http://localhost:3000/api/health"
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
---
|
|
@@ -101,9 +101,9 @@ appUrl('/api/health'); // "http://localhost:3000/api/health"
|
|
|
101
101
|
Returns `'dev'` or `'prod'` based on the `NODE_ENV` environment variable. Defaults to `'dev'` for any unrecognised value.
|
|
102
102
|
|
|
103
103
|
```ts
|
|
104
|
-
import { nodeEnv } from '@arkstack/common'
|
|
104
|
+
import { nodeEnv } from '@arkstack/common'
|
|
105
105
|
|
|
106
|
-
nodeEnv()
|
|
106
|
+
nodeEnv() // "dev" | "prod"
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
---
|
|
@@ -124,9 +124,9 @@ Resolves the build output directory. In development, defaults to `.arkstack/buil
|
|
|
124
124
|
Dynamically imports a file using [Jiti](https://github.com/unjs/jiti), with TypeScript and `tsconfig` path support. Useful for loading user-defined config or plugin files at runtime.
|
|
125
125
|
|
|
126
126
|
```ts
|
|
127
|
-
import { importFile } from '@arkstack/common'
|
|
127
|
+
import { importFile } from '@arkstack/common'
|
|
128
128
|
|
|
129
|
-
const module = await importFile<{ default: MyConfig }>('./config/app.ts')
|
|
129
|
+
const module = await importFile<{ default: MyConfig }>('./config/app.ts')
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
---
|
|
@@ -140,13 +140,13 @@ A structured, chalk-powered console logger with verbosity control, two-column fo
|
|
|
140
140
|
#### Basic log levels
|
|
141
141
|
|
|
142
142
|
```ts
|
|
143
|
-
import { Logger } from '@arkstack/common'
|
|
143
|
+
import { Logger } from '@arkstack/common'
|
|
144
144
|
|
|
145
|
-
Logger.success('Server started')
|
|
146
|
-
Logger.info('Listening on port 3000')
|
|
147
|
-
Logger.warn('Deprecated option used')
|
|
148
|
-
Logger.error('Something went wrong', false)
|
|
149
|
-
Logger.debug('Internal state dump')
|
|
145
|
+
Logger.success('Server started')
|
|
146
|
+
Logger.info('Listening on port 3000')
|
|
147
|
+
Logger.warn('Deprecated option used')
|
|
148
|
+
Logger.error('Something went wrong', false) // false = don't exit
|
|
149
|
+
Logger.debug('Internal state dump') // only shown at verbosity >= 3
|
|
150
150
|
```
|
|
151
151
|
|
|
152
152
|
Each level uses a distinct icon and colour:
|
|
@@ -172,7 +172,7 @@ Logger.configure({
|
|
|
172
172
|
verbosity: 3, // enables debug output
|
|
173
173
|
quiet: true, // suppresses info and success
|
|
174
174
|
silent: true, // suppresses all output
|
|
175
|
-
})
|
|
175
|
+
})
|
|
176
176
|
```
|
|
177
177
|
|
|
178
178
|
---
|
|
@@ -182,10 +182,10 @@ Logger.configure({
|
|
|
182
182
|
Renders a right-aligned two-column layout padded to the terminal width.
|
|
183
183
|
|
|
184
184
|
```ts
|
|
185
|
-
Logger.twoColumnDetail('Route', 'GET /api/users')
|
|
185
|
+
Logger.twoColumnDetail('Route', 'GET /api/users')
|
|
186
186
|
// "Route ......................................... GET /api/users"
|
|
187
187
|
|
|
188
|
-
const row = Logger.twoColumnDetail('Route', 'GET /api/users', false)
|
|
188
|
+
const row = Logger.twoColumnDetail('Route', 'GET /api/users', false)
|
|
189
189
|
// returns [name, dots, value] without printing
|
|
190
190
|
```
|
|
191
191
|
|
|
@@ -196,7 +196,7 @@ const row = Logger.twoColumnDetail('Route', 'GET /api/users', false);
|
|
|
196
196
|
Similar to `twoColumnDetail`, but uses a fixed width with space padding rather than dots. Useful for command help listings.
|
|
197
197
|
|
|
198
198
|
```ts
|
|
199
|
-
Logger.describe('--port', 'Port to listen on', 40)
|
|
199
|
+
Logger.describe('--port', 'Port to listen on', 40)
|
|
200
200
|
// "--port Port to listen on"
|
|
201
201
|
```
|
|
202
202
|
|
|
@@ -207,8 +207,8 @@ Logger.describe('--port', 'Port to listen on', 40);
|
|
|
207
207
|
Like `twoColumnDetail`, but wraps the left column with a coloured background badge based on `status`.
|
|
208
208
|
|
|
209
209
|
```ts
|
|
210
|
-
Logger.split('Database', 'Connected', 'success')
|
|
211
|
-
Logger.split('Migration', 'Failed', 'error', true)
|
|
210
|
+
Logger.split('Database', 'Connected', 'success')
|
|
211
|
+
Logger.split('Migration', 'Failed', 'error', true) // exits after logging
|
|
212
212
|
```
|
|
213
213
|
|
|
214
214
|
---
|
|
@@ -224,10 +224,10 @@ Logger.parse(
|
|
|
224
224
|
['v1.0.0', 'gray'],
|
|
225
225
|
],
|
|
226
226
|
' ',
|
|
227
|
-
)
|
|
227
|
+
) // "Arkstack v1.0.0"
|
|
228
228
|
|
|
229
229
|
// Return instead of print
|
|
230
|
-
const str = Logger.parse([['Ready', 'green']], ' ', false)
|
|
230
|
+
const str = Logger.parse([['Ready', 'green']], ' ', false)
|
|
231
231
|
```
|
|
232
232
|
|
|
233
233
|
---
|
|
@@ -237,14 +237,14 @@ const str = Logger.parse([['Ready', 'green']], ' ', false);
|
|
|
237
237
|
A flexible polymorphic logger that accepts either a string + style, or a `LoggerParseSignature` array. Returns the `Logger` class when called with no arguments.
|
|
238
238
|
|
|
239
239
|
```ts
|
|
240
|
-
Logger.log('PORT:3000', 'cyan')
|
|
240
|
+
Logger.log('PORT:3000', 'cyan')
|
|
241
241
|
Logger.log(
|
|
242
242
|
[
|
|
243
243
|
['PORT', 'bold'],
|
|
244
244
|
['3000', 'cyan'],
|
|
245
245
|
],
|
|
246
246
|
' ',
|
|
247
|
-
)
|
|
247
|
+
)
|
|
248
248
|
```
|
|
249
249
|
|
|
250
250
|
---
|
|
@@ -254,8 +254,8 @@ Logger.log(
|
|
|
254
254
|
Returns a function that applies a chain of chalk styles to any input.
|
|
255
255
|
|
|
256
256
|
```ts
|
|
257
|
-
const highlight = Logger.chalker(['bold', 'green'])
|
|
258
|
-
console.log(highlight('Ready'))
|
|
257
|
+
const highlight = Logger.chalker(['bold', 'green'])
|
|
258
|
+
console.log(highlight('Ready'))
|
|
259
259
|
```
|
|
260
260
|
|
|
261
261
|
---
|
|
@@ -265,9 +265,9 @@ console.log(highlight('Ready'));
|
|
|
265
265
|
Returns a `Console`-compatible object with `log`, `debug`, `warn`, `info`, and `error` methods. Can be used as a drop-in replacement for `globalThis.console`.
|
|
266
266
|
|
|
267
267
|
```ts
|
|
268
|
-
const console = Logger.console()
|
|
269
|
-
console.log('hello')
|
|
270
|
-
console.warn('watch out')
|
|
268
|
+
const console = Logger.console()
|
|
269
|
+
console.log('hello')
|
|
270
|
+
console.warn('watch out')
|
|
271
271
|
```
|
|
272
272
|
|
|
273
273
|
---
|
|
@@ -283,12 +283,12 @@ A static utility class for normalising, serialising, classifying, and logging er
|
|
|
283
283
|
The primary method. Converts any thrown value into a consistent `ArkstackErrorPayload` object, handling validation errors, model-not-found errors, and generic errors uniformly.
|
|
284
284
|
|
|
285
285
|
```ts
|
|
286
|
-
import { ErrorHandler } from '@arkstack/common'
|
|
286
|
+
import { ErrorHandler } from '@arkstack/common'
|
|
287
287
|
|
|
288
288
|
try {
|
|
289
289
|
// ...
|
|
290
290
|
} catch (err) {
|
|
291
|
-
const payload = ErrorHandler.createErrorPayload(err, 'Request failed')
|
|
291
|
+
const payload = ErrorHandler.createErrorPayload(err, 'Request failed')
|
|
292
292
|
// { status: 'error', code: 422, message: '...', errors: {...} }
|
|
293
293
|
}
|
|
294
294
|
```
|
|
@@ -320,7 +320,7 @@ try {
|
|
|
320
320
|
Recursively serialises any value — including `Error` instances and circular references — into a plain JSON-safe object. Circular references are replaced with `'[Circular]'`.
|
|
321
321
|
|
|
322
322
|
```ts
|
|
323
|
-
const serialized = ErrorHandler.serializeError(new Error('oops'))
|
|
323
|
+
const serialized = ErrorHandler.serializeError(new Error('oops'))
|
|
324
324
|
// { name: 'Error', message: 'oops', stack: '...' }
|
|
325
325
|
```
|
|
326
326
|
|
|
@@ -331,8 +331,8 @@ const serialized = ErrorHandler.serializeError(new Error('oops'));
|
|
|
331
331
|
Ensures a status code is a valid integer in the range `100–599`. Returns the fallback (default `500`) for anything invalid.
|
|
332
332
|
|
|
333
333
|
```ts
|
|
334
|
-
ErrorHandler.normalizeStatusCode('422')
|
|
335
|
-
ErrorHandler.normalizeStatusCode('xyz')
|
|
334
|
+
ErrorHandler.normalizeStatusCode('422') // 422
|
|
335
|
+
ErrorHandler.normalizeStatusCode('xyz') // 500
|
|
336
336
|
```
|
|
337
337
|
|
|
338
338
|
---
|
|
@@ -348,11 +348,7 @@ Returns a Pino logger instance that writes to `storage/logs/error.log` (created
|
|
|
348
348
|
Persists an unhandled error to the error log file, including the serialised error and the associated request context.
|
|
349
349
|
|
|
350
350
|
```ts
|
|
351
|
-
ErrorHandler.logUnhandledError(
|
|
352
|
-
err,
|
|
353
|
-
{ method: 'GET', url: '/api' },
|
|
354
|
-
'Unhandled exception',
|
|
355
|
-
);
|
|
351
|
+
ErrorHandler.logUnhandledError(err, { method: 'GET', url: '/api' }, 'Unhandled exception')
|
|
356
352
|
```
|
|
357
353
|
|
|
358
354
|
---
|
|
@@ -360,12 +356,12 @@ ErrorHandler.logUnhandledError(
|
|
|
360
356
|
#### Classification helpers
|
|
361
357
|
|
|
362
358
|
```ts
|
|
363
|
-
ErrorHandler.isValidationError(err)
|
|
364
|
-
ErrorHandler.isModelNotFoundError(err)
|
|
365
|
-
ErrorHandler.shouldLogError(err)
|
|
366
|
-
ErrorHandler.shouldHideStack()
|
|
367
|
-
ErrorHandler.getPrimaryError(err)
|
|
368
|
-
ErrorHandler.toErrorShape(value)
|
|
359
|
+
ErrorHandler.isValidationError(err) // true if err.errors is defined
|
|
360
|
+
ErrorHandler.isModelNotFoundError(err) // true if err.getModelName is a function
|
|
361
|
+
ErrorHandler.shouldLogError(err) // false for validation/model-not-found errors
|
|
362
|
+
ErrorHandler.shouldHideStack() // true if HIDE_ERROR_STACK env is set
|
|
363
|
+
ErrorHandler.getPrimaryError(err) // unwraps err.cause if present
|
|
364
|
+
ErrorHandler.toErrorShape(value) // casts unknown to ArkstackErrorShape if object
|
|
369
365
|
```
|
|
370
366
|
|
|
371
367
|
All static methods are also exported as named standalone functions for convenience:
|
|
@@ -377,7 +373,7 @@ import {
|
|
|
377
373
|
serializeError,
|
|
378
374
|
logUnhandledError,
|
|
379
375
|
// ...
|
|
380
|
-
} from '@arkstack/common'
|
|
376
|
+
} from '@arkstack/common'
|
|
381
377
|
```
|
|
382
378
|
|
|
383
379
|
---
|
|
@@ -393,9 +389,9 @@ A three-level exception hierarchy for structured error throwing.
|
|
|
393
389
|
Base class extending `Error`. Sets `.name` to `'Exception'`.
|
|
394
390
|
|
|
395
391
|
```ts
|
|
396
|
-
import { Exception } from '@arkstack/common'
|
|
392
|
+
import { Exception } from '@arkstack/common'
|
|
397
393
|
|
|
398
|
-
throw new Exception('Something went wrong')
|
|
394
|
+
throw new Exception('Something went wrong')
|
|
399
395
|
```
|
|
400
396
|
|
|
401
397
|
---
|
|
@@ -405,10 +401,10 @@ throw new Exception('Something went wrong');
|
|
|
405
401
|
Extends `Exception`. Adds `statusCode` (default `400`) and an optional `errors` map for field-level validation errors.
|
|
406
402
|
|
|
407
403
|
```ts
|
|
408
|
-
import { AppException } from '@arkstack/common'
|
|
404
|
+
import { AppException } from '@arkstack/common'
|
|
409
405
|
|
|
410
|
-
const err = new AppException('Validation failed', 422)
|
|
411
|
-
err.errors = { email: ['Email is required'] }
|
|
406
|
+
const err = new AppException('Validation failed', 422)
|
|
407
|
+
err.errors = { email: ['Email is required'] }
|
|
412
408
|
```
|
|
413
409
|
|
|
414
410
|
---
|
|
@@ -422,10 +418,10 @@ Extends `AppException`. Intended for HTTP request-level errors. Provides two sta
|
|
|
422
418
|
Throws a `RequestException` if the value is `null` or `undefined`. Narrows the type on success.
|
|
423
419
|
|
|
424
420
|
```ts
|
|
425
|
-
import { RequestException } from '@arkstack/common'
|
|
421
|
+
import { RequestException } from '@arkstack/common'
|
|
426
422
|
|
|
427
|
-
const user = await User.find(id)
|
|
428
|
-
RequestException.assertNotEmpty(user, 'User not found', 404)
|
|
423
|
+
const user = await User.find(id)
|
|
424
|
+
RequestException.assertNotEmpty(user, 'User not found', 404)
|
|
429
425
|
// user is now User (not null | undefined)
|
|
430
426
|
```
|
|
431
427
|
|
|
@@ -434,7 +430,7 @@ RequestException.assertNotEmpty(user, 'User not found', 404);
|
|
|
434
430
|
Throws if the condition is truthy.
|
|
435
431
|
|
|
436
432
|
```ts
|
|
437
|
-
RequestException.abortIf(!user.isActive, 'Account is suspended', 403)
|
|
433
|
+
RequestException.abortIf(!user.isActive, 'Account is suspended', 403)
|
|
438
434
|
```
|
|
439
435
|
|
|
440
436
|
---
|
|
@@ -450,12 +446,12 @@ A global, named hook registry for extending Arkstack internals without modifying
|
|
|
450
446
|
Registers a hook. Multiple calls for the same name are merged.
|
|
451
447
|
|
|
452
448
|
```ts
|
|
453
|
-
import { Hook } from '@arkstack/common'
|
|
449
|
+
import { Hook } from '@arkstack/common'
|
|
454
450
|
|
|
455
451
|
Hook.set('request:handle', {
|
|
456
452
|
before: (ctx) => console.log('before handler'),
|
|
457
453
|
after: (ctx) => console.log('after handler'),
|
|
458
|
-
})
|
|
454
|
+
})
|
|
459
455
|
```
|
|
460
456
|
|
|
461
457
|
---
|
|
@@ -465,8 +461,8 @@ Hook.set('request:handle', {
|
|
|
465
461
|
Retrieves the full hook object or a specific positional handler.
|
|
466
462
|
|
|
467
463
|
```ts
|
|
468
|
-
const hook = Hook.get('request:handle')
|
|
469
|
-
const before = Hook.get('request:handle', 'before')
|
|
464
|
+
const hook = Hook.get('request:handle') // IHook | undefined
|
|
465
|
+
const before = Hook.get('request:handle', 'before') // function | undefined
|
|
470
466
|
```
|
|
471
467
|
|
|
472
468
|
---
|
|
@@ -476,8 +472,8 @@ const before = Hook.get('request:handle', 'before'); // function | undefined
|
|
|
476
472
|
Checks whether a hook (or a specific position within it) exists.
|
|
477
473
|
|
|
478
474
|
```ts
|
|
479
|
-
Hook.has('request:handle')
|
|
480
|
-
Hook.has('request:handle', 'after')
|
|
475
|
+
Hook.has('request:handle') // true | false
|
|
476
|
+
Hook.has('request:handle', 'after') // true | false
|
|
481
477
|
```
|
|
482
478
|
|
|
483
479
|
---
|
|
@@ -487,9 +483,9 @@ Hook.has('request:handle', 'after'); // true | false
|
|
|
487
483
|
Removes a hook or a single positional handler. If the hook becomes empty after removal, it is deleted entirely. Called with no arguments, it delegates to `Hook.clear()`.
|
|
488
484
|
|
|
489
485
|
```ts
|
|
490
|
-
Hook.unset('request:handle', 'before')
|
|
491
|
-
Hook.unset('request:handle')
|
|
492
|
-
Hook.unset()
|
|
486
|
+
Hook.unset('request:handle', 'before') // removes only the 'before' handler
|
|
487
|
+
Hook.unset('request:handle') // removes the entire hook
|
|
488
|
+
Hook.unset() // clears all hooks
|
|
493
489
|
```
|
|
494
490
|
|
|
495
491
|
---
|
|
@@ -499,7 +495,7 @@ Hook.unset(); // clears all hooks
|
|
|
499
495
|
Returns all registered hooks as a plain record.
|
|
500
496
|
|
|
501
497
|
```ts
|
|
502
|
-
const hooks = Hook.getAll()
|
|
498
|
+
const hooks = Hook.getAll()
|
|
503
499
|
// { 'request:handle': { before: fn, after: fn } }
|
|
504
500
|
```
|
|
505
501
|
|
|
@@ -522,9 +518,9 @@ A thin wrapper around [`@arkstack/encryption`](https://www.npmjs.com/package/@ar
|
|
|
522
518
|
Encrypts a string. Returns a colon-delimited base64url string: `<iv>:<authTag>:<ciphertext>`.
|
|
523
519
|
|
|
524
520
|
```ts
|
|
525
|
-
import { Encryption } from '@arkstack/common'
|
|
521
|
+
import { Encryption } from '@arkstack/common'
|
|
526
522
|
|
|
527
|
-
const token = Encryption.encrypt('my-secret-value')
|
|
523
|
+
const token = Encryption.encrypt('my-secret-value')
|
|
528
524
|
// "abc123:def456:ghi789"
|
|
529
525
|
```
|
|
530
526
|
|
|
@@ -535,7 +531,7 @@ const token = Encryption.encrypt('my-secret-value');
|
|
|
535
531
|
Decrypts a payload produced by `encrypt`. Throws if the format is invalid or the key is wrong.
|
|
536
532
|
|
|
537
533
|
```ts
|
|
538
|
-
const original = Encryption.decrypt(token)
|
|
534
|
+
const original = Encryption.decrypt(token)
|
|
539
535
|
// "my-secret-value"
|
|
540
536
|
```
|
|
541
537
|
|
|
@@ -556,11 +552,11 @@ A `Cipher` bound to the application key, for encrypting many values without re-d
|
|
|
556
552
|
#### Key utilities
|
|
557
553
|
|
|
558
554
|
```ts
|
|
559
|
-
Encryption.generateKey()
|
|
560
|
-
await Encryption.generateKeyPair()
|
|
561
|
-
await Encryption.deriveKey(password)
|
|
562
|
-
await Encryption.compareKeys(left, right)
|
|
563
|
-
await Encryption.fingerprint()
|
|
555
|
+
Encryption.generateKey() // random base64url key
|
|
556
|
+
await Encryption.generateKeyPair() // { publicKey, privateKey } ECDH identity
|
|
557
|
+
await Encryption.deriveKey(password) // PBKDF2 → { key, salt, iterations }
|
|
558
|
+
await Encryption.compareKeys(left, right) // constant time
|
|
559
|
+
await Encryption.fingerprint() // displayable digest of the app key
|
|
564
560
|
```
|
|
565
561
|
|
|
566
562
|
---
|
|
@@ -568,14 +564,14 @@ await Encryption.fingerprint(); // displayable digest of the app key
|
|
|
568
564
|
#### End-to-end encryption
|
|
569
565
|
|
|
570
566
|
```ts
|
|
571
|
-
const channel = await Encryption.channel(myPrivateKey, peerPublicKey)
|
|
567
|
+
const channel = await Encryption.channel(myPrivateKey, peerPublicKey)
|
|
572
568
|
|
|
573
|
-
await channel.decrypt(await channel.encrypt('hey'))
|
|
569
|
+
await channel.decrypt(await channel.encrypt('hey'))
|
|
574
570
|
|
|
575
|
-
await Encryption.seal('anonymous tip', peerPublicKey)
|
|
576
|
-
await Encryption.open(payload, myPrivateKey)
|
|
571
|
+
await Encryption.seal('anonymous tip', peerPublicKey)
|
|
572
|
+
await Encryption.open(payload, myPrivateKey)
|
|
577
573
|
|
|
578
|
-
await Encryption.safetyNumber(myPublicKey, peerPublicKey)
|
|
574
|
+
await Encryption.safetyNumber(myPublicKey, peerPublicKey)
|
|
579
575
|
```
|
|
580
576
|
|
|
581
577
|
The full `@arkstack/encryption` surface — `Cipher`, `Codec`, `EncryptionKey`, `KeyPair`, `Keys`, `SealedBox`, `SecureChannel`, `NodeCipher` — is re-exported from this package.
|
|
@@ -601,9 +597,9 @@ Password hashing and OTP generation utilities.
|
|
|
601
597
|
Hashes a string using bcrypt with a salt factor of 10.
|
|
602
598
|
|
|
603
599
|
```ts
|
|
604
|
-
import { Hash } from '@arkstack/common'
|
|
600
|
+
import { Hash } from '@arkstack/common'
|
|
605
601
|
|
|
606
|
-
const hashed = await Hash.make('user-password')
|
|
602
|
+
const hashed = await Hash.make('user-password')
|
|
607
603
|
```
|
|
608
604
|
|
|
609
605
|
#### `Hash.verify(value, hashedValue)`
|
|
@@ -611,7 +607,7 @@ const hashed = await Hash.make('user-password');
|
|
|
611
607
|
Compares a plain-text value against a bcrypt hash.
|
|
612
608
|
|
|
613
609
|
```ts
|
|
614
|
-
const isValid = await Hash.verify('user-password', hashed)
|
|
610
|
+
const isValid = await Hash.verify('user-password', hashed)
|
|
615
611
|
```
|
|
616
612
|
|
|
617
613
|
#### `Hash.otp(digits?, label?, period?)`
|
|
@@ -619,8 +615,8 @@ const isValid = await Hash.verify('user-password', hashed);
|
|
|
619
615
|
Creates a TOTP instance using the `otpauth` library with `SHA1` and a static secret. Suitable for simple time-based OTP flows.
|
|
620
616
|
|
|
621
617
|
```ts
|
|
622
|
-
const totp = Hash.otp(6, 'user@example.com', 60)
|
|
623
|
-
const token = totp.generate()
|
|
618
|
+
const totp = Hash.otp(6, 'user@example.com', 60)
|
|
619
|
+
const token = totp.generate()
|
|
624
620
|
```
|
|
625
621
|
|
|
626
622
|
#### `Hash.totp(secret, label, issuer?, period?)`
|
|
@@ -628,8 +624,8 @@ const token = totp.generate();
|
|
|
628
624
|
Creates a TOTP instance from a base32-encoded secret. Intended for user-specific TOTP (e.g. authenticator app integration).
|
|
629
625
|
|
|
630
626
|
```ts
|
|
631
|
-
const totp = Hash.totp(user.totpSecret, user.email)
|
|
632
|
-
const isValid = totp.validate({ token: userInput }) !== null
|
|
627
|
+
const totp = Hash.totp(user.totpSecret, user.email)
|
|
628
|
+
const isValid = totp.validate({ token: userInput }) !== null
|
|
633
629
|
```
|
|
634
630
|
|
|
635
631
|
---
|
|
@@ -645,16 +641,16 @@ Utilities for starting an HTTP server with automatic port detection and renderin
|
|
|
645
641
|
Detects whether the preferred port is available (using `detect-port`) and boots the server on the first free port. Also initialises key globals: `env`, `config`, `str`, `app`, and `arkctx`.
|
|
646
642
|
|
|
647
643
|
```ts
|
|
648
|
-
import { bootWithDetectedPort } from '@arkstack/common'
|
|
644
|
+
import { bootWithDetectedPort } from '@arkstack/common'
|
|
649
645
|
|
|
650
646
|
await bootWithDetectedPort(
|
|
651
647
|
async (port) => {
|
|
652
|
-
server.listen(port)
|
|
653
|
-
Logger.success(`Server:http://localhost:${port}`)
|
|
648
|
+
server.listen(port)
|
|
649
|
+
Logger.success(`Server:http://localhost:${port}`)
|
|
654
650
|
},
|
|
655
651
|
3000,
|
|
656
652
|
appInstance,
|
|
657
|
-
)
|
|
653
|
+
)
|
|
658
654
|
```
|
|
659
655
|
|
|
660
656
|
**Globals set:**
|
|
@@ -674,9 +670,9 @@ await bootWithDetectedPort(
|
|
|
674
670
|
Renders an error page using the `~arkstack/common.error` view template. Falls back to a human-readable title from a built-in status code map.
|
|
675
671
|
|
|
676
672
|
```ts
|
|
677
|
-
import { renderError } from '@arkstack/common'
|
|
673
|
+
import { renderError } from '@arkstack/common'
|
|
678
674
|
|
|
679
|
-
const html = renderError({ code: 404, message: 'Page not found' })
|
|
675
|
+
const html = renderError({ code: 404, message: 'Page not found' })
|
|
680
676
|
```
|
|
681
677
|
|
|
682
678
|
**Built-in status titles:** `400`, `401`, `403`, `404`, `500`, `502`, `503`, `504`.
|
|
@@ -692,12 +688,12 @@ const html = renderError({ code: 404, message: 'Page not found' });
|
|
|
692
688
|
Registers a cleanup callback for `SIGINT`, `SIGTERM`, and `SIGQUIT` signals, ensuring the application shuts down cleanly.
|
|
693
689
|
|
|
694
690
|
```ts
|
|
695
|
-
import { bindGracefulShutdown } from '@arkstack/common'
|
|
691
|
+
import { bindGracefulShutdown } from '@arkstack/common'
|
|
696
692
|
|
|
697
693
|
bindGracefulShutdown(async () => {
|
|
698
|
-
await db.disconnect()
|
|
699
|
-
Logger.info('Server shut down gracefully')
|
|
700
|
-
})
|
|
694
|
+
await db.disconnect()
|
|
695
|
+
Logger.info('Server shut down gracefully')
|
|
696
|
+
})
|
|
701
697
|
```
|
|
702
698
|
|
|
703
699
|
---
|
|
@@ -711,9 +707,9 @@ bindGracefulShutdown(async () => {
|
|
|
711
707
|
Extends `String.prototype` with four utility methods. Call this once during application bootstrap.
|
|
712
708
|
|
|
713
709
|
```ts
|
|
714
|
-
import { loadPrototypes } from '@arkstack/common'
|
|
710
|
+
import { loadPrototypes } from '@arkstack/common'
|
|
715
711
|
|
|
716
|
-
loadPrototypes()
|
|
712
|
+
loadPrototypes()
|
|
717
713
|
```
|
|
718
714
|
|
|
719
715
|
**Methods added:**
|
|
@@ -735,14 +731,14 @@ When `loadPrototypes()` is called and `bootWithDetectedPort()` initialises the r
|
|
|
735
731
|
|
|
736
732
|
```ts
|
|
737
733
|
// Globals (set by bootWithDetectedPort)
|
|
738
|
-
globalThis.env
|
|
739
|
-
globalThis.config
|
|
734
|
+
globalThis.env // GlobalEnv — typed env() accessor
|
|
735
|
+
globalThis.config // GlobalConfig — typed config() accessor
|
|
740
736
|
|
|
741
737
|
// String prototype extensions (set by loadPrototypes)
|
|
742
|
-
'my_string'.titleCase()
|
|
743
|
-
'my_string'.camelCase()
|
|
744
|
-
'my_string'.pascalCase()
|
|
745
|
-
'my long string'.truncate(10, '…')
|
|
738
|
+
'my_string'.titleCase()
|
|
739
|
+
'my_string'.camelCase()
|
|
740
|
+
'my_string'.pascalCase()
|
|
741
|
+
'my long string'.truncate(10, '…')
|
|
746
742
|
```
|
|
747
743
|
|
|
748
744
|
---
|
|
@@ -789,12 +785,12 @@ Key exported types from the package:
|
|
|
789
785
|
Extracts a safe pagination limit from a query object. Clamps the result between `1` and `50` or the configured default `maxPerPage`, defaulting to `25` or the configured default `perPage`.
|
|
790
786
|
|
|
791
787
|
```ts
|
|
792
|
-
import { perPage } from '@arkstack/common'
|
|
788
|
+
import { perPage } from '@arkstack/common'
|
|
793
789
|
|
|
794
|
-
const limit = perPage({ limit: 100 })
|
|
795
|
-
const limit2 = perPage({ limit: 100 }, { maxPerPage: 100 })
|
|
796
|
-
const limit3 = perPage({})
|
|
797
|
-
const limit3 = perPage({}, { perPage: 100 })
|
|
790
|
+
const limit = perPage({ limit: 100 }) // 50 (clamped)
|
|
791
|
+
const limit2 = perPage({ limit: 100 }, { maxPerPage: 100 }) // 100
|
|
792
|
+
const limit3 = perPage({}) // 25 (default)
|
|
793
|
+
const limit3 = perPage({}, { perPage: 100 }) // 100
|
|
798
794
|
```
|
|
799
795
|
|
|
800
796
|
#### `resolvePagination(query, defaults?)`
|
|
@@ -802,12 +798,12 @@ const limit3 = perPage({}, { perPage: 100 }); // 100
|
|
|
802
798
|
Extracts the current page and a safe pagination limit from a query object. Clamps the resulting `perPage` between `1` and `50` or the configured default `maxPerPage`, defaulting to `25` or the configured default `perPage`.
|
|
803
799
|
|
|
804
800
|
```ts
|
|
805
|
-
import { resolvePagination } from '@arkstack/common'
|
|
801
|
+
import { resolvePagination } from '@arkstack/common'
|
|
806
802
|
|
|
807
|
-
const limit = resolvePagination({ limit: 100 })
|
|
808
|
-
const limi2 = resolvePagination({ limit: 100 }, { maxPerPage: 100 })
|
|
809
|
-
const limit3 = resolvePagination({})
|
|
810
|
-
const limit4 = resolvePagination({}, { perPage: 100, page: 2 })
|
|
803
|
+
const limit = resolvePagination({ limit: 100 }) // {perPage: 50, page: 1} (clamped)
|
|
804
|
+
const limi2 = resolvePagination({ limit: 100 }, { maxPerPage: 100 }) // {perPage: 100, page: 1}
|
|
805
|
+
const limit3 = resolvePagination({}) // {perPage: 50, page: 1} (default)
|
|
806
|
+
const limit4 = resolvePagination({}, { perPage: 100, page: 2 }) // {perPage: 100, page: 2}
|
|
811
807
|
```
|
|
812
808
|
|
|
813
809
|
#### `getModel(modelName)`
|
|
@@ -815,17 +811,17 @@ const limit4 = resolvePagination({}, { perPage: 100, page: 2 }); // {perPage: 10
|
|
|
815
811
|
Dynamically imports an application model by name from the configured models directory (default: `./src/app/models`). Supports augmenting `ModelRegistry` for type-safe lookups.
|
|
816
812
|
|
|
817
813
|
```ts
|
|
818
|
-
import { getModel } from '@arkstack/common'
|
|
814
|
+
import { getModel } from '@arkstack/common'
|
|
819
815
|
|
|
820
|
-
const User = await getModel('User')
|
|
821
|
-
const users = await User.findAll()
|
|
816
|
+
const User = await getModel('User')
|
|
817
|
+
const users = await User.findAll()
|
|
822
818
|
|
|
823
819
|
// With type augmentation:
|
|
824
820
|
declare module '@arkstack/common' {
|
|
825
821
|
interface ModelRegistry {
|
|
826
|
-
User: typeof User
|
|
822
|
+
User: typeof User
|
|
827
823
|
}
|
|
828
824
|
}
|
|
829
825
|
|
|
830
|
-
const TypedUser = await getModel('User')
|
|
826
|
+
const TypedUser = await getModel('User') // typeof User
|
|
831
827
|
```
|