@arkstack/common 0.7.9 → 0.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -168
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
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)
|
|
50
|
-
const debug = env('DEBUG', false)
|
|
51
|
-
const name = env('APP_NAME', 'App')
|
|
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
|
|
57
|
-
|
|
58
|
-
| `"true"` / `"on"`
|
|
59
|
-
| `"false"` / `"off"` | `false`
|
|
60
|
-
| Numeric string
|
|
61
|
-
| `"null"`
|
|
62
|
-
| `""`
|
|
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()
|
|
92
|
-
appUrl('/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
|
|
114
|
-
|
|
113
|
+
| Variable | Context | Default |
|
|
114
|
+
| ---------------- | ----------- | ----------------- |
|
|
115
115
|
| `OUTPUT_DIR_DEV` | Development | `.arkstack/build` |
|
|
116
|
-
| `OUTPUT_DIR`
|
|
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)
|
|
147
|
-
Logger.debug('Internal state dump')
|
|
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
|
|
153
|
-
|
|
154
|
-
| `success` | `✓`
|
|
155
|
-
| `info`
|
|
156
|
-
| `warn`
|
|
157
|
-
| `error`
|
|
158
|
-
| `debug`
|
|
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,
|
|
171
|
-
quiet: true,
|
|
172
|
-
silent: true,
|
|
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
|
-
[
|
|
221
|
-
|
|
222
|
-
|
|
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(
|
|
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
|
|
300
|
-
|
|
301
|
-
| Validation error (has `.errors`)
|
|
302
|
-
| Model not found (has `.getModelName()`) | `404`
|
|
303
|
-
| Generic error
|
|
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')
|
|
324
|
-
ErrorHandler.normalizeStatusCode('xyz')
|
|
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(
|
|
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)
|
|
349
|
-
ErrorHandler.isModelNotFoundError(err)
|
|
350
|
-
ErrorHandler.shouldLogError(err)
|
|
351
|
-
ErrorHandler.shouldHideStack()
|
|
352
|
-
ErrorHandler.getPrimaryError(err)
|
|
353
|
-
ErrorHandler.toErrorShape(value)
|
|
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')
|
|
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')
|
|
465
|
-
Hook.has('request:handle', 'after')
|
|
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')
|
|
476
|
-
Hook.unset('request:handle')
|
|
477
|
-
Hook.unset()
|
|
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
|
|
528
|
-
|
|
529
|
-
| `TWO_FACTOR_ENCRYPTION_KEY` | Yes
|
|
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(
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
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
|
|
599
|
-
|
|
600
|
-
| `globalThis.app`
|
|
601
|
-
| `globalThis.env`
|
|
602
|
-
| `globalThis.config` | `config`
|
|
603
|
-
| `globalThis.str`
|
|
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
|
|
658
|
-
|
|
659
|
-
| `.titleCase()`
|
|
660
|
-
| `.camelCase()`
|
|
661
|
-
| `.pascalCase()`
|
|
662
|
-
| `.truncate(len, suffix?)` | Truncates at word boundary
|
|
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
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
|
693
|
-
|
|
694
|
-
| `GlobalEnv`
|
|
695
|
-
| `GlobalConfig`
|
|
696
|
-
| `ArkstackErrorShape`
|
|
697
|
-
| `ArkstackErrorPayload` | Normalised HTTP error response shape produced by `ErrorHandler`
|
|
698
|
-
| `LoggerChalk`
|
|
699
|
-
| `LoggerParseSignature` | Array of `[string, LoggerChalk]` pairs for `Logger.parse()`
|
|
700
|
-
| `LoggerLog`
|
|
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
|
|
707
|
-
|
|
708
|
-
| `PORT`
|
|
709
|
-
| `APP_URL`
|
|
710
|
-
| `APP_NAME`
|
|
711
|
-
| `NODE_ENV`
|
|
712
|
-
| `OUTPUT_DIR`
|
|
713
|
-
| `OUTPUT_DIR_DEV`
|
|
714
|
-
| `TWO_FACTOR_ENCRYPTION_KEY` | `encryption`
|
|
715
|
-
| `HIDE_ERROR_STACK`
|
|
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 })
|
|
731
|
-
const limit2 = perPage({})
|
|
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
|
```
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -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>;
|
package/dist/utils/index.js.map
CHANGED
|
@@ -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