@composed-di/instrumentation-core 0.6.0-alpha

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.
@@ -0,0 +1,120 @@
1
+ import type { DisposeContext, EventSpan, InitializeContext, MethodCallContext, ServiceInstrumentation } from './serviceInstrumentation';
2
+ import type { ServiceKey } from '@composed-di/core';
3
+ /**
4
+ * The placeholder that replaces a redacted value when no custom
5
+ * transform is given for it.
6
+ */
7
+ export declare const REDACTED_VALUE = "[REDACTED]";
8
+ /**
9
+ * Custom masking for one included property, narrowed to the exact
10
+ * method named when {@link RedactionRuleBuilder.redact} is called.
11
+ * Omitting a side fully blanks it with {@link REDACTED_VALUE}; providing
12
+ * one lets you report a partial mask instead (e.g. the last 4 digits of
13
+ * a card number) — both always return a `string`, the masked
14
+ * representation to report in place of the real value.
15
+ */
16
+ export type Mask<T, K extends Extract<keyof T, string>> = T[K] extends (...args: infer A) => infer R ? {
17
+ maskArgs?: (...args: A) => string;
18
+ maskResult?: (result: R) => string;
19
+ } : never;
20
+ /**
21
+ * Marks a service — or specific properties of it — as sensitive, so the
22
+ * values flowing through its events are redacted. Returned by
23
+ * {@link RedactionRuleBuilder.build}, which is the only way to construct
24
+ * one — the `redactAll`/per-property merge logic lives entirely inside
25
+ * `maskArgs`/`maskResult`, so callers never need to know how a rule
26
+ * reached its decision, only what to do with it.
27
+ */
28
+ export interface RedactionRule<T> {
29
+ readonly key: ServiceKey<T>;
30
+ /**
31
+ * The args to report for a call to `functionName`: unchanged if not
32
+ * redacted, otherwise blanked or run through a custom `maskArgs`.
33
+ */
34
+ maskArgs(functionName: string, args: readonly unknown[]): readonly unknown[];
35
+ /**
36
+ * The value to report for a success outcome: unchanged if not
37
+ * redacted, otherwise blanked or run through a custom `maskResult`.
38
+ * Omit `functionName` to ask about the initialize result (the service
39
+ * instance itself), which only the rule's `redactAll` default touches.
40
+ */
41
+ maskResult(functionName: string | undefined, result: unknown): unknown;
42
+ }
43
+ /**
44
+ * Fluent, single-key rule builder returned by {@link redactionRule}.
45
+ * `redactAll`, `redact`, and `exclude` all merge into the same rule —
46
+ * call them in any combination, in any order; the more specific
47
+ * per-property calls (`redact`/`exclude`) always win over the blanket
48
+ * `redactAll` default for the properties they name.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * const rules = [
53
+ * redactionRule(SecretClientKey)
54
+ * .redactAll()
55
+ * .build(), // whole service is sensitive
56
+ * redactionRule(BillingKey)
57
+ * .redactAll()
58
+ * .redact('chargeCard', { maskResult: (card) => `card ending in ${card.number.slice(-4)}` })
59
+ * .exclude('ping') // redact everything except this, with one custom mask
60
+ * .build(),
61
+ * redactionRule(VaultKey)
62
+ * .redact('getSecret')
63
+ * .build(), // only this call, nothing else
64
+ * ];
65
+ * ```
66
+ */
67
+ export declare class RedactionRuleBuilder<T> {
68
+ private readonly key;
69
+ private redactAllFlag;
70
+ private hasRedact;
71
+ private readonly overrides;
72
+ constructor(key: ServiceKey<T>);
73
+ /** Redacts every property, plus the initialize result, by default. */
74
+ redactAll(): this;
75
+ /**
76
+ * Marks one property (method) as redacted, with optional custom
77
+ * masking. Call repeatedly for several properties. Overrides
78
+ * `redactAll`/`exclude` for this specific property.
79
+ */
80
+ redact<K extends Extract<keyof T, string>>(name: K, mask?: Mask<T, K>): this;
81
+ /**
82
+ * Marks one or more properties as explicitly NOT redacted, overriding
83
+ * `redactAll` for just these.
84
+ */
85
+ exclude(...names: Extract<keyof T, string>[]): this;
86
+ build(): RedactionRule<any>;
87
+ }
88
+ export declare function redactionRule<T>(key: ServiceKey<T>): RedactionRuleBuilder<T>;
89
+ /**
90
+ * A ServiceInstrumentation decorator that redacts sensitive values before
91
+ * they reach the wrapped instrumentation. Works with any implementation via
92
+ * delegation: arguments in MethodCallContext and success values in
93
+ * EventOutcome are replaced (wholesale, or via a custom transform)
94
+ * before the delegate ever sees them — whatever it captures or exports
95
+ * is already scrubbed.
96
+ *
97
+ * Failure outcomes are passed through unchanged so error reporting keeps
98
+ * working; keep secrets out of error messages at the throwing site.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * const instrumentation = new RedactingInstrumentation(
103
+ * new OTELInstrumentation({ captureArguments: true, captureResults: true }),
104
+ * [
105
+ * redactionRule(SecretClientKey).redactAll().build(), // whole service is sensitive
106
+ * redactionRule(VaultKey).redact('getSecret').build(), // only this call
107
+ * redactionRule(HealthKey).redactAll().exclude('ping').build(), // everything but this call
108
+ * ],
109
+ * );
110
+ * ```
111
+ */
112
+ export declare class RedactingInstrumentation implements ServiceInstrumentation {
113
+ private readonly delegate;
114
+ private readonly rules;
115
+ constructor(delegate: ServiceInstrumentation, rules: readonly RedactionRule<any>[]);
116
+ onInitialize(context: InitializeContext): EventSpan | void;
117
+ onDispose(context: DisposeContext): EventSpan | void;
118
+ onMethodCall(context: MethodCallContext): EventSpan | void;
119
+ }
120
+ //# sourceMappingURL=redactingInstrumentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redactingInstrumentation.d.ts","sourceRoot":"","sources":["../src/redactingInstrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEd,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACvB,MAAM,0BAA0B,CAAA;AACjC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;;GAGG;AACH,eAAO,MAAM,cAAc,eAAe,CAAA;AAE1C;;;;;;;GAOG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CACrE,GAAG,IAAI,EAAE,MAAM,CAAC,KACb,MAAM,CAAC,GACR;IACE,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,MAAM,CAAA;IACjC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAA;CACnC,GACD,KAAK,CAAA;AAeT;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IAE3B;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,OAAO,EAAE,CAAA;IAE5E;;;;;OAKG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAA;CACvE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,oBAAoB,CAAC,CAAC;IAKrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAJhC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;gBAEpC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;IAE/C,sEAAsE;IACtE,SAAS,IAAI,IAAI;IAMjB;;;;OAIG;IACH,MAAM,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI;IAM5E;;;OAGG;IACH,OAAO,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI;IAOnD,KAAK,IAAI,aAAa,CAAC,GAAG,CAAC;CA0C5B;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAE5E;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,wBAAyB,YAAW,sBAAsB;IAEnE,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;gBADL,QAAQ,EAAE,sBAAsB,EAChC,KAAK,EAAE,SAAS,aAAa,CAAC,GAAG,CAAC,EAAE;IAGvD,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI;IAK1D,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI;IAKpD,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI;CAY3D"}
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RedactingInstrumentation = exports.RedactionRuleBuilder = exports.REDACTED_VALUE = void 0;
4
+ exports.redactionRule = redactionRule;
5
+ /**
6
+ * The placeholder that replaces a redacted value when no custom
7
+ * transform is given for it.
8
+ */
9
+ exports.REDACTED_VALUE = '[REDACTED]';
10
+ /**
11
+ * Fluent, single-key rule builder returned by {@link redactionRule}.
12
+ * `redactAll`, `redact`, and `exclude` all merge into the same rule —
13
+ * call them in any combination, in any order; the more specific
14
+ * per-property calls (`redact`/`exclude`) always win over the blanket
15
+ * `redactAll` default for the properties they name.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const rules = [
20
+ * redactionRule(SecretClientKey)
21
+ * .redactAll()
22
+ * .build(), // whole service is sensitive
23
+ * redactionRule(BillingKey)
24
+ * .redactAll()
25
+ * .redact('chargeCard', { maskResult: (card) => `card ending in ${card.number.slice(-4)}` })
26
+ * .exclude('ping') // redact everything except this, with one custom mask
27
+ * .build(),
28
+ * redactionRule(VaultKey)
29
+ * .redact('getSecret')
30
+ * .build(), // only this call, nothing else
31
+ * ];
32
+ * ```
33
+ */
34
+ class RedactionRuleBuilder {
35
+ constructor(key) {
36
+ this.key = key;
37
+ this.redactAllFlag = false;
38
+ this.hasRedact = false;
39
+ this.overrides = {};
40
+ }
41
+ /** Redacts every property, plus the initialize result, by default. */
42
+ redactAll() {
43
+ this.redactAllFlag = true;
44
+ this.hasRedact = true;
45
+ return this;
46
+ }
47
+ /**
48
+ * Marks one property (method) as redacted, with optional custom
49
+ * masking. Call repeatedly for several properties. Overrides
50
+ * `redactAll`/`exclude` for this specific property.
51
+ */
52
+ redact(name, mask) {
53
+ this.overrides[name] = Object.assign({ redacted: true }, mask);
54
+ this.hasRedact = true;
55
+ return this;
56
+ }
57
+ /**
58
+ * Marks one or more properties as explicitly NOT redacted, overriding
59
+ * `redactAll` for just these.
60
+ */
61
+ exclude(...names) {
62
+ for (const name of names) {
63
+ this.overrides[name] = { redacted: false };
64
+ }
65
+ return this;
66
+ }
67
+ build() {
68
+ // `exclude` alone never redacts anything — at least one call to
69
+ // `redactAll`/`redact` is required for this rule to have any effect.
70
+ if (!this.hasRedact) {
71
+ throw new Error(`redactionRule(${this.key.name}) has no effect: call .redactAll() and/or .redact(...) ` +
72
+ 'before .build() — .exclude() alone never redacts anything.');
73
+ }
74
+ const redactAllFlag = this.redactAllFlag;
75
+ const overrides = this.overrides;
76
+ return {
77
+ key: this.key,
78
+ maskArgs(functionName, args) {
79
+ const override = overrides[functionName];
80
+ const redacted = override ? override.redacted : redactAllFlag;
81
+ if (!redacted) {
82
+ return args;
83
+ }
84
+ return (override === null || override === void 0 ? void 0 : override.maskArgs)
85
+ ? // A custom mask reports one string for the whole call,
86
+ // rather than a value per argument.
87
+ [override.maskArgs(...args)]
88
+ : // Replace each argument rather than the whole array, so the
89
+ // delegate still sees the call's arity.
90
+ args.map(() => exports.REDACTED_VALUE);
91
+ },
92
+ maskResult(functionName, result) {
93
+ const override = functionName === undefined ? undefined : overrides[functionName];
94
+ const redacted = override ? override.redacted : redactAllFlag;
95
+ if (!redacted) {
96
+ return result;
97
+ }
98
+ return (override === null || override === void 0 ? void 0 : override.maskResult)
99
+ ? override.maskResult(result)
100
+ : exports.REDACTED_VALUE;
101
+ },
102
+ };
103
+ }
104
+ }
105
+ exports.RedactionRuleBuilder = RedactionRuleBuilder;
106
+ function redactionRule(key) {
107
+ return new RedactionRuleBuilder(key);
108
+ }
109
+ /**
110
+ * A ServiceInstrumentation decorator that redacts sensitive values before
111
+ * they reach the wrapped instrumentation. Works with any implementation via
112
+ * delegation: arguments in MethodCallContext and success values in
113
+ * EventOutcome are replaced (wholesale, or via a custom transform)
114
+ * before the delegate ever sees them — whatever it captures or exports
115
+ * is already scrubbed.
116
+ *
117
+ * Failure outcomes are passed through unchanged so error reporting keeps
118
+ * working; keep secrets out of error messages at the throwing site.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * const instrumentation = new RedactingInstrumentation(
123
+ * new OTELInstrumentation({ captureArguments: true, captureResults: true }),
124
+ * [
125
+ * redactionRule(SecretClientKey).redactAll().build(), // whole service is sensitive
126
+ * redactionRule(VaultKey).redact('getSecret').build(), // only this call
127
+ * redactionRule(HealthKey).redactAll().exclude('ping').build(), // everything but this call
128
+ * ],
129
+ * );
130
+ * ```
131
+ */
132
+ class RedactingInstrumentation {
133
+ constructor(delegate, rules) {
134
+ this.delegate = delegate;
135
+ this.rules = rules;
136
+ }
137
+ onInitialize(context) {
138
+ var _a, _b;
139
+ const rule = this.rules.find((r) => r.key === context.key);
140
+ return redactSpan((_b = (_a = this.delegate).onInitialize) === null || _b === void 0 ? void 0 : _b.call(_a, context), rule);
141
+ }
142
+ onDispose(context) {
143
+ var _a, _b;
144
+ // Dispose carries no arguments and no result value; nothing to redact.
145
+ return (_b = (_a = this.delegate).onDispose) === null || _b === void 0 ? void 0 : _b.call(_a, context);
146
+ }
147
+ onMethodCall(context) {
148
+ var _a, _b;
149
+ const rule = this.rules.find((r) => r.key === context.key);
150
+ const span = (_b = (_a = this.delegate).onMethodCall) === null || _b === void 0 ? void 0 : _b.call(_a, rule
151
+ ? Object.assign(Object.assign({}, context), { args: rule.maskArgs(context.functionName, context.args) }) : context);
152
+ return redactSpan(span, rule, context.functionName);
153
+ }
154
+ }
155
+ exports.RedactingInstrumentation = RedactingInstrumentation;
156
+ /**
157
+ * Wraps the delegate's EventSpan so success values are redacted before
158
+ * `end` sees them, by delegating to the rule's `maskResult`. `run` (and
159
+ * any future fields) pass through untouched. `functionName` is omitted
160
+ * for the initialize result (the instance itself).
161
+ */
162
+ function redactSpan(span, rule, functionName) {
163
+ if (!rule || !span) {
164
+ return span;
165
+ }
166
+ const end = span.end.bind(span);
167
+ return Object.assign(Object.assign({}, span), { end: (outcome) => end(outcome.type === 'success'
168
+ ? {
169
+ type: 'success',
170
+ value: rule.maskResult(functionName, outcome.value),
171
+ }
172
+ : outcome) });
173
+ }
174
+ //# sourceMappingURL=redactingInstrumentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redactingInstrumentation.js","sourceRoot":"","sources":["../src/redactingInstrumentation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import type { ServiceKey } from '@composed-di/core';
2
+ /**
3
+ * The placeholder that replaces a redacted value when no custom
4
+ * transform is given for it.
5
+ */
6
+ export declare const REDACTED_VALUE = "[REDACTED]";
7
+ /**
8
+ * Custom masking for one included property, narrowed to the exact
9
+ * method named when {@link RedactionRuleBuilder.redact} is called.
10
+ * Omitting a side fully blanks it with {@link REDACTED_VALUE}; providing
11
+ * one lets you report a partial mask instead (e.g. the last 4 digits of
12
+ * a card number) — both always return a `string`, the masked
13
+ * representation to report in place of the real value.
14
+ */
15
+ export type Mask<T, K extends Extract<keyof T, string>> = T[K] extends (...args: infer A) => infer R ? {
16
+ maskArgs?: (...args: A) => string;
17
+ maskResult?: (result: R) => string;
18
+ } : never;
19
+ /**
20
+ * Marks a service — or specific properties of it — as sensitive, so the
21
+ * values flowing through its events are redacted. Passed to
22
+ * {@link instrument} via InstrumentOptions.redactionRules and applied
23
+ * centrally, after the capture flags: values a rule matches are scrubbed
24
+ * before the instrumentation ever sees them, and when capture is off
25
+ * there is nothing to redact. Returned by
26
+ * {@link RedactionRuleBuilder.build}, which is the only way to construct
27
+ * one — the `redactAll`/per-property merge logic lives entirely inside
28
+ * `maskArgs`/`maskResult`, so callers never need to know how a rule
29
+ * reached its decision, only what to do with it.
30
+ */
31
+ export interface RedactionRule<T> {
32
+ readonly key: ServiceKey<T>;
33
+ /**
34
+ * The args to report for a call to `functionName`: unchanged if not
35
+ * redacted, otherwise blanked or run through a custom `maskArgs`.
36
+ */
37
+ maskArgs(functionName: string, args: readonly unknown[]): readonly unknown[];
38
+ /**
39
+ * The value to report for a method call's success outcome: unchanged
40
+ * if not redacted, otherwise blanked or run through a custom
41
+ * `maskResult`.
42
+ */
43
+ maskResult(functionName: string, result: unknown): unknown;
44
+ }
45
+ /**
46
+ * Fluent, single-key rule builder returned by {@link redactionRule}.
47
+ * `redactAll`, `redact`, and `exclude` all merge into the same rule —
48
+ * call them in any combination, in any order; the more specific
49
+ * per-property calls (`redact`/`exclude`) always win over the blanket
50
+ * `redactAll` default for the properties they name.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const rules = [
55
+ * redactionRule(SecretClientKey)
56
+ * .redactAll()
57
+ * .build(), // whole service is sensitive
58
+ * redactionRule(BillingKey)
59
+ * .redactAll()
60
+ * .redact('chargeCard', { maskResult: (card) => `card ending in ${card.number.slice(-4)}` })
61
+ * .exclude('ping') // redact everything except this, with one custom mask
62
+ * .build(),
63
+ * redactionRule(VaultKey)
64
+ * .redact('getSecret')
65
+ * .build(), // only this call, nothing else
66
+ * ];
67
+ * ```
68
+ */
69
+ export declare class RedactionRuleBuilder<T> {
70
+ private readonly key;
71
+ private redactAllFlag;
72
+ private hasRedact;
73
+ private readonly overrides;
74
+ constructor(key: ServiceKey<T>);
75
+ /** Redacts every property by default. */
76
+ redactAll(): this;
77
+ /**
78
+ * Marks one property (method) as redacted, with optional custom
79
+ * masking. Call repeatedly for several properties. Overrides
80
+ * `redactAll`/`exclude` for this specific property.
81
+ */
82
+ redact<K extends Extract<keyof T, string>>(name: K, mask?: Mask<T, K>): this;
83
+ /**
84
+ * Marks one or more properties as explicitly NOT redacted, overriding
85
+ * `redactAll` for just these.
86
+ */
87
+ exclude(...names: Extract<keyof T, string>[]): this;
88
+ build(): RedactionRule<any>;
89
+ }
90
+ export declare function redactionRule<T>(key: ServiceKey<T>): RedactionRuleBuilder<T>;
91
+ //# sourceMappingURL=redaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;;GAGG;AACH,eAAO,MAAM,cAAc,eAAe,CAAA;AAE1C;;;;;;;GAOG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CACrE,GAAG,IAAI,EAAE,MAAM,CAAC,KACb,MAAM,CAAC,GACR;IACE,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,MAAM,CAAA;IACjC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAA;CACnC,GACD,KAAK,CAAA;AAeT;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IAE3B;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,OAAO,EAAE,CAAA;IAE5E;;;;OAIG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAA;CAC3D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,oBAAoB,CAAC,CAAC;IAKrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAJhC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;gBAEpC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;IAE/C,yCAAyC;IACzC,SAAS,IAAI,IAAI;IAMjB;;;;OAIG;IACH,MAAM,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI;IAM5E;;;OAGG;IACH,OAAO,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI;IAOnD,KAAK,IAAI,aAAa,CAAC,GAAG,CAAC;CAyC5B;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAE5E"}
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RedactionRuleBuilder = exports.REDACTED_VALUE = void 0;
4
+ exports.redactionRule = redactionRule;
5
+ /**
6
+ * The placeholder that replaces a redacted value when no custom
7
+ * transform is given for it.
8
+ */
9
+ exports.REDACTED_VALUE = '[REDACTED]';
10
+ /**
11
+ * Fluent, single-key rule builder returned by {@link redactionRule}.
12
+ * `redactAll`, `redact`, and `exclude` all merge into the same rule —
13
+ * call them in any combination, in any order; the more specific
14
+ * per-property calls (`redact`/`exclude`) always win over the blanket
15
+ * `redactAll` default for the properties they name.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const rules = [
20
+ * redactionRule(SecretClientKey)
21
+ * .redactAll()
22
+ * .build(), // whole service is sensitive
23
+ * redactionRule(BillingKey)
24
+ * .redactAll()
25
+ * .redact('chargeCard', { maskResult: (card) => `card ending in ${card.number.slice(-4)}` })
26
+ * .exclude('ping') // redact everything except this, with one custom mask
27
+ * .build(),
28
+ * redactionRule(VaultKey)
29
+ * .redact('getSecret')
30
+ * .build(), // only this call, nothing else
31
+ * ];
32
+ * ```
33
+ */
34
+ class RedactionRuleBuilder {
35
+ constructor(key) {
36
+ this.key = key;
37
+ this.redactAllFlag = false;
38
+ this.hasRedact = false;
39
+ this.overrides = {};
40
+ }
41
+ /** Redacts every property by default. */
42
+ redactAll() {
43
+ this.redactAllFlag = true;
44
+ this.hasRedact = true;
45
+ return this;
46
+ }
47
+ /**
48
+ * Marks one property (method) as redacted, with optional custom
49
+ * masking. Call repeatedly for several properties. Overrides
50
+ * `redactAll`/`exclude` for this specific property.
51
+ */
52
+ redact(name, mask) {
53
+ this.overrides[name] = Object.assign({ redacted: true }, mask);
54
+ this.hasRedact = true;
55
+ return this;
56
+ }
57
+ /**
58
+ * Marks one or more properties as explicitly NOT redacted, overriding
59
+ * `redactAll` for just these.
60
+ */
61
+ exclude(...names) {
62
+ for (const name of names) {
63
+ this.overrides[name] = { redacted: false };
64
+ }
65
+ return this;
66
+ }
67
+ build() {
68
+ // `exclude` alone never redacts anything — at least one call to
69
+ // `redactAll`/`redact` is required for this rule to have any effect.
70
+ if (!this.hasRedact) {
71
+ throw new Error(`redactionRule(${this.key.name}) has no effect: call .redactAll() and/or .redact(...) ` +
72
+ 'before .build() — .exclude() alone never redacts anything.');
73
+ }
74
+ const redactAllFlag = this.redactAllFlag;
75
+ const overrides = this.overrides;
76
+ return {
77
+ key: this.key,
78
+ maskArgs(functionName, args) {
79
+ const override = overrides[functionName];
80
+ const redacted = override ? override.redacted : redactAllFlag;
81
+ if (!redacted) {
82
+ return args;
83
+ }
84
+ return (override === null || override === void 0 ? void 0 : override.maskArgs)
85
+ ? // A custom mask reports one string for the whole call,
86
+ // rather than a value per argument.
87
+ [override.maskArgs(...args)]
88
+ : // Replace each argument rather than the whole array, so the
89
+ // delegate still sees the call's arity.
90
+ args.map(() => exports.REDACTED_VALUE);
91
+ },
92
+ maskResult(functionName, result) {
93
+ const override = overrides[functionName];
94
+ const redacted = override ? override.redacted : redactAllFlag;
95
+ if (!redacted) {
96
+ return result;
97
+ }
98
+ return (override === null || override === void 0 ? void 0 : override.maskResult)
99
+ ? override.maskResult(result)
100
+ : exports.REDACTED_VALUE;
101
+ },
102
+ };
103
+ }
104
+ }
105
+ exports.RedactionRuleBuilder = RedactionRuleBuilder;
106
+ function redactionRule(key) {
107
+ return new RedactionRuleBuilder(key);
108
+ }
109
+ //# sourceMappingURL=redaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,151 @@
1
+ import type { ServiceKey } from '@composed-di/core';
2
+ /**
3
+ * A handle representing a single in-flight operation (initialization,
4
+ * disposal, or method call), returned by a ServiceInstrumentation when the
5
+ * operation starts.
6
+ *
7
+ * `end` is invoked exactly once when the operation finishes, so
8
+ * implementations can close over per-call state (a start time, a span
9
+ * handle, a correlation id) without any bookkeeping to pair concurrent
10
+ * start/finish events.
11
+ */
12
+ export interface EventSpan {
13
+ /**
14
+ * Wrapper around the operation itself. The instrumented factory invokes
15
+ * the operation as `run(() => operation())`, so the instrumentation can
16
+ * establish ambient state that the operation body and its async
17
+ * continuations inherit — this is what lets spans of nested service
18
+ * calls form a parent-child hierarchy.
19
+ *
20
+ * Implementations must invoke `fn` exactly once, synchronously, and
21
+ * return its result unchanged (for async operations, the promise itself).
22
+ * `end` is still delivered separately when the operation finishes.
23
+ *
24
+ * @param fn - A thunk that performs the operation.
25
+ * @return The value returned by `fn`.
26
+ */
27
+ run<T>(fn: () => T): T;
28
+ /**
29
+ * Invoked exactly once when the operation finishes, whether it succeeded
30
+ * or failed. For methods that return a promise, this fires when the
31
+ * promise settles, not when the method returns. On failure, the error is
32
+ * rethrown to the caller after this is invoked.
33
+ *
34
+ * Whether to retain or log the outcome is the implementation's choice;
35
+ * values are passed by reference, so implementations must not mutate them.
36
+ *
37
+ * @param outcome - How the operation finished, and its value or error.
38
+ * @return void
39
+ */
40
+ end(outcome: EventOutcome): void;
41
+ }
42
+ /**
43
+ * How an operation finished, delivered to EventSpan.end: `success` may
44
+ * carry the return or resolved value of a method call (initialize and
45
+ * dispose outcomes never carry one); `failure` carries the error that was
46
+ * thrown or rejected.
47
+ *
48
+ * Whether `value` is present is decided by {@link instrument}, not by the
49
+ * implementation: it is absent unless result capture is enabled in the
50
+ * InstrumentOptions, and holds the redacted value when a redaction rule
51
+ * matches. Implementations must record the value exactly when it is
52
+ * present (`'value' in outcome` — a captured `undefined` return is
53
+ * delivered as a present `value: undefined`) and must not record any
54
+ * result when it is absent.
55
+ */
56
+ export type EventOutcome = {
57
+ type: 'success';
58
+ value?: unknown;
59
+ } | {
60
+ type: 'failure';
61
+ error: unknown;
62
+ };
63
+ /**
64
+ * Context of a service initialization, delivered to onInitialize.
65
+ * Future fields are added here rather than as extra parameters.
66
+ */
67
+ export interface InitializeContext {
68
+ /**
69
+ * The unique identifier of the service that is being initialized.
70
+ */
71
+ key: ServiceKey<unknown>;
72
+ }
73
+ /**
74
+ * Context of a service disposal, delivered to onDispose.
75
+ * Future fields are added here rather than as extra parameters.
76
+ */
77
+ export interface DisposeContext {
78
+ /**
79
+ * The unique identifier of the service that is being disposed.
80
+ */
81
+ key: ServiceKey<unknown>;
82
+ }
83
+ /**
84
+ * Context of a method invocation, delivered to onMethodCall.
85
+ * Future fields are added here rather than as extra parameters.
86
+ */
87
+ export interface MethodCallContext {
88
+ /**
89
+ * The unique identifier of the service the method belongs to.
90
+ */
91
+ key: ServiceKey<unknown>;
92
+ /**
93
+ * The name of the class implementing the service (the instance's
94
+ * constructor name), which may differ from `key.name`. Undefined for
95
+ * services that are not instances of a named class, such as plain
96
+ * object literals.
97
+ */
98
+ className?: string;
99
+ /**
100
+ * The name of the method that is being called.
101
+ */
102
+ functionName: string;
103
+ /**
104
+ * The arguments to report for this call, passed by reference;
105
+ * implementations must not mutate them.
106
+ *
107
+ * Whether they are present is decided by {@link instrument}, not by the
108
+ * implementation: absent unless argument capture is enabled in the
109
+ * InstrumentOptions, and already redacted when a redaction rule matches.
110
+ * Implementations must record the arguments exactly when present and
111
+ * must not record any arguments when absent.
112
+ */
113
+ args?: readonly unknown[];
114
+ }
115
+ /**
116
+ * Interface for instrumenting services wrapped by {@link instrument}.
117
+ * Implement this interface to observe lifecycle events and method calls
118
+ * of the instrumented services.
119
+ *
120
+ * Instrumentation is strictly observational: implementations see every
121
+ * operation but must never alter it — see the EventSpan contract.
122
+ *
123
+ * Each method is invoked when the corresponding operation starts and may
124
+ * return an EventSpan that is notified when that operation finishes.
125
+ * Returning nothing opts out of completion tracking for that call.
126
+ */
127
+ export interface ServiceInstrumentation {
128
+ /**
129
+ * Invoked at the start of the initialization process for a specific service.
130
+ *
131
+ * @param context - Context of the initialization, including the service key.
132
+ * @return An EventSpan notified when initialization finishes, or void.
133
+ */
134
+ onInitialize?(context: InitializeContext): EventSpan | void;
135
+ /**
136
+ * Invoked when the disposal process for a service starts.
137
+ *
138
+ * @param context - Context of the disposal, including the service key.
139
+ * @return An EventSpan notified when disposal finishes, or void.
140
+ */
141
+ onDispose?(context: DisposeContext): EventSpan | void;
142
+ /**
143
+ * Invoked when a method call starts on a service instance.
144
+ *
145
+ * @param context - Context of the invocation, including the service key,
146
+ * the method name, and its arguments.
147
+ * @return An EventSpan notified when the call finishes, or void.
148
+ */
149
+ onMethodCall?(context: MethodCallContext): EventSpan | void;
150
+ }
151
+ //# sourceMappingURL=serviceInstrumentation.d.ts.map