@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.
package/README.md CHANGED
@@ -44,22 +44,22 @@ Provides core application-level utilities for environment variable access, confi
44
44
  Reads a value from `process.env` with automatic type coercion. Booleans (`true`, `false`, `on`, `off`), numbers, `null`, and empty strings are all handled gracefully.
45
45
 
46
46
  ```ts
47
- import { env } from '@arkstack/common'
47
+ import { env } from '@arkstack/common';
48
48
 
49
- const port = env('PORT', 3000) // number
50
- const debug = env('DEBUG', false) // boolean
51
- const name = env('APP_NAME', 'App') // string
49
+ const port = env('PORT', 3000); // number
50
+ const debug = env('DEBUG', false); // boolean
51
+ const name = env('APP_NAME', 'App'); // string
52
52
  ```
53
53
 
54
54
  **Type coercion rules:**
55
55
 
56
- | Raw value | Resolved type |
57
- |-----------|---------------|
58
- | `"true"` / `"on"` | `true` |
59
- | `"false"` / `"off"` | `false` |
60
- | Numeric string | `number` |
61
- | `"null"` | `null` |
62
- | `""` | `undefined` (falls back to `defaultValue`) |
56
+ | Raw value | Resolved type |
57
+ | ------------------- | ------------------------------------------ |
58
+ | `"true"` / `"on"` | `true` |
59
+ | `"false"` / `"off"` | `false` |
60
+ | Numeric string | `number` |
61
+ | `"null"` | `null` |
62
+ | `""` | `undefined` (falls back to `defaultValue`) |
63
63
 
64
64
  ---
65
65
 
@@ -68,13 +68,13 @@ const name = env('APP_NAME', 'App') // string
68
68
  Loads and merges all configuration files from the build output's `config/` directory. Supports dot-path key access with full TypeScript inference.
69
69
 
70
70
  ```ts
71
- import { config } from '@arkstack/common'
71
+ import { config } from '@arkstack/common';
72
72
 
73
73
  // Get the full config object
74
- const allConfig = config()
74
+ const allConfig = config();
75
75
 
76
76
  // Access a nested key
77
- const dbHost = config('database.host', 'localhost')
77
+ const dbHost = config('database.host', 'localhost');
78
78
  ```
79
79
 
80
80
  Config files are loaded from the resolved `outputDir()` using `createRequire`. Middleware config files are skipped when running in CLI context.
@@ -86,10 +86,10 @@ Config files are loaded from the resolved `outputDir()` using `createRequire`. M
86
86
  Builds a fully-qualified application URL from `APP_URL` and `PORT` environment variables.
87
87
 
88
88
  ```ts
89
- import { appUrl } from '@arkstack/common'
89
+ import { appUrl } from '@arkstack/common';
90
90
 
91
- appUrl() // "http://localhost:3000"
92
- appUrl('/api/health') // "http://localhost:3000/api/health"
91
+ appUrl(); // "http://localhost:3000"
92
+ appUrl('/api/health'); // "http://localhost:3000/api/health"
93
93
  ```
94
94
 
95
95
  ---
@@ -99,9 +99,9 @@ appUrl('/api/health') // "http://localhost:3000/api/health"
99
99
  Returns `'dev'` or `'prod'` based on the `NODE_ENV` environment variable. Defaults to `'dev'` for any unrecognised value.
100
100
 
101
101
  ```ts
102
- import { nodeEnv } from '@arkstack/common'
102
+ import { nodeEnv } from '@arkstack/common';
103
103
 
104
- nodeEnv() // "dev" | "prod"
104
+ nodeEnv(); // "dev" | "prod"
105
105
  ```
106
106
 
107
107
  ---
@@ -110,10 +110,10 @@ nodeEnv() // "dev" | "prod"
110
110
 
111
111
  Resolves the build output directory. In development, defaults to `.arkstack/build`; in production, to `dist`. Both can be overridden via environment variables:
112
112
 
113
- | Variable | Context | Default |
114
- |----------|---------|---------|
113
+ | Variable | Context | Default |
114
+ | ---------------- | ----------- | ----------------- |
115
115
  | `OUTPUT_DIR_DEV` | Development | `.arkstack/build` |
116
- | `OUTPUT_DIR` | Production | `dist` |
116
+ | `OUTPUT_DIR` | Production | `dist` |
117
117
 
118
118
  ---
119
119
 
@@ -122,9 +122,9 @@ Resolves the build output directory. In development, defaults to `.arkstack/buil
122
122
  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.
123
123
 
124
124
  ```ts
125
- import { importFile } from '@arkstack/common'
125
+ import { importFile } from '@arkstack/common';
126
126
 
127
- const module = await importFile<{ default: MyConfig }>('./config/app.ts')
127
+ const module = await importFile<{ default: MyConfig }>('./config/app.ts');
128
128
  ```
129
129
 
130
130
  ---
@@ -138,24 +138,24 @@ A structured, chalk-powered console logger with verbosity control, two-column fo
138
138
  #### Basic log levels
139
139
 
140
140
  ```ts
141
- import { Logger } from '@arkstack/common'
141
+ import { Logger } from '@arkstack/common';
142
142
 
143
- Logger.success('Server started')
144
- Logger.info('Listening on port 3000')
145
- Logger.warn('Deprecated option used')
146
- Logger.error('Something went wrong', false) // false = don't exit
147
- Logger.debug('Internal state dump') // only shown at verbosity >= 3
143
+ Logger.success('Server started');
144
+ Logger.info('Listening on port 3000');
145
+ Logger.warn('Deprecated option used');
146
+ Logger.error('Something went wrong', false); // false = don't exit
147
+ Logger.debug('Internal state dump'); // only shown at verbosity >= 3
148
148
  ```
149
149
 
150
150
  Each level uses a distinct icon and colour:
151
151
 
152
- | Method | Icon | Colour |
153
- |--------|------|--------|
154
- | `success` | `✓` | Green |
155
- | `info` | `ℹ` | Blue |
156
- | `warn` | `⚠` | Yellow |
157
- | `error` | `✖` | Red |
158
- | `debug` | `🐛` | Gray |
152
+ | Method | Icon | Colour |
153
+ | --------- | ---- | ------ |
154
+ | `success` | `✓` | Green |
155
+ | `info` | `ℹ` | Blue |
156
+ | `warn` | `⚠` | Yellow |
157
+ | `error` | `✖` | Red |
158
+ | `debug` | `🐛` | Gray |
159
159
 
160
160
  The second argument for all level methods is `exit` (boolean). When `true`, the process exits after logging. `error` exits by default (`exit = true`); all others default to `false`.
161
161
 
@@ -167,10 +167,10 @@ Sets global verbosity and suppression behaviour.
167
167
 
168
168
  ```ts
169
169
  Logger.configure({
170
- verbosity: 3, // enables debug output
171
- quiet: true, // suppresses info and success
172
- silent: true, // suppresses all output
173
- })
170
+ verbosity: 3, // enables debug output
171
+ quiet: true, // suppresses info and success
172
+ silent: true, // suppresses all output
173
+ });
174
174
  ```
175
175
 
176
176
  ---
@@ -180,10 +180,10 @@ Logger.configure({
180
180
  Renders a right-aligned two-column layout padded to the terminal width.
181
181
 
182
182
  ```ts
183
- Logger.twoColumnDetail('Route', 'GET /api/users')
183
+ Logger.twoColumnDetail('Route', 'GET /api/users');
184
184
  // "Route ......................................... GET /api/users"
185
185
 
186
- const row = Logger.twoColumnDetail('Route', 'GET /api/users', false)
186
+ const row = Logger.twoColumnDetail('Route', 'GET /api/users', false);
187
187
  // returns [name, dots, value] without printing
188
188
  ```
189
189
 
@@ -194,7 +194,7 @@ const row = Logger.twoColumnDetail('Route', 'GET /api/users', false)
194
194
  Similar to `twoColumnDetail`, but uses a fixed width with space padding rather than dots. Useful for command help listings.
195
195
 
196
196
  ```ts
197
- Logger.describe('--port', 'Port to listen on', 40)
197
+ Logger.describe('--port', 'Port to listen on', 40);
198
198
  // "--port Port to listen on"
199
199
  ```
200
200
 
@@ -205,8 +205,8 @@ Logger.describe('--port', 'Port to listen on', 40)
205
205
  Like `twoColumnDetail`, but wraps the left column with a coloured background badge based on `status`.
206
206
 
207
207
  ```ts
208
- Logger.split('Database', 'Connected', 'success')
209
- Logger.split('Migration', 'Failed', 'error', true) // exits after logging
208
+ Logger.split('Database', 'Connected', 'success');
209
+ Logger.split('Migration', 'Failed', 'error', true); // exits after logging
210
210
  ```
211
211
 
212
212
  ---
@@ -216,13 +216,16 @@ Logger.split('Migration', 'Failed', 'error', true) // exits after logging
216
216
  Composes a styled string from a `[text, chalkStyle]` pair array.
217
217
 
218
218
  ```ts
219
- Logger.parse([
220
- ['Arkstack', 'bold'],
221
- ['v1.0.0', 'gray'],
222
- ], ' ') // "Arkstack v1.0.0"
219
+ Logger.parse(
220
+ [
221
+ ['Arkstack', 'bold'],
222
+ ['v1.0.0', 'gray'],
223
+ ],
224
+ ' ',
225
+ ); // "Arkstack v1.0.0"
223
226
 
224
227
  // Return instead of print
225
- const str = Logger.parse([['Ready', 'green']], ' ', false)
228
+ const str = Logger.parse([['Ready', 'green']], ' ', false);
226
229
  ```
227
230
 
228
231
  ---
@@ -232,8 +235,14 @@ const str = Logger.parse([['Ready', 'green']], ' ', false)
232
235
  A flexible polymorphic logger that accepts either a string + style, or a `LoggerParseSignature` array. Returns the `Logger` class when called with no arguments.
233
236
 
234
237
  ```ts
235
- Logger.log('PORT:3000', 'cyan')
236
- Logger.log([['PORT', 'bold'], ['3000', 'cyan']], ' ')
238
+ Logger.log('PORT:3000', 'cyan');
239
+ Logger.log(
240
+ [
241
+ ['PORT', 'bold'],
242
+ ['3000', 'cyan'],
243
+ ],
244
+ ' ',
245
+ );
237
246
  ```
238
247
 
239
248
  ---
@@ -243,8 +252,8 @@ Logger.log([['PORT', 'bold'], ['3000', 'cyan']], ' ')
243
252
  Returns a function that applies a chain of chalk styles to any input.
244
253
 
245
254
  ```ts
246
- const highlight = Logger.chalker(['bold', 'green'])
247
- console.log(highlight('Ready'))
255
+ const highlight = Logger.chalker(['bold', 'green']);
256
+ console.log(highlight('Ready'));
248
257
  ```
249
258
 
250
259
  ---
@@ -254,9 +263,9 @@ console.log(highlight('Ready'))
254
263
  Returns a `Console`-compatible object with `log`, `debug`, `warn`, `info`, and `error` methods. Can be used as a drop-in replacement for `globalThis.console`.
255
264
 
256
265
  ```ts
257
- const console = Logger.console()
258
- console.log('hello')
259
- console.warn('watch out')
266
+ const console = Logger.console();
267
+ console.log('hello');
268
+ console.warn('watch out');
260
269
  ```
261
270
 
262
271
  ---
@@ -272,12 +281,12 @@ A static utility class for normalising, serialising, classifying, and logging er
272
281
  The primary method. Converts any thrown value into a consistent `ArkstackErrorPayload` object, handling validation errors, model-not-found errors, and generic errors uniformly.
273
282
 
274
283
  ```ts
275
- import { ErrorHandler } from '@arkstack/common'
284
+ import { ErrorHandler } from '@arkstack/common';
276
285
 
277
286
  try {
278
287
  // ...
279
288
  } catch (err) {
280
- const payload = ErrorHandler.createErrorPayload(err, 'Request failed')
289
+ const payload = ErrorHandler.createErrorPayload(err, 'Request failed');
281
290
  // { status: 'error', code: 422, message: '...', errors: {...} }
282
291
  }
283
292
  ```
@@ -296,11 +305,11 @@ try {
296
305
 
297
306
  **Classification logic:**
298
307
 
299
- | Error type | `code` | `errors` populated |
300
- |------------|--------|--------------------|
301
- | Validation error (has `.errors`) | `statusCode` / `status` or `422` | Yes |
302
- | Model not found (has `.getModelName()`) | `404` | No |
303
- | Generic error | `statusCode` / `status` or `500` | Stack trace as object |
308
+ | Error type | `code` | `errors` populated |
309
+ | --------------------------------------- | -------------------------------- | --------------------- |
310
+ | Validation error (has `.errors`) | `statusCode` / `status` or `422` | Yes |
311
+ | Model not found (has `.getModelName()`) | `404` | No |
312
+ | Generic error | `statusCode` / `status` or `500` | Stack trace as object |
304
313
 
305
314
  ---
306
315
 
@@ -309,7 +318,7 @@ try {
309
318
  Recursively serialises any value — including `Error` instances and circular references — into a plain JSON-safe object. Circular references are replaced with `'[Circular]'`.
310
319
 
311
320
  ```ts
312
- const serialized = ErrorHandler.serializeError(new Error('oops'))
321
+ const serialized = ErrorHandler.serializeError(new Error('oops'));
313
322
  // { name: 'Error', message: 'oops', stack: '...' }
314
323
  ```
315
324
 
@@ -320,8 +329,8 @@ const serialized = ErrorHandler.serializeError(new Error('oops'))
320
329
  Ensures a status code is a valid integer in the range `100–599`. Returns the fallback (default `500`) for anything invalid.
321
330
 
322
331
  ```ts
323
- ErrorHandler.normalizeStatusCode('422') // 422
324
- ErrorHandler.normalizeStatusCode('xyz') // 500
332
+ ErrorHandler.normalizeStatusCode('422'); // 422
333
+ ErrorHandler.normalizeStatusCode('xyz'); // 500
325
334
  ```
326
335
 
327
336
  ---
@@ -337,7 +346,11 @@ Returns a Pino logger instance that writes to `storage/logs/error.log` (created
337
346
  Persists an unhandled error to the error log file, including the serialised error and the associated request context.
338
347
 
339
348
  ```ts
340
- ErrorHandler.logUnhandledError(err, { method: 'GET', url: '/api' }, 'Unhandled exception')
349
+ ErrorHandler.logUnhandledError(
350
+ err,
351
+ { method: 'GET', url: '/api' },
352
+ 'Unhandled exception',
353
+ );
341
354
  ```
342
355
 
343
356
  ---
@@ -345,12 +358,12 @@ ErrorHandler.logUnhandledError(err, { method: 'GET', url: '/api' }, 'Unhandled e
345
358
  #### Classification helpers
346
359
 
347
360
  ```ts
348
- ErrorHandler.isValidationError(err) // true if err.errors is defined
349
- ErrorHandler.isModelNotFoundError(err) // true if err.getModelName is a function
350
- ErrorHandler.shouldLogError(err) // false for validation/model-not-found errors
351
- ErrorHandler.shouldHideStack() // true if HIDE_ERROR_STACK env is set
352
- ErrorHandler.getPrimaryError(err) // unwraps err.cause if present
353
- ErrorHandler.toErrorShape(value) // casts unknown to ArkstackErrorShape if object
361
+ ErrorHandler.isValidationError(err); // true if err.errors is defined
362
+ ErrorHandler.isModelNotFoundError(err); // true if err.getModelName is a function
363
+ ErrorHandler.shouldLogError(err); // false for validation/model-not-found errors
364
+ ErrorHandler.shouldHideStack(); // true if HIDE_ERROR_STACK env is set
365
+ ErrorHandler.getPrimaryError(err); // unwraps err.cause if present
366
+ ErrorHandler.toErrorShape(value); // casts unknown to ArkstackErrorShape if object
354
367
  ```
355
368
 
356
369
  All static methods are also exported as named standalone functions for convenience:
@@ -362,7 +375,7 @@ import {
362
375
  serializeError,
363
376
  logUnhandledError,
364
377
  // ...
365
- } from '@arkstack/common'
378
+ } from '@arkstack/common';
366
379
  ```
367
380
 
368
381
  ---
@@ -378,9 +391,9 @@ A three-level exception hierarchy for structured error throwing.
378
391
  Base class extending `Error`. Sets `.name` to `'Exception'`.
379
392
 
380
393
  ```ts
381
- import { Exception } from '@arkstack/common'
394
+ import { Exception } from '@arkstack/common';
382
395
 
383
- throw new Exception('Something went wrong')
396
+ throw new Exception('Something went wrong');
384
397
  ```
385
398
 
386
399
  ---
@@ -390,10 +403,10 @@ throw new Exception('Something went wrong')
390
403
  Extends `Exception`. Adds `statusCode` (default `400`) and an optional `errors` map for field-level validation errors.
391
404
 
392
405
  ```ts
393
- import { AppException } from '@arkstack/common'
406
+ import { AppException } from '@arkstack/common';
394
407
 
395
- const err = new AppException('Validation failed', 422)
396
- err.errors = { email: ['Email is required'] }
408
+ const err = new AppException('Validation failed', 422);
409
+ err.errors = { email: ['Email is required'] };
397
410
  ```
398
411
 
399
412
  ---
@@ -407,10 +420,10 @@ Extends `AppException`. Intended for HTTP request-level errors. Provides two sta
407
420
  Throws a `RequestException` if the value is `null` or `undefined`. Narrows the type on success.
408
421
 
409
422
  ```ts
410
- import { RequestException } from '@arkstack/common'
423
+ import { RequestException } from '@arkstack/common';
411
424
 
412
- const user = await User.find(id)
413
- RequestException.assertNotEmpty(user, 'User not found', 404)
425
+ const user = await User.find(id);
426
+ RequestException.assertNotEmpty(user, 'User not found', 404);
414
427
  // user is now User (not null | undefined)
415
428
  ```
416
429
 
@@ -419,7 +432,7 @@ RequestException.assertNotEmpty(user, 'User not found', 404)
419
432
  Throws if the condition is truthy.
420
433
 
421
434
  ```ts
422
- RequestException.abortIf(!user.isActive, 'Account is suspended', 403)
435
+ RequestException.abortIf(!user.isActive, 'Account is suspended', 403);
423
436
  ```
424
437
 
425
438
  ---
@@ -435,12 +448,12 @@ A global, named hook registry for extending Arkstack internals without modifying
435
448
  Registers a hook. Multiple calls for the same name are merged.
436
449
 
437
450
  ```ts
438
- import { Hook } from '@arkstack/common'
451
+ import { Hook } from '@arkstack/common';
439
452
 
440
453
  Hook.set('request:handle', {
441
454
  before: (ctx) => console.log('before handler'),
442
455
  after: (ctx) => console.log('after handler'),
443
- })
456
+ });
444
457
  ```
445
458
 
446
459
  ---
@@ -450,8 +463,8 @@ Hook.set('request:handle', {
450
463
  Retrieves the full hook object or a specific positional handler.
451
464
 
452
465
  ```ts
453
- const hook = Hook.get('request:handle') // IHook | undefined
454
- const before = Hook.get('request:handle', 'before') // function | undefined
466
+ const hook = Hook.get('request:handle'); // IHook | undefined
467
+ const before = Hook.get('request:handle', 'before'); // function | undefined
455
468
  ```
456
469
 
457
470
  ---
@@ -461,8 +474,8 @@ const before = Hook.get('request:handle', 'before') // function | undefined
461
474
  Checks whether a hook (or a specific position within it) exists.
462
475
 
463
476
  ```ts
464
- Hook.has('request:handle') // true | false
465
- Hook.has('request:handle', 'after') // true | false
477
+ Hook.has('request:handle'); // true | false
478
+ Hook.has('request:handle', 'after'); // true | false
466
479
  ```
467
480
 
468
481
  ---
@@ -472,9 +485,9 @@ Hook.has('request:handle', 'after') // true | false
472
485
  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()`.
473
486
 
474
487
  ```ts
475
- Hook.unset('request:handle', 'before') // removes only the 'before' handler
476
- Hook.unset('request:handle') // removes the entire hook
477
- Hook.unset() // clears all hooks
488
+ Hook.unset('request:handle', 'before'); // removes only the 'before' handler
489
+ Hook.unset('request:handle'); // removes the entire hook
490
+ Hook.unset(); // clears all hooks
478
491
  ```
479
492
 
480
493
  ---
@@ -484,7 +497,7 @@ Hook.unset() // clears all hooks
484
497
  Returns all registered hooks as a plain record.
485
498
 
486
499
  ```ts
487
- const hooks = Hook.getAll()
500
+ const hooks = Hook.getAll();
488
501
  // { 'request:handle': { before: fn, after: fn } }
489
502
  ```
490
503
 
@@ -507,9 +520,9 @@ AES-256-GCM symmetric encryption for sensitive values (e.g. two-factor authentic
507
520
  Encrypts a string. Returns a colon-delimited base64url string: `<iv>:<authTag>:<ciphertext>`.
508
521
 
509
522
  ```ts
510
- import { Encryption } from '@arkstack/common'
523
+ import { Encryption } from '@arkstack/common';
511
524
 
512
- const token = Encryption.encrypt('my-secret-value')
525
+ const token = Encryption.encrypt('my-secret-value');
513
526
  // "abc123:def456:ghi789"
514
527
  ```
515
528
 
@@ -518,15 +531,15 @@ const token = Encryption.encrypt('my-secret-value')
518
531
  Decrypts a payload produced by `encrypt`. Throws if the format is invalid or the key is wrong.
519
532
 
520
533
  ```ts
521
- const original = Encryption.decrypt(token)
534
+ const original = Encryption.decrypt(token);
522
535
  // "my-secret-value"
523
536
  ```
524
537
 
525
538
  **Environment variable:**
526
539
 
527
- | Variable | Required | Description |
528
- |----------|----------|-------------|
529
- | `TWO_FACTOR_ENCRYPTION_KEY` | Yes | Raw secret; hashed to a 256-bit key internally via SHA-256 |
540
+ | Variable | Required | Description |
541
+ | --------------------------- | -------- | ---------------------------------------------------------- |
542
+ | `TWO_FACTOR_ENCRYPTION_KEY` | Yes | Raw secret; hashed to a 256-bit key internally via SHA-256 |
530
543
 
531
544
  ---
532
545
 
@@ -541,9 +554,9 @@ Password hashing and OTP generation utilities.
541
554
  Hashes a string using bcrypt with a salt factor of 10.
542
555
 
543
556
  ```ts
544
- import { Hash } from '@arkstack/common'
557
+ import { Hash } from '@arkstack/common';
545
558
 
546
- const hashed = await Hash.make('user-password')
559
+ const hashed = await Hash.make('user-password');
547
560
  ```
548
561
 
549
562
  #### `Hash.verify(value, hashedValue)`
@@ -551,7 +564,7 @@ const hashed = await Hash.make('user-password')
551
564
  Compares a plain-text value against a bcrypt hash.
552
565
 
553
566
  ```ts
554
- const isValid = await Hash.verify('user-password', hashed)
567
+ const isValid = await Hash.verify('user-password', hashed);
555
568
  ```
556
569
 
557
570
  #### `Hash.otp(digits?, label?, period?)`
@@ -559,8 +572,8 @@ const isValid = await Hash.verify('user-password', hashed)
559
572
  Creates a TOTP instance using the `otpauth` library with `SHA1` and a static secret. Suitable for simple time-based OTP flows.
560
573
 
561
574
  ```ts
562
- const totp = Hash.otp(6, 'user@example.com', 60)
563
- const token = totp.generate()
575
+ const totp = Hash.otp(6, 'user@example.com', 60);
576
+ const token = totp.generate();
564
577
  ```
565
578
 
566
579
  #### `Hash.totp(secret, label, issuer?, period?)`
@@ -568,8 +581,8 @@ const token = totp.generate()
568
581
  Creates a TOTP instance from a base32-encoded secret. Intended for user-specific TOTP (e.g. authenticator app integration).
569
582
 
570
583
  ```ts
571
- const totp = Hash.totp(user.totpSecret, user.email)
572
- const isValid = totp.validate({ token: userInput }) !== null
584
+ const totp = Hash.totp(user.totpSecret, user.email);
585
+ const isValid = totp.validate({ token: userInput }) !== null;
573
586
  ```
574
587
 
575
588
  ---
@@ -585,23 +598,27 @@ Utilities for starting an HTTP server with automatic port detection and renderin
585
598
  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`.
586
599
 
587
600
  ```ts
588
- import { bootWithDetectedPort } from '@arkstack/common'
601
+ import { bootWithDetectedPort } from '@arkstack/common';
589
602
 
590
- await bootWithDetectedPort(async (port) => {
591
- server.listen(port)
592
- Logger.success(`Server:http://localhost:${port}`)
593
- }, 3000, appInstance)
603
+ await bootWithDetectedPort(
604
+ async (port) => {
605
+ server.listen(port);
606
+ Logger.success(`Server:http://localhost:${port}`);
607
+ },
608
+ 3000,
609
+ appInstance,
610
+ );
594
611
  ```
595
612
 
596
613
  **Globals set:**
597
614
 
598
- | Global | Value |
599
- |--------|-------|
600
- | `globalThis.app` | `() => app` |
601
- | `globalThis.env` | `env` |
602
- | `globalThis.config` | `config` |
603
- | `globalThis.str` | `str` (from `@h3ravel/support`) |
604
- | `globalThis.arkctx` | `{ runtime: 'HTTP' }` |
615
+ | Global | Value |
616
+ | ------------------- | ------------------------------- |
617
+ | `globalThis.app` | `() => app` |
618
+ | `globalThis.env` | `env` |
619
+ | `globalThis.config` | `config` |
620
+ | `globalThis.str` | `str` (from `@h3ravel/support`) |
621
+ | `globalThis.arkctx` | `{ runtime: 'HTTP' }` |
605
622
 
606
623
  ---
607
624
 
@@ -610,9 +627,9 @@ await bootWithDetectedPort(async (port) => {
610
627
  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.
611
628
 
612
629
  ```ts
613
- import { renderError } from '@arkstack/common'
630
+ import { renderError } from '@arkstack/common';
614
631
 
615
- const html = renderError({ code: 404, message: 'Page not found' })
632
+ const html = renderError({ code: 404, message: 'Page not found' });
616
633
  ```
617
634
 
618
635
  **Built-in status titles:** `400`, `401`, `403`, `404`, `500`, `502`, `503`, `504`.
@@ -628,12 +645,12 @@ const html = renderError({ code: 404, message: 'Page not found' })
628
645
  Registers a cleanup callback for `SIGINT`, `SIGTERM`, and `SIGQUIT` signals, ensuring the application shuts down cleanly.
629
646
 
630
647
  ```ts
631
- import { bindGracefulShutdown } from '@arkstack/common'
648
+ import { bindGracefulShutdown } from '@arkstack/common';
632
649
 
633
650
  bindGracefulShutdown(async () => {
634
- await db.disconnect()
635
- Logger.info('Server shut down gracefully')
636
- })
651
+ await db.disconnect();
652
+ Logger.info('Server shut down gracefully');
653
+ });
637
654
  ```
638
655
 
639
656
  ---
@@ -647,19 +664,19 @@ bindGracefulShutdown(async () => {
647
664
  Extends `String.prototype` with four utility methods. Call this once during application bootstrap.
648
665
 
649
666
  ```ts
650
- import { loadPrototypes } from '@arkstack/common'
667
+ import { loadPrototypes } from '@arkstack/common';
651
668
 
652
- loadPrototypes()
669
+ loadPrototypes();
653
670
  ```
654
671
 
655
672
  **Methods added:**
656
673
 
657
- | Method | Description | Example |
658
- |--------|-------------|---------|
659
- | `.titleCase()` | Converts to Title Case (handles `_` and `-`) | `"hello_world".titleCase()` → `"Hello World"` |
660
- | `.camelCase()` | Converts to camelCase | `"Hello World".camelCase()` → `"helloWorld"` |
661
- | `.pascalCase()` | Converts to PascalCase | `"hello world".pascalCase()` → `"HelloWorld"` |
662
- | `.truncate(len, suffix?)` | Truncates at word boundary | `"Hello World".truncate(7)` → `"Hello..."` |
674
+ | Method | Description | Example |
675
+ | ------------------------- | -------------------------------------------- | --------------------------------------------- |
676
+ | `.titleCase()` | Converts to Title Case (handles `_` and `-`) | `"hello_world".titleCase()` → `"Hello World"` |
677
+ | `.camelCase()` | Converts to camelCase | `"Hello World".camelCase()` → `"helloWorld"` |
678
+ | `.pascalCase()` | Converts to PascalCase | `"hello world".pascalCase()` → `"HelloWorld"` |
679
+ | `.truncate(len, suffix?)` | Truncates at word boundary | `"Hello World".truncate(7)` → `"Hello..."` |
663
680
 
664
681
  ---
665
682
 
@@ -671,14 +688,14 @@ When `loadPrototypes()` is called and `bootWithDetectedPort()` initialises the r
671
688
 
672
689
  ```ts
673
690
  // Globals (set by bootWithDetectedPort)
674
- globalThis.env // GlobalEnv — typed env() accessor
675
- globalThis.config // GlobalConfig — typed config() accessor
691
+ globalThis.env; // GlobalEnv — typed env() accessor
692
+ globalThis.config; // GlobalConfig — typed config() accessor
676
693
 
677
694
  // String prototype extensions (set by loadPrototypes)
678
- "my_string".titleCase()
679
- "my_string".camelCase()
680
- "my_string".pascalCase()
681
- "my long string".truncate(10, '…')
695
+ 'my_string'.titleCase();
696
+ 'my_string'.camelCase();
697
+ 'my_string'.pascalCase();
698
+ 'my long string'.truncate(10, '…');
682
699
  ```
683
700
 
684
701
  ---
@@ -689,30 +706,30 @@ globalThis.config // GlobalConfig — typed config() accessor
689
706
 
690
707
  Key exported types from the package:
691
708
 
692
- | Type | Description |
693
- |------|-------------|
694
- | `GlobalEnv` | Typed signature for the `env()` function |
695
- | `GlobalConfig` | Typed signature for the `config()` function with dot-path support |
696
- | `ArkstackErrorShape` | Union of common error properties across frameworks (`statusCode`, `status`, `errors`, `cause`, etc.) |
697
- | `ArkstackErrorPayload` | Normalised HTTP error response shape produced by `ErrorHandler` |
698
- | `LoggerChalk` | Chalk style identifier(s) accepted by `Logger` methods |
699
- | `LoggerParseSignature` | Array of `[string, LoggerChalk]` pairs for `Logger.parse()` |
700
- | `LoggerLog` | Overloaded function type for `Logger.log()` |
709
+ | Type | Description |
710
+ | ---------------------- | ---------------------------------------------------------------------------------------------------- |
711
+ | `GlobalEnv` | Typed signature for the `env()` function |
712
+ | `GlobalConfig` | Typed signature for the `config()` function with dot-path support |
713
+ | `ArkstackErrorShape` | Union of common error properties across frameworks (`statusCode`, `status`, `errors`, `cause`, etc.) |
714
+ | `ArkstackErrorPayload` | Normalised HTTP error response shape produced by `ErrorHandler` |
715
+ | `LoggerChalk` | Chalk style identifier(s) accepted by `Logger` methods |
716
+ | `LoggerParseSignature` | Array of `[string, LoggerChalk]` pairs for `Logger.parse()` |
717
+ | `LoggerLog` | Overloaded function type for `Logger.log()` |
701
718
 
702
719
  ---
703
720
 
704
721
  ## Environment Variables Reference
705
722
 
706
- | Variable | Module | Required | Description |
707
- |----------|--------|----------|-------------|
708
- | `PORT` | `system`, `network` | No | HTTP server port (default: `3000`) |
709
- | `APP_URL` | `system` | No | Base application URL |
710
- | `APP_NAME` | `hash` | No | Application name used as TOTP issuer |
711
- | `NODE_ENV` | `system` | No | `development` or `production` |
712
- | `OUTPUT_DIR` | `system` | No | Production build output directory (default: `dist`) |
713
- | `OUTPUT_DIR_DEV` | `system` | No | Development build output directory (default: `.arkstack/build`) |
714
- | `TWO_FACTOR_ENCRYPTION_KEY` | `encryption` | Yes* | Secret key for AES-256-GCM encryption (*required only if using `Encryption`) |
715
- | `HIDE_ERROR_STACK` | `ErrorHandler` | No | Set to `true`, `1`, or `on` to suppress stack traces in error payloads |
723
+ | Variable | Module | Required | Description |
724
+ | --------------------------- | ------------------- | -------- | ----------------------------------------------------------------------------- |
725
+ | `PORT` | `system`, `network` | No | HTTP server port (default: `3000`) |
726
+ | `APP_URL` | `system` | No | Base application URL |
727
+ | `APP_NAME` | `hash` | No | Application name used as TOTP issuer |
728
+ | `NODE_ENV` | `system` | No | `development` or `production` |
729
+ | `OUTPUT_DIR` | `system` | No | Production build output directory (default: `dist`) |
730
+ | `OUTPUT_DIR_DEV` | `system` | No | Development build output directory (default: `.arkstack/build`) |
731
+ | `TWO_FACTOR_ENCRYPTION_KEY` | `encryption` | Yes\* | Secret key for AES-256-GCM encryption (\*required only if using `Encryption`) |
732
+ | `HIDE_ERROR_STACK` | `ErrorHandler` | No | Set to `true`, `1`, or `on` to suppress stack traces in error payloads |
716
733
 
717
734
  ---
718
735
 
@@ -725,28 +742,28 @@ Key exported types from the package:
725
742
  Extracts a safe pagination limit from a query object. Clamps the result between `1` and `50`, defaulting to `15`.
726
743
 
727
744
  ```ts
728
- import { perPage } from '@arkstack/common'
745
+ import { perPage } from '@arkstack/common';
729
746
 
730
- const limit = perPage({ limit: 100 }) // 50 (clamped)
731
- const limit2 = perPage({}) // 15 (default)
747
+ const limit = perPage({ limit: 100 }); // 50 (clamped)
748
+ const limit2 = perPage({}); // 15 (default)
732
749
  ```
733
750
 
734
751
  #### `getModel(modelName)`
735
752
 
736
- Dynamically imports an application model by name from the configured models directory (default: `./src/models`). Supports augmenting `ModelRegistry` for type-safe lookups.
753
+ Dynamically imports an application model by name from the configured models directory (default: `./src/app/models`). Supports augmenting `ModelRegistry` for type-safe lookups.
737
754
 
738
755
  ```ts
739
- import { getModel } from '@arkstack/common'
756
+ import { getModel } from '@arkstack/common';
740
757
 
741
- const User = await getModel('User')
742
- const users = await User.findAll()
758
+ const User = await getModel('User');
759
+ const users = await User.findAll();
743
760
 
744
761
  // With type augmentation:
745
762
  declare module '@arkstack/common' {
746
763
  interface ModelRegistry {
747
- User: typeof User
764
+ User: typeof User;
748
765
  }
749
766
  }
750
767
 
751
- const TypedUser = await getModel('User') // typeof User
768
+ const TypedUser = await getModel('User'); // typeof User
752
769
  ```