@arkstack/common 0.7.9 → 0.7.11

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
  ```
@@ -38,14 +38,14 @@ type DeriveTraitsConsCons<T extends Cons> = new (...args: ConstructorParameters<
38
38
  type DeriveTraitsConsTraitParts<C extends Cons, ST extends ((Trait | TypeFactory<Trait>)[] | undefined)> = ST extends undefined ? DeriveTraitsConsCons<C> : ST extends [] ? DeriveTraitsConsCons<C> : DeriveTraitsConsConsMerge<DeriveTraitsConsCons<C>, DeriveTraitsConsAll<ST>>;
39
39
  type DeriveTraitsConsTrait<T extends Trait> = DeriveTraitsConsTraitParts<ExtractFactory<T>, ExtractSuperTrait<T>>;
40
40
  type DeriveTraitsConsOne<T extends (Trait | TypeFactory<Trait>)> = T extends Trait ? DeriveTraitsConsTrait<T> : T extends TypeFactory<Trait> ? DeriveTraitsConsTrait<ReturnType<T>> : never;
41
- type DeriveTraitsConsAll<T extends (((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons]) | undefined)> = T extends [...infer Others extends (Trait | TypeFactory<Trait>)[], infer Last extends Cons] ? (DeriveTraitsConsConsMerge<DeriveTraitsConsAll<Others>, DeriveTraitsConsCons<Last>>) : T extends (Trait | TypeFactory<Trait>)[] ? (T extends [infer First extends (Trait | TypeFactory<Trait>)] ? (DeriveTraitsConsOne<First>) : (T extends [infer First extends (Trait | TypeFactory<Trait>), ...infer Rest extends (Trait | TypeFactory<Trait>)[]] ? (DeriveTraitsConsConsMerge<DeriveTraitsConsOne<First>, DeriveTraitsConsAll<Rest>>) : never)) : never;
41
+ type DeriveTraitsConsAll<T extends (((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons]) | undefined)> = T extends [infer Only extends Cons] ? DeriveTraitsConsCons<Only> : T extends [...infer Others extends (Trait | TypeFactory<Trait>)[], infer Last extends Cons] ? (Others extends [] ? DeriveTraitsConsCons<Last> : DeriveTraitsConsConsMerge<DeriveTraitsConsAll<Others>, DeriveTraitsConsCons<Last>>) : T extends (Trait | TypeFactory<Trait>)[] ? (T extends [infer First extends (Trait | TypeFactory<Trait>)] ? (DeriveTraitsConsOne<First>) : (T extends [infer First extends (Trait | TypeFactory<Trait>), ...infer Rest extends (Trait | TypeFactory<Trait>)[]] ? (DeriveTraitsConsConsMerge<DeriveTraitsConsOne<First>, DeriveTraitsConsAll<Rest>>) : never)) : never;
42
42
  type DeriveTraitsCons<T extends ((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons])> = DeriveTraitsConsAll<T>;
43
43
  type DeriveTraitsStatsConsMerge<T1 extends object, T2 extends object> = T1 & T2;
44
44
  type DeriveTraitsStatsCons<T extends Cons> = Explode<T>;
45
45
  type DeriveTraitsStatsTraitParts<C extends Cons, ST extends ((Trait | TypeFactory<Trait>)[] | undefined)> = ST extends undefined ? DeriveTraitsStatsCons<C> : ST extends [] ? DeriveTraitsStatsCons<C> : DeriveTraitsStatsConsMerge<DeriveTraitsStatsCons<C>, DeriveTraitsStatsAll<ST>>;
46
46
  type DeriveTraitsStatsTrait<T extends Trait> = DeriveTraitsStatsTraitParts<ExtractFactory<T>, ExtractSuperTrait<T>>;
47
47
  type DeriveTraitsStatsOne<T extends (Trait | TypeFactory<Trait>)> = T extends Trait ? DeriveTraitsStatsTrait<T> : T extends TypeFactory<Trait> ? DeriveTraitsStatsTrait<ReturnType<T>> : never;
48
- type DeriveTraitsStatsAll<T extends (((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons]) | undefined)> = T extends [...infer Others extends (Trait | TypeFactory<Trait>)[], infer Last extends Cons] ? (DeriveTraitsStatsConsMerge<DeriveTraitsStatsAll<Others>, DeriveTraitsStatsCons<Last>>) : T extends (Trait | TypeFactory<Trait>)[] ? (T extends [infer First extends (Trait | TypeFactory<Trait>)] ? (DeriveTraitsStatsOne<First>) : (T extends [infer First extends (Trait | TypeFactory<Trait>), ...infer Rest extends (Trait | TypeFactory<Trait>)[]] ? (DeriveTraitsStatsConsMerge<DeriveTraitsStatsOne<First>, DeriveTraitsStatsAll<Rest>>) : never)) : never;
48
+ type DeriveTraitsStatsAll<T extends (((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons]) | undefined)> = T extends [infer Only extends Cons] ? DeriveTraitsStatsCons<Only> : T extends [...infer Others extends (Trait | TypeFactory<Trait>)[], infer Last extends Cons] ? (Others extends [] ? DeriveTraitsStatsCons<Last> : DeriveTraitsStatsConsMerge<DeriveTraitsStatsAll<Others>, DeriveTraitsStatsCons<Last>>) : T extends (Trait | TypeFactory<Trait>)[] ? (T extends [infer First extends (Trait | TypeFactory<Trait>)] ? (DeriveTraitsStatsOne<First>) : (T extends [infer First extends (Trait | TypeFactory<Trait>), ...infer Rest extends (Trait | TypeFactory<Trait>)[]] ? (DeriveTraitsStatsConsMerge<DeriveTraitsStatsOne<First>, DeriveTraitsStatsAll<Rest>>) : never)) : never;
49
49
  type DeriveTraitsStats<T extends ((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons])> = DeriveTraitsStatsAll<T>;
50
50
  type DeriveTraits<T extends ((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons])> = DeriveTraitsCons<T> & DeriveTraitsStats<T>;
51
51
  declare function use<T extends ([Trait | TypeFactory<Trait>, ...(Trait | TypeFactory<Trait>)[]] | [...(Trait | TypeFactory<Trait>)[], Cons])>(...traits: T): DeriveTraits<T>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/utils/traits.ts"],"sourcesContent":["/*\n** Extracted from @traits-ts/core - Traits for TypeScript Classes\n** Copyright (c) 2025 Dr. Ralf S. Engelschall <rse@engelschall.com>\n** Licensed under MIT license <https://spdx.org/licenses/MIT>\n*/\n\n/**\n * CRC32 implementation in TypeScript, adapted from https://stackoverflow.com/a/18639999\n * Note: This implementation is not cryptographically secure and is only used for generating \n * unique identifiers for traits based on their factory function's string representation. \n */\nconst crcTable = [] as number[]\nfor (let n = 0; n < 256; n++) {\n let c = n\n for (let k = 0; k < 8; k++)\n c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1))\n crcTable[n] = c\n}\nexport const crc32 = (str: string) => {\n let crc = 0 ^ (-1)\n for (let i = 0; i < str.length; i++)\n crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF]\n\n return (crc ^ (-1)) >>> 0\n}\n\ntype ResolveTraitLike<T extends Trait | TypeFactory<Trait>> =\n T extends TypeFactory<Trait>\n ? ExtractFactory<ReturnType<T>>\n : T extends Trait\n ? ExtractFactory<T>\n : unknown;\n\ntype Combine<T extends any[]> =\n T extends [infer Head, ...infer Tail]\n ? Head & Combine<Tail>\n : object;\n\ntype MapClassesToPrototypes<T extends Array<(new () => any) & { prototype: any }>> = {\n [K in keyof T]: T[K]['prototype'];\n}\n\ntype MapClassesToInstances<T extends Array<(new () => any) & { prototype: any }>> = {\n [K in keyof T]: InstanceType<T[K]>;\n}\n\ntype CombineClasses<T extends Array<(new () => any) & { prototype: any }>> =\n (new () => Combine<MapClassesToInstances<T>>) & { prototype: Combine<MapClassesToPrototypes<T>> };\n\ntype ResolveTraitLikeArray<T extends Array<Trait | TypeFactory<Trait>>> = CombineClasses<{\n [K in keyof T]: ResolveTraitLike<T[K]>;\n}>;\n\n/**\n * utility type and function: constructor (function)\n */\ntype Cons<T = any> =\n new (...args: any[]) => T\nconst isCons =\n <T = any>\n (fn: unknown): fn is Cons<T> =>\n typeof fn === 'function' && !!fn.prototype && !!fn.prototype.constructor\n\n/**\n * utility type and function: constructor factory (function)\n */\ntype ConsFactory<T extends Cons = Cons, B = any> =\n (base: B) => T\n\n/**\n * utility type and function: type factory (function)\n */\ntype TypeFactory<T = any> =\n () => T\nconst isTypeFactory =\n <T = any>\n (fn: unknown): fn is TypeFactory<T> =>\n typeof fn === 'function' && !fn.prototype && fn.length === 0\n\n/**\n * utility type: map an object type into a bare properties type\n */\ntype Explode<T = any> =\n { [P in keyof T]: T[P] }\n\n/**\n * utility type: convert two arrays of types into an array of union types\n */\ntype MixParams<T1 extends any[], T2 extends any[]> =\n T1 extends [] ? (\n T2 extends [] ? [] : T2\n ) : (\n T2 extends [] ? T1 : (\n T1 extends [infer H1, ...infer R1] ? (\n T2 extends [infer H2, ...infer R2] ?\n [H1 & H2, ...MixParams<R1, R2>]\n : []\n ) : []\n )\n )\n\n/**\n * API: trait type\n */\ntype TraitDefTypeT = ConsFactory<Cons>\ntype TraitDefTypeST = (Trait | TypeFactory<Trait>)[] | undefined\nexport type Trait<\n T extends TraitDefTypeT = TraitDefTypeT,\n ST extends TraitDefTypeST = TraitDefTypeST\n> = {\n id: number /* unique id (primary, for hasTrait) */\n symbol: symbol /* unique id (secondary, currently unused) */\n factory: T\n superTraits: ST\n}\n\n/**\n * API: generate trait (regular variant)\n * \n * @param factory \n */\nexport function trait<\n T extends ConsFactory<Cons>\n> (factory: T): Trait<T, undefined>\n\n/**\n * API: generate trait (super-trait variant)\n * \n * @param superTraits \n * @param factory \n */\nexport function trait<\n const ST extends (Trait | TypeFactory<Trait>)[],\n T extends ConsFactory<Cons, ResolveTraitLikeArray<ST>>\n> (superTraits: ST, factory: T): Trait<T, ST>\n\n/**\n * API: generate trait (technical implementation)\n * \n * @param args \n */\nexport function trait (...args: any[]): Trait<any, any> {\n const factory: ConsFactory<any, any> = (args.length === 2 ? args[1] : args[0])\n const superTraits: (Trait | TypeFactory<Trait>)[] = (args.length === 2 ? args[0] : undefined)\n\n return {\n id: crc32(factory.toString()),\n symbol: Symbol('trait'),\n factory,\n superTraits\n }\n}\n\n/**\n * utility types: extract factory from a trait\n */\ntype ExtractFactory<\n T extends Trait\n> =\n T extends Trait<\n ConsFactory<infer C>,\n TraitDefTypeST\n > ? C : never\n\n/**\n * utility types: extract supertraits from a trait\n */\ntype ExtractSuperTrait<\n T extends Trait\n> =\n T extends Trait<\n TraitDefTypeT,\n infer ST extends TraitDefTypeST\n > ? ST : never\n\n/**\n * utility type: derive type constructor: merge two constructors\n */\ntype DeriveTraitsConsConsMerge<\n A extends Cons,\n B extends Cons\n> =\n A extends (new (...args: infer ArgsA) => infer RetA) ? (\n B extends (new (...args: infer ArgsB) => infer RetB) ? (\n new (...args: MixParams<ArgsA, ArgsB>) => RetA & RetB\n ) : never\n ) : never\n\n/**\n * utility type: derive type constructor: extract plain constructor\n */\ntype DeriveTraitsConsCons<\n T extends Cons\n> =\n new (...args: ConstructorParameters<T>) => InstanceType<T>\n\n/**\n * utility type: derive type constructor: from trait parts\n */\ntype DeriveTraitsConsTraitParts<\n C extends Cons,\n ST extends ((Trait | TypeFactory<Trait>)[] | undefined)\n> =\n ST extends undefined ? DeriveTraitsConsCons<C> :\n ST extends [] ? DeriveTraitsConsCons<C> :\n DeriveTraitsConsConsMerge<\n DeriveTraitsConsCons<C>,\n DeriveTraitsConsAll<ST>> /* RECURSION */\n\n/**\n * utility type: derive type constructor: from single trait\n */\ntype DeriveTraitsConsTrait<\n T extends Trait\n> =\n DeriveTraitsConsTraitParts<\n ExtractFactory<T>,\n ExtractSuperTrait<T>>\n\n/**\n * utility type: derive type constructor: from single trait or trait factory\n */\ntype DeriveTraitsConsOne<\n T extends (Trait | TypeFactory<Trait>)\n> =\n T extends Trait ? DeriveTraitsConsTrait<T> :\n T extends TypeFactory<Trait> ? DeriveTraitsConsTrait<ReturnType<T>> :\n never\n\n/**\n * utility type: derive type constructor: from one or more traits or trait factories\n */\ntype DeriveTraitsConsAll<\n T extends (((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons]) | undefined)\n> =\n T extends [...infer Others extends (Trait | TypeFactory<Trait>)[], infer Last extends Cons] ? (\n DeriveTraitsConsConsMerge<\n DeriveTraitsConsAll<Others>, /* RECURSION */\n DeriveTraitsConsCons<Last>>\n ) :\n T extends (Trait | TypeFactory<Trait>)[] ? (\n T extends [infer First extends (Trait | TypeFactory<Trait>)] ? (\n DeriveTraitsConsOne<First>\n ) : (\n T extends [\n infer First extends (Trait | TypeFactory<Trait>),\n ...infer Rest extends (Trait | TypeFactory<Trait>)[]] ? (\n DeriveTraitsConsConsMerge<\n DeriveTraitsConsOne<First>,\n DeriveTraitsConsAll<Rest>> /* RECURSION */\n ) : never\n )\n ) : never\n\n/**\n * utility type: derive type constructor\n */\ntype DeriveTraitsCons<\n T extends ((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons])\n> =\n DeriveTraitsConsAll<T>\n\n/**\n * utility type: derive type statics: merge two objects with statics\n */\ntype DeriveTraitsStatsConsMerge<\n T1 extends object,\n T2 extends object\n> =\n T1 & T2\n\n/**\n * utility type: derive type statics: extract plain statics\n */\ntype DeriveTraitsStatsCons<\n T extends Cons\n> =\n Explode<T>\n\n/**\n * utility type: derive type statics: from trait parts\n */\ntype DeriveTraitsStatsTraitParts<\n C extends Cons,\n ST extends ((Trait | TypeFactory<Trait>)[] | undefined)\n> =\n ST extends undefined ? DeriveTraitsStatsCons<C> :\n ST extends [] ? DeriveTraitsStatsCons<C> :\n DeriveTraitsStatsConsMerge<\n DeriveTraitsStatsCons<C>,\n DeriveTraitsStatsAll<ST>> /* RECURSION */\n\n/**\n * utility type: derive type statics: from single trait\n */\ntype DeriveTraitsStatsTrait<\n T extends Trait\n> =\n DeriveTraitsStatsTraitParts<\n ExtractFactory<T>,\n ExtractSuperTrait<T>>\n\n/**\n * utility type: derive type statics: from single trait or trait factory\n */\ntype DeriveTraitsStatsOne<\n T extends (Trait | TypeFactory<Trait>)\n> =\n T extends Trait ? DeriveTraitsStatsTrait<T> :\n T extends TypeFactory<Trait> ? DeriveTraitsStatsTrait<ReturnType<T>> :\n never\n\n/**\n * utility type: derive type statics: from one or more traits or trait factories\n */\ntype DeriveTraitsStatsAll<\n T extends (((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons]) | undefined)\n> =\n T extends [...infer Others extends (Trait | TypeFactory<Trait>)[], infer Last extends Cons] ? (\n DeriveTraitsStatsConsMerge<\n DeriveTraitsStatsAll<Others>, /* RECURSION */\n DeriveTraitsStatsCons<Last>>\n ) :\n T extends (Trait | TypeFactory<Trait>)[] ? (\n T extends [infer First extends (Trait | TypeFactory<Trait>)] ? (\n DeriveTraitsStatsOne<First>\n ) : (\n T extends [\n infer First extends (Trait | TypeFactory<Trait>),\n ...infer Rest extends (Trait | TypeFactory<Trait>)[]] ? (\n DeriveTraitsStatsConsMerge<\n DeriveTraitsStatsOne<First>,\n DeriveTraitsStatsAll<Rest>> /* RECURSION */\n ) : never\n )\n ) : never\n\n/**\n * utility type: derive type statics\n */\ntype DeriveTraitsStats<\n T extends ((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons])\n> =\n DeriveTraitsStatsAll<T>\n\n/**\n * utility type: derive type from one or more traits or trait type factories\n */\ntype DeriveTraits<\n T extends ((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons])\n> =\n DeriveTraitsCons<T> &\n DeriveTraitsStats<T>\n\n/**\n * utility function: add an additional invisible property to an object\n * \n * @param cons \n * @param field \n * @param value \n * @returns \n */\nconst extendProperties =\n (cons: Cons, field: string | symbol, value: any) =>\n Object.defineProperty(cons, field, { value, enumerable: false, writable: false })\n\n/**\n * utility function: get raw trait\n * \n * @param x \n * @returns \n */\nconst rawTrait = (x: (Trait | TypeFactory<Trait>)) =>\n isTypeFactory(x) ? x() : x\n\n/**\n * utility function: derive a trait\n * \n * @param trait$ \n * @param baseClz \n * @param derived \n * @returns \n */\nconst deriveTrait = (\n trait$: Trait | TypeFactory<Trait>,\n baseClz: Cons<any>,\n derived: Map<number, boolean>\n) => {\n /* get real trait */\n const trait = rawTrait(trait$)\n\n /* start with base class */\n let clz = baseClz\n\n /* in case we still have not derived this trait... */\n if (!derived.has(trait.id)) {\n derived.set(trait.id, true)\n\n /* iterate over all of its super traits */\n if (trait.superTraits !== undefined)\n for (const superTrait of reverseTraitList(trait.superTraits))\n clz = deriveTrait(superTrait, clz, derived) /* RECURSION */\n\n /* derive this trait */\n clz = trait.factory(clz)\n extendProperties(clz, 'id', crc32(trait.factory.toString()))\n extendProperties(clz, trait.symbol, true)\n }\n\n return clz\n}\n\n/**\n * utility function: get reversed trait list\n * \n * @param traits \n * @returns \n */\nconst reverseTraitList = (traits: (Trait | TypeFactory<Trait>)[]) =>\n traits.slice().reverse() as (Trait | TypeFactory<Trait>)[]\n\n/**\n * API: derive a class from one or more traits or trait type factories\n * \n * @param traits \n * @returns \n */\nexport function use\n <T extends (\n [Trait | TypeFactory<Trait>, ...(Trait | TypeFactory<Trait>)[]] |\n [...(Trait | TypeFactory<Trait>)[], Cons]\n )>\n (...traits: T): DeriveTraits<T> {\n /* run-time sanity check */\n if (traits.length === 0)\n throw new Error('invalid number of parameters (expected one or more traits)')\n\n /* determine the base class (clz) and the list of traits (lot) */\n let clz: Cons<any>\n let lot: (Trait | TypeFactory<Trait>)[]\n const last = traits[traits.length - 1]\n if (isCons(last) && !isTypeFactory(last)) {\n /* case 1: with trailing regular class */\n clz = last\n lot = traits.slice(0, -1) as (Trait | TypeFactory<Trait>)[]\n } else {\n /* case 2: just regular traits or trait type factories */\n clz = class ROOT { }\n lot = traits as (Trait | TypeFactory<Trait>)[]\n }\n\n /* track already derived traits */\n const derived = new Map<number, boolean>()\n\n /* iterate over all traits */\n for (const trait of reverseTraitList(lot))\n clz = deriveTrait(trait, clz, derived)\n\n return clz as DeriveTraits<T>\n}\n\n/**\n * internal type: implements trait type\n */\ntype DerivedType<T extends Trait> =\n InstanceType<ExtractFactory<T>>\n\n/**\n * internal type: implements trait type or trait type factory\n */\nexport type Derived<T extends (Trait | TypeFactory<Trait> | Cons)> =\n T extends TypeFactory<Trait> ? DerivedType<ReturnType<T>> :\n T extends Trait ? DerivedType<T> :\n T extends Cons ? T :\n never\n\n/**\n * API: type guard for checking whether class instance is derived from a trait\n * \n * @param instance \n * @param trait \n * @returns \n */\nexport function uses\n <T extends (Trait | TypeFactory<Trait> | Cons)>\n (instance: unknown, trait: T): instance is Derived<T> {\n /* ensure the class instance is really an object */\n if (typeof instance !== 'object' || instance === null)\n return false\n let obj = instance\n\n /* special case: regular class */\n if (isCons(trait) && !isTypeFactory(trait))\n return (instance instanceof trait)\n\n /* regular case: trait or trait type factory... */\n const t = (isTypeFactory(trait) ? trait() : trait) as Trait\n const idTrait = t['id']\n while (obj) {\n if (Object.hasOwn(obj, 'constructor')) {\n const id = ((obj.constructor as any)['id'] as number) ?? 0\n if (id === idTrait)\n return true\n }\n obj = Object.getPrototypeOf(obj)\n }\n\n return false\n}"],"mappings":";;;;;;;AAWA,MAAM,WAAW,EAAE;AACnB,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK;CAC1B,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KACnB,IAAM,IAAI,IAAM,aAAc,MAAM,IAAO,MAAM;CACrD,SAAS,KAAK;;AAElB,MAAa,SAAS,QAAgB;CAClC,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAC5B,MAAO,QAAQ,IAAK,UAAU,MAAM,IAAI,WAAW,EAAE,IAAI;CAE7D,QAAQ,MAAO,QAAS;;AAmC5B,MAAM,UAEG,OACD,OAAO,OAAO,cAAc,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,GAAG,UAAU;AAarE,MAAM,iBAEG,OACD,OAAO,OAAO,cAAc,CAAC,GAAG,aAAa,GAAG,WAAW;;;;;;AAgEnE,SAAgB,MAAO,GAAG,MAA8B;CACpD,MAAM,UAAkC,KAAK,WAAW,IAAI,KAAK,KAAK,KAAK;CAC3E,MAAM,cAA+C,KAAK,WAAW,IAAI,KAAK,KAAK,KAAA;CAEnF,OAAO;EACH,IAAI,MAAM,QAAQ,UAAU,CAAC;EAC7B,QAAQ,OAAO,QAAQ;EACvB;EACA;EACH;;;;;;;;;;AAoNL,MAAM,oBACD,MAAY,OAAwB,UACjC,OAAO,eAAe,MAAM,OAAO;CAAE;CAAO,YAAY;CAAO,UAAU;CAAO,CAAC;;;;;;;AAQzF,MAAM,YAAY,MACd,cAAc,EAAE,GAAG,GAAG,GAAG;;;;;;;;;AAU7B,MAAM,eACF,QACA,SACA,YACC;CAED,MAAM,QAAQ,SAAS,OAAO;CAG9B,IAAI,MAAM;CAGV,IAAI,CAAC,QAAQ,IAAI,MAAM,GAAG,EAAE;EACxB,QAAQ,IAAI,MAAM,IAAI,KAAK;EAG3B,IAAI,MAAM,gBAAgB,KAAA,GACtB,KAAK,MAAM,cAAc,iBAAiB,MAAM,YAAY,EACxD,MAAM,YAAY,YAAY,KAAK,QAAQ;EAGnD,MAAM,MAAM,QAAQ,IAAI;EACxB,iBAAiB,KAAK,MAAM,MAAM,MAAM,QAAQ,UAAU,CAAC,CAAC;EAC5D,iBAAiB,KAAK,MAAM,QAAQ,KAAK;;CAG7C,OAAO;;;;;;;;AASX,MAAM,oBAAoB,WACtB,OAAO,OAAO,CAAC,SAAS;;;;;;;AAQ5B,SAAgB,IAKX,GAAG,QAA4B;CAEhC,IAAI,OAAO,WAAW,GAClB,MAAM,IAAI,MAAM,6DAA6D;CAGjF,IAAI;CACJ,IAAI;CACJ,MAAM,OAAO,OAAO,OAAO,SAAS;CACpC,IAAI,OAAO,KAAK,IAAI,CAAC,cAAc,KAAK,EAAE;EAEtC,MAAM;EACN,MAAM,OAAO,MAAM,GAAG,GAAG;QACtB;EAEH,MAAM,MAAM,KAAK;EACjB,MAAM;;CAIV,MAAM,0BAAU,IAAI,KAAsB;CAG1C,KAAK,MAAM,SAAS,iBAAiB,IAAI,EACrC,MAAM,YAAY,OAAO,KAAK,QAAQ;CAE1C,OAAO;;;;;;;;;AAyBX,SAAgB,KAEX,UAAmB,OAAkC;CAEtD,IAAI,OAAO,aAAa,YAAY,aAAa,MAC7C,OAAO;CACX,IAAI,MAAM;CAGV,IAAI,OAAO,MAAM,IAAI,CAAC,cAAc,MAAM,EACtC,OAAQ,oBAAoB;CAIhC,MAAM,WADK,cAAc,MAAM,GAAG,OAAO,GAAG,OAC1B;CAClB,OAAO,KAAK;EACR,IAAI,OAAO,OAAO,KAAK,cAAc;QACpB,IAAI,YAAoB,SAAoB,OAC9C,SACP,OAAO;;EAEf,MAAM,OAAO,eAAe,IAAI;;CAGpC,OAAO"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/utils/traits.ts"],"sourcesContent":["/*\n** Extracted from @traits-ts/core - Traits for TypeScript Classes\n** Copyright (c) 2025 Dr. Ralf S. Engelschall <rse@engelschall.com>\n** Licensed under MIT license <https://spdx.org/licenses/MIT>\n*/\n\n/**\n * CRC32 implementation in TypeScript, adapted from https://stackoverflow.com/a/18639999\n * Note: This implementation is not cryptographically secure and is only used for generating \n * unique identifiers for traits based on their factory function's string representation. \n */\nconst crcTable = [] as number[]\nfor (let n = 0; n < 256; n++) {\n let c = n\n for (let k = 0; k < 8; k++)\n c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1))\n crcTable[n] = c\n}\nexport const crc32 = (str: string) => {\n let crc = 0 ^ (-1)\n for (let i = 0; i < str.length; i++)\n crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF]\n\n return (crc ^ (-1)) >>> 0\n}\n\ntype ResolveTraitLike<T extends Trait | TypeFactory<Trait>> =\n T extends TypeFactory<Trait>\n ? ExtractFactory<ReturnType<T>>\n : T extends Trait\n ? ExtractFactory<T>\n : unknown;\n\ntype Combine<T extends any[]> =\n T extends [infer Head, ...infer Tail]\n ? Head & Combine<Tail>\n : object;\n\ntype MapClassesToPrototypes<T extends Array<(new () => any) & { prototype: any }>> = {\n [K in keyof T]: T[K]['prototype'];\n}\n\ntype MapClassesToInstances<T extends Array<(new () => any) & { prototype: any }>> = {\n [K in keyof T]: InstanceType<T[K]>;\n}\n\ntype CombineClasses<T extends Array<(new () => any) & { prototype: any }>> =\n (new () => Combine<MapClassesToInstances<T>>) & { prototype: Combine<MapClassesToPrototypes<T>> };\n\ntype ResolveTraitLikeArray<T extends Array<Trait | TypeFactory<Trait>>> = CombineClasses<{\n [K in keyof T]: ResolveTraitLike<T[K]>;\n}>;\n\n/**\n * utility type and function: constructor (function)\n */\ntype Cons<T = any> =\n new (...args: any[]) => T\nconst isCons =\n <T = any>\n (fn: unknown): fn is Cons<T> =>\n typeof fn === 'function' && !!fn.prototype && !!fn.prototype.constructor\n\n/**\n * utility type and function: constructor factory (function)\n */\ntype ConsFactory<T extends Cons = Cons, B = any> =\n (base: B) => T\n\n/**\n * utility type and function: type factory (function)\n */\ntype TypeFactory<T = any> =\n () => T\nconst isTypeFactory =\n <T = any>\n (fn: unknown): fn is TypeFactory<T> =>\n typeof fn === 'function' && !fn.prototype && fn.length === 0\n\n/**\n * utility type: map an object type into a bare properties type\n */\ntype Explode<T = any> =\n { [P in keyof T]: T[P] }\n\n/**\n * utility type: convert two arrays of types into an array of union types\n */\ntype MixParams<T1 extends any[], T2 extends any[]> =\n T1 extends [] ? (\n T2 extends [] ? [] : T2\n ) : (\n T2 extends [] ? T1 : (\n T1 extends [infer H1, ...infer R1] ? (\n T2 extends [infer H2, ...infer R2] ?\n [H1 & H2, ...MixParams<R1, R2>]\n : []\n ) : []\n )\n )\n\n/**\n * API: trait type\n */\ntype TraitDefTypeT = ConsFactory<Cons>\ntype TraitDefTypeST = (Trait | TypeFactory<Trait>)[] | undefined\nexport type Trait<\n T extends TraitDefTypeT = TraitDefTypeT,\n ST extends TraitDefTypeST = TraitDefTypeST\n> = {\n id: number /* unique id (primary, for hasTrait) */\n symbol: symbol /* unique id (secondary, currently unused) */\n factory: T\n superTraits: ST\n}\n\n/**\n * API: generate trait (regular variant)\n * \n * @param factory \n */\nexport function trait<\n T extends ConsFactory<Cons>\n> (factory: T): Trait<T, undefined>\n\n/**\n * API: generate trait (super-trait variant)\n * \n * @param superTraits \n * @param factory \n */\nexport function trait<\n const ST extends (Trait | TypeFactory<Trait>)[],\n T extends ConsFactory<Cons, ResolveTraitLikeArray<ST>>\n> (superTraits: ST, factory: T): Trait<T, ST>\n\n/**\n * API: generate trait (technical implementation)\n * \n * @param args \n */\nexport function trait (...args: any[]): Trait<any, any> {\n const factory: ConsFactory<any, any> = (args.length === 2 ? args[1] : args[0])\n const superTraits: (Trait | TypeFactory<Trait>)[] = (args.length === 2 ? args[0] : undefined)\n\n return {\n id: crc32(factory.toString()),\n symbol: Symbol('trait'),\n factory,\n superTraits\n }\n}\n\n/**\n * utility types: extract factory from a trait\n */\ntype ExtractFactory<\n T extends Trait\n> =\n T extends Trait<\n ConsFactory<infer C>,\n TraitDefTypeST\n > ? C : never\n\n/**\n * utility types: extract supertraits from a trait\n */\ntype ExtractSuperTrait<\n T extends Trait\n> =\n T extends Trait<\n TraitDefTypeT,\n infer ST extends TraitDefTypeST\n > ? ST : never\n\n/**\n * utility type: derive type constructor: merge two constructors\n */\ntype DeriveTraitsConsConsMerge<\n A extends Cons,\n B extends Cons\n> =\n A extends (new (...args: infer ArgsA) => infer RetA) ? (\n B extends (new (...args: infer ArgsB) => infer RetB) ? (\n new (...args: MixParams<ArgsA, ArgsB>) => RetA & RetB\n ) : never\n ) : never\n\n/**\n * utility type: derive type constructor: extract plain constructor\n */\ntype DeriveTraitsConsCons<\n T extends Cons\n> =\n new (...args: ConstructorParameters<T>) => InstanceType<T>\n\n/**\n * utility type: derive type constructor: from trait parts\n */\ntype DeriveTraitsConsTraitParts<\n C extends Cons,\n ST extends ((Trait | TypeFactory<Trait>)[] | undefined)\n> =\n ST extends undefined ? DeriveTraitsConsCons<C> :\n ST extends [] ? DeriveTraitsConsCons<C> :\n DeriveTraitsConsConsMerge<\n DeriveTraitsConsCons<C>,\n DeriveTraitsConsAll<ST>> /* RECURSION */\n\n/**\n * utility type: derive type constructor: from single trait\n */\ntype DeriveTraitsConsTrait<\n T extends Trait\n> =\n DeriveTraitsConsTraitParts<\n ExtractFactory<T>,\n ExtractSuperTrait<T>>\n\n/**\n * utility type: derive type constructor: from single trait or trait factory\n */\ntype DeriveTraitsConsOne<\n T extends (Trait | TypeFactory<Trait>)\n> =\n T extends Trait ? DeriveTraitsConsTrait<T> :\n T extends TypeFactory<Trait> ? DeriveTraitsConsTrait<ReturnType<T>> :\n never\n\n/**\n * utility type: derive type constructor: from one or more traits or trait factories\n */\ntype DeriveTraitsConsAll<\n T extends (((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons]) | undefined)\n> =\n T extends [infer Only extends Cons] ? DeriveTraitsConsCons<Only> :\n T extends [...infer Others extends (Trait | TypeFactory<Trait>)[], infer Last extends Cons] ? (\n Others extends [] ? DeriveTraitsConsCons<Last> :\n DeriveTraitsConsConsMerge<\n DeriveTraitsConsAll<Others>, /* RECURSION */\n DeriveTraitsConsCons<Last>>\n ) :\n T extends (Trait | TypeFactory<Trait>)[] ? (\n T extends [infer First extends (Trait | TypeFactory<Trait>)] ? (\n DeriveTraitsConsOne<First>\n ) : (\n T extends [\n infer First extends (Trait | TypeFactory<Trait>),\n ...infer Rest extends (Trait | TypeFactory<Trait>)[]] ? (\n DeriveTraitsConsConsMerge<\n DeriveTraitsConsOne<First>,\n DeriveTraitsConsAll<Rest>> /* RECURSION */\n ) : never\n )\n ) : never\n\n/**\n * utility type: derive type constructor\n */\ntype DeriveTraitsCons<\n T extends ((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons])\n> =\n DeriveTraitsConsAll<T>\n\n/**\n * utility type: derive type statics: merge two objects with statics\n */\ntype DeriveTraitsStatsConsMerge<\n T1 extends object,\n T2 extends object\n> =\n T1 & T2\n\n/**\n * utility type: derive type statics: extract plain statics\n */\ntype DeriveTraitsStatsCons<\n T extends Cons\n> =\n Explode<T>\n\n/**\n * utility type: derive type statics: from trait parts\n */\ntype DeriveTraitsStatsTraitParts<\n C extends Cons,\n ST extends ((Trait | TypeFactory<Trait>)[] | undefined)\n> =\n ST extends undefined ? DeriveTraitsStatsCons<C> :\n ST extends [] ? DeriveTraitsStatsCons<C> :\n DeriveTraitsStatsConsMerge<\n DeriveTraitsStatsCons<C>,\n DeriveTraitsStatsAll<ST>> /* RECURSION */\n\n/**\n * utility type: derive type statics: from single trait\n */\ntype DeriveTraitsStatsTrait<\n T extends Trait\n> =\n DeriveTraitsStatsTraitParts<\n ExtractFactory<T>,\n ExtractSuperTrait<T>>\n\n/**\n * utility type: derive type statics: from single trait or trait factory\n */\ntype DeriveTraitsStatsOne<\n T extends (Trait | TypeFactory<Trait>)\n> =\n T extends Trait ? DeriveTraitsStatsTrait<T> :\n T extends TypeFactory<Trait> ? DeriveTraitsStatsTrait<ReturnType<T>> :\n never\n\n/**\n * utility type: derive type statics: from one or more traits or trait factories\n */\ntype DeriveTraitsStatsAll<\n T extends (((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons]) | undefined)\n> =\n T extends [infer Only extends Cons] ? DeriveTraitsStatsCons<Only> :\n T extends [...infer Others extends (Trait | TypeFactory<Trait>)[], infer Last extends Cons] ? (\n Others extends [] ? DeriveTraitsStatsCons<Last> :\n DeriveTraitsStatsConsMerge<\n DeriveTraitsStatsAll<Others>, /* RECURSION */\n DeriveTraitsStatsCons<Last>>\n ) :\n T extends (Trait | TypeFactory<Trait>)[] ? (\n T extends [infer First extends (Trait | TypeFactory<Trait>)] ? (\n DeriveTraitsStatsOne<First>\n ) : (\n T extends [\n infer First extends (Trait | TypeFactory<Trait>),\n ...infer Rest extends (Trait | TypeFactory<Trait>)[]] ? (\n DeriveTraitsStatsConsMerge<\n DeriveTraitsStatsOne<First>,\n DeriveTraitsStatsAll<Rest>> /* RECURSION */\n ) : never\n )\n ) : never\n\n/**\n * utility type: derive type statics\n */\ntype DeriveTraitsStats<\n T extends ((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons])\n> =\n DeriveTraitsStatsAll<T>\n\n/**\n * utility type: derive type from one or more traits or trait type factories\n */\ntype DeriveTraits<\n T extends ((Trait | TypeFactory<Trait>)[] | [...(Trait | TypeFactory<Trait>)[], Cons])\n> =\n DeriveTraitsCons<T> &\n DeriveTraitsStats<T>\n\n/**\n * utility function: add an additional invisible property to an object\n * \n * @param cons \n * @param field \n * @param value \n * @returns \n */\nconst extendProperties =\n (cons: Cons, field: string | symbol, value: any) =>\n Object.defineProperty(cons, field, { value, enumerable: false, writable: false })\n\n/**\n * utility function: get raw trait\n * \n * @param x \n * @returns \n */\nconst rawTrait = (x: (Trait | TypeFactory<Trait>)) =>\n isTypeFactory(x) ? x() : x\n\n/**\n * utility function: derive a trait\n * \n * @param trait$ \n * @param baseClz \n * @param derived \n * @returns \n */\nconst deriveTrait = (\n trait$: Trait | TypeFactory<Trait>,\n baseClz: Cons<any>,\n derived: Map<number, boolean>\n) => {\n /* get real trait */\n const trait = rawTrait(trait$)\n\n /* start with base class */\n let clz = baseClz\n\n /* in case we still have not derived this trait... */\n if (!derived.has(trait.id)) {\n derived.set(trait.id, true)\n\n /* iterate over all of its super traits */\n if (trait.superTraits !== undefined)\n for (const superTrait of reverseTraitList(trait.superTraits))\n clz = deriveTrait(superTrait, clz, derived) /* RECURSION */\n\n /* derive this trait */\n clz = trait.factory(clz)\n extendProperties(clz, 'id', crc32(trait.factory.toString()))\n extendProperties(clz, trait.symbol, true)\n }\n\n return clz\n}\n\n/**\n * utility function: get reversed trait list\n * \n * @param traits \n * @returns \n */\nconst reverseTraitList = (traits: (Trait | TypeFactory<Trait>)[]) =>\n traits.slice().reverse() as (Trait | TypeFactory<Trait>)[]\n\n/**\n * API: derive a class from one or more traits or trait type factories\n * \n * @param traits \n * @returns \n */\nexport function use\n <T extends (\n [Trait | TypeFactory<Trait>, ...(Trait | TypeFactory<Trait>)[]] |\n [...(Trait | TypeFactory<Trait>)[], Cons]\n )>\n (...traits: T): DeriveTraits<T> {\n /* run-time sanity check */\n if (traits.length === 0)\n throw new Error('invalid number of parameters (expected one or more traits)')\n\n /* determine the base class (clz) and the list of traits (lot) */\n let clz: Cons<any>\n let lot: (Trait | TypeFactory<Trait>)[]\n const last = traits[traits.length - 1]\n if (isCons(last) && !isTypeFactory(last)) {\n /* case 1: with trailing regular class */\n clz = last\n lot = traits.slice(0, -1) as (Trait | TypeFactory<Trait>)[]\n } else {\n /* case 2: just regular traits or trait type factories */\n clz = class ROOT { }\n lot = traits as (Trait | TypeFactory<Trait>)[]\n }\n\n /* track already derived traits */\n const derived = new Map<number, boolean>()\n\n /* iterate over all traits */\n for (const trait of reverseTraitList(lot))\n clz = deriveTrait(trait, clz, derived)\n\n return clz as DeriveTraits<T>\n}\n\n/**\n * internal type: implements trait type\n */\ntype DerivedType<T extends Trait> =\n InstanceType<ExtractFactory<T>>\n\n/**\n * internal type: implements trait type or trait type factory\n */\nexport type Derived<T extends (Trait | TypeFactory<Trait> | Cons)> =\n T extends TypeFactory<Trait> ? DerivedType<ReturnType<T>> :\n T extends Trait ? DerivedType<T> :\n T extends Cons ? T :\n never\n\n/**\n * API: type guard for checking whether class instance is derived from a trait\n * \n * @param instance \n * @param trait \n * @returns \n */\nexport function uses\n <T extends (Trait | TypeFactory<Trait> | Cons)>\n (instance: unknown, trait: T): instance is Derived<T> {\n /* ensure the class instance is really an object */\n if (typeof instance !== 'object' || instance === null)\n return false\n let obj = instance\n\n /* special case: regular class */\n if (isCons(trait) && !isTypeFactory(trait))\n return (instance instanceof trait)\n\n /* regular case: trait or trait type factory... */\n const t = (isTypeFactory(trait) ? trait() : trait) as Trait\n const idTrait = t['id']\n while (obj) {\n if (Object.hasOwn(obj, 'constructor')) {\n const id = ((obj.constructor as any)['id'] as number) ?? 0\n if (id === idTrait)\n return true\n }\n obj = Object.getPrototypeOf(obj)\n }\n\n return false\n}\n"],"mappings":";;;;;;;AAWA,MAAM,WAAW,EAAE;AACnB,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK;CAC1B,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KACnB,IAAM,IAAI,IAAM,aAAc,MAAM,IAAO,MAAM;CACrD,SAAS,KAAK;;AAElB,MAAa,SAAS,QAAgB;CAClC,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAC5B,MAAO,QAAQ,IAAK,UAAU,MAAM,IAAI,WAAW,EAAE,IAAI;CAE7D,QAAQ,MAAO,QAAS;;AAmC5B,MAAM,UAEG,OACD,OAAO,OAAO,cAAc,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,GAAG,UAAU;AAarE,MAAM,iBAEG,OACD,OAAO,OAAO,cAAc,CAAC,GAAG,aAAa,GAAG,WAAW;;;;;;AAgEnE,SAAgB,MAAO,GAAG,MAA8B;CACpD,MAAM,UAAkC,KAAK,WAAW,IAAI,KAAK,KAAK,KAAK;CAC3E,MAAM,cAA+C,KAAK,WAAW,IAAI,KAAK,KAAK,KAAA;CAEnF,OAAO;EACH,IAAI,MAAM,QAAQ,UAAU,CAAC;EAC7B,QAAQ,OAAO,QAAQ;EACvB;EACA;EACH;;;;;;;;;;AAwNL,MAAM,oBACD,MAAY,OAAwB,UACjC,OAAO,eAAe,MAAM,OAAO;CAAE;CAAO,YAAY;CAAO,UAAU;CAAO,CAAC;;;;;;;AAQzF,MAAM,YAAY,MACd,cAAc,EAAE,GAAG,GAAG,GAAG;;;;;;;;;AAU7B,MAAM,eACF,QACA,SACA,YACC;CAED,MAAM,QAAQ,SAAS,OAAO;CAG9B,IAAI,MAAM;CAGV,IAAI,CAAC,QAAQ,IAAI,MAAM,GAAG,EAAE;EACxB,QAAQ,IAAI,MAAM,IAAI,KAAK;EAG3B,IAAI,MAAM,gBAAgB,KAAA,GACtB,KAAK,MAAM,cAAc,iBAAiB,MAAM,YAAY,EACxD,MAAM,YAAY,YAAY,KAAK,QAAQ;EAGnD,MAAM,MAAM,QAAQ,IAAI;EACxB,iBAAiB,KAAK,MAAM,MAAM,MAAM,QAAQ,UAAU,CAAC,CAAC;EAC5D,iBAAiB,KAAK,MAAM,QAAQ,KAAK;;CAG7C,OAAO;;;;;;;;AASX,MAAM,oBAAoB,WACtB,OAAO,OAAO,CAAC,SAAS;;;;;;;AAQ5B,SAAgB,IAKX,GAAG,QAA4B;CAEhC,IAAI,OAAO,WAAW,GAClB,MAAM,IAAI,MAAM,6DAA6D;CAGjF,IAAI;CACJ,IAAI;CACJ,MAAM,OAAO,OAAO,OAAO,SAAS;CACpC,IAAI,OAAO,KAAK,IAAI,CAAC,cAAc,KAAK,EAAE;EAEtC,MAAM;EACN,MAAM,OAAO,MAAM,GAAG,GAAG;QACtB;EAEH,MAAM,MAAM,KAAK;EACjB,MAAM;;CAIV,MAAM,0BAAU,IAAI,KAAsB;CAG1C,KAAK,MAAM,SAAS,iBAAiB,IAAI,EACrC,MAAM,YAAY,OAAO,KAAK,QAAQ;CAE1C,OAAO;;;;;;;;;AAyBX,SAAgB,KAEX,UAAmB,OAAkC;CAEtD,IAAI,OAAO,aAAa,YAAY,aAAa,MAC7C,OAAO;CACX,IAAI,MAAM;CAGV,IAAI,OAAO,MAAM,IAAI,CAAC,cAAc,MAAM,EACtC,OAAQ,oBAAoB;CAIhC,MAAM,WADK,cAAc,MAAM,GAAG,OAAO,GAAG,OAC1B;CAClB,OAAO,KAAK;EACR,IAAI,OAAO,OAAO,KAAK,cAAc;QACpB,IAAI,YAAoB,SAAoB,OAC9C,SACP,OAAO;;EAEf,MAAM,OAAO,eAAe,IAAI;;CAGpC,OAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/common",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
4
4
  "type": "module",
5
5
  "description": "Core utilities, primitives, and shared infrastructure for the Arkstack ecosystem.",
6
6
  "homepage": "https://arkstack.toneflix.net",