@c9up/ream 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +3 -9
  2. package/dist/Application.d.ts +85 -5
  3. package/dist/Application.d.ts.map +1 -1
  4. package/dist/Application.js +171 -21
  5. package/dist/Application.js.map +1 -1
  6. package/dist/ErrorBoundary.d.ts +1 -7
  7. package/dist/ErrorBoundary.d.ts.map +1 -1
  8. package/dist/ErrorBoundary.js +31 -31
  9. package/dist/ErrorBoundary.js.map +1 -1
  10. package/dist/HttpKernel.js +2 -2
  11. package/dist/HttpKernel.js.map +1 -1
  12. package/dist/Ignitor.d.ts +18 -49
  13. package/dist/Ignitor.d.ts.map +1 -1
  14. package/dist/Ignitor.js +211 -173
  15. package/dist/Ignitor.js.map +1 -1
  16. package/dist/env/Env.d.ts +6 -0
  17. package/dist/env/Env.d.ts.map +1 -1
  18. package/dist/env/Env.js +6 -0
  19. package/dist/env/Env.js.map +1 -1
  20. package/dist/events/Emitter.d.ts +0 -6
  21. package/dist/events/Emitter.d.ts.map +1 -1
  22. package/dist/events/Emitter.js +42 -42
  23. package/dist/events/Emitter.js.map +1 -1
  24. package/dist/events/native.d.ts +13 -11
  25. package/dist/events/native.d.ts.map +1 -1
  26. package/dist/events/native.js.map +1 -1
  27. package/dist/helpers/compose.d.ts +30 -0
  28. package/dist/helpers/compose.d.ts.map +1 -0
  29. package/dist/helpers/compose.js +27 -0
  30. package/dist/helpers/compose.js.map +1 -0
  31. package/dist/helpers/index.d.ts +1 -0
  32. package/dist/helpers/index.d.ts.map +1 -1
  33. package/dist/helpers/index.js +1 -0
  34. package/dist/helpers/index.js.map +1 -1
  35. package/dist/helpers/is.d.ts +69 -16
  36. package/dist/helpers/is.d.ts.map +1 -1
  37. package/dist/helpers/is.js +79 -16
  38. package/dist/helpers/is.js.map +1 -1
  39. package/dist/index.d.ts +4 -3
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2 -3
  42. package/dist/index.js.map +1 -1
  43. package/dist/middleware/Pipeline.d.ts +9 -3
  44. package/dist/middleware/Pipeline.d.ts.map +1 -1
  45. package/dist/middleware/Pipeline.js +10 -4
  46. package/dist/middleware/Pipeline.js.map +1 -1
  47. package/dist/native/generated.d.ts +243 -0
  48. package/dist/native/generated.d.ts.map +1 -0
  49. package/dist/native/generated.js +7 -0
  50. package/dist/native/generated.js.map +1 -0
  51. package/dist/rpc/RpcRouter.js +2 -2
  52. package/dist/rpc/RpcRouter.js.map +1 -1
  53. package/dist/services/app.d.ts +6 -0
  54. package/dist/services/app.d.ts.map +1 -1
  55. package/dist/services/app.js +8 -0
  56. package/dist/services/app.js.map +1 -1
  57. package/dist/services/config.d.ts +15 -0
  58. package/dist/services/config.d.ts.map +1 -0
  59. package/dist/services/config.js +18 -0
  60. package/dist/services/config.js.map +1 -0
  61. package/dist/services/encryption.d.ts +23 -0
  62. package/dist/services/encryption.d.ts.map +1 -0
  63. package/dist/services/encryption.js +33 -0
  64. package/dist/services/encryption.js.map +1 -0
  65. package/dist/services/urlBuilder.d.ts +21 -0
  66. package/dist/services/urlBuilder.d.ts.map +1 -0
  67. package/dist/services/urlBuilder.js +34 -0
  68. package/dist/services/urlBuilder.js.map +1 -0
  69. package/dist/session/drivers/CookieDriver.d.ts +1 -1
  70. package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
  71. package/dist/session/drivers/CookieDriver.js +4 -4
  72. package/dist/session/drivers/CookieDriver.js.map +1 -1
  73. package/dist/testing/ApiResponse.d.ts +44 -44
  74. package/dist/testing/ApiResponse.d.ts.map +1 -1
  75. package/dist/testing/ApiResponse.js +48 -48
  76. package/dist/testing/ApiResponse.js.map +1 -1
  77. package/dist/testing/RequestBuilder.d.ts +39 -39
  78. package/dist/testing/RequestBuilder.d.ts.map +1 -1
  79. package/dist/testing/RequestBuilder.js +49 -49
  80. package/dist/testing/RequestBuilder.js.map +1 -1
  81. package/dist/testing/TestClient.d.ts +5 -5
  82. package/dist/testing/TestClient.d.ts.map +1 -1
  83. package/dist/testing/TestClient.js +7 -7
  84. package/dist/testing/TestClient.js.map +1 -1
  85. package/events.win32-x64-msvc.node +0 -0
  86. package/index.win32-x64-msvc.node +0 -0
  87. package/package.json +15 -2
  88. package/scheduler.win32-x64-msvc.node +0 -0
  89. package/scripts/build-napi-types.mjs +68 -0
  90. package/scripts/generate-napi-types.mjs +171 -0
  91. package/src/Application.ts +221 -21
  92. package/src/ErrorBoundary.ts +31 -31
  93. package/src/HttpKernel.ts +2 -2
  94. package/src/Ignitor.ts +228 -176
  95. package/src/env/Env.ts +6 -0
  96. package/src/events/Emitter.ts +42 -42
  97. package/src/events/native.ts +21 -14
  98. package/src/helpers/compose.ts +84 -0
  99. package/src/helpers/index.ts +1 -0
  100. package/src/helpers/is.ts +87 -16
  101. package/src/index.ts +3 -3
  102. package/src/middleware/Pipeline.ts +10 -4
  103. package/src/native/generated.ts +291 -0
  104. package/src/rpc/RpcRouter.ts +2 -2
  105. package/src/services/app.ts +9 -0
  106. package/src/services/config.ts +24 -0
  107. package/src/services/encryption.ts +42 -0
  108. package/src/services/urlBuilder.ts +41 -0
  109. package/src/session/drivers/CookieDriver.ts +4 -4
  110. package/src/testing/ApiResponse.ts +52 -52
  111. package/src/testing/RequestBuilder.ts +55 -55
  112. package/src/testing/TestClient.ts +8 -8
package/src/env/Env.ts CHANGED
@@ -68,6 +68,12 @@ export class Env<Values extends Record<string, unknown>> {
68
68
 
69
69
  readonly #values: Values
70
70
 
71
+ /**
72
+ * Private on purpose: an `Env` is only ever built by {@link create}, which is
73
+ * what validates. `private` and not `#` because a private CONSTRUCTOR has no
74
+ * native form — this is the one place the keyword carries a meaning `#`
75
+ * cannot express.
76
+ */
71
77
  private constructor(values: Values) {
72
78
  this.#values = values
73
79
  }
@@ -66,10 +66,10 @@ export type UnsubscribeFunction = () => void
66
66
  export type AsyncUnsubscribeFunction = () => Promise<void>
67
67
 
68
68
  export class Emitter {
69
- private bus: EventBus
70
- private resolver?: EmitterResolver
71
- private classListeners: Map<EventConstructor, Listener[]> = new Map()
72
- private stringListeners: Map<string, ListenerFn[]> = new Map()
69
+ #bus: EventBus
70
+ #resolver?: EmitterResolver
71
+ #classListeners: Map<EventConstructor, Listener[]> = new Map()
72
+ #stringListeners: Map<string, ListenerFn[]> = new Map()
73
73
  /** Set while faking; every emission lands here instead of running. */
74
74
  #buffer?: EventsBuffer
75
75
  /** Which events are faked. `undefined` means all of them. */
@@ -78,8 +78,8 @@ export class Emitter {
78
78
  readonly #anySubscriptions = new Map<(eventName: string, data: unknown) => void, Set<number>>()
79
79
 
80
80
  constructor(bus: EventBus, resolver?: EmitterResolver) {
81
- this.bus = bus
82
- this.resolver = resolver
81
+ this.#bus = bus
82
+ this.#resolver = resolver
83
83
  }
84
84
 
85
85
  // ─── Class-based events ───────────────────────────────────
@@ -102,18 +102,18 @@ export class Emitter {
102
102
  on<T>(event: string, listener: ListenerFn<T>): UnsubscribeFunction
103
103
  on(event: EventConstructor | string, listener: Listener): UnsubscribeFunction {
104
104
  if (typeof event === 'string') {
105
- const list = this.stringListeners.get(event) ?? []
105
+ const list = this.#stringListeners.get(event) ?? []
106
106
  list.push(listener as ListenerFn)
107
- this.stringListeners.set(event, list)
107
+ this.#stringListeners.set(event, list)
108
108
  return () => {
109
109
  this.off(event, listener as ListenerFn)
110
110
  }
111
111
  }
112
- const list = this.classListeners.get(event) ?? []
112
+ const list = this.#classListeners.get(event) ?? []
113
113
  list.push(listener)
114
- this.classListeners.set(event, list)
114
+ this.#classListeners.set(event, list)
115
115
  return () => {
116
- const current = this.classListeners.get(event)
116
+ const current = this.#classListeners.get(event)
117
117
  if (!current) return
118
118
  const index = current.indexOf(listener)
119
119
  if (index !== -1) current.splice(index, 1)
@@ -159,11 +159,11 @@ export class Emitter {
159
159
 
160
160
  /** Remove one listener from an event (AdonisJS `off`). */
161
161
  off(event: string, listener: ListenerFn): void {
162
- const list = this.stringListeners.get(event)
162
+ const list = this.#stringListeners.get(event)
163
163
  if (!list) return
164
164
  const index = list.indexOf(listener)
165
165
  if (index !== -1) list.splice(index, 1)
166
- if (list.length === 0) this.stringListeners.delete(event)
166
+ if (list.length === 0) this.#stringListeners.delete(event)
167
167
  }
168
168
 
169
169
  /** Alias of {@link off} (AdonisJS `clearListener`). */
@@ -173,13 +173,13 @@ export class Emitter {
173
173
 
174
174
  /** Drop every listener of one event (AdonisJS `clearListeners`). */
175
175
  clearListeners(event: string): void {
176
- this.stringListeners.delete(event)
176
+ this.#stringListeners.delete(event)
177
177
  }
178
178
 
179
179
  /** Drop every listener of every event (AdonisJS `clearAllListeners`). */
180
180
  clearAllListeners(): void {
181
- this.stringListeners.clear()
182
- this.classListeners.clear()
181
+ this.#stringListeners.clear()
182
+ this.#classListeners.clear()
183
183
  }
184
184
 
185
185
  /**
@@ -191,17 +191,17 @@ export class Emitter {
191
191
  */
192
192
  get eventsListeners(): Map<EventConstructor | string, Listener[]> {
193
193
  const all = new Map<EventConstructor | string, Listener[]>()
194
- for (const [event, listeners] of this.classListeners) all.set(event, [...listeners])
195
- for (const [event, listeners] of this.stringListeners) all.set(event, [...listeners])
194
+ for (const [event, listeners] of this.#classListeners) all.set(event, [...listeners])
195
+ for (const [event, listeners] of this.#stringListeners) all.set(event, [...listeners])
196
196
  return all
197
197
  }
198
198
 
199
199
  /** How many listeners an event has, or all of them (AdonisJS `listenerCount`). */
200
200
  listenerCount(event?: string): number {
201
- if (event !== undefined) return this.stringListeners.get(event)?.length ?? 0
201
+ if (event !== undefined) return this.#stringListeners.get(event)?.length ?? 0
202
202
  let total = 0
203
- for (const list of this.stringListeners.values()) total += list.length
204
- for (const list of this.classListeners.values()) total += list.length
203
+ for (const list of this.#stringListeners.values()) total += list.length
204
+ for (const list of this.#classListeners.values()) total += list.length
205
205
  return total
206
206
  }
207
207
 
@@ -216,7 +216,7 @@ export class Emitter {
216
216
  */
217
217
  async #dispatchListeners(event: string, data: unknown, serial: boolean): Promise<void> {
218
218
  const listeners = [
219
- ...(this.stringListeners.get(event) ?? []),
219
+ ...(this.#stringListeners.get(event) ?? []),
220
220
  ...this.#wildcardListenersFor(event),
221
221
  ]
222
222
  if (listeners.length === 0) return
@@ -224,7 +224,7 @@ export class Emitter {
224
224
  try {
225
225
  await fn(data)
226
226
  } catch (err) {
227
- this.emitError(event, err)
227
+ this.#emitError(event, err)
228
228
  }
229
229
  }
230
230
  if (serial) {
@@ -281,17 +281,17 @@ export class Emitter {
281
281
  try {
282
282
  payload = JSON.stringify(this.#wrapForBus(data))
283
283
  } catch (err) {
284
- this.emitError(event, err)
284
+ this.#emitError(event, err)
285
285
  return
286
286
  }
287
- void this.bus.emit(event, payload).catch((err: unknown) => {
288
- this.emitError(event, err)
287
+ void this.#bus.emit(event, payload).catch((err: unknown) => {
288
+ this.#emitError(event, err)
289
289
  })
290
290
  }
291
291
 
292
292
  /** Emit an error event for listener failures. */
293
- private emitError(event: string, error: unknown): void {
294
- const errorListeners = this.stringListeners.get('emitter:error')
293
+ #emitError(event: string, error: unknown): void {
294
+ const errorListeners = this.#stringListeners.get('emitter:error')
295
295
  if (errorListeners && errorListeners.length > 0) {
296
296
  for (const fn of errorListeners) {
297
297
  try {
@@ -314,7 +314,7 @@ export class Emitter {
314
314
  // Faked by the class, as AdonisJS does — `fake([TaskDeclared])` catches
315
315
  // `new TaskDeclared(...).emit()`.
316
316
  if (this.#capture(EventClass, event)) return
317
- const listeners = this.classListeners.get(EventClass) ?? []
317
+ const listeners = this.#classListeners.get(EventClass) ?? []
318
318
  const name = (EventClass as { eventName?: string }).eventName ?? classToEventName(EventClass)
319
319
 
320
320
  // Per-listener error isolation — mirrors the string `emit()` contract.
@@ -326,22 +326,22 @@ export class Emitter {
326
326
  try {
327
327
  if (isListenerClass(listener)) {
328
328
  // Listener class — resolve via container for @inject() support
329
- const instance = this.resolver
330
- ? await this.resolver.make(listener as ListenerConstructor<T>)
329
+ const instance = this.#resolver
330
+ ? await this.#resolver.make(listener as ListenerConstructor<T>)
331
331
  : new (listener as ListenerConstructor<T>)()
332
332
  await instance.handle(event)
333
333
  } else {
334
334
  await (listener as ListenerFn<T>)(event)
335
335
  }
336
336
  } catch (err) {
337
- this.emitError(name, err)
337
+ this.#emitError(name, err)
338
338
  }
339
339
  }
340
340
 
341
341
  // Also push through EventBus — same correlation-envelope wrapping as
342
342
  // the string-event path so distributed tracing covers class events too.
343
343
  // Reached unconditionally: a listener failure above no longer skips it.
344
- await this.bus.emit(name, JSON.stringify(this.#wrapForBus(event)))
344
+ await this.#bus.emit(name, JSON.stringify(this.#wrapForBus(event)))
345
345
  }
346
346
 
347
347
  // ─── Faking (AdonisJS `fake` / `restore`) ─────────────────
@@ -436,7 +436,7 @@ export class Emitter {
436
436
  pattern: string,
437
437
  listener: (eventName: string, data: unknown) => void,
438
438
  ): Promise<number> {
439
- return this.bus.subscribe(pattern, (eventJson: string) => {
439
+ return this.#bus.subscribe(pattern, (eventJson: string) => {
440
440
  try {
441
441
  const event = JSON.parse(eventJson)
442
442
  // The bus delivers a Rust `Event` envelope whose `correlationId`
@@ -471,7 +471,7 @@ export class Emitter {
471
471
  */
472
472
  async offAny(listenerOrId: number | ((eventName: string, data: unknown) => void)): Promise<this> {
473
473
  if (typeof listenerOrId === 'number') {
474
- await this.bus.unsubscribe(listenerOrId)
474
+ await this.#bus.unsubscribe(listenerOrId)
475
475
  for (const ids of this.#anySubscriptions.values()) ids.delete(listenerOrId)
476
476
  return this
477
477
  }
@@ -479,13 +479,13 @@ export class Emitter {
479
479
  const ids = this.#anySubscriptions.get(listenerOrId)
480
480
  if (!ids) return this
481
481
  this.#anySubscriptions.delete(listenerOrId)
482
- await Promise.all([...ids].map((id) => this.bus.unsubscribe(id)))
482
+ await Promise.all([...ids].map((id) => this.#bus.unsubscribe(id)))
483
483
  return this
484
484
  }
485
485
 
486
486
  /** Check if a pattern matches an event name (wildcard matching via Rust). */
487
487
  matchesPattern(pattern: string, eventName: string): boolean {
488
- return this.bus.matchesWildcard(pattern, eventName)
488
+ return this.#bus.matchesWildcard(pattern, eventName)
489
489
  }
490
490
 
491
491
  // ─── Request / Reply ──────────────────────────────────────
@@ -499,7 +499,7 @@ export class Emitter {
499
499
  // Same correlation injection as `emit()` so a `setCorrelationId`
500
500
  // preceding a request actually reaches the responder via the
501
501
  // top-level `correlationId` field on the parsed payload.
502
- const result = await this.bus.request(name, JSON.stringify(this.#wrapForBus(data)), timeoutMs)
502
+ const result = await this.#bus.request(name, JSON.stringify(this.#wrapForBus(data)), timeoutMs)
503
503
  try {
504
504
  return JSON.parse(result) as T
505
505
  } catch {
@@ -519,7 +519,7 @@ export class Emitter {
519
519
  name: string,
520
520
  handler: (eventJson: string, reply: (response: string) => void) => void,
521
521
  ): void {
522
- this.bus.onRequest(name, handler)
522
+ this.#bus.onRequest(name, handler)
523
523
  }
524
524
 
525
525
  // ─── Correlation context ──────────────────────────────────
@@ -565,13 +565,13 @@ export class Emitter {
565
565
  // AdonisJS makes the argument optional: no argument asks whether ANY event
566
566
  // has a listener at all.
567
567
  if (event === undefined) return this.listenerCount() > 0
568
- if (typeof event === 'string') return (this.stringListeners.get(event)?.length ?? 0) > 0
569
- return (this.classListeners.get(event)?.length ?? 0) > 0
568
+ if (typeof event === 'string') return (this.#stringListeners.get(event)?.length ?? 0) > 0
569
+ return (this.#classListeners.get(event)?.length ?? 0) > 0
570
570
  }
571
571
 
572
572
  /** Get the number of Rust-side subscriptions. */
573
573
  async subscriptionCount(): Promise<number> {
574
- return this.bus.subscriptionCount()
574
+ return this.#bus.subscriptionCount()
575
575
  }
576
576
  }
577
577
 
@@ -10,22 +10,28 @@
10
10
 
11
11
  import { loadNapi } from '../helpers/napi-loader.js'
12
12
 
13
- /** Rust-backed bus surface (mirrors the napi `#[napi] EventBus` exports). */
14
- export interface EventBus {
15
- emit(name: string, data: string): Promise<string>
16
- subscribe(pattern: string, callback: (eventJson: string) => void): number
17
- unsubscribe(subscriptionId: number): Promise<void>
18
- onRequest(
19
- name: string,
20
- callback: (eventJson: string, reply: (response: string) => void) => void,
21
- ): void
22
- request(name: string, data: string, timeoutMs?: number): Promise<string>
23
- matchesWildcard(pattern: string, eventName: string): boolean
24
- subscriptionCount(): Promise<number>
25
- }
13
+ /**
14
+ * Rust-backed bus surface.
15
+ *
16
+ * Re-exported from the generated declarations rather than restated here: the
17
+ * shape is derived from the `#[napi]` items themselves, so it cannot drift
18
+ * from the Rust. It did before — making `Bus::on_request` synchronous changed
19
+ * the Rust with nothing on this side to notice.
20
+ *
21
+ * Run `pnpm build:napi-types` after touching a `#[napi]` signature.
22
+ */
23
+ import type { EventBus as NativeEventBus } from '../native/generated.js'
26
24
 
25
+ /**
26
+ * What the `events` binary exports.
27
+ *
28
+ * The instance shape comes from the generated declarations — derived from the
29
+ * `#[napi]` items, so it cannot drift from the Rust without the generated file
30
+ * changing. Only the constructor is named here, because the binary is a module
31
+ * object rather than a class the loader can see.
32
+ */
27
33
  interface EventsNative {
28
- EventBus: new (requestHandlerTimeoutMs?: number) => EventBus
34
+ EventBus: new (requestHandlerTimeoutMs?: number) => NativeEventBus
29
35
  }
30
36
 
31
37
  const native = loadNapi<EventsNative>({
@@ -35,3 +41,4 @@ const native = loadNapi<EventsNative>({
35
41
  })
36
42
 
37
43
  export const EventBus = native.EventBus
44
+ export type EventBus = NativeEventBus
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Compose a class by applying mixins to it.
3
+ *
4
+ * class User extends compose(BaseModel, SoftDeletes, Sluggable) {}
5
+ *
6
+ * Each mixin is a function taking the class built so far and returning a
7
+ * subclass of it, so the chain is a plain left fold. Written out by hand that
8
+ * is `Sluggable(SoftDeletes(BaseModel))`, which reads inside-out and gets
9
+ * worse with every mixin — the whole point of the helper.
10
+ *
11
+ * The overloads exist so the RETURN type is the last mixin's, not the base
12
+ * class's: without them a composed class would lose every member the mixins
13
+ * added, statics included.
14
+ *
15
+ * Note there is no `Constructor` bound on `T`. The mixin's own parameter type
16
+ * already constrains what can be passed, and a bound would have to be written
17
+ * with `any` to admit a class with typed constructor arguments.
18
+ */
19
+
20
+ /** A mixin: takes the class built so far, returns a subclass of it. */
21
+ type Mixin<T, R> = (superclass: T) => R
22
+
23
+ export function compose<T, A>(superclass: T, a: Mixin<T, A>): A
24
+ export function compose<T, A, B>(superclass: T, a: Mixin<T, A>, b: Mixin<A, B>): B
25
+ export function compose<T, A, B, C>(
26
+ superclass: T,
27
+ a: Mixin<T, A>,
28
+ b: Mixin<A, B>,
29
+ c: Mixin<B, C>,
30
+ ): C
31
+ export function compose<T, A, B, C, D>(
32
+ superclass: T,
33
+ a: Mixin<T, A>,
34
+ b: Mixin<A, B>,
35
+ c: Mixin<B, C>,
36
+ d: Mixin<C, D>,
37
+ ): D
38
+ export function compose<T, A, B, C, D, E>(
39
+ superclass: T,
40
+ a: Mixin<T, A>,
41
+ b: Mixin<A, B>,
42
+ c: Mixin<B, C>,
43
+ d: Mixin<C, D>,
44
+ e: Mixin<D, E>,
45
+ ): E
46
+ export function compose<T, A, B, C, D, E, F>(
47
+ superclass: T,
48
+ a: Mixin<T, A>,
49
+ b: Mixin<A, B>,
50
+ c: Mixin<B, C>,
51
+ d: Mixin<C, D>,
52
+ e: Mixin<D, E>,
53
+ f: Mixin<E, F>,
54
+ ): F
55
+ export function compose<T, A, B, C, D, E, F, G>(
56
+ superclass: T,
57
+ a: Mixin<T, A>,
58
+ b: Mixin<A, B>,
59
+ c: Mixin<B, C>,
60
+ d: Mixin<C, D>,
61
+ e: Mixin<D, E>,
62
+ f: Mixin<E, F>,
63
+ g: Mixin<F, G>,
64
+ ): G
65
+ export function compose<T, A, B, C, D, E, F, G, H>(
66
+ superclass: T,
67
+ a: Mixin<T, A>,
68
+ b: Mixin<A, B>,
69
+ c: Mixin<B, C>,
70
+ d: Mixin<C, D>,
71
+ e: Mixin<D, E>,
72
+ f: Mixin<E, F>,
73
+ g: Mixin<F, G>,
74
+ h: Mixin<G, H>,
75
+ ): H
76
+
77
+ /**
78
+ * The single implementation. Its parameters are deliberately looser than the
79
+ * overloads above — a variadic fold cannot express "each mixin takes what the
80
+ * previous one returned" — and the overloads are what callers see.
81
+ */
82
+ export function compose(superclass: unknown, ...mixins: Mixin<unknown, unknown>[]): unknown {
83
+ return mixins.reduce((applied, mixin) => mixin(applied), superclass)
84
+ }
@@ -1,5 +1,6 @@
1
1
  export { Secret } from '../env/Secret.js'
2
2
  export { base64 } from './base64.js'
3
+ export { compose } from './compose.js'
3
4
  export { defineStaticProperty } from './defineStaticProperty.js'
4
5
  export { milliseconds, seconds } from './duration.js'
5
6
  export type { ImportAllFilesOptions, ReadAllFilesOptions } from './fs.js'
package/src/helpers/is.ts CHANGED
@@ -140,23 +140,94 @@ export function isEmpty(v: unknown): boolean {
140
140
  return false
141
141
  }
142
142
 
143
+ /**
144
+ * Returns true if value is a string with at least one character.
145
+ * @example isNonEmptyString('hi') // true; isNonEmptyString('') // false
146
+ */
147
+ export function isNonEmptyString(v: unknown): v is string {
148
+ return typeof v === 'string' && v.length > 0
149
+ }
150
+
151
+ /**
152
+ * Returns true if value is an array with at least one element.
153
+ * @example isNonEmptyArray([1]) // true; isNonEmptyArray([]) // false
154
+ */
155
+ export function isNonEmptyArray<T = unknown>(v: unknown): v is [T, ...T[]] {
156
+ return Array.isArray(v) && v.length > 0
157
+ }
158
+
159
+ /**
160
+ * Returns true if value is an integer. `Number.isInteger` already excludes NaN
161
+ * and the infinities.
162
+ * @example isInteger(42) // true; isInteger(4.2) // false
163
+ */
164
+ export function isInteger(v: unknown): v is number {
165
+ return Number.isInteger(v)
166
+ }
167
+
168
+ /**
169
+ * Returns true if value is a number greater than zero. Zero is NOT positive
170
+ * here, matching `@sindresorhus/is`.
171
+ * @example isPositive(1) // true; isPositive(0) // false
172
+ */
173
+ export function isPositive(v: unknown): v is number {
174
+ return typeof v === 'number' && !Number.isNaN(v) && v > 0
175
+ }
176
+
177
+ /**
178
+ * Returns true if `key` is an OWN property of `value`.
179
+ *
180
+ * Own, not inherited: an inherited `toString` is not something the caller put
181
+ * there, and treating it as present is how a prototype member gets mistaken
182
+ * for data.
183
+ * @example hasProperty({ a: 1 }, 'a') // true; hasProperty({}, 'toString') // false
184
+ */
185
+ export function hasProperty<K extends PropertyKey>(
186
+ value: unknown,
187
+ key: K,
188
+ ): value is Record<K, unknown> {
189
+ return value !== null && typeof value === 'object' && Object.hasOwn(value, key)
190
+ }
191
+
192
+ /**
193
+ * The `is` namespace, keyed the way AdonisJS keys it.
194
+ *
195
+ * Adonis re-exports `@sindresorhus/is`, whose members read `is.string(v)`,
196
+ * `is.plainObject(v)`, `is.nonEmptyArray(v)`. This object used to mirror the
197
+ * function names instead — `is.isString(v)` — which stutters, and which turns
198
+ * `is.string(value)` ported from an Adonis app into "is.string is not a
199
+ * function" at runtime rather than a compile error.
200
+ *
201
+ * The standalone `isX` functions above keep their names: they read correctly
202
+ * on their own, and this object is the namespace form.
203
+ */
143
204
  const is = {
144
- isString,
145
- isNumber,
146
- isBoolean,
147
- isFunction,
148
- isObject,
149
- isPlainObject,
150
- isArray,
151
- isPromise,
152
- isNull,
153
- isUndefined,
154
- isNullOrUndefined,
155
- isBuffer,
156
- isDate,
157
- isRegExp,
158
- isError,
159
- isEmpty,
205
+ string: isString,
206
+ number: isNumber,
207
+ boolean: isBoolean,
208
+ function: isFunction,
209
+ object: isObject,
210
+ plainObject: isPlainObject,
211
+ array: isArray,
212
+ promise: isPromise,
213
+ null: isNull,
214
+ undefined: isUndefined,
215
+ nullOrUndefined: isNullOrUndefined,
216
+ buffer: isBuffer,
217
+ date: isDate,
218
+ regExp: isRegExp,
219
+ error: isError,
220
+ nonEmptyString: isNonEmptyString,
221
+ nonEmptyArray: isNonEmptyArray,
222
+ integer: isInteger,
223
+ positive: isPositive,
224
+ hasProperty: hasProperty,
225
+ /**
226
+ * Named deviation: `@sindresorhus/is` has `emptyString` / `emptyArray` /
227
+ * `emptyObject` / `emptyMap` / `emptySet` but no umbrella. This is the
228
+ * umbrella, and the one member here that is not a type guard.
229
+ */
230
+ empty: isEmpty,
160
231
  }
161
232
 
162
233
  export default is
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import 'reflect-metadata'
8
8
 
9
9
  // ─── Core ───────────────────────────────────────────────────
10
10
 
11
+ export type { AppEnvironment, ApplicationMode, ApplicationState } from './Application.js'
11
12
  export { Application } from './Application.js'
12
13
  export { ConfigStore, configProvider, defineModuleConfig, env } from './ConfigLoader.js'
13
14
  export { Container } from './container/Container.js'
@@ -201,7 +202,7 @@ export {
201
202
  // ─── Middleware ──────────────────────────────────────────────
202
203
 
203
204
  export type { MiddlewareFunction } from './middleware/Pipeline.js'
204
- export { compose, MiddlewareRegistry } from './middleware/Pipeline.js'
205
+ export { composeMiddleware, MiddlewareRegistry } from './middleware/Pipeline.js'
205
206
 
206
207
  // ─── HttpKernel ─────────────────────────────────────────────
207
208
 
@@ -242,7 +243,6 @@ export type {
242
243
  FlagMetaData,
243
244
  } from './console/types.js'
244
245
  export type {
245
- AppEnvironment,
246
246
  HyperServerLike,
247
247
  IgnitorConfig,
248
248
  ReamrcConfig,
@@ -371,4 +371,4 @@ export {
371
371
  StatsTracker,
372
372
  } from './scheduler/index.js'
373
373
 
374
- export const VERSION = '0.2.4'
374
+ export const VERSION = '0.2.6'
@@ -170,12 +170,18 @@ export class MiddlewareRegistry {
170
170
  handler,
171
171
  ]
172
172
 
173
- return compose(stack)
173
+ return composeMiddleware(stack)
174
174
  }
175
175
  }
176
176
 
177
- /** Compose middleware into a single handler (onion pattern). */
178
- function compose(middleware: MiddlewareFunction[]): MiddlewareFunction {
177
+ /**
178
+ * Compose middleware into a single handler (onion pattern).
179
+ *
180
+ * NOT `compose` — that name means mixin composition in `helpers`, the way it
181
+ * does in AdonisJS, and the two were one import away from each other on the
182
+ * barrel.
183
+ */
184
+ function composeMiddleware(middleware: MiddlewareFunction[]): MiddlewareFunction {
179
185
  return async (ctx: HttpContext, finalNext: () => Promise<void>) => {
180
186
  let index = -1
181
187
 
@@ -257,4 +263,4 @@ function createValidationMiddleware(validators: RuntimeValidator[]): MiddlewareF
257
263
  }
258
264
  }
259
265
 
260
- export { compose }
266
+ export { composeMiddleware }