@ddd-ts-kit/aspects 0.0.0-stage → 0.1.0
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 +381 -2
- package/dist/aspect/advice-factory.d.ts +9 -0
- package/dist/aspect/advice-factory.d.ts.map +1 -0
- package/dist/aspect/advice-factory.js +2 -0
- package/dist/aspect/advice-factory.js.map +1 -0
- package/dist/aspect/advice.d.ts +3 -0
- package/dist/aspect/advice.d.ts.map +1 -0
- package/dist/aspect/advice.js +2 -0
- package/dist/aspect/advice.js.map +1 -0
- package/dist/aspect/aspect-info.d.ts +12 -0
- package/dist/aspect/aspect-info.d.ts.map +1 -0
- package/dist/aspect/aspect-info.js +2 -0
- package/dist/aspect/aspect-info.js.map +1 -0
- package/dist/aspect/aspect-kind.d.ts +4 -0
- package/dist/aspect/aspect-kind.d.ts.map +1 -0
- package/dist/aspect/aspect-kind.js +2 -0
- package/dist/aspect/aspect-kind.js.map +1 -0
- package/dist/aspect/aspect-origins.d.ts +10 -0
- package/dist/aspect/aspect-origins.d.ts.map +1 -0
- package/dist/aspect/aspect-origins.js +3 -0
- package/dist/aspect/aspect-origins.js.map +1 -0
- package/dist/aspect/aspect.d.ts +9 -0
- package/dist/aspect/aspect.d.ts.map +1 -0
- package/dist/aspect/aspect.js +2 -0
- package/dist/aspect/aspect.js.map +1 -0
- package/dist/aspect/compose.d.ts +7 -0
- package/dist/aspect/compose.d.ts.map +1 -0
- package/dist/aspect/compose.js +11 -0
- package/dist/aspect/compose.js.map +1 -0
- package/dist/aspect/define-aspect.d.ts +5 -0
- package/dist/aspect/define-aspect.d.ts.map +1 -0
- package/dist/aspect/define-aspect.js +17 -0
- package/dist/aspect/define-aspect.js.map +1 -0
- package/dist/aspect/invocation.d.ts +6 -0
- package/dist/aspect/invocation.d.ts.map +1 -0
- package/dist/aspect/invocation.js +2 -0
- package/dist/aspect/invocation.js.map +1 -0
- package/dist/aspect/join-point.d.ts +13 -0
- package/dist/aspect/join-point.d.ts.map +1 -0
- package/dist/aspect/join-point.js +2 -0
- package/dist/aspect/join-point.js.map +1 -0
- package/dist/aspect/selector.d.ts +3 -0
- package/dist/aspect/selector.d.ts.map +1 -0
- package/dist/aspect/selector.js +2 -0
- package/dist/aspect/selector.js.map +1 -0
- package/dist/aspect/selectors.d.ts +14 -0
- package/dist/aspect/selectors.d.ts.map +1 -0
- package/dist/aspect/selectors.js +32 -0
- package/dist/aspect/selectors.js.map +1 -0
- package/dist/aspect/when.d.ts +8 -0
- package/dist/aspect/when.d.ts.map +1 -0
- package/dist/aspect/when.js +21 -0
- package/dist/aspect/when.js.map +1 -0
- package/dist/bulkhead/bulkhead-options.d.ts +10 -0
- package/dist/bulkhead/bulkhead-options.d.ts.map +1 -0
- package/dist/bulkhead/bulkhead-options.js +2 -0
- package/dist/bulkhead/bulkhead-options.js.map +1 -0
- package/dist/bulkhead/bulkhead.d.ts +11 -0
- package/dist/bulkhead/bulkhead.d.ts.map +1 -0
- package/dist/bulkhead/bulkhead.js +22 -0
- package/dist/bulkhead/bulkhead.js.map +1 -0
- package/dist/bulkhead/compartment.d.ts +13 -0
- package/dist/bulkhead/compartment.d.ts.map +1 -0
- package/dist/bulkhead/compartment.js +40 -0
- package/dist/bulkhead/compartment.js.map +1 -0
- package/dist/cached/cached.d.ts +26 -0
- package/dist/cached/cached.d.ts.map +1 -0
- package/dist/cached/cached.js +64 -0
- package/dist/cached/cached.js.map +1 -0
- package/dist/circuit-breaker/circuit-breaker-options.d.ts +20 -0
- package/dist/circuit-breaker/circuit-breaker-options.d.ts.map +1 -0
- package/dist/circuit-breaker/circuit-breaker-options.js +2 -0
- package/dist/circuit-breaker/circuit-breaker-options.js.map +1 -0
- package/dist/circuit-breaker/circuit-breaker.d.ts +11 -0
- package/dist/circuit-breaker/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker/circuit-breaker.js +27 -0
- package/dist/circuit-breaker/circuit-breaker.js.map +1 -0
- package/dist/circuit-breaker/circuit-state-change.d.ts +10 -0
- package/dist/circuit-breaker/circuit-state-change.d.ts.map +1 -0
- package/dist/circuit-breaker/circuit-state-change.js +2 -0
- package/dist/circuit-breaker/circuit-state-change.js.map +1 -0
- package/dist/circuit-breaker/circuit-state-gauge.d.ts +8 -0
- package/dist/circuit-breaker/circuit-state-gauge.d.ts.map +1 -0
- package/dist/circuit-breaker/circuit-state-gauge.js +14 -0
- package/dist/circuit-breaker/circuit-state-gauge.js.map +1 -0
- package/dist/circuit-breaker/circuit-state.d.ts +2 -0
- package/dist/circuit-breaker/circuit-state.d.ts.map +1 -0
- package/dist/circuit-breaker/circuit-state.js +2 -0
- package/dist/circuit-breaker/circuit-state.js.map +1 -0
- package/dist/circuit-breaker/circuit.d.ts +22 -0
- package/dist/circuit-breaker/circuit.d.ts.map +1 -0
- package/dist/circuit-breaker/circuit.js +79 -0
- package/dist/circuit-breaker/circuit.js.map +1 -0
- package/dist/decorators/async-method-decorator.d.ts +13 -0
- package/dist/decorators/async-method-decorator.d.ts.map +1 -0
- package/dist/decorators/async-method-decorator.js +2 -0
- package/dist/decorators/async-method-decorator.js.map +1 -0
- package/dist/decorators/bulkhead.d.ts +5 -0
- package/dist/decorators/bulkhead.d.ts.map +1 -0
- package/dist/decorators/bulkhead.js +7 -0
- package/dist/decorators/bulkhead.js.map +1 -0
- package/dist/decorators/cached.d.ts +5 -0
- package/dist/decorators/cached.d.ts.map +1 -0
- package/dist/decorators/cached.js +7 -0
- package/dist/decorators/cached.js.map +1 -0
- package/dist/decorators/circuit-breaker.d.ts +5 -0
- package/dist/decorators/circuit-breaker.d.ts.map +1 -0
- package/dist/decorators/circuit-breaker.js +7 -0
- package/dist/decorators/circuit-breaker.js.map +1 -0
- package/dist/decorators/decorator-of.d.ts +9 -0
- package/dist/decorators/decorator-of.d.ts.map +1 -0
- package/dist/decorators/decorator-of.js +43 -0
- package/dist/decorators/decorator-of.js.map +1 -0
- package/dist/decorators/logged.d.ts +5 -0
- package/dist/decorators/logged.d.ts.map +1 -0
- package/dist/decorators/logged.js +7 -0
- package/dist/decorators/logged.js.map +1 -0
- package/dist/decorators/retry.d.ts +5 -0
- package/dist/decorators/retry.d.ts.map +1 -0
- package/dist/decorators/retry.js +7 -0
- package/dist/decorators/retry.js.map +1 -0
- package/dist/decorators/timed.d.ts +5 -0
- package/dist/decorators/timed.d.ts.map +1 -0
- package/dist/decorators/timed.js +7 -0
- package/dist/decorators/timed.js.map +1 -0
- package/dist/decorators/timeout.d.ts +5 -0
- package/dist/decorators/timeout.d.ts.map +1 -0
- package/dist/decorators/timeout.js +7 -0
- package/dist/decorators/timeout.js.map +1 -0
- package/dist/decorators.d.ts +10 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +9 -0
- package/dist/decorators.js.map +1 -0
- package/dist/di/aspects.d.ts +19 -0
- package/dist/di/aspects.d.ts.map +1 -0
- package/dist/di/aspects.js +22 -0
- package/dist/di/aspects.js.map +1 -0
- package/dist/errors/aspect-not-allowed-error.d.ts +6 -0
- package/dist/errors/aspect-not-allowed-error.d.ts.map +1 -0
- package/dist/errors/aspect-not-allowed-error.js +9 -0
- package/dist/errors/aspect-not-allowed-error.js.map +1 -0
- package/dist/errors/bulkhead-full-error.d.ts +7 -0
- package/dist/errors/bulkhead-full-error.d.ts.map +1 -0
- package/dist/errors/bulkhead-full-error.js +10 -0
- package/dist/errors/bulkhead-full-error.js.map +1 -0
- package/dist/errors/circuit-open-error.d.ts +11 -0
- package/dist/errors/circuit-open-error.d.ts.map +1 -0
- package/dist/errors/circuit-open-error.js +14 -0
- package/dist/errors/circuit-open-error.js.map +1 -0
- package/dist/errors/is-business-refusal.d.ts +3 -0
- package/dist/errors/is-business-refusal.d.ts.map +1 -0
- package/dist/errors/is-business-refusal.js +6 -0
- package/dist/errors/is-business-refusal.js.map +1 -0
- package/dist/errors/timeout-error.d.ts +8 -0
- package/dist/errors/timeout-error.d.ts.map +1 -0
- package/dist/errors/timeout-error.js +12 -0
- package/dist/errors/timeout-error.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/observability/log-call.d.ts +13 -0
- package/dist/observability/log-call.d.ts.map +1 -0
- package/dist/observability/log-call.js +22 -0
- package/dist/observability/log-call.js.map +1 -0
- package/dist/observability/logged.d.ts +22 -0
- package/dist/observability/logged.d.ts.map +1 -0
- package/dist/observability/logged.js +38 -0
- package/dist/observability/logged.js.map +1 -0
- package/dist/observability/logging-middleware.d.ts +13 -0
- package/dist/observability/logging-middleware.d.ts.map +1 -0
- package/dist/observability/logging-middleware.js +16 -0
- package/dist/observability/logging-middleware.js.map +1 -0
- package/dist/observability/metrics-middleware.d.ts +10 -0
- package/dist/observability/metrics-middleware.d.ts.map +1 -0
- package/dist/observability/metrics-middleware.js +12 -0
- package/dist/observability/metrics-middleware.js.map +1 -0
- package/dist/observability/observed.d.ts +14 -0
- package/dist/observability/observed.d.ts.map +1 -0
- package/dist/observability/observed.js +27 -0
- package/dist/observability/observed.js.map +1 -0
- package/dist/observability/safely.d.ts +3 -0
- package/dist/observability/safely.d.ts.map +1 -0
- package/dist/observability/safely.js +10 -0
- package/dist/observability/safely.js.map +1 -0
- package/dist/observability/stdout-logger.d.ts +45 -0
- package/dist/observability/stdout-logger.d.ts.map +1 -0
- package/dist/observability/stdout-logger.js +85 -0
- package/dist/observability/stdout-logger.js.map +1 -0
- package/dist/observability/time-call.d.ts +10 -0
- package/dist/observability/time-call.d.ts.map +1 -0
- package/dist/observability/time-call.js +11 -0
- package/dist/observability/time-call.js.map +1 -0
- package/dist/observability/timed.d.ts +19 -0
- package/dist/observability/timed.d.ts.map +1 -0
- package/dist/observability/timed.js +33 -0
- package/dist/observability/timed.js.map +1 -0
- package/dist/retry/backoff.d.ts +12 -0
- package/dist/retry/backoff.d.ts.map +1 -0
- package/dist/retry/backoff.js +2 -0
- package/dist/retry/backoff.js.map +1 -0
- package/dist/retry/delay-before-retry.d.ts +4 -0
- package/dist/retry/delay-before-retry.d.ts.map +1 -0
- package/dist/retry/delay-before-retry.js +10 -0
- package/dist/retry/delay-before-retry.js.map +1 -0
- package/dist/retry/full-jitter.d.ts +7 -0
- package/dist/retry/full-jitter.d.ts.map +1 -0
- package/dist/retry/full-jitter.js +8 -0
- package/dist/retry/full-jitter.js.map +1 -0
- package/dist/retry/is-transient.d.ts +6 -0
- package/dist/retry/is-transient.d.ts.map +1 -0
- package/dist/retry/is-transient.js +11 -0
- package/dist/retry/is-transient.js.map +1 -0
- package/dist/retry/jitter.d.ts +3 -0
- package/dist/retry/jitter.d.ts.map +1 -0
- package/dist/retry/jitter.js +2 -0
- package/dist/retry/jitter.js.map +1 -0
- package/dist/retry/no-jitter.d.ts +3 -0
- package/dist/retry/no-jitter.d.ts.map +1 -0
- package/dist/retry/no-jitter.js +2 -0
- package/dist/retry/no-jitter.js.map +1 -0
- package/dist/retry/retry.d.ts +20 -0
- package/dist/retry/retry.d.ts.map +1 -0
- package/dist/retry/retry.js +38 -0
- package/dist/retry/retry.js.map +1 -0
- package/dist/runtime/aspect-runtime-error.d.ts +6 -0
- package/dist/runtime/aspect-runtime-error.d.ts.map +1 -0
- package/dist/runtime/aspect-runtime-error.js +9 -0
- package/dist/runtime/aspect-runtime-error.js.map +1 -0
- package/dist/runtime/aspect-runtime.d.ts +15 -0
- package/dist/runtime/aspect-runtime.d.ts.map +1 -0
- package/dist/runtime/aspect-runtime.js +2 -0
- package/dist/runtime/aspect-runtime.js.map +1 -0
- package/dist/runtime/cache-or.d.ts +3 -0
- package/dist/runtime/cache-or.d.ts.map +1 -0
- package/dist/runtime/cache-or.js +6 -0
- package/dist/runtime/cache-or.js.map +1 -0
- package/dist/runtime/clock-or.d.ts +3 -0
- package/dist/runtime/clock-or.d.ts.map +1 -0
- package/dist/runtime/clock-or.js +7 -0
- package/dist/runtime/clock-or.js.map +1 -0
- package/dist/runtime/installed-runtime.d.ts +8 -0
- package/dist/runtime/installed-runtime.d.ts.map +1 -0
- package/dist/runtime/installed-runtime.js +16 -0
- package/dist/runtime/installed-runtime.js.map +1 -0
- package/dist/runtime/logger-or.d.ts +3 -0
- package/dist/runtime/logger-or.d.ts.map +1 -0
- package/dist/runtime/logger-or.js +6 -0
- package/dist/runtime/logger-or.js.map +1 -0
- package/dist/runtime/metrics-or.d.ts +3 -0
- package/dist/runtime/metrics-or.d.ts.map +1 -0
- package/dist/runtime/metrics-or.js +6 -0
- package/dist/runtime/metrics-or.js.map +1 -0
- package/dist/runtime/missing-collaborator.d.ts +2 -0
- package/dist/runtime/missing-collaborator.d.ts.map +1 -0
- package/dist/runtime/missing-collaborator.js +5 -0
- package/dist/runtime/missing-collaborator.js.map +1 -0
- package/dist/runtime/scheduler-or.d.ts +3 -0
- package/dist/runtime/scheduler-or.d.ts.map +1 -0
- package/dist/runtime/scheduler-or.js +6 -0
- package/dist/runtime/scheduler-or.js.map +1 -0
- package/dist/runtime/scheduler.d.ts +7 -0
- package/dist/runtime/scheduler.d.ts.map +1 -0
- package/dist/runtime/scheduler.js +5 -0
- package/dist/runtime/scheduler.js.map +1 -0
- package/dist/runtime/system-scheduler.d.ts +3 -0
- package/dist/runtime/system-scheduler.d.ts.map +1 -0
- package/dist/runtime/system-scheduler.js +9 -0
- package/dist/runtime/system-scheduler.js.map +1 -0
- package/dist/state/state-for.d.ts +5 -0
- package/dist/state/state-for.d.ts.map +1 -0
- package/dist/state/state-for.js +17 -0
- package/dist/state/state-for.js.map +1 -0
- package/dist/state/state-scope.d.ts +3 -0
- package/dist/state/state-scope.d.ts.map +1 -0
- package/dist/state/state-scope.js +2 -0
- package/dist/state/state-scope.js.map +1 -0
- package/dist/testing/create-aspect-fakes.d.ts +13 -0
- package/dist/testing/create-aspect-fakes.d.ts.map +1 -0
- package/dist/testing/create-aspect-fakes.js +15 -0
- package/dist/testing/create-aspect-fakes.js.map +1 -0
- package/dist/testing/manual-scheduler.d.ts +14 -0
- package/dist/testing/manual-scheduler.d.ts.map +1 -0
- package/dist/testing/manual-scheduler.js +30 -0
- package/dist/testing/manual-scheduler.js.map +1 -0
- package/dist/testing/with-aspect-runtime.d.ts +8 -0
- package/dist/testing/with-aspect-runtime.d.ts.map +1 -0
- package/dist/testing/with-aspect-runtime.js +10 -0
- package/dist/testing/with-aspect-runtime.js.map +1 -0
- package/dist/testing.d.ts +4 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +4 -0
- package/dist/testing.js.map +1 -0
- package/dist/timeout/timeout.d.ts +21 -0
- package/dist/timeout/timeout.d.ts.map +1 -0
- package/dist/timeout/timeout.js +42 -0
- package/dist/timeout/timeout.js.map +1 -0
- package/dist/weaving/advise-method.d.ts +7 -0
- package/dist/weaving/advise-method.d.ts.map +1 -0
- package/dist/weaving/advise-method.js +19 -0
- package/dist/weaving/advise-method.js.map +1 -0
- package/dist/weaving/advise-methods.d.ts +13 -0
- package/dist/weaving/advise-methods.d.ts.map +1 -0
- package/dist/weaving/advise-methods.js +119 -0
- package/dist/weaving/advise-methods.js.map +1 -0
- package/dist/weaving/advise-prototype-method.d.ts +8 -0
- package/dist/weaving/advise-prototype-method.d.ts.map +1 -0
- package/dist/weaving/advise-prototype-method.js +30 -0
- package/dist/weaving/advise-prototype-method.js.map +1 -0
- package/dist/weaving/advised.d.ts +6 -0
- package/dist/weaving/advised.d.ts.map +1 -0
- package/dist/weaving/advised.js +19 -0
- package/dist/weaving/advised.js.map +1 -0
- package/dist/weaving/from-outside.d.ts +10 -0
- package/dist/weaving/from-outside.d.ts.map +1 -0
- package/dist/weaving/from-outside.js +29 -0
- package/dist/weaving/from-outside.js.map +1 -0
- package/dist/weaving/is-wrappable.d.ts +3 -0
- package/dist/weaving/is-wrappable.d.ts.map +1 -0
- package/dist/weaving/is-wrappable.js +7 -0
- package/dist/weaving/is-wrappable.js.map +1 -0
- package/dist/weaving/known-async.d.ts +4 -0
- package/dist/weaving/known-async.d.ts.map +1 -0
- package/dist/weaving/known-async.js +3 -0
- package/dist/weaving/known-async.js.map +1 -0
- package/dist/weaving/lifecycle-hooks.d.ts +3 -0
- package/dist/weaving/lifecycle-hooks.d.ts.map +1 -0
- package/dist/weaving/lifecycle-hooks.js +9 -0
- package/dist/weaving/lifecycle-hooks.js.map +1 -0
- package/dist/weaving/method.d.ts +2 -0
- package/dist/weaving/method.d.ts.map +1 -0
- package/dist/weaving/method.js +2 -0
- package/dist/weaving/method.js.map +1 -0
- package/dist/weaving/named.d.ts +4 -0
- package/dist/weaving/named.d.ts.map +1 -0
- package/dist/weaving/named.js +7 -0
- package/dist/weaving/named.js.map +1 -0
- package/dist/weaving/shadowed.d.ts +4 -0
- package/dist/weaving/shadowed.d.ts.map +1 -0
- package/dist/weaving/shadowed.js +14 -0
- package/dist/weaving/shadowed.js.map +1 -0
- package/dist/weaving/site-of.d.ts +5 -0
- package/dist/weaving/site-of.d.ts.map +1 -0
- package/dist/weaving/site-of.js +15 -0
- package/dist/weaving/site-of.js.map +1 -0
- package/package.json +40 -4
package/README.md
CHANGED
|
@@ -1,3 +1,382 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ddd-ts-kit/aspects
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Logging, metrics, retries, timeouts, circuit breakers and caching are not business logic, so
|
|
4
|
+
they are never written inside it. Each one is an aspect: one implementation, put around an
|
|
5
|
+
adapter from outside, the way `@ddd-ts-kit/tracing` puts spans around it. The wrapped class
|
|
6
|
+
imports nothing, and `domain/` and `application/` never see this package.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
npm i @ddd-ts-kit/aspects
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Three forms, one implementation
|
|
13
|
+
|
|
14
|
+
The same policy, written three ways. All three run the same code.
|
|
15
|
+
|
|
16
|
+
### 1. At the composition root, on one binding (preferred)
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// contexts/billing/application/ports/payment-gateway.port.ts
|
|
20
|
+
export abstract class PaymentGateway {
|
|
21
|
+
abstract charge(payment: Payment, signal?: AbortSignal): Promise<Receipt>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// contexts/billing/application/ports/payment-settings.port.ts
|
|
25
|
+
export abstract class PaymentSettings {
|
|
26
|
+
abstract readonly baseUrl: string;
|
|
27
|
+
|
|
28
|
+
abstract readonly apiKey: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// contexts/billing/infrastructure/external/stripe.gateway.ts
|
|
32
|
+
export class StripeGateway implements PaymentGateway {
|
|
33
|
+
constructor(private readonly settings: PaymentSettings) {}
|
|
34
|
+
|
|
35
|
+
async charge(payment: Payment, signal?: AbortSignal): Promise<Receipt> {
|
|
36
|
+
return post(`${this.settings.baseUrl}/charges`, payment, { apiKey: this.settings.apiKey, signal });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// contexts/billing/billing.module.ts
|
|
41
|
+
import { retry, timeout } from '@ddd-ts-kit/aspects';
|
|
42
|
+
|
|
43
|
+
builder.bind(PaymentSettings).toValue({ baseUrl: config.stripe.url, apiKey: config.stripe.key });
|
|
44
|
+
builder
|
|
45
|
+
.bind(PaymentGateway)
|
|
46
|
+
.toClass(StripeGateway, [PaymentSettings])
|
|
47
|
+
.as('external')
|
|
48
|
+
.with(retry({ attempts: 3 }), timeout(2000, { signal: true }));
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The port is an abstract class and the class is what is bound. `StripeGateway` has no import
|
|
52
|
+
from this package and no knowledge of the policy. In the declared style, the default of a
|
|
53
|
+
generated project, there is no module file and the same policy is stated in the declaration
|
|
54
|
+
of the adapter: `@Adapter(PaymentGateway, { kind: 'external', aspects: [retry({ attempts: 3 }), timeout(2000)] }, [PaymentSettings])`. Without a container an aspect is a function:
|
|
55
|
+
`retry({ attempts: 3 })(new StripeGateway(settings), { kind: 'external' })`.
|
|
56
|
+
|
|
57
|
+
### 2. At the composition root, on every binding
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// main.ts
|
|
61
|
+
import { Cache, Clock, Logger, Metrics } from '@ddd-ts-kit/kernel';
|
|
62
|
+
import { createApplication, kernelModule } from '@ddd-ts-kit/di';
|
|
63
|
+
import { StdoutLogger, aspects, forKinds, installAspectRuntime, logged, retry, timed, timeout, when } from '@ddd-ts-kit/aspects';
|
|
64
|
+
|
|
65
|
+
const app = createApplication({
|
|
66
|
+
modules: [kernelModule({ logger: new StdoutLogger() }), platformModule, billingModule],
|
|
67
|
+
decorate: aspects(
|
|
68
|
+
tracing,
|
|
69
|
+
when(forKinds('repository', 'gateway', 'external'), logged(), timed()),
|
|
70
|
+
when(forKinds('external'), retry({ attempts: 3 }), timeout(2000)),
|
|
71
|
+
),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
installAspectRuntime({
|
|
75
|
+
logger: app.resolve(Logger),
|
|
76
|
+
metrics: app.resolve(Metrics),
|
|
77
|
+
cache: app.resolve(Cache),
|
|
78
|
+
clock: app.resolve(Clock),
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`tracing` is the hook of the tracing package written as an aspect:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const tracing: Aspect = (instance, info) =>
|
|
86
|
+
info?.kind === undefined ? instance : traceDependency(instance, { kind: info.kind, ...(info.context === undefined ? {} : { context: info.context }) });
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
An application on `kernelModule({ instrumentation: traceApplication() })`, which is what the
|
|
90
|
+
CLI generates, does not write this `tracing` aspect: the kernel module traces the bindings
|
|
91
|
+
itself. Its span is inside whatever the `decorate` hook adds and outside the aspects of a
|
|
92
|
+
binding (see "Order"). A generated project does not use the `decorate` hook either: its
|
|
93
|
+
platform module puts the default policy around the adapters that leave the process with
|
|
94
|
+
`builder.around(['external', 'gateway'], () => when(forKinds('external', 'gateway'), retry(...), timeout(...)))`.
|
|
95
|
+
|
|
96
|
+
### 3. As a decorator, on an adapter
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// contexts/billing/infrastructure/external/stripe.gateway.ts
|
|
100
|
+
import { Retry, Timeout } from '@ddd-ts-kit/aspects/decorators';
|
|
101
|
+
|
|
102
|
+
export class StripeGateway implements PaymentGateway {
|
|
103
|
+
constructor(private readonly settings: PaymentSettings) {}
|
|
104
|
+
|
|
105
|
+
@Retry({ attempts: 3 })
|
|
106
|
+
@Timeout(2000, { signal: true })
|
|
107
|
+
async charge(payment: Payment, signal?: AbortSignal): Promise<Receipt> {
|
|
108
|
+
return post(`${this.settings.baseUrl}/charges`, payment, { apiKey: this.settings.apiKey, signal });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`@Retry`, `@Timeout`, `@Cached`, `@Logged`, `@Timed`, `@CircuitBreaker`, `@Bulkhead`, and
|
|
114
|
+
`decoratorOf(adviceFactory)` for an aspect of the project. Several on one method nest as they
|
|
115
|
+
read: the first written is outermost. The class takes no logger, metrics or cache in its
|
|
116
|
+
constructor: the decorators read them from the aspect runtime when a call is made.
|
|
117
|
+
|
|
118
|
+
### Which form
|
|
119
|
+
|
|
120
|
+
- **Prefer the composition root** (forms 1 and 2). The adapter stays free of any import, the
|
|
121
|
+
policy of a whole application is read in one place, and it changes without touching a class.
|
|
122
|
+
- **A decorator is acceptable** on an adapter in `infrastructure/` or `platform/` whose policy
|
|
123
|
+
is part of what the adapter is: a client of a provider that documents its own retry and
|
|
124
|
+
timeout rules. Do not apply the same aspect in both forms: it would run twice.
|
|
125
|
+
- **Never in `domain/` or `application/`**, in any form: `layer/domain-purity` and
|
|
126
|
+
`layer/application-inward` forbid the import. A use case gets its cross-cutting behaviour
|
|
127
|
+
from a bus middleware, below.
|
|
128
|
+
|
|
129
|
+
## Aspects
|
|
130
|
+
|
|
131
|
+
| Aspect | What it does | Default policy | Never records |
|
|
132
|
+
|---|---|---|---|
|
|
133
|
+
| `logged({ level?, fields?, logger?, clock? })` | entries `<Class>.<method> started` (debug), then `succeeded` (`level`, default info) or `failed` | a `DomainError` fails at `warn`, a fault at `error`; fields `class`, `method`, `kind`, `context`, `durationMs`, `errorType`, `errorCode` | arguments, results, error messages |
|
|
134
|
+
| `timed({ name?, metrics?, clock? })` | histogram `<name>_duration_ms`, counter `<name>_total` | `name` is `dependency_call`; labels `class`, `method`, `kind`, `context`, `outcome`, `error_code` | arguments, results, error messages, identifiers |
|
|
135
|
+
| `retry({ attempts, backoff?, retryOn?, sleep? })` | calls again after a failure | never after a `DomainError`, an open circuit or a full bulkhead (`isTransient`); 100 ms doubling up to 30 s with full jitter | nothing: it records nothing |
|
|
136
|
+
| `timeout(ms, { signal?, scheduler? })` | rejects with `TimeoutError` (`TIMEOUT`) | the underlying call keeps running unless `signal: true` | the message names the class and method only |
|
|
137
|
+
| `circuitBreaker({ failureThreshold, resetAfterMs, halfOpenMax?, scope?, isFailure?, onStateChange?, clock? })` | refuses calls with `CircuitOpenError` (`CIRCUIT_OPEN`) after consecutive failures, then lets trial calls through | one circuit per instance and method; a `DomainError` is not a failure; one trial call | `onStateChange` gets class, method, context and the two states |
|
|
138
|
+
| `cached({ key, ttlMs, cache?, allowKinds? })` | answers a repeated read from the `Cache` port | refuses kinds `repository` and `use-case`; a call whose key is `undefined` is not cached; failures are not cached; concurrent identical calls share one call; a failing cache is a miss | the key is built by the project from the arguments and goes to the cache only |
|
|
139
|
+
| `bulkhead({ maxConcurrent, maxQueued?, scope? })` | bounds the calls running at once, refuses beyond with `BulkheadFullError` (`BULKHEAD_FULL`) | no queue; one compartment per instance and method | nothing |
|
|
140
|
+
|
|
141
|
+
**Privacy.** The rule is the one of tracing: an entry, a label or an error raised by this
|
|
142
|
+
package carries names from the code (class, method, context, kind), a duration, the class of
|
|
143
|
+
an error and its short `code`. Never an argument, a result or the message of a caught error:
|
|
144
|
+
they quote data. Errors are rethrown untouched.
|
|
145
|
+
|
|
146
|
+
**`timeout` and cancellation.** With `signal: true` the method is handed an `AbortSignal` as its
|
|
147
|
+
last declared parameter, aborted when the time is up. Declare `signal?: AbortSignal` last,
|
|
148
|
+
without a default value, and pass it to the underlying call. A signal the caller passed there is
|
|
149
|
+
kept: either one aborts. A method without parameter receives none.
|
|
150
|
+
|
|
151
|
+
**`retry` and jitter.** `backoff: { baseMs, factor, maxMs, jitter }`. `jitter` is a function
|
|
152
|
+
`(delayMs, attempt) => delayMs`: `fullJitter()` by default, `fullJitter(seededRandom)` or
|
|
153
|
+
`noJitter` for a deterministic schedule. `sleep` replaces the wait altogether.
|
|
154
|
+
|
|
155
|
+
**`circuitBreaker` and metrics.** `onStateChange: circuitStateGauge()` publishes the state as
|
|
156
|
+
the gauge `circuit_breaker_state`: 0 closed, 1 half-open, 2 open.
|
|
157
|
+
|
|
158
|
+
**`cached` is for reads of gateways and external adapters.** An aggregate served from a cache
|
|
159
|
+
behind a repository hides that it is stale, and the next command would decide on old state.
|
|
160
|
+
Wrapping a repository or a use case therefore takes an explicit `allowKinds: ['repository']`,
|
|
161
|
+
which is a decision to record.
|
|
162
|
+
|
|
163
|
+
A project writes its own aspect with `defineAspect((joinPoint) => async ({ args, proceed }) => ...)`.
|
|
164
|
+
|
|
165
|
+
## Selecting what is wrapped
|
|
166
|
+
|
|
167
|
+
`when(selector, ...aspects)` applies aspects to the methods a selector picks:
|
|
168
|
+
|
|
169
|
+
| Selector | Picks |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `forKinds('repository', 'external')` | instances bound with `.as(kind)` |
|
|
172
|
+
| `forContext('billing')` | instances of a module |
|
|
173
|
+
| `forClasses('Http*')` | instances by class name |
|
|
174
|
+
| `forMethods('find*', 'get*')` | methods by name |
|
|
175
|
+
| `allOf(...)`, `anyOf(...)`, `not(...)` | combinations |
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
when(allOf(forKinds('gateway', 'external'), forMethods('find*')), cached({ key, ttlMs: 60_000 }));
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Order
|
|
182
|
+
|
|
183
|
+
`compose(a, b)`, `.with(a, b)`, `aspects(a, b)` and stacked decorators all read the same way:
|
|
184
|
+
**the first is outermost**. Around one instance the application nests, outermost first: the
|
|
185
|
+
`decorate` hook, the aspects modules put around a kind with `builder.around(kinds, ...)` in
|
|
186
|
+
the order of the modules (the span of `kernelModule({ instrumentation })` comes first, then
|
|
187
|
+
for a repository the hand-over of its domain events, then the ones of the platform module),
|
|
188
|
+
and last the aspects of the binding itself (`.with(...)`, or `aspects` of a declaration).
|
|
189
|
+
Order changes meaning:
|
|
190
|
+
|
|
191
|
+
- `retry` then `timeout`: each attempt has its own time limit. The other way round, one limit
|
|
192
|
+
covers all the attempts and the waits between them.
|
|
193
|
+
- `timed` and `logged` outside `retry`: one measurement per call as the caller lived it,
|
|
194
|
+
retries included. Inside, one per attempt.
|
|
195
|
+
- `cached` outside `retry` and `circuitBreaker`: a hit costs nothing and is not refused by an
|
|
196
|
+
open circuit.
|
|
197
|
+
- `circuitBreaker` inside `retry`: each failed attempt counts, and an open circuit ends the
|
|
198
|
+
retries at once, since `retry` does not retry a `CircuitOpenError`.
|
|
199
|
+
|
|
200
|
+
The order that fits most adapters, outermost first:
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
tracing, logged, timed, cached, retry, circuitBreaker, bulkhead, timeout, the adapter
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
With the instrumentation of the kernel module, the tracing span is where the kernel module
|
|
207
|
+
puts it: `logged` and `timed` given to the `decorate` hook are outside the span, the ones
|
|
208
|
+
given to `builder.around` or to a binding are inside it.
|
|
209
|
+
|
|
210
|
+
A repository has one more wrapper, put by `kernelModule()` between the `decorate` hook and the
|
|
211
|
+
aspects of its binding: the hand-over of the domain events of what it saves, which the unit of
|
|
212
|
+
work dispatches at its end, before the commit. So the aspects of a binding
|
|
213
|
+
(`.with(retry(...))`) are inside it, where a retried save announces its events once. Inside a
|
|
214
|
+
unit of work a handler of those events no longer runs during the save, so no retry of a save
|
|
215
|
+
ever covers it. Outside of any, the save dispatches before it resolves: a `retry` given to the
|
|
216
|
+
`decorate` hook would then run the save again after a handler failed, with no event left to
|
|
217
|
+
dispatch. Put the retry of a repository on its binding.
|
|
218
|
+
|
|
219
|
+
## The aspect runtime
|
|
220
|
+
|
|
221
|
+
`logged`, `timed` and `cached` need a logger, metrics and a cache. They take them from their
|
|
222
|
+
options, or from the runtime that the composition root installs once with
|
|
223
|
+
`installAspectRuntime({ logger, metrics, cache, clock, scheduler })`. The runtime is read when
|
|
224
|
+
a call is made, never when a class is decorated or an instance wrapped, so the order of the
|
|
225
|
+
statements in `main.ts` does not matter. A missing collaborator raises `AspectRuntimeError`,
|
|
226
|
+
which names it. The clock and the scheduler default to the system ones. `Scheduler` is the port
|
|
227
|
+
of this package for "run this later" (`schedule(work, delayMs)`), an abstract class like the
|
|
228
|
+
ports of the kernel: `systemScheduler` is its default, `ManualScheduler` of `./testing` the one
|
|
229
|
+
of a spec.
|
|
230
|
+
|
|
231
|
+
## Use cases: bus middlewares
|
|
232
|
+
|
|
233
|
+
Handlers are not wrapped one by one; the buses are:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
new InMemoryCommandBus()
|
|
237
|
+
.use(loggingMiddleware(logger))
|
|
238
|
+
.use(metricsMiddleware(metrics));
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Same entries and series as `logged` and `timed` (prefix `use_case`), named after the message
|
|
242
|
+
class, never its content. There is no middleware opening an `ExecutionScope`: a command does not
|
|
243
|
+
carry a correlation id, and the scope must already be open when the route or the consumer runs,
|
|
244
|
+
before any bus. Open it at the boundary.
|
|
245
|
+
|
|
246
|
+
## StdoutLogger
|
|
247
|
+
|
|
248
|
+
A small adapter of the kernel `Logger` port writing JSON lines:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
const logger = new StdoutLogger({
|
|
252
|
+
level: 'info',
|
|
253
|
+
base: { service: 'shop-api' },
|
|
254
|
+
fields: () => {
|
|
255
|
+
const context = scope.current();
|
|
256
|
+
return { ...traceLogFields(), ...(context === undefined ? {} : logFieldsOf(context)) };
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
// {"time":"2026-03-01T10:00:00.000Z","level":"info","message":"...","service":"shop-api","trace_id":"..."}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`fields` is read at each entry: here the trace ids of the tracing package and, through
|
|
263
|
+
`logFieldsOf` of the kernel, the correlation id, actor and tenant of the current execution
|
|
264
|
+
context. This package depends on neither. When the same field comes from several places, the
|
|
265
|
+
later wins: `base`, then the fields of `child(...)`, then `fields()`, then the fields of the
|
|
266
|
+
log call. None can overwrite `time`, `level` or `message`.
|
|
267
|
+
|
|
268
|
+
It implements the whole port, held by `loggerContract` like the pino and winston adapters:
|
|
269
|
+
|
|
270
|
+
- **Errors.** An `Error` passed as a field (`logger.error('payment failed', { error })`) is
|
|
271
|
+
written as `describeError` of the kernel describes it: `type`, `code` for a `DomainError`,
|
|
272
|
+
`message`, `stack`, then `cause` the same way. `scrubMessages: true` writes
|
|
273
|
+
`[message removed]` for the message of every error that is not a `DomainError`: turn it on
|
|
274
|
+
when the logs leave your hands. `logged` and `loggingMiddleware` are not concerned: they
|
|
275
|
+
never pass an error, only `errorType` and `errorCode`.
|
|
276
|
+
- **`isLevelEnabled(level)`** answers the threshold (`level`, default `info`). `logged` and
|
|
277
|
+
`loggingMiddleware` ask it first: below the threshold no field is built and the `fields`
|
|
278
|
+
function of `logged` is not called.
|
|
279
|
+
- **`flush()`** resolves once the standard output took every line written so far, and never
|
|
280
|
+
rejects; the logger stays usable afterwards. `app.stop()` of the di package calls it last.
|
|
281
|
+
- **A log call never throws.** Fields that cannot be read or serialized (a getter that throws,
|
|
282
|
+
a circular object), on an entry or on a child, give the entry without them, marked
|
|
283
|
+
`logError: "unserializable fields"`.
|
|
284
|
+
|
|
285
|
+
It redacts no credential: `@ddd-ts-kit/pino` and `@ddd-ts-kit/winston` do.
|
|
286
|
+
|
|
287
|
+
## What wrapping guarantees
|
|
288
|
+
|
|
289
|
+
- The instance is wrapped in place: same object, `instanceof`, `this` and `private` members
|
|
290
|
+
intact. A frozen instance is fronted by a proxy of the same type, and its methods run with
|
|
291
|
+
that proxy as `this`.
|
|
292
|
+
- **An aspect applied to an instance advises the calls that reach it from outside.** `private`
|
|
293
|
+
is erased by the compiler, and a name does not say whether a method is public (an underscore
|
|
294
|
+
is a habit, not a rule), so the public API cannot be picked out reliably: every asynchronous
|
|
295
|
+
method reachable by name, a `private async` method and a field holding an asynchronous
|
|
296
|
+
function included, is wrapped. What tells the calls apart is where they come from. While an
|
|
297
|
+
advised call is running on an instance, a call that instance makes on itself, to a public or
|
|
298
|
+
a `private` method, is the inside of that call and is not advised again by the same aspect:
|
|
299
|
+
`retry({ attempts: 3 })` around a public method that goes through a private step makes three
|
|
300
|
+
attempts, not nine, and `logged()` and `timed()` record one call. The guard is kept per
|
|
301
|
+
instance and per aspect, and follows the asynchronous flow of the call
|
|
302
|
+
(`AsyncLocalStorage`): two calls made from outside at the same time are both advised, and so
|
|
303
|
+
is whatever an advised call left behind (a timer, a listener) once that call is over.
|
|
304
|
+
- What follows from it: a call that comes back to the instance from a collaborator while the
|
|
305
|
+
advised call is running is the inside of that call too; `cached` serves the callers from
|
|
306
|
+
outside, not a method reading another through `this`; `bulkhead` and `circuitBreaker` count
|
|
307
|
+
one call. A method decorator is not concerned: it advises the one method it is put on, at
|
|
308
|
+
every call, the ones made through `this` included.
|
|
309
|
+
- Tracing differs on purpose: `traceDependency` of `@ddd-ts-kit/tracing` opens a child span for
|
|
310
|
+
a call an instance makes on itself. A span describes a step, where an advice acts on it.
|
|
311
|
+
- A frozen instance stays frozen: its fields, `private` ones included, cannot be assigned. State
|
|
312
|
+
it must change lives in an object it holds (a `Map`, an array).
|
|
313
|
+
- Applying the same aspect twice to an instance is a no-op.
|
|
314
|
+
- Getters, symbols, generators, synchronous methods and lifecycle hooks (`LIFECYCLE_HOOKS`) are
|
|
315
|
+
left alone.
|
|
316
|
+
- A method declared `async` is advised from its first call. A method that merely returns a
|
|
317
|
+
promise is advised from its second call: declare adapter methods `async`. A decorated method
|
|
318
|
+
is always advised.
|
|
319
|
+
|
|
320
|
+
## Testing
|
|
321
|
+
|
|
322
|
+
Wrappers take their collaborators as options, so a spec passes fakes of the kernel. For
|
|
323
|
+
decorated classes and for aspects that read the runtime, `@ddd-ts-kit/aspects/testing` gives:
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
import { createAspectFakes, withAspectRuntime } from '@ddd-ts-kit/aspects/testing';
|
|
327
|
+
|
|
328
|
+
const fakes = createAspectFakes(); // logger, metrics, cache, clock, scheduler
|
|
329
|
+
const gateway = new StripeGateway({ baseUrl: 'http://stripe.test', apiKey: 'test' });
|
|
330
|
+
|
|
331
|
+
const pending = withAspectRuntime(fakes, () => gateway.charge(payment));
|
|
332
|
+
fakes.scheduler.advance(2000); // time moves when the test says so
|
|
333
|
+
|
|
334
|
+
await expect(pending).rejects.toMatchObject({ code: 'TIMEOUT' });
|
|
335
|
+
expect(fakes.logger.entries).toEqual([...]);
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
`withAspectRuntime` holds for the work and everything it awaits, and concurrent tests do not
|
|
339
|
+
see each other's runtime. With `createTestApplication` of the di package the application is the
|
|
340
|
+
runtime: `withAspectRuntime(app, () => ...)`, then assert `app.logger.entries` and `app.metrics`.
|
|
341
|
+
|
|
342
|
+
## Decorator modes
|
|
343
|
+
|
|
344
|
+
The decorators of `./decorators` work under both decorator modes of TypeScript, with the same
|
|
345
|
+
source line.
|
|
346
|
+
|
|
347
|
+
| Stack | `tsconfig.json` |
|
|
348
|
+
|---|---|
|
|
349
|
+
| class-validator, NestJS, TypeORM entities, any library with legacy decorators | `"experimentalDecorators": true` |
|
|
350
|
+
| anything else | leave `experimentalDecorators` unset: standard (TC39) decorators need no flag |
|
|
351
|
+
|
|
352
|
+
- **How the mode is detected.** By the call: the standard one is `(method, context)` with a
|
|
353
|
+
context object, the legacy one `(prototype, name, descriptor)`. `decoratorOf` reads which it
|
|
354
|
+
received, so one function serves both and an aspect has one implementation.
|
|
355
|
+
- **What differs.** In the standard mode the method of each instance is wrapped when the
|
|
356
|
+
instance is constructed. The legacy mode has no initializer, so the method is replaced on
|
|
357
|
+
the prototype and the advice of each instance is built at its first call. State (a circuit,
|
|
358
|
+
a compartment, a single flight) is per instance and per method in both, several decorators
|
|
359
|
+
nest as they read in both, and neither needs `emitDecoratorMetadata`.
|
|
360
|
+
- **Compilers.** TypeScript 5, 6 and the native TypeScript 7 compile both modes. Node does not
|
|
361
|
+
run decorator syntax, so the code is compiled either way.
|
|
362
|
+
- **Vitest.** It lowers legacy decorators by itself: a project with `experimentalDecorators`
|
|
363
|
+
needs nothing. It does not lower standard ones (a spec importing a `.ts` file that uses
|
|
364
|
+
`@Retry` fails with a syntax error): add `decorators()` of `@ddd-ts-kit/testing/vitest` to
|
|
365
|
+
the Vitest config. Files wrapped at the composition root need nothing in either mode.
|
|
366
|
+
- **With a declaration.** `@Adapter(...)` on the class and `@Retry(...)` on its methods
|
|
367
|
+
compose, with the `aspects` of the declaration around the decorators of the methods:
|
|
368
|
+
`@Adapter(PaymentGateway, { kind: 'external' }, [PaymentSettings])`. A static method, a
|
|
369
|
+
method named by a symbol and a hard-private (`#`) method are refused.
|
|
370
|
+
|
|
371
|
+
The `./decorators` entry is separate: nothing else in the package imports it.
|
|
372
|
+
|
|
373
|
+
## Limits
|
|
374
|
+
|
|
375
|
+
- State (circuits, compartments, single-flight) lives in the process, per wrapped instance. A
|
|
376
|
+
transient binding gets a new state with each instance.
|
|
377
|
+
- `retry` repeats the call as it is: only for operations that are safe to repeat.
|
|
378
|
+
- `timeout` without `signal` does not stop the underlying call.
|
|
379
|
+
- A wrapped call always answers a promise: do not wrap an object whose methods return a
|
|
380
|
+
chainable query builder.
|
|
381
|
+
- `when` selects methods of aspects made by `defineAspect`, `compose` or `when`; a plain
|
|
382
|
+
function used as an aspect filters by itself.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Advice } from './advice.js';
|
|
2
|
+
import type { JoinPoint } from './join-point.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the advice of one method of one instance, once, when the instance is wrapped: state
|
|
5
|
+
* kept in the closure is per instance and per method. Answering `undefined` leaves the method
|
|
6
|
+
* alone.
|
|
7
|
+
*/
|
|
8
|
+
export type AdviceFactory = (joinPoint: JoinPoint) => Advice | undefined;
|
|
9
|
+
//# sourceMappingURL=advice-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advice-factory.d.ts","sourceRoot":"","sources":["../../src/aspect/advice-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,SAAS,KAAK,MAAM,GAAG,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advice-factory.js","sourceRoot":"","sources":["../../src/aspect/advice-factory.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advice.d.ts","sourceRoot":"","sources":["../../src/aspect/advice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,MAAM,MAAM,GAAG,CAAC,UAAU,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advice.js","sourceRoot":"","sources":["../../src/aspect/advice.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AspectKind } from './aspect-kind.js';
|
|
2
|
+
/** What the composition root knows about the instance it wraps. Everything is optional. */
|
|
3
|
+
export interface AspectInfo {
|
|
4
|
+
readonly kind?: AspectKind;
|
|
5
|
+
/** Defaults to the constructor name; set it when the build minifies class names. */
|
|
6
|
+
readonly className?: string;
|
|
7
|
+
/** Bounded context owning the instance. */
|
|
8
|
+
readonly context?: string;
|
|
9
|
+
/** Description of the port the instance is bound to. */
|
|
10
|
+
readonly token?: string;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=aspect-info.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aspect-info.d.ts","sourceRoot":"","sources":["../../src/aspect/aspect-info.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,2FAA2F;AAC3F,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC3B,oFAAoF;IACpF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aspect-info.js","sourceRoot":"","sources":["../../src/aspect/aspect-info.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aspect-kind.d.ts","sourceRoot":"","sources":["../../src/aspect/aspect-kind.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,iFAAiF;AACjF,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aspect-kind.js","sourceRoot":"","sources":["../../src/aspect/aspect-kind.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AdviceFactory } from './advice-factory.js';
|
|
2
|
+
import type { Aspect } from './aspect.js';
|
|
3
|
+
export type AspectOrigin = {
|
|
4
|
+
readonly advise: AdviceFactory;
|
|
5
|
+
} | {
|
|
6
|
+
readonly parts: readonly Aspect[];
|
|
7
|
+
};
|
|
8
|
+
/** How each aspect of this package was made, so that `when` can select the methods of one. */
|
|
9
|
+
export declare const aspectOrigins: WeakMap<Aspect, AspectOrigin>;
|
|
10
|
+
//# sourceMappingURL=aspect-origins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aspect-origins.d.ts","sourceRoot":"","sources":["../../src/aspect/aspect-origins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAEtG,8FAA8F;AAC9F,eAAO,MAAM,aAAa,+BAAsC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aspect-origins.js","sourceRoot":"","sources":["../../src/aspect/aspect-origins.ts"],"names":[],"mappings":"AAKA,8FAA8F;AAC9F,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,OAAO,EAAwB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AspectInfo } from './aspect-info.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-cutting behaviour applied from outside: takes an instance, answers it with its
|
|
4
|
+
* asynchronous methods wrapped. The instance is the same object (a proxy stands in for a
|
|
5
|
+
* frozen one), so `this`, `instanceof` and its private members keep working, and applying the
|
|
6
|
+
* same aspect twice is a no-op.
|
|
7
|
+
*/
|
|
8
|
+
export type Aspect = <T extends object>(instance: T, info?: AspectInfo) => T;
|
|
9
|
+
//# sourceMappingURL=aspect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aspect.d.ts","sourceRoot":"","sources":["../../src/aspect/aspect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,UAAU,KAAK,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aspect.js","sourceRoot":"","sources":["../../src/aspect/aspect.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Aspect } from './aspect.js';
|
|
2
|
+
/**
|
|
3
|
+
* One aspect out of several, the first outermost: `compose(retry(...), timeout(2000))` bounds
|
|
4
|
+
* each attempt, the other order bounds them all together.
|
|
5
|
+
*/
|
|
6
|
+
export declare function compose(...aspects: readonly Aspect[]): Aspect;
|
|
7
|
+
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/aspect/compose.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C;;;GAGG;AACH,wBAAgB,OAAO,CAAC,GAAG,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAO7D"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { aspectOrigins } from './aspect-origins.js';
|
|
2
|
+
/**
|
|
3
|
+
* One aspect out of several, the first outermost: `compose(retry(...), timeout(2000))` bounds
|
|
4
|
+
* each attempt, the other order bounds them all together.
|
|
5
|
+
*/
|
|
6
|
+
export function compose(...aspects) {
|
|
7
|
+
const composed = (instance, info = {}) => aspects.reduceRight((wrapped, aspect) => aspect(wrapped, info), instance);
|
|
8
|
+
aspectOrigins.set(composed, { parts: aspects });
|
|
9
|
+
return composed;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/aspect/compose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,GAAG,OAA0B;IACnD,MAAM,QAAQ,GAAW,CAAC,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,CAC/C,OAAO,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;IAE5E,aAAa,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IAEhD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AdviceFactory } from './advice-factory.js';
|
|
2
|
+
import type { Aspect } from './aspect.js';
|
|
3
|
+
/** Makes an aspect out of an advice factory. Every aspect of this package is made this way. */
|
|
4
|
+
export declare function defineAspect(advise: AdviceFactory): Aspect;
|
|
5
|
+
//# sourceMappingURL=define-aspect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-aspect.d.ts","sourceRoot":"","sources":["../../src/aspect/define-aspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,+FAA+F;AAC/F,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAkB1D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { adviseMethods } from '../weaving/advise-methods.js';
|
|
2
|
+
import { aspectOrigins } from './aspect-origins.js';
|
|
3
|
+
/** Makes an aspect out of an advice factory. Every aspect of this package is made this way. */
|
|
4
|
+
export function defineAspect(advise) {
|
|
5
|
+
const applied = new WeakSet();
|
|
6
|
+
const aspect = (instance, info = {}) => {
|
|
7
|
+
if (applied.has(instance)) {
|
|
8
|
+
return instance;
|
|
9
|
+
}
|
|
10
|
+
const result = adviseMethods(instance, info, advise);
|
|
11
|
+
applied.add(result);
|
|
12
|
+
return result;
|
|
13
|
+
};
|
|
14
|
+
aspectOrigins.set(aspect, { advise });
|
|
15
|
+
return aspect;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=define-aspect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-aspect.js","sourceRoot":"","sources":["../../src/aspect/define-aspect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,+FAA+F;AAC/F,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAU,CAAC;IAEtC,MAAM,MAAM,GAAW,CAAC,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE;QAC7C,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAErD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAEpB,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAEtC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** One call. `proceed` runs what is inside, with the same arguments unless others are given. */
|
|
2
|
+
export interface Invocation {
|
|
3
|
+
readonly args: readonly unknown[];
|
|
4
|
+
readonly proceed: (args?: readonly unknown[]) => Promise<unknown>;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=invocation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invocation.d.ts","sourceRoot":"","sources":["../../src/aspect/invocation.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invocation.js","sourceRoot":"","sources":["../../src/aspect/invocation.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AspectKind } from './aspect-kind.js';
|
|
2
|
+
/** One method of one instance: what a selector tests and what an advice is built for. */
|
|
3
|
+
export interface JoinPoint {
|
|
4
|
+
readonly instance: object;
|
|
5
|
+
readonly className: string;
|
|
6
|
+
readonly method: string;
|
|
7
|
+
/** Number of declared parameters of the method. */
|
|
8
|
+
readonly arity: number;
|
|
9
|
+
readonly kind?: AspectKind;
|
|
10
|
+
readonly context?: string;
|
|
11
|
+
readonly token?: string;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=join-point.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"join-point.d.ts","sourceRoot":"","sources":["../../src/aspect/join-point.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,yFAAyF;AACzF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"join-point.js","sourceRoot":"","sources":["../../src/aspect/join-point.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../../src/aspect/selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,MAAM,QAAQ,GAAG,CAAC,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC"}
|