@arkstack/common 0.7.6 → 0.7.8

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.
Files changed (2) hide show
  1. package/README.md +735 -20
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -1,37 +1,752 @@
1
- # @arkstack/common
1
+ # `@arkstack/common`
2
2
 
3
- Shared foundations and core utilities for Arkstack applications and packages providing:
3
+ Core utilities, primitives, and shared infrastructure for the Arkstack framework ecosystem. This package provides the building blocks used across all Arkstack packages — error handling, logging, hashing, encryption, lifecycle management, hooks, and more.
4
4
 
5
- - Logging utilities
6
- - Configuration management
7
- - `ErrorHandler` and shared exception classes
8
- - Hashing and encryption helpers
9
- - Typed model resolution
10
- - Pagination helpers
5
+ ---
11
6
 
12
- ## Model Resolution
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [Modules](#modules)
11
+ - [System](#system)
12
+ - [Logger](#logger)
13
+ - [ErrorHandler](#errorhandler)
14
+ - [Exceptions](#exceptions)
15
+ - [Hook](#hook)
16
+ - [Encryption](#encryption)
17
+ - [Hash](#hash)
18
+ - [Network](#network)
19
+ - [Lifecycle](#lifecycle)
20
+ - [Prototypes](#prototypes)
21
+ - [Global Augmentations](#global-augmentations)
22
+ - [Types](#types)
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pnpm add @arkstack/common
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Modules
35
+
36
+ ### System
37
+
38
+ **`src/system.ts`**
39
+
40
+ Provides core application-level utilities for environment variable access, configuration loading, path resolution, and dynamic file importing.
41
+
42
+ #### `env(key, defaultValue?)`
43
+
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.
13
45
 
14
46
  ```ts
15
- import { getModel } from '@arkstack/common';
16
- import type User from './src/app/models/User';
47
+ import { env } from '@arkstack/common'
17
48
 
18
- const UserModel = await getModel<typeof User>('User');
49
+ const port = env('PORT', 3000) // number
50
+ const debug = env('DEBUG', false) // boolean
51
+ const name = env('APP_NAME', 'App') // string
19
52
  ```
20
53
 
21
- Apps can augment `ModelRegistry` for typed model names:
54
+ **Type coercion rules:**
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`) |
63
+
64
+ ---
65
+
66
+ #### `config(key?, defaultValue?)`
67
+
68
+ Loads and merges all configuration files from the build output's `config/` directory. Supports dot-path key access with full TypeScript inference.
22
69
 
23
70
  ```ts
24
- declare module '@arkstack/common' {
25
- interface ModelRegistry {
26
- User: typeof User;
27
- }
71
+ import { config } from '@arkstack/common'
72
+
73
+ // Get the full config object
74
+ const allConfig = config()
75
+
76
+ // Access a nested key
77
+ const dbHost = config('database.host', 'localhost')
78
+ ```
79
+
80
+ Config files are loaded from the resolved `outputDir()` using `createRequire`. Middleware config files are skipped when running in CLI context.
81
+
82
+ ---
83
+
84
+ #### `appUrl(link?)`
85
+
86
+ Builds a fully-qualified application URL from `APP_URL` and `PORT` environment variables.
87
+
88
+ ```ts
89
+ import { appUrl } from '@arkstack/common'
90
+
91
+ appUrl() // "http://localhost:3000"
92
+ appUrl('/api/health') // "http://localhost:3000/api/health"
93
+ ```
94
+
95
+ ---
96
+
97
+ #### `nodeEnv()`
98
+
99
+ Returns `'dev'` or `'prod'` based on the `NODE_ENV` environment variable. Defaults to `'dev'` for any unrecognised value.
100
+
101
+ ```ts
102
+ import { nodeEnv } from '@arkstack/common'
103
+
104
+ nodeEnv() // "dev" | "prod"
105
+ ```
106
+
107
+ ---
108
+
109
+ #### `outputDir(cwd?)`
110
+
111
+ Resolves the build output directory. In development, defaults to `.arkstack/build`; in production, to `dist`. Both can be overridden via environment variables:
112
+
113
+ | Variable | Context | Default |
114
+ |----------|---------|---------|
115
+ | `OUTPUT_DIR_DEV` | Development | `.arkstack/build` |
116
+ | `OUTPUT_DIR` | Production | `dist` |
117
+
118
+ ---
119
+
120
+ #### `importFile<T>(filePath)`
121
+
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
+
124
+ ```ts
125
+ import { importFile } from '@arkstack/common'
126
+
127
+ const module = await importFile<{ default: MyConfig }>('./config/app.ts')
128
+ ```
129
+
130
+ ---
131
+
132
+ ### Logger
133
+
134
+ **`src/Logger.ts`**
135
+
136
+ A structured, chalk-powered console logger with verbosity control, two-column formatting, and a composable parsing API.
137
+
138
+ #### Basic log levels
139
+
140
+ ```ts
141
+ import { Logger } from '@arkstack/common'
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
148
+ ```
149
+
150
+ Each level uses a distinct icon and colour:
151
+
152
+ | Method | Icon | Colour |
153
+ |--------|------|--------|
154
+ | `success` | `✓` | Green |
155
+ | `info` | `ℹ` | Blue |
156
+ | `warn` | `⚠` | Yellow |
157
+ | `error` | `✖` | Red |
158
+ | `debug` | `🐛` | Gray |
159
+
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
+
162
+ ---
163
+
164
+ #### `Logger.configure(options)`
165
+
166
+ Sets global verbosity and suppression behaviour.
167
+
168
+ ```ts
169
+ Logger.configure({
170
+ verbosity: 3, // enables debug output
171
+ quiet: true, // suppresses info and success
172
+ silent: true, // suppresses all output
173
+ })
174
+ ```
175
+
176
+ ---
177
+
178
+ #### `Logger.twoColumnDetail(name, value, log?, spacer?)`
179
+
180
+ Renders a right-aligned two-column layout padded to the terminal width.
181
+
182
+ ```ts
183
+ Logger.twoColumnDetail('Route', 'GET /api/users')
184
+ // "Route ......................................... GET /api/users"
185
+
186
+ const row = Logger.twoColumnDetail('Route', 'GET /api/users', false)
187
+ // returns [name, dots, value] without printing
188
+ ```
189
+
190
+ ---
191
+
192
+ #### `Logger.describe(name, desc, width?, log?)`
193
+
194
+ Similar to `twoColumnDetail`, but uses a fixed width with space padding rather than dots. Useful for command help listings.
195
+
196
+ ```ts
197
+ Logger.describe('--port', 'Port to listen on', 40)
198
+ // "--port Port to listen on"
199
+ ```
200
+
201
+ ---
202
+
203
+ #### `Logger.split(name, value, status?, exit?, preserveCol?, spacer?)`
204
+
205
+ Like `twoColumnDetail`, but wraps the left column with a coloured background badge based on `status`.
206
+
207
+ ```ts
208
+ Logger.split('Database', 'Connected', 'success')
209
+ Logger.split('Migration', 'Failed', 'error', true) // exits after logging
210
+ ```
211
+
212
+ ---
213
+
214
+ #### `Logger.parse(config, joiner?, log?, sc?)`
215
+
216
+ Composes a styled string from a `[text, chalkStyle]` pair array.
217
+
218
+ ```ts
219
+ Logger.parse([
220
+ ['Arkstack', 'bold'],
221
+ ['v1.0.0', 'gray'],
222
+ ], ' ') // "Arkstack v1.0.0"
223
+
224
+ // Return instead of print
225
+ const str = Logger.parse([['Ready', 'green']], ' ', false)
226
+ ```
227
+
228
+ ---
229
+
230
+ #### `Logger.log(config, joiner?, log?, sc?)`
231
+
232
+ A flexible polymorphic logger that accepts either a string + style, or a `LoggerParseSignature` array. Returns the `Logger` class when called with no arguments.
233
+
234
+ ```ts
235
+ Logger.log('PORT:3000', 'cyan')
236
+ Logger.log([['PORT', 'bold'], ['3000', 'cyan']], ' ')
237
+ ```
238
+
239
+ ---
240
+
241
+ #### `Logger.chalker(styles[])`
242
+
243
+ Returns a function that applies a chain of chalk styles to any input.
244
+
245
+ ```ts
246
+ const highlight = Logger.chalker(['bold', 'green'])
247
+ console.log(highlight('Ready'))
248
+ ```
249
+
250
+ ---
251
+
252
+ #### `Logger.console()`
253
+
254
+ 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
+
256
+ ```ts
257
+ const console = Logger.console()
258
+ console.log('hello')
259
+ console.warn('watch out')
260
+ ```
261
+
262
+ ---
263
+
264
+ ### ErrorHandler
265
+
266
+ **`src/ErrorHandler.ts`**
267
+
268
+ A static utility class for normalising, serialising, classifying, and logging errors. Integrates with [Pino](https://getpino.io) for persistent error file logging.
269
+
270
+ #### `ErrorHandler.createErrorPayload(err, fallbackMessage?)`
271
+
272
+ The primary method. Converts any thrown value into a consistent `ArkstackErrorPayload` object, handling validation errors, model-not-found errors, and generic errors uniformly.
273
+
274
+ ```ts
275
+ import { ErrorHandler } from '@arkstack/common'
276
+
277
+ try {
278
+ // ...
279
+ } catch (err) {
280
+ const payload = ErrorHandler.createErrorPayload(err, 'Request failed')
281
+ // { status: 'error', code: 422, message: '...', errors: {...} }
282
+ }
283
+ ```
284
+
285
+ **Payload shape:**
286
+
287
+ ```ts
288
+ {
289
+ status: 'error'
290
+ code: number // HTTP status code (100–599)
291
+ message: string
292
+ errors?: unknown // present for validation errors
293
+ stack?: string // present in development unless HIDE_ERROR_STACK is set
28
294
  }
29
295
  ```
30
296
 
31
- ## Error Handling
297
+ **Classification logic:**
298
+
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 |
304
+
305
+ ---
306
+
307
+ #### `ErrorHandler.serializeError(value, seen?)`
308
+
309
+ Recursively serialises any value — including `Error` instances and circular references — into a plain JSON-safe object. Circular references are replaced with `'[Circular]'`.
310
+
311
+ ```ts
312
+ const serialized = ErrorHandler.serializeError(new Error('oops'))
313
+ // { name: 'Error', message: 'oops', stack: '...' }
314
+ ```
315
+
316
+ ---
317
+
318
+ #### `ErrorHandler.normalizeStatusCode(value, fallback?)`
319
+
320
+ Ensures a status code is a valid integer in the range `100–599`. Returns the fallback (default `500`) for anything invalid.
321
+
322
+ ```ts
323
+ ErrorHandler.normalizeStatusCode('422') // 422
324
+ ErrorHandler.normalizeStatusCode('xyz') // 500
325
+ ```
326
+
327
+ ---
328
+
329
+ #### `ErrorHandler.getErrorLogger()`
330
+
331
+ Returns a Pino logger instance that writes to `storage/logs/error.log` (created automatically). Instances are cached per destination path.
332
+
333
+ ---
334
+
335
+ #### `ErrorHandler.logUnhandledError(err, request, message)`
336
+
337
+ Persists an unhandled error to the error log file, including the serialised error and the associated request context.
338
+
339
+ ```ts
340
+ ErrorHandler.logUnhandledError(err, { method: 'GET', url: '/api' }, 'Unhandled exception')
341
+ ```
342
+
343
+ ---
344
+
345
+ #### Classification helpers
346
+
347
+ ```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
354
+ ```
355
+
356
+ All static methods are also exported as named standalone functions for convenience:
357
+
358
+ ```ts
359
+ import {
360
+ createErrorPayload,
361
+ isValidationError,
362
+ serializeError,
363
+ logUnhandledError,
364
+ // ...
365
+ } from '@arkstack/common'
366
+ ```
367
+
368
+ ---
369
+
370
+ ### Exceptions
371
+
372
+ **`src/Exceptions/`**
373
+
374
+ A three-level exception hierarchy for structured error throwing.
375
+
376
+ #### `Exception`
377
+
378
+ Base class extending `Error`. Sets `.name` to `'Exception'`.
379
+
380
+ ```ts
381
+ import { Exception } from '@arkstack/common'
382
+
383
+ throw new Exception('Something went wrong')
384
+ ```
385
+
386
+ ---
387
+
388
+ #### `AppException`
389
+
390
+ Extends `Exception`. Adds `statusCode` (default `400`) and an optional `errors` map for field-level validation errors.
391
+
392
+ ```ts
393
+ import { AppException } from '@arkstack/common'
394
+
395
+ const err = new AppException('Validation failed', 422)
396
+ err.errors = { email: ['Email is required'] }
397
+ ```
398
+
399
+ ---
400
+
401
+ #### `RequestException`
402
+
403
+ Extends `AppException`. Intended for HTTP request-level errors. Provides two static assertion helpers:
404
+
405
+ **`RequestException.assertNotEmpty(value, message, code?)`**
406
+
407
+ Throws a `RequestException` if the value is `null` or `undefined`. Narrows the type on success.
408
+
409
+ ```ts
410
+ import { RequestException } from '@arkstack/common'
411
+
412
+ const user = await User.find(id)
413
+ RequestException.assertNotEmpty(user, 'User not found', 404)
414
+ // user is now User (not null | undefined)
415
+ ```
416
+
417
+ **`RequestException.abortIf(condition, message, code?)`**
418
+
419
+ Throws if the condition is truthy.
32
420
 
33
421
  ```ts
34
- import { ErrorHandler } from '@arkstack/common';
422
+ RequestException.abortIf(!user.isActive, 'Account is suspended', 403)
423
+ ```
424
+
425
+ ---
426
+
427
+ ### Hook
428
+
429
+ **`src/Hook.ts`**
430
+
431
+ A global, named hook registry for extending Arkstack internals without modifying core code. Hooks are keyed by name and support positional slots (`before`, `after`, or any custom string).
432
+
433
+ #### `Hook.set(name, hook)`
434
+
435
+ Registers a hook. Multiple calls for the same name are merged.
436
+
437
+ ```ts
438
+ import { Hook } from '@arkstack/common'
439
+
440
+ Hook.set('request:handle', {
441
+ before: (ctx) => console.log('before handler'),
442
+ after: (ctx) => console.log('after handler'),
443
+ })
444
+ ```
445
+
446
+ ---
447
+
448
+ #### `Hook.get(name, pos?)`
449
+
450
+ Retrieves the full hook object or a specific positional handler.
451
+
452
+ ```ts
453
+ const hook = Hook.get('request:handle') // IHook | undefined
454
+ const before = Hook.get('request:handle', 'before') // function | undefined
455
+ ```
456
+
457
+ ---
458
+
459
+ #### `Hook.has(name, pos?)`
460
+
461
+ Checks whether a hook (or a specific position within it) exists.
462
+
463
+ ```ts
464
+ Hook.has('request:handle') // true | false
465
+ Hook.has('request:handle', 'after') // true | false
466
+ ```
467
+
468
+ ---
469
+
470
+ #### `Hook.unset(name?, pos?)`
471
+
472
+ 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
+
474
+ ```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
478
+ ```
479
+
480
+ ---
481
+
482
+ #### `Hook.getAll()`
483
+
484
+ Returns all registered hooks as a plain record.
485
+
486
+ ```ts
487
+ const hooks = Hook.getAll()
488
+ // { 'request:handle': { before: fn, after: fn } }
489
+ ```
490
+
491
+ ---
492
+
493
+ #### `Hook.clear()`
494
+
495
+ Clears all registered hooks.
496
+
497
+ ---
498
+
499
+ ### Encryption
500
+
501
+ **`src/utils/encryption.ts`**
502
+
503
+ AES-256-GCM symmetric encryption for sensitive values (e.g. two-factor authentication secrets). Requires the `TWO_FACTOR_ENCRYPTION_KEY` environment variable.
504
+
505
+ #### `Encryption.encrypt(value)`
506
+
507
+ Encrypts a string. Returns a colon-delimited base64url string: `<iv>:<authTag>:<ciphertext>`.
508
+
509
+ ```ts
510
+ import { Encryption } from '@arkstack/common'
511
+
512
+ const token = Encryption.encrypt('my-secret-value')
513
+ // "abc123:def456:ghi789"
514
+ ```
515
+
516
+ #### `Encryption.decrypt(payload)`
517
+
518
+ Decrypts a payload produced by `encrypt`. Throws if the format is invalid or the key is wrong.
519
+
520
+ ```ts
521
+ const original = Encryption.decrypt(token)
522
+ // "my-secret-value"
523
+ ```
524
+
525
+ **Environment variable:**
526
+
527
+ | Variable | Required | Description |
528
+ |----------|----------|-------------|
529
+ | `TWO_FACTOR_ENCRYPTION_KEY` | Yes | Raw secret; hashed to a 256-bit key internally via SHA-256 |
530
+
531
+ ---
532
+
533
+ ### Hash
534
+
535
+ **`src/utils/hash.ts`**
536
+
537
+ Password hashing and OTP generation utilities.
538
+
539
+ #### `Hash.make(value)`
540
+
541
+ Hashes a string using bcrypt with a salt factor of 10.
542
+
543
+ ```ts
544
+ import { Hash } from '@arkstack/common'
545
+
546
+ const hashed = await Hash.make('user-password')
547
+ ```
548
+
549
+ #### `Hash.verify(value, hashedValue)`
550
+
551
+ Compares a plain-text value against a bcrypt hash.
552
+
553
+ ```ts
554
+ const isValid = await Hash.verify('user-password', hashed)
555
+ ```
556
+
557
+ #### `Hash.otp(digits?, label?, period?)`
558
+
559
+ Creates a TOTP instance using the `otpauth` library with `SHA1` and a static secret. Suitable for simple time-based OTP flows.
560
+
561
+ ```ts
562
+ const totp = Hash.otp(6, 'user@example.com', 60)
563
+ const token = totp.generate()
564
+ ```
565
+
566
+ #### `Hash.totp(secret, label, issuer?, period?)`
567
+
568
+ Creates a TOTP instance from a base32-encoded secret. Intended for user-specific TOTP (e.g. authenticator app integration).
569
+
570
+ ```ts
571
+ const totp = Hash.totp(user.totpSecret, user.email)
572
+ const isValid = totp.validate({ token: userInput }) !== null
573
+ ```
574
+
575
+ ---
576
+
577
+ ### Network
578
+
579
+ **`src/network.ts`**
580
+
581
+ Utilities for starting an HTTP server with automatic port detection and rendering error views.
582
+
583
+ #### `bootWithDetectedPort(boot, preferredPort?, app?)`
584
+
585
+ 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
+
587
+ ```ts
588
+ import { bootWithDetectedPort } from '@arkstack/common'
589
+
590
+ await bootWithDetectedPort(async (port) => {
591
+ server.listen(port)
592
+ Logger.success(`Server:http://localhost:${port}`)
593
+ }, 3000, appInstance)
594
+ ```
595
+
596
+ **Globals set:**
597
+
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' }` |
605
+
606
+ ---
607
+
608
+ #### `renderError({ message, stack, title, code })`
609
+
610
+ 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
+
612
+ ```ts
613
+ import { renderError } from '@arkstack/common'
614
+
615
+ const html = renderError({ code: 404, message: 'Page not found' })
616
+ ```
617
+
618
+ **Built-in status titles:** `400`, `401`, `403`, `404`, `500`, `502`, `503`, `504`.
619
+
620
+ ---
621
+
622
+ ### Lifecycle
623
+
624
+ **`src/lifecycle.ts`**
625
+
626
+ #### `bindGracefulShutdown(shutdown)`
627
+
628
+ Registers a cleanup callback for `SIGINT`, `SIGTERM`, and `SIGQUIT` signals, ensuring the application shuts down cleanly.
629
+
630
+ ```ts
631
+ import { bindGracefulShutdown } from '@arkstack/common'
632
+
633
+ bindGracefulShutdown(async () => {
634
+ await db.disconnect()
635
+ Logger.info('Server shut down gracefully')
636
+ })
637
+ ```
638
+
639
+ ---
640
+
641
+ ### Prototypes
642
+
643
+ **`src/prototypes.ts`**
644
+
645
+ #### `loadPrototypes()`
646
+
647
+ Extends `String.prototype` with four utility methods. Call this once during application bootstrap.
648
+
649
+ ```ts
650
+ import { loadPrototypes } from '@arkstack/common'
651
+
652
+ loadPrototypes()
653
+ ```
654
+
655
+ **Methods added:**
656
+
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..."` |
663
+
664
+ ---
665
+
666
+ ## Global Augmentations
667
+
668
+ **`src/app.d.ts`**
669
+
670
+ When `loadPrototypes()` is called and `bootWithDetectedPort()` initialises the runtime, the following globals and String prototype extensions are available throughout the application:
671
+
672
+ ```ts
673
+ // Globals (set by bootWithDetectedPort)
674
+ globalThis.env // GlobalEnv — typed env() accessor
675
+ globalThis.config // GlobalConfig — typed config() accessor
676
+
677
+ // String prototype extensions (set by loadPrototypes)
678
+ "my_string".titleCase()
679
+ "my_string".camelCase()
680
+ "my_string".pascalCase()
681
+ "my long string".truncate(10, '…')
682
+ ```
683
+
684
+ ---
685
+
686
+ ## Types
687
+
688
+ **`src/types.ts`**
689
+
690
+ Key exported types from the package:
691
+
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()` |
701
+
702
+ ---
703
+
704
+ ## Environment Variables Reference
705
+
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 |
716
+
717
+ ---
718
+
719
+ ## Helpers
720
+
721
+ **`src/utils/helpers.ts`**
722
+
723
+ #### `perPage(query)`
724
+
725
+ Extracts a safe pagination limit from a query object. Clamps the result between `1` and `50`, defaulting to `15`.
726
+
727
+ ```ts
728
+ import { perPage } from '@arkstack/common'
729
+
730
+ const limit = perPage({ limit: 100 }) // 50 (clamped)
731
+ const limit2 = perPage({}) // 15 (default)
732
+ ```
733
+
734
+ #### `getModel(modelName)`
735
+
736
+ Dynamically imports an application model by name from the configured models directory (default: `./src/models`). Supports augmenting `ModelRegistry` for type-safe lookups.
737
+
738
+ ```ts
739
+ import { getModel } from '@arkstack/common'
740
+
741
+ const User = await getModel('User')
742
+ const users = await User.findAll()
743
+
744
+ // With type augmentation:
745
+ declare module '@arkstack/common' {
746
+ interface ModelRegistry {
747
+ User: typeof User
748
+ }
749
+ }
35
750
 
36
- const payload = ErrorHandler.createErrorPayload(error);
751
+ const TypedUser = await getModel('User') // typeof User
37
752
  ```
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@arkstack/common",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "type": "module",
5
- "description": "Shared foundations and core utilities for Arkstack applications and packages.",
5
+ "description": "Core utilities, primitives, and shared infrastructure for the Arkstack ecosystem.",
6
6
  "homepage": "https://arkstack.toneflix.net",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/arkstack-hq/arkstack.git",
9
+ "url": "git+https://github.com/arkstack-tmp/arkstack.git",
10
10
  "directory": "packages/common"
11
11
  },
12
12
  "keywords": [