@mnemonica/otel 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/LICENSE +21 -0
- package/README.md +109 -0
- package/build/hooks/attach-hooks.d.ts +3 -0
- package/build/hooks/attach-hooks.d.ts.map +1 -0
- package/build/hooks/attach-hooks.js +52 -0
- package/build/index.d.ts +32 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +32 -0
- package/build/providers/async-flow.provider.d.ts +40 -0
- package/build/providers/async-flow.provider.d.ts.map +1 -0
- package/build/providers/async-flow.provider.js +128 -0
- package/build/providers/dive-otel.provider.d.ts +51 -0
- package/build/providers/dive-otel.provider.d.ts.map +1 -0
- package/build/providers/dive-otel.provider.js +238 -0
- package/build/providers/mnemonica-otel.provider.d.ts +12 -0
- package/build/providers/mnemonica-otel.provider.d.ts.map +1 -0
- package/build/providers/mnemonica-otel.provider.js +113 -0
- package/build/request-scope.d.ts +41 -0
- package/build/request-scope.d.ts.map +1 -0
- package/build/request-scope.js +35 -0
- package/build/thunderstruck/feed-from-request.d.ts +21 -0
- package/build/thunderstruck/feed-from-request.d.ts.map +1 -0
- package/build/thunderstruck/feed-from-request.js +34 -0
- package/build/thunderstruck/pre-root.d.ts +67 -0
- package/build/thunderstruck/pre-root.d.ts.map +1 -0
- package/build/thunderstruck/pre-root.js +78 -0
- package/build/unblind.d.ts +46 -0
- package/build/unblind.d.ts.map +1 -0
- package/build/unblind.js +120 -0
- package/build/utils/dive-flow.d.ts +24 -0
- package/build/utils/dive-flow.d.ts.map +1 -0
- package/build/utils/dive-flow.js +33 -0
- package/build/utils/is-mnemonica-instance.d.ts +2 -0
- package/build/utils/is-mnemonica-instance.d.ts.map +1 -0
- package/build/utils/is-mnemonica-instance.js +9 -0
- package/build-cjs/hooks/attach-hooks.js +55 -0
- package/build-cjs/index.js +52 -0
- package/build-cjs/package.json +1 -0
- package/build-cjs/providers/async-flow.provider.js +132 -0
- package/build-cjs/providers/dive-otel.provider.js +242 -0
- package/build-cjs/providers/mnemonica-otel.provider.js +117 -0
- package/build-cjs/request-scope.js +38 -0
- package/build-cjs/thunderstruck/feed-from-request.js +37 -0
- package/build-cjs/thunderstruck/pre-root.js +83 -0
- package/build-cjs/unblind.js +127 -0
- package/build-cjs/utils/dive-flow.js +37 -0
- package/build-cjs/utils/is-mnemonica-instance.js +12 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 https://github.com/wentout
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# @mnemonica/otel
|
|
2
|
+
|
|
3
|
+
The framework-free Node.js core of the mnemonica observability stack.
|
|
4
|
+
|
|
5
|
+
This package is everything a framework adapter needs that has nothing to
|
|
6
|
+
do with any framework: the dive hook wiring, the OpenTelemetry providers,
|
|
7
|
+
the ALS async-flow backbone, the thunderstruck pre-root forensics store,
|
|
8
|
+
and the Unblinder error reports — all against plain structural types, so
|
|
9
|
+
they run in Express, Fastify, raw `http`, queue consumers, and CLIs
|
|
10
|
+
unchanged.
|
|
11
|
+
|
|
12
|
+
- [`mnemonica`](https://www.npmjs.com/package/mnemonica) — instance
|
|
13
|
+
inheritance; lineage carried in the prototype chain.
|
|
14
|
+
- [`@mnemonica/dive`](https://www.npmjs.com/package/@mnemonica/dive) —
|
|
15
|
+
execution-flow tracing engine (no ALS, no async_hooks).
|
|
16
|
+
- **this package** — the Node boundary: dive wiring + telemetry + the ALS
|
|
17
|
+
backbone (Node-only by design — dive stays engine-only for Deno/Bun,
|
|
18
|
+
ALS lives here where it is free).
|
|
19
|
+
|
|
20
|
+
Framework wiring is layered on top, never inside: frameworks with their
|
|
21
|
+
own DI container, pipes, interceptors and decorator lifecycles get a
|
|
22
|
+
dedicated first-party adapter package in the `@mnemonica` org — that
|
|
23
|
+
wiring is genuinely complicated and must be packaged and tested as such.
|
|
24
|
+
For simple frameworks the recipes below are all you need.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @mnemonica/otel mnemonica @mnemonica/dive @opentelemetry/api
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`mnemonica`, `@mnemonica/dive` and `@opentelemetry/api` are peer
|
|
33
|
+
dependencies — they are process singletons (the type registry, the edge
|
|
34
|
+
ring, the OTel API), so the app must own exactly one copy of each.
|
|
35
|
+
|
|
36
|
+
Dual build: ESM (`import`) and CommonJS (`require`) both work. The CJS
|
|
37
|
+
flavor loads `@mnemonica/dive` through `require(esm)`, so `require()`
|
|
38
|
+
consumers need Node ≥ 20.19 / 22.
|
|
39
|
+
|
|
40
|
+
## What's inside
|
|
41
|
+
|
|
42
|
+
| Export | Role |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `attachHooks(collection)` | wires a mnemonica TypesCollection to dive's lifecycle tracing (create edges + wrapped methods + context-carrying constructor args) |
|
|
45
|
+
| `MnemonicaOtelProvider` | OTel spans for mnemonica constructions (pre/post/error), parented by lineage, ALS-propagated |
|
|
46
|
+
| `DiveOtelProvider` | OTel spans for EVERY dive-wrapped call, parented on dive's own trace; async spans close at settle |
|
|
47
|
+
| `AsyncFlowProvider` | the ALS backbone: attributes UNWRAPPED async hops (timers, promise continuations, generator suspensions) to the parental dive edge; pins context instances for the scope's lifetime |
|
|
48
|
+
| `runInRequestScope(req, res, deps, fn)` | one OTel span per HTTP request + the triple async scope (provider ALS, OTEL global context, async-flow root frame) |
|
|
49
|
+
| `feedPreRoot` / `feedValidatedPreRoot` / `getPreRoot` | thunderstruck pre-root store: request payloads correlated by OBJECT IDENTITY (WeakMap), retention = the request's lifetime |
|
|
50
|
+
| `feedPreRootFromRequest(req, opts?)` | the boundary helper: shapes any framework's request into the pre-root feed |
|
|
51
|
+
| `buildUnblindReport(error)` / `recordUnblindTelemetry(report, error)` | the Unblinder core: the dive branch + errored construction + attempted args, plus the unconditional span and stdout marker |
|
|
52
|
+
| `isMnemonicaInstance(value)` | realm-safe type guard via `getProps()` |
|
|
53
|
+
| `formatFlow(target?)` / `errorContext(error)` | read-side helpers over dive's trace |
|
|
54
|
+
|
|
55
|
+
## Use it from your own boundary
|
|
56
|
+
|
|
57
|
+
### Express
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { runInRequestScope, feedPreRootFromRequest } from '@mnemonica/otel';
|
|
61
|
+
|
|
62
|
+
app.use((req, res, next) => {
|
|
63
|
+
feedPreRootFromRequest(req); // thunderstruck boundary
|
|
64
|
+
runInRequestScope(req, res, { tracer, otel, asyncFlow }, () => next());
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Fastify
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
fastify.addHook('onRequest', (request, reply, done) => {
|
|
72
|
+
feedPreRootFromRequest(request); // params/query/body/headers
|
|
73
|
+
runInRequestScope(request.raw, reply.raw, { tracer, otel, asyncFlow }, () => done());
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Error boundary (any framework)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { buildUnblindReport, recordUnblindTelemetry } from '@mnemonica/otel';
|
|
81
|
+
|
|
82
|
+
// Express error middleware / Fastify setErrorHandler / process handler:
|
|
83
|
+
const report = buildUnblindReport(error); // branch + errored type + args
|
|
84
|
+
recordUnblindTelemetry(report, error); // span + [unblind] stdout line
|
|
85
|
+
if (!res.headersSent) res.status(500).json(report);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`report.kind` (`'caught-unblinded'`), the span name
|
|
89
|
+
(`'mnemonica.caught-exception'`) and the `[unblind]` stdout marker are
|
|
90
|
+
pinned by downstream consumers — keep them stable.
|
|
91
|
+
|
|
92
|
+
### Non-HTTP scopes (queues, CLI, tests)
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { AsyncFlowProvider } from '@mnemonica/otel';
|
|
96
|
+
|
|
97
|
+
const asyncFlow = new AsyncFlowProvider();
|
|
98
|
+
asyncFlow.attach();
|
|
99
|
+
asyncFlow.runInScope(() => consume(message)); // root frame per job
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## For AI agents
|
|
103
|
+
|
|
104
|
+
The contributor contract — file map, invariants, the testing gate — lives
|
|
105
|
+
in [`AGENTS.md`](./AGENTS.md).
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach-hooks.d.ts","sourceRoot":"","sources":["../../src/hooks/attach-hooks.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,wBAAgB,WAAW,CAAE,UAAU,EAAE,eAAe,GAAG,IAAI,CAsC9D"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* attachHooks — the mnemonica lifecycle wiring for @mnemonica/dive.
|
|
3
|
+
*
|
|
4
|
+
* This lived inside dive itself until dive became engine-only: dive imports
|
|
5
|
+
* nothing but node:crypto and exposes primitives (wrap, recordCreation, …);
|
|
6
|
+
* the knowledge of mnemonica's hook contract — hook names, hooksOpts fields,
|
|
7
|
+
* and what a ROOT construction is — belongs here at adapter level.
|
|
8
|
+
*
|
|
9
|
+
* preCreation → enter the parent (existentInstance) context BEFORE the
|
|
10
|
+
* constructor runs, and wrap any function arguments so
|
|
11
|
+
* callbacks handed to the constructor carry that context.
|
|
12
|
+
* postCreation → record the instance's 'create' edge via recordCreation,
|
|
13
|
+
* then wrap the instance's methods.
|
|
14
|
+
* creationError→ record a failed 'create' edge (status: 'error') under the
|
|
15
|
+
* surviving parent and pin the error to it: the failure is
|
|
16
|
+
* recoverable off the error object itself.
|
|
17
|
+
*/
|
|
18
|
+
import { enterContext, wrapConstructorArg, upgradeConstructorArg, wrapInstanceMethods, recordCreation, recordCreationError, isWrappedFunction, } from '@mnemonica/dive';
|
|
19
|
+
export function attachHooks(collection) {
|
|
20
|
+
collection.registerHook('preCreation', (hookData) => {
|
|
21
|
+
const parent = hookData.existentInstance;
|
|
22
|
+
if (parent) {
|
|
23
|
+
enterContext(parent);
|
|
24
|
+
}
|
|
25
|
+
const args = hookData.args;
|
|
26
|
+
if (Array.isArray(args)) {
|
|
27
|
+
for (let i = 0; i < args.length; i++) {
|
|
28
|
+
const arg = args[i];
|
|
29
|
+
if (typeof arg === 'function' && !isWrappedFunction(arg)) {
|
|
30
|
+
args[i] = wrapConstructorArg(arg, parent);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
collection.registerHook('postCreation', (hookData) => {
|
|
36
|
+
const instance = hookData.inheritedInstance;
|
|
37
|
+
if (!instance) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(hookData.args)) {
|
|
41
|
+
for (const arg of hookData.args) {
|
|
42
|
+
upgradeConstructorArg(arg, instance);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
recordCreation(hookData.TypeName || 'anonymous', instance, hookData.existentInstance);
|
|
46
|
+
wrapInstanceMethods(instance);
|
|
47
|
+
});
|
|
48
|
+
collection.registerHook('creationError', (hookData) => {
|
|
49
|
+
recordCreationError(hookData.TypeName || 'anonymous', hookData.inheritedInstance, hookData.existentInstance);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXR0YWNoLWhvb2tzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2hvb2tzL2F0dGFjaC1ob29rcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7OztHQWdCRztBQUNILE9BQU8sRUFDTixZQUFZLEVBQ1osa0JBQWtCLEVBQ2xCLHFCQUFxQixFQUNyQixtQkFBbUIsRUFDbkIsY0FBYyxFQUNkLG1CQUFtQixFQUNuQixpQkFBaUIsR0FDakIsTUFBTSxpQkFBaUIsQ0FBQztBQUd6QixNQUFNLFVBQVUsV0FBVyxDQUFFLFVBQTJCO0lBQ3ZELFVBQVUsQ0FBQyxZQUFZLENBQUMsYUFBYSxFQUFFLENBQUMsUUFBUSxFQUFFLEVBQUU7UUFDbkQsTUFBTSxNQUFNLEdBQUcsUUFBUSxDQUFDLGdCQUFnQixDQUFDO1FBQ3pDLElBQUksTUFBTSxFQUFFLENBQUM7WUFDWixZQUFZLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDdEIsQ0FBQztRQUNELE1BQU0sSUFBSSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUM7UUFDM0IsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDekIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztnQkFDdEMsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUNwQixJQUFJLE9BQU8sR0FBRyxLQUFLLFVBQVUsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7b0JBQzFELElBQUksQ0FBQyxDQUFDLENBQUMsR0FBRyxrQkFBa0IsQ0FBQyxHQUFtQyxFQUFFLE1BQU0sQ0FBQyxDQUFDO2dCQUMzRSxDQUFDO1lBQ0YsQ0FBQztRQUNGLENBQUM7SUFDRixDQUFDLENBQUMsQ0FBQztJQUVILFVBQVUsQ0FBQyxZQUFZLENBQUMsY0FBYyxFQUFFLENBQUMsUUFBUSxFQUFFLEVBQUU7UUFDcEQsTUFBTSxRQUFRLEdBQUcsUUFBUSxDQUFDLGlCQUFpQixDQUFDO1FBQzVDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNmLE9BQU87UUFDUixDQUFDO1FBQ0QsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQ2xDLEtBQUssTUFBTSxHQUFHLElBQUksUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDO2dCQUNqQyxxQkFBcUIsQ0FBQyxHQUFHLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFDdEMsQ0FBQztRQUNGLENBQUM7UUFDRCxjQUFjLENBQUMsUUFBUSxDQUFDLFFBQVEsSUFBSSxXQUFXLEVBQUUsUUFBUSxFQUFFLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBQ3RGLG1CQUFtQixDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQy9CLENBQUMsQ0FBQyxDQUFDO0lBRUgsVUFBVSxDQUFDLFlBQVksQ0FBQyxlQUFlLEVBQUUsQ0FBQyxRQUFRLEVBQUUsRUFBRTtRQUNyRCxtQkFBbUIsQ0FDbEIsUUFBUSxDQUFDLFFBQVEsSUFBSSxXQUFXLEVBQ2hDLFFBQVEsQ0FBQyxpQkFBaUIsRUFDMUIsUUFBUSxDQUFDLGdCQUFnQixDQUN6QixDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBhdHRhY2hIb29rcyDigJQgdGhlIG1uZW1vbmljYSBsaWZlY3ljbGUgd2lyaW5nIGZvciBAbW5lbW9uaWNhL2RpdmUuXG4gKlxuICogVGhpcyBsaXZlZCBpbnNpZGUgZGl2ZSBpdHNlbGYgdW50aWwgZGl2ZSBiZWNhbWUgZW5naW5lLW9ubHk6IGRpdmUgaW1wb3J0c1xuICogbm90aGluZyBidXQgbm9kZTpjcnlwdG8gYW5kIGV4cG9zZXMgcHJpbWl0aXZlcyAod3JhcCwgcmVjb3JkQ3JlYXRpb24sIOKApik7XG4gKiB0aGUga25vd2xlZGdlIG9mIG1uZW1vbmljYSdzIGhvb2sgY29udHJhY3Qg4oCUIGhvb2sgbmFtZXMsIGhvb2tzT3B0cyBmaWVsZHMsXG4gKiBhbmQgd2hhdCBhIFJPT1QgY29uc3RydWN0aW9uIGlzIOKAlCBiZWxvbmdzIGhlcmUgYXQgYWRhcHRlciBsZXZlbC5cbiAqXG4gKiBwcmVDcmVhdGlvbiAg4oaSIGVudGVyIHRoZSBwYXJlbnQgKGV4aXN0ZW50SW5zdGFuY2UpIGNvbnRleHQgQkVGT1JFIHRoZVxuICogICAgICAgICAgICAgICAgY29uc3RydWN0b3IgcnVucywgYW5kIHdyYXAgYW55IGZ1bmN0aW9uIGFyZ3VtZW50cyBzb1xuICogICAgICAgICAgICAgICAgY2FsbGJhY2tzIGhhbmRlZCB0byB0aGUgY29uc3RydWN0b3IgY2FycnkgdGhhdCBjb250ZXh0LlxuICogcG9zdENyZWF0aW9uIOKGkiByZWNvcmQgdGhlIGluc3RhbmNlJ3MgJ2NyZWF0ZScgZWRnZSB2aWEgcmVjb3JkQ3JlYXRpb24sXG4gKiAgICAgICAgICAgICAgICB0aGVuIHdyYXAgdGhlIGluc3RhbmNlJ3MgbWV0aG9kcy5cbiAqIGNyZWF0aW9uRXJyb3LihpIgcmVjb3JkIGEgZmFpbGVkICdjcmVhdGUnIGVkZ2UgKHN0YXR1czogJ2Vycm9yJykgdW5kZXIgdGhlXG4gKiAgICAgICAgICAgICAgICBzdXJ2aXZpbmcgcGFyZW50IGFuZCBwaW4gdGhlIGVycm9yIHRvIGl0OiB0aGUgZmFpbHVyZSBpc1xuICogICAgICAgICAgICAgICAgcmVjb3ZlcmFibGUgb2ZmIHRoZSBlcnJvciBvYmplY3QgaXRzZWxmLlxuICovXG5pbXBvcnQge1xuXHRlbnRlckNvbnRleHQsXG5cdHdyYXBDb25zdHJ1Y3RvckFyZyxcblx0dXBncmFkZUNvbnN0cnVjdG9yQXJnLFxuXHR3cmFwSW5zdGFuY2VNZXRob2RzLFxuXHRyZWNvcmRDcmVhdGlvbixcblx0cmVjb3JkQ3JlYXRpb25FcnJvcixcblx0aXNXcmFwcGVkRnVuY3Rpb24sXG59IGZyb20gJ0BtbmVtb25pY2EvZGl2ZSc7XG5pbXBvcnQgdHlwZSB7IFR5cGVzQ29sbGVjdGlvbiB9IGZyb20gJ21uZW1vbmljYS9tb2R1bGUnO1xuXG5leHBvcnQgZnVuY3Rpb24gYXR0YWNoSG9va3MgKGNvbGxlY3Rpb246IFR5cGVzQ29sbGVjdGlvbik6IHZvaWQge1xuXHRjb2xsZWN0aW9uLnJlZ2lzdGVySG9vaygncHJlQ3JlYXRpb24nLCAoaG9va0RhdGEpID0+IHtcblx0XHRjb25zdCBwYXJlbnQgPSBob29rRGF0YS5leGlzdGVudEluc3RhbmNlO1xuXHRcdGlmIChwYXJlbnQpIHtcblx0XHRcdGVudGVyQ29udGV4dChwYXJlbnQpO1xuXHRcdH1cblx0XHRjb25zdCBhcmdzID0gaG9va0RhdGEuYXJncztcblx0XHRpZiAoQXJyYXkuaXNBcnJheShhcmdzKSkge1xuXHRcdFx0Zm9yIChsZXQgaSA9IDA7IGkgPCBhcmdzLmxlbmd0aDsgaSsrKSB7XG5cdFx0XHRcdGNvbnN0IGFyZyA9IGFyZ3NbaV07XG5cdFx0XHRcdGlmICh0eXBlb2YgYXJnID09PSAnZnVuY3Rpb24nICYmICFpc1dyYXBwZWRGdW5jdGlvbihhcmcpKSB7XG5cdFx0XHRcdFx0YXJnc1tpXSA9IHdyYXBDb25zdHJ1Y3RvckFyZyhhcmcgYXMgKC4uLmE6IHVua25vd25bXSkgPT4gdW5rbm93biwgcGFyZW50KTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdH1cblx0fSk7XG5cblx0Y29sbGVjdGlvbi5yZWdpc3Rlckhvb2soJ3Bvc3RDcmVhdGlvbicsIChob29rRGF0YSkgPT4ge1xuXHRcdGNvbnN0IGluc3RhbmNlID0gaG9va0RhdGEuaW5oZXJpdGVkSW5zdGFuY2U7XG5cdFx0aWYgKCFpbnN0YW5jZSkge1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblx0XHRpZiAoQXJyYXkuaXNBcnJheShob29rRGF0YS5hcmdzKSkge1xuXHRcdFx0Zm9yIChjb25zdCBhcmcgb2YgaG9va0RhdGEuYXJncykge1xuXHRcdFx0XHR1cGdyYWRlQ29uc3RydWN0b3JBcmcoYXJnLCBpbnN0YW5jZSk7XG5cdFx0XHR9XG5cdFx0fVxuXHRcdHJlY29yZENyZWF0aW9uKGhvb2tEYXRhLlR5cGVOYW1lIHx8ICdhbm9ueW1vdXMnLCBpbnN0YW5jZSwgaG9va0RhdGEuZXhpc3RlbnRJbnN0YW5jZSk7XG5cdFx0d3JhcEluc3RhbmNlTWV0aG9kcyhpbnN0YW5jZSk7XG5cdH0pO1xuXG5cdGNvbGxlY3Rpb24ucmVnaXN0ZXJIb29rKCdjcmVhdGlvbkVycm9yJywgKGhvb2tEYXRhKSA9PiB7XG5cdFx0cmVjb3JkQ3JlYXRpb25FcnJvcihcblx0XHRcdGhvb2tEYXRhLlR5cGVOYW1lIHx8ICdhbm9ueW1vdXMnLFxuXHRcdFx0aG9va0RhdGEuaW5oZXJpdGVkSW5zdGFuY2UsXG5cdFx0XHRob29rRGF0YS5leGlzdGVudEluc3RhbmNlXG5cdFx0KTtcblx0fSk7XG59XG4iXX0=
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mnemonica/otel — the framework-free Node.js core of the mnemonica
|
|
3
|
+
* observability stack.
|
|
4
|
+
*
|
|
5
|
+
* Everything here works in ANY Node.js runtime (Express, Fastify, raw
|
|
6
|
+
* http, queue consumers, CLI); framework wiring lives in dedicated
|
|
7
|
+
* adapter packages built on these primitives.
|
|
8
|
+
*
|
|
9
|
+
* Provides:
|
|
10
|
+
* - attachHooks() — mnemonica lifecycle → dive edge wiring
|
|
11
|
+
* - MnemonicaOtelProvider — OTel spans for constructions
|
|
12
|
+
* - DiveOtelProvider — OTel spans for every dive-wrapped call
|
|
13
|
+
* - AsyncFlowProvider — ALS backbone attributing unwrapped async hops
|
|
14
|
+
* - runInRequestScope() — one request span + triple async scope
|
|
15
|
+
* - feedPreRoot()/feedValidatedPreRoot()/getPreRoot() — thunderstruck
|
|
16
|
+
* pre-root forensics store (identity-correlated, request-lifetime)
|
|
17
|
+
* - feedPreRootFromRequest() — boundary helper over any HTTP request
|
|
18
|
+
* - buildUnblindReport()/recordUnblindTelemetry() — the Unblinder core
|
|
19
|
+
* - isMnemonicaInstance() — realm-safe type guard
|
|
20
|
+
* - formatFlow()/errorContext() — read-side helpers over dive's trace
|
|
21
|
+
*/
|
|
22
|
+
export { attachHooks } from './hooks/attach-hooks.js';
|
|
23
|
+
export { MnemonicaOtelProvider } from './providers/mnemonica-otel.provider.js';
|
|
24
|
+
export { DiveOtelProvider } from './providers/dive-otel.provider.js';
|
|
25
|
+
export { AsyncFlowProvider, type FlowFrame, type CrashContext } from './providers/async-flow.provider.js';
|
|
26
|
+
export { runInRequestScope, type HttpRequestLike, type HttpResponseLike, type RequestScopeDeps } from './request-scope.js';
|
|
27
|
+
export { feedPreRoot, feedValidatedPreRoot, getPreRoot, type RawPreRootPayload, type PreRootRecord, type PreRootData, } from './thunderstruck/pre-root.js';
|
|
28
|
+
export { feedPreRootFromRequest, type RequestLike, type FeedPreRootOptions } from './thunderstruck/feed-from-request.js';
|
|
29
|
+
export { buildUnblindReport, recordUnblindTelemetry, extractSafe, erroredArgsSafe, stringifySafe, type UnblindReport, } from './unblind.js';
|
|
30
|
+
export { isMnemonicaInstance } from './utils/is-mnemonica-instance.js';
|
|
31
|
+
export { formatFlow, errorContext, type FormattedFlowEdge } from './utils/dive-flow.js';
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAC1G,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3H,OAAO,EACN,WAAW,EACX,oBAAoB,EACpB,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,WAAW,GAChB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,sBAAsB,EAAE,KAAK,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AACzH,OAAO,EACN,kBAAkB,EAClB,sBAAsB,EACtB,WAAW,EACX,eAAe,EACf,aAAa,EACb,KAAK,aAAa,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mnemonica/otel — the framework-free Node.js core of the mnemonica
|
|
3
|
+
* observability stack.
|
|
4
|
+
*
|
|
5
|
+
* Everything here works in ANY Node.js runtime (Express, Fastify, raw
|
|
6
|
+
* http, queue consumers, CLI); framework wiring lives in dedicated
|
|
7
|
+
* adapter packages built on these primitives.
|
|
8
|
+
*
|
|
9
|
+
* Provides:
|
|
10
|
+
* - attachHooks() — mnemonica lifecycle → dive edge wiring
|
|
11
|
+
* - MnemonicaOtelProvider — OTel spans for constructions
|
|
12
|
+
* - DiveOtelProvider — OTel spans for every dive-wrapped call
|
|
13
|
+
* - AsyncFlowProvider — ALS backbone attributing unwrapped async hops
|
|
14
|
+
* - runInRequestScope() — one request span + triple async scope
|
|
15
|
+
* - feedPreRoot()/feedValidatedPreRoot()/getPreRoot() — thunderstruck
|
|
16
|
+
* pre-root forensics store (identity-correlated, request-lifetime)
|
|
17
|
+
* - feedPreRootFromRequest() — boundary helper over any HTTP request
|
|
18
|
+
* - buildUnblindReport()/recordUnblindTelemetry() — the Unblinder core
|
|
19
|
+
* - isMnemonicaInstance() — realm-safe type guard
|
|
20
|
+
* - formatFlow()/errorContext() — read-side helpers over dive's trace
|
|
21
|
+
*/
|
|
22
|
+
export { attachHooks } from './hooks/attach-hooks.js';
|
|
23
|
+
export { MnemonicaOtelProvider } from './providers/mnemonica-otel.provider.js';
|
|
24
|
+
export { DiveOtelProvider } from './providers/dive-otel.provider.js';
|
|
25
|
+
export { AsyncFlowProvider } from './providers/async-flow.provider.js';
|
|
26
|
+
export { runInRequestScope } from './request-scope.js';
|
|
27
|
+
export { feedPreRoot, feedValidatedPreRoot, getPreRoot, } from './thunderstruck/pre-root.js';
|
|
28
|
+
export { feedPreRootFromRequest } from './thunderstruck/feed-from-request.js';
|
|
29
|
+
export { buildUnblindReport, recordUnblindTelemetry, extractSafe, erroredArgsSafe, stringifySafe, } from './unblind.js';
|
|
30
|
+
export { isMnemonicaInstance } from './utils/is-mnemonica-instance.js';
|
|
31
|
+
export { formatFlow, errorContext } from './utils/dive-flow.js';
|
|
32
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBb0JHO0FBRUgsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3RELE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxNQUFNLHdDQUF3QyxDQUFDO0FBQy9FLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBQ3JFLE9BQU8sRUFBRSxpQkFBaUIsRUFBcUMsTUFBTSxvQ0FBb0MsQ0FBQztBQUMxRyxPQUFPLEVBQUUsaUJBQWlCLEVBQXNFLE1BQU0sb0JBQW9CLENBQUM7QUFDM0gsT0FBTyxFQUNOLFdBQVcsRUFDWCxvQkFBb0IsRUFDcEIsVUFBVSxHQUlWLE1BQU0sNkJBQTZCLENBQUM7QUFDckMsT0FBTyxFQUFFLHNCQUFzQixFQUE2QyxNQUFNLHNDQUFzQyxDQUFDO0FBQ3pILE9BQU8sRUFDTixrQkFBa0IsRUFDbEIsc0JBQXNCLEVBQ3RCLFdBQVcsRUFDWCxlQUFlLEVBQ2YsYUFBYSxHQUViLE1BQU0sY0FBYyxDQUFDO0FBQ3RCLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBQ3ZFLE9BQU8sRUFBRSxVQUFVLEVBQUUsWUFBWSxFQUEwQixNQUFNLHNCQUFzQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbW5lbW9uaWNhL290ZWwg4oCUIHRoZSBmcmFtZXdvcmstZnJlZSBOb2RlLmpzIGNvcmUgb2YgdGhlIG1uZW1vbmljYVxuICogb2JzZXJ2YWJpbGl0eSBzdGFjay5cbiAqXG4gKiBFdmVyeXRoaW5nIGhlcmUgd29ya3MgaW4gQU5ZIE5vZGUuanMgcnVudGltZSAoRXhwcmVzcywgRmFzdGlmeSwgcmF3XG4gKiBodHRwLCBxdWV1ZSBjb25zdW1lcnMsIENMSSk7IGZyYW1ld29yayB3aXJpbmcgbGl2ZXMgaW4gZGVkaWNhdGVkXG4gKiBhZGFwdGVyIHBhY2thZ2VzIGJ1aWx0IG9uIHRoZXNlIHByaW1pdGl2ZXMuXG4gKlxuICogUHJvdmlkZXM6XG4gKiAgIC0gYXR0YWNoSG9va3MoKSDigJQgbW5lbW9uaWNhIGxpZmVjeWNsZSDihpIgZGl2ZSBlZGdlIHdpcmluZ1xuICogICAtIE1uZW1vbmljYU90ZWxQcm92aWRlciDigJQgT1RlbCBzcGFucyBmb3IgY29uc3RydWN0aW9uc1xuICogICAtIERpdmVPdGVsUHJvdmlkZXIg4oCUIE9UZWwgc3BhbnMgZm9yIGV2ZXJ5IGRpdmUtd3JhcHBlZCBjYWxsXG4gKiAgIC0gQXN5bmNGbG93UHJvdmlkZXIg4oCUIEFMUyBiYWNrYm9uZSBhdHRyaWJ1dGluZyB1bndyYXBwZWQgYXN5bmMgaG9wc1xuICogICAtIHJ1bkluUmVxdWVzdFNjb3BlKCkg4oCUIG9uZSByZXF1ZXN0IHNwYW4gKyB0cmlwbGUgYXN5bmMgc2NvcGVcbiAqICAgLSBmZWVkUHJlUm9vdCgpL2ZlZWRWYWxpZGF0ZWRQcmVSb290KCkvZ2V0UHJlUm9vdCgpIOKAlCB0aHVuZGVyc3RydWNrXG4gKiAgICAgcHJlLXJvb3QgZm9yZW5zaWNzIHN0b3JlIChpZGVudGl0eS1jb3JyZWxhdGVkLCByZXF1ZXN0LWxpZmV0aW1lKVxuICogICAtIGZlZWRQcmVSb290RnJvbVJlcXVlc3QoKSDigJQgYm91bmRhcnkgaGVscGVyIG92ZXIgYW55IEhUVFAgcmVxdWVzdFxuICogICAtIGJ1aWxkVW5ibGluZFJlcG9ydCgpL3JlY29yZFVuYmxpbmRUZWxlbWV0cnkoKSDigJQgdGhlIFVuYmxpbmRlciBjb3JlXG4gKiAgIC0gaXNNbmVtb25pY2FJbnN0YW5jZSgpIOKAlCByZWFsbS1zYWZlIHR5cGUgZ3VhcmRcbiAqICAgLSBmb3JtYXRGbG93KCkvZXJyb3JDb250ZXh0KCkg4oCUIHJlYWQtc2lkZSBoZWxwZXJzIG92ZXIgZGl2ZSdzIHRyYWNlXG4gKi9cblxuZXhwb3J0IHsgYXR0YWNoSG9va3MgfSBmcm9tICcuL2hvb2tzL2F0dGFjaC1ob29rcy5qcyc7XG5leHBvcnQgeyBNbmVtb25pY2FPdGVsUHJvdmlkZXIgfSBmcm9tICcuL3Byb3ZpZGVycy9tbmVtb25pY2Etb3RlbC5wcm92aWRlci5qcyc7XG5leHBvcnQgeyBEaXZlT3RlbFByb3ZpZGVyIH0gZnJvbSAnLi9wcm92aWRlcnMvZGl2ZS1vdGVsLnByb3ZpZGVyLmpzJztcbmV4cG9ydCB7IEFzeW5jRmxvd1Byb3ZpZGVyLCB0eXBlIEZsb3dGcmFtZSwgdHlwZSBDcmFzaENvbnRleHQgfSBmcm9tICcuL3Byb3ZpZGVycy9hc3luYy1mbG93LnByb3ZpZGVyLmpzJztcbmV4cG9ydCB7IHJ1bkluUmVxdWVzdFNjb3BlLCB0eXBlIEh0dHBSZXF1ZXN0TGlrZSwgdHlwZSBIdHRwUmVzcG9uc2VMaWtlLCB0eXBlIFJlcXVlc3RTY29wZURlcHMgfSBmcm9tICcuL3JlcXVlc3Qtc2NvcGUuanMnO1xuZXhwb3J0IHtcblx0ZmVlZFByZVJvb3QsXG5cdGZlZWRWYWxpZGF0ZWRQcmVSb290LFxuXHRnZXRQcmVSb290LFxuXHR0eXBlIFJhd1ByZVJvb3RQYXlsb2FkLFxuXHR0eXBlIFByZVJvb3RSZWNvcmQsXG5cdHR5cGUgUHJlUm9vdERhdGEsXG59IGZyb20gJy4vdGh1bmRlcnN0cnVjay9wcmUtcm9vdC5qcyc7XG5leHBvcnQgeyBmZWVkUHJlUm9vdEZyb21SZXF1ZXN0LCB0eXBlIFJlcXVlc3RMaWtlLCB0eXBlIEZlZWRQcmVSb290T3B0aW9ucyB9IGZyb20gJy4vdGh1bmRlcnN0cnVjay9mZWVkLWZyb20tcmVxdWVzdC5qcyc7XG5leHBvcnQge1xuXHRidWlsZFVuYmxpbmRSZXBvcnQsXG5cdHJlY29yZFVuYmxpbmRUZWxlbWV0cnksXG5cdGV4dHJhY3RTYWZlLFxuXHRlcnJvcmVkQXJnc1NhZmUsXG5cdHN0cmluZ2lmeVNhZmUsXG5cdHR5cGUgVW5ibGluZFJlcG9ydCxcbn0gZnJvbSAnLi91bmJsaW5kLmpzJztcbmV4cG9ydCB7IGlzTW5lbW9uaWNhSW5zdGFuY2UgfSBmcm9tICcuL3V0aWxzL2lzLW1uZW1vbmljYS1pbnN0YW5jZS5qcyc7XG5leHBvcnQgeyBmb3JtYXRGbG93LCBlcnJvckNvbnRleHQsIHR5cGUgRm9ybWF0dGVkRmxvd0VkZ2UgfSBmcm9tICcuL3V0aWxzL2RpdmUtZmxvdy5qcyc7XG4iXX0=
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type FlowFrame = {
|
|
2
|
+
/** the dive edge this frame belongs to (null on the root frame) */
|
|
3
|
+
edgeId: number | null;
|
|
4
|
+
/** the frame active when this one was entered */
|
|
5
|
+
parent: FlowFrame | null;
|
|
6
|
+
/** strong pins of context instances — ONE set per scope, shared down
|
|
7
|
+
* the chain by reference; dies with the scope's async executions */
|
|
8
|
+
pinSet: Set<object>;
|
|
9
|
+
};
|
|
10
|
+
/** Read-only crash-time view of the active frame. */
|
|
11
|
+
export type CrashContext = {
|
|
12
|
+
edgeId: number | null;
|
|
13
|
+
instances: object[];
|
|
14
|
+
};
|
|
15
|
+
export declare class AsyncFlowProvider {
|
|
16
|
+
private frames;
|
|
17
|
+
private detachers;
|
|
18
|
+
/**
|
|
19
|
+
* Subscribe to dive's edge lifecycle. Idempotent: attaching twice would
|
|
20
|
+
* double every frame push. Dive's clear() wipes subscribers — re-attach
|
|
21
|
+
* after it.
|
|
22
|
+
*/
|
|
23
|
+
attach(): void;
|
|
24
|
+
detach(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Establish a root frame for non-HTTP scopes (queue consumers, CLI,
|
|
27
|
+
* tests). The middleware is the HTTP root.
|
|
28
|
+
*/
|
|
29
|
+
runInScope<T>(fn: () => T): T;
|
|
30
|
+
/**
|
|
31
|
+
* The frame active RIGHT NOW — in an uncaughtException handler this is
|
|
32
|
+
* the failing execution's frame: the parental edge id plus every
|
|
33
|
+
* context instance pinned by the scope. Undefined outside any scope.
|
|
34
|
+
*/
|
|
35
|
+
currentFrame(): CrashContext | undefined;
|
|
36
|
+
private onEnter;
|
|
37
|
+
private onLeave;
|
|
38
|
+
private onCreate;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=async-flow.provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-flow.provider.d.ts","sourceRoot":"","sources":["../../src/providers/async-flow.provider.ts"],"names":[],"mappings":"AA8BA,MAAM,MAAM,SAAS,GAAG;IACvB,mEAAmE;IACnE,MAAM,EAAK,MAAM,GAAG,IAAI,CAAC;IACzB,iDAAiD;IACjD,MAAM,EAAK,SAAS,GAAG,IAAI,CAAC;IAC5B;yEACqE;IACrE,MAAM,EAAK,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB,CAAC;AAEF,qDAAqD;AACrD,MAAM,MAAM,YAAY,GAAG;IAC1B,MAAM,EAAM,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAG,MAAM,EAAE,CAAC;CACrB,CAAC;AAIF,qBAAa,iBAAiB;IAE7B,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,SAAS,CAAyB;IAE1C;;;;OAIG;IACH,MAAM,IAAK,IAAI;IAwBf,MAAM,IAAK,IAAI;IAQf;;;OAGG;IACH,UAAU,CAAC,CAAC,EAAG,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;IAU9B;;;;OAIG;IACH,YAAY,IAAK,YAAY,GAAG,SAAS;IAYzC,OAAO,CAAC,OAAO;IAiBf,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,QAAQ;CAWhB"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async-flow provider — the ALS backbone for dive attribution.
|
|
3
|
+
*
|
|
4
|
+
* Design: reports/async-flow-tracking-design.md (2026-09-02).
|
|
5
|
+
*
|
|
6
|
+
* One AsyncLocalStorage carrying a linked list of FlowFrames. The root
|
|
7
|
+
* frame is created per HTTP request by MnemonicaTraceMiddleware (or
|
|
8
|
+
* manually via runInScope). Every dive 'enter' hook pushes a child frame
|
|
9
|
+
* (edgeId = the entering edge); 'leave' restores the parent. ALS
|
|
10
|
+
* propagation then does the tracking for free: an UNWRAPPED async hop
|
|
11
|
+
* (setTimeout, promise continuation, async-generator suspension) fires
|
|
12
|
+
* with the scheduling frame in als.getStore() — the parental dive edge
|
|
13
|
+
* is known without wrapping anything.
|
|
14
|
+
*
|
|
15
|
+
* The scoped pin: the root frame owns a pinSet of context instances
|
|
16
|
+
* (strong refs), filled on enter/create from edge.instance. Lifetime is
|
|
17
|
+
* the request's async executions — when they die, the store and pinSet
|
|
18
|
+
* die with them. edge.instance never derefs to undefined mid-request.
|
|
19
|
+
*
|
|
20
|
+
* Node-only by design: dive imports no async_hooks (Deno/Bun), the
|
|
21
|
+
* adapter is the Node boundary where ALS is free.
|
|
22
|
+
*/
|
|
23
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
24
|
+
import { registerHook } from '@mnemonica/dive';
|
|
25
|
+
const als = new AsyncLocalStorage();
|
|
26
|
+
export class AsyncFlowProvider {
|
|
27
|
+
// edgeId → the frame entered for it, so leave restores the exact parent
|
|
28
|
+
frames = new Map();
|
|
29
|
+
detachers = [];
|
|
30
|
+
/**
|
|
31
|
+
* Subscribe to dive's edge lifecycle. Idempotent: attaching twice would
|
|
32
|
+
* double every frame push. Dive's clear() wipes subscribers — re-attach
|
|
33
|
+
* after it.
|
|
34
|
+
*/
|
|
35
|
+
attach() {
|
|
36
|
+
if (this.detachers.length > 0) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.detachers.push(registerHook('enter', (payload) => {
|
|
40
|
+
this.onEnter(payload);
|
|
41
|
+
}), registerHook('leave', (payload) => {
|
|
42
|
+
this.onLeave(payload);
|
|
43
|
+
}));
|
|
44
|
+
try {
|
|
45
|
+
this.detachers.push(registerHook('create', (payload) => {
|
|
46
|
+
this.onCreate(payload);
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// 'create' exists since dive 0.8.0; on 0.7.x registerHook throws —
|
|
51
|
+
// skipping preserves exactly the pre-subscription behavior.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
detach() {
|
|
55
|
+
for (const detach of this.detachers) {
|
|
56
|
+
detach();
|
|
57
|
+
}
|
|
58
|
+
this.detachers = [];
|
|
59
|
+
this.frames.clear();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Establish a root frame for non-HTTP scopes (queue consumers, CLI,
|
|
63
|
+
* tests). The middleware is the HTTP root.
|
|
64
|
+
*/
|
|
65
|
+
runInScope(fn) {
|
|
66
|
+
const root = {
|
|
67
|
+
edgeId: null,
|
|
68
|
+
parent: null,
|
|
69
|
+
pinSet: new Set(),
|
|
70
|
+
};
|
|
71
|
+
const result = als.run(root, fn);
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The frame active RIGHT NOW — in an uncaughtException handler this is
|
|
76
|
+
* the failing execution's frame: the parental edge id plus every
|
|
77
|
+
* context instance pinned by the scope. Undefined outside any scope.
|
|
78
|
+
*/
|
|
79
|
+
currentFrame() {
|
|
80
|
+
const frame = als.getStore();
|
|
81
|
+
if (!frame) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const result = {
|
|
85
|
+
edgeId: frame.edgeId,
|
|
86
|
+
instances: [...frame.pinSet],
|
|
87
|
+
};
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
onEnter({ edge }) {
|
|
91
|
+
const current = als.getStore();
|
|
92
|
+
if (!current) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const frame = {
|
|
96
|
+
edgeId: edge.id,
|
|
97
|
+
parent: current,
|
|
98
|
+
pinSet: current.pinSet,
|
|
99
|
+
};
|
|
100
|
+
if (edge.instance !== undefined) {
|
|
101
|
+
frame.pinSet.add(edge.instance);
|
|
102
|
+
}
|
|
103
|
+
this.frames.set(edge.id, frame);
|
|
104
|
+
als.enterWith(frame);
|
|
105
|
+
}
|
|
106
|
+
onLeave({ edge }) {
|
|
107
|
+
const frame = this.frames.get(edge.id);
|
|
108
|
+
if (!frame) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.frames.delete(edge.id);
|
|
112
|
+
if (frame.parent) {
|
|
113
|
+
als.enterWith(frame.parent);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
onCreate({ edge }) {
|
|
117
|
+
const current = als.getStore();
|
|
118
|
+
if (!current) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Constructions are one-shot edges (no leave) — no frame of their
|
|
122
|
+
// own, but the constructed instance is DATA: pin it into the scope.
|
|
123
|
+
if (edge.instance !== undefined) {
|
|
124
|
+
current.pinSet.add(edge.instance);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXN5bmMtZmxvdy5wcm92aWRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9wcm92aWRlcnMvYXN5bmMtZmxvdy5wcm92aWRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBcUJHO0FBQ0gsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ2hELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQXVCL0MsTUFBTSxHQUFHLEdBQUcsSUFBSSxpQkFBaUIsRUFBYSxDQUFDO0FBRS9DLE1BQU0sT0FBTyxpQkFBaUI7SUFDN0Isd0VBQXdFO0lBQ2hFLE1BQU0sR0FBRyxJQUFJLEdBQUcsRUFBcUIsQ0FBQztJQUN0QyxTQUFTLEdBQXNCLEVBQUUsQ0FBQztJQUUxQzs7OztPQUlHO0lBQ0gsTUFBTTtRQUNMLElBQUksSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDL0IsT0FBTztRQUNSLENBQUM7UUFDRCxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FDbEIsWUFBWSxDQUFDLE9BQU8sRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ2pDLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDdkIsQ0FBQyxDQUFDLEVBQ0YsWUFBWSxDQUFDLE9BQU8sRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ2pDLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDdkIsQ0FBQyxDQUFDLENBQ0YsQ0FBQztRQUNGLElBQUksQ0FBQztZQUNKLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUNsQixZQUFZLENBQUMsUUFBUSxFQUFFLENBQUMsT0FBTyxFQUFFLEVBQUU7Z0JBQ2xDLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDeEIsQ0FBQyxDQUFDLENBQ0Ys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LENBQUMsUUFBUSxDQUFDLENBQUM7UUFDbkMsQ0FBQztJQUNGLENBQUM7Q0FDRCIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQXN5bmMtZmxvdyBwcm92aWRlciDigJQgdGhlIEFMUyBiYWNrYm9uZSBmb3IgZGl2ZSBhdHRyaWJ1dGlvbi5cbiAqXG4gKiBEZXNpZ246IHJlcG9ydHMvYXN5bmMtZmxvdy10cmFja2luZy1kZXNpZ24ubWQgKDIwMjYtMDktMDIpLlxuICpcbiAqIE9uZSBBc3luY0xvY2FsU3RvcmFnZSBjYXJyeWluZyBhIGxpbmtlZCBsaXN0IG9mIEZsb3dGcmFtZXMuIFRoZSByb290XG4gKiBmcmFtZSBpcyBjcmVhdGVkIHBlciBIVFRQIHJlcXVlc3QgYnkgTW5lbW9uaWNhVHJhY2VNaWRkbGV3YXJlIChvclxuICogbWFudWFsbHkgdmlhIHJ1bkluU2NvcGUpLiBFdmVyeSBkaXZlICdlbnRlcicgaG9vayBwdXNoZXMgYSBjaGlsZCBmcmFtZVxuICogKGVkZ2VJZCA9IHRoZSBlbnRlcmluZyBlZGdlKTsgJ2xlYXZlJyByZXN0b3JlcyB0aGUgcGFyZW50LiBBTFNcbiAqIHByb3BhZ2F0aW9uIHRoZW4gZG9lcyB0aGUgdHJhY2tpbmcgZm9yIGZyZWU6IGFuIFVOV1JBUFBFRCBhc3luYyBob3BcbiAqIChzZXRUaW1lb3V0LCBwcm9taXNlIGNvbnRpbnVhdGlvbiwgYXN5bmMtZ2VuZXJhdG9yIHN1c3BlbnNpb24pIGZpcmVzXG4gKiB3aXRoIHRoZSBzY2hlZHVsaW5nIGZyYW1lIGluIGFscy5nZXRTdG9yZSgpIOKAlCB0aGUgcGFyZW50YWwgZGl2ZSBlZGdlXG4gKiBpcyBrbm93biB3aXRob3V0IHdyYXBwaW5nIGFueXRoaW5nLlxuICpcbiAqIFRoZSBzY29wZWQgcGluOiB0aGUgcm9vdCBmcmFtZSBvd25zIGEgcGluU2V0IG9mIGNvbnRleHQgaW5zdGFuY2VzXG4gKiAoc3Ryb25nIHJlZnMpLCBmaWxsZWQgb24gZW50ZXIvY3JlYXRlIGZyb20gZWRnZS5pbnN0YW5jZS4gTGlmZXRpbWUgaXNcbiAqIHRoZSByZXF1ZXN0J3MgYXN5bmMgZXhlY3V0aW9ucyDigJQgd2hlbiB0aGV5IGRpZSwgdGhlIHN0b3JlIGFuZCBwaW5TZXRcbiAqIGRpZSB3aXRoIHRoZW0uIGVkZ2UuaW5zdGFuY2UgbmV2ZXIgZGVyZWZzIHRvIHVuZGVmaW5lZCBtaWQtcmVxdWVzdC5cbiAqXG4gKiBOb2RlLW9ubHkgYnkgZGVzaWduOiBkaXZlIGltcG9ydHMgbm8gYXN5bmNfaG9va3MgKERlbm8vQnVuKSwgdGhlXG4gKiBhZGFwdGVyIGlzIHRoZSBOb2RlIGJvdW5kYXJ5IHdoZXJlIEFMUyBpcyBmcmVlLlxuICovXG5pbXBvcnQgeyBBc3luY0xvY2FsU3RvcmFnZSB9IGZyb20gJ2FzeW5jX2hvb2tzJztcbmltcG9ydCB7IHJlZ2lzdGVySG9vayB9IGZyb20gJ0BtbmVtb25pY2EvZGl2ZSc7XG5pbXBvcnQgdHlwZSB7XG5cdERpdmVDcmVhdGVQYXlsb2FkLFxuXHREaXZlRW50ZXJQYXlsb2FkLFxuXHREaXZlTGVhdmVQYXlsb2FkLFxufSBmcm9tICdAbW5lbW9uaWNhL2RpdmUnO1xuXG5leHBvcnQgdHlwZSBGbG93RnJhbWUgPSB7XG5cdC8qKiB0aGUgZGl2ZSBlZGdlIHRoaXMgZnJhbWUgYmVsb25ncyB0byAobnVsbCBvbiB0aGUgcm9vdCBmcmFtZSkgKi9cblx0ZWRnZUlkICAgOiBudW1iZXIgfCBudWxsO1xuXHQvKiogdGhlIGZyYW1lIGFjdGl2ZSB3aGVuIHRoaXMgb25lIHdhcyBlbnRlcmVkICovXG5cdHBhcmVudCAgIDogRmxvd0ZyYW1lIHwgbnVsbDtcblx0LyoqIHN0cm9uZyBwaW5zIG9mIGNvbnRleHQgaW5zdGFuY2VzIOKAlCBPTkUgc2V0IHBlciBzY29wZSwgc2hhcmVkIGRvd25cblx0ICogIHRoZSBjaGFpbiBieSByZWZlcmVuY2U7IGRpZXMgd2l0aCB0aGUgc2NvcGUncyBhc3luYyBleGVjdXRpb25zICovXG5cdHBpblNldCAgIDogU2V0PG9iamVjdD47XG59O1xuXG4vKiogUmVhZC1vbmx5IGNyYXNoLXRpbWUgdmlldyBvZiB0aGUgYWN0aXZlIGZyYW1lLiAqL1xuZXhwb3J0IHR5cGUgQ3Jhc2hDb250ZXh0ID0ge1xuXHRlZGdlSWQgICAgOiBudW1iZXIgfCBudWxsO1xuXHRpbnN0YW5jZXMgOiBvYmplY3RbXTtcbn07XG5cbmNvbnN0IGFscyA9IG5ldyBBc3luY0xvY2FsU3RvcmFnZTxGbG93RnJhbWU+KCk7XG5cbmV4cG9ydCBjbGFzcyBBc3luY0Zsb3dQcm92aWRlciB7XG5cdC8vIGVkZ2VJZCDihpIgdGhlIGZyYW1lIGVudGVyZWQgZm9yIGl0LCBzbyBsZWF2ZSByZXN0b3JlcyB0aGUgZXhhY3QgcGFyZW50XG5cdHByaXZhdGUgZnJhbWVzID0gbmV3IE1hcDxudW1iZXIsIEZsb3dGcmFtZT4oKTtcblx0cHJpdmF0ZSBkZXRhY2hlcnM6IEFycmF5PCgpID0+IHZvaWQ+ID0gW107XG5cblx0LyoqXG5cdCAqIFN1YnNjcmliZSB0byBkaXZlJ3MgZWRnZSBsaWZlY3ljbGUuIElkZW1wb3RlbnQ6IGF0dGFjaGluZyB0d2ljZSB3b3VsZFxuXHQgKiBkb3VibGUgZXZlcnkgZnJhbWUgcHVzaC4gRGl2ZSdzIGNsZWFyKCkgd2lwZXMgc3Vic2NyaWJlcnMg4oCUIHJlLWF0dGFjaFxuXHQgKiBhZnRlciBpdC5cblx0ICovXG5cdGF0dGFjaCAoKTogdm9pZCB7XG5cdFx0aWYgKHRoaXMuZGV0YWNoZXJzLmxlbmd0aCA+IDApIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0dGhpcy5kZXRhY2hlcnMucHVzaChcblx0XHRcdHJlZ2lzdGVySG9vaygnZW50ZXInLCAocGF5bG9hZCkgPT4ge1xuXHRcdFx0XHR0aGlzLm9uRW50ZXIocGF5bG9hZCk7XG5cdFx0XHR9KSxcblx0XHRcdHJlZ2lzdGVySG9vaygnbGVhdmUnLCAocGF5bG9hZCkgPT4ge1xuXHRcdFx0XHR0aGlzLm9uTGVhdmUocGF5bG9hZCk7XG5cdFx0XHR9KSxcblx0XHQpO1xuXHRcdHRyeSB7XG5cdFx0XHR0aGlzLmRldGFjaGVycy5wdXNoKFxuXHRcdFx0XHRyZWdpc3Rlckhvb2soJ2NyZWF0ZScsIChwYXlsb2FkKSA9PiB7XG5cdFx0XHRcdFx0dGhpcy5vbkNyZWF0ZShwYXlsb2FkKTtcblx0XHRcdFx0fSksXG5cdFx0XHQpO1xuXHRcdH0gY2F0Y2gge1xuXHRcdFx0Ly8gJ2NyZWF0ZScgZXhpc3RzIHNpbmNlIGRpdmUgMC44LjA7IG9uIDAuNy54IHJlZ2lzdGVySG9vayB0aHJvd3Mg4oCUXG5cdFx0XHQvLyBza2lwcGluZyBwcmVzZXJ2ZXMgZXhhY3RseSB0aGUgcHJlLXN1YnNjcmlwdGlvbiBiZWhhdmlvci5cblx0XHR9XG5cdH1cblxuXHRkZXRhY2ggKCk6IHZvaWQge1xuXHRcdGZvciAoY29uc3QgZGV0YWNoIG9mIHRoaXMuZGV0YWNoZXJzKSB7XG5cdFx0XHRkZXRhY2goKTtcblx0XHR9XG5cdFx0dGhpcy5kZXRhY2hlcnMgPSBbXTtcblx0XHR0aGlzLmZyYW1lcy5jbGVhcigpO1xuXHR9XG5cblx0LyoqXG5cdCAqIEVzdGFibGlzaCBhIHJvb3QgZnJhbWUgZm9yIG5vbi1IVFRQIHNjb3BlcyAocXVldWUgY29uc3VtZXJzLCBDTEksXG5cdCAqIHRlc3RzKS4gVGhlIG1pZGRsZXdhcmUgaXMgdGhlIEhUVFAgcm9vdC5cblx0ICovXG5cdHJ1bkluU2NvcGU8VD4gKGZuOiAoKSA9PiBUKTogVCB7XG5cdFx0Y29uc3Qgcm9vdDogRmxvd0ZyYW1lID0ge1xuXHRcdFx0ZWRnZUlkIDogbnVsbCxcblx0XHRcdHBhcmVudCA6IG51bGwsXG5cdFx0XHRwaW5TZXQgOiBuZXcgU2V0PG9iamVjdD4oKSxcblx0XHR9O1xuXHRcdGNvbnN0IHJlc3VsdCA9IGFscy5ydW4ocm9vdCwgZm4pO1xuXHRcdHJldHVybiByZXN1bHQ7XG5cdH1cblxuXHQvKipcblx0ICogVGhlIGZyYW1lIGFjdGl2ZSBSSUdIVCBOT1cg4oCUIGluIGFuIHVuY2F1Z2h0RXhjZXB0aW9uIGhhbmRsZXIgdGhpcyBpc1xuXHQgKiB0aGUgZmFpbGluZyBleGVjdXRpb24ncyBmcmFtZTogdGhlIHBhcmVudGFsIGVkZ2UgaWQgcGx1cyBldmVyeVxuXHQgKiBjb250ZXh0IGluc3RhbmNlIHBpbm5lZCBieSB0aGUgc2NvcGUuIFVuZGVmaW5lZCBvdXRzaWRlIGFueSBzY29wZS5cblx0ICovXG5cdGN1cnJlbnRGcmFtZSAoKTogQ3Jhc2hDb250ZXh0IHwgdW5kZWZpbmVkIHtcblx0XHRjb25zdCBmcmFtZSA9IGFscy5nZXRTdG9yZSgpO1xuXHRcdGlmICghZnJhbWUpIHtcblx0XHRcdHJldHVybiB1bmRlZmluZWQ7XG5cdFx0fVxuXHRcdGNvbnN0IHJlc3VsdDogQ3Jhc2hDb250ZXh0ID0ge1xuXHRcdFx0ZWRnZUlkICAgIDogZnJhbWUuZWRnZUlkLFxuXHRcdFx0aW5zdGFuY2VzIDogWy4uLmZyYW1lLnBpblNldF0sXG5cdFx0fTtcblx0XHRyZXR1cm4gcmVzdWx0O1xuXHR9XG5cblx0cHJpdmF0ZSBvbkVudGVyICh7IGVkZ2UgfTogRGl2ZUVudGVyUGF5bG9hZCk6IHZvaWQge1xuXHRcdGNvbnN0IGN1cnJlbnQgPSBhbHMuZ2V0U3RvcmUoKTtcblx0XHRpZiAoIWN1cnJlbnQpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0Y29uc3QgZnJhbWU6IEZsb3dGcmFtZSA9IHtcblx0XHRcdGVkZ2VJZCA6IGVkZ2UuaWQsXG5cdFx0XHRwYXJlbnQgOiBjdXJyZW50LFxuXHRcdFx0cGluU2V0IDogY3VycmVudC5waW5TZXQsXG5cdFx0fTtcblx0XHRpZiAoZWRnZS5pbnN0YW5jZSAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0XHRmcmFtZS5waW5TZXQuYWRkKGVkZ2UuaW5zdGFuY2UpO1xuXHRcdH1cblx0XHR0aGlzLmZyYW1lcy5zZXQoZWRnZS5pZCwgZnJhbWUpO1xuXHRcdGFscy5lbnRlcldpdGgoZnJhbWUpO1xuXHR9XG5cblx0cHJpdmF0ZSBvbkxlYXZlICh7IGVkZ2UgfTogRGl2ZUxlYXZlUGF5bG9hZCk6IHZvaWQge1xuXHRcdGNvbnN0IGZyYW1lID0gdGhpcy5mcmFtZXMuZ2V0KGVkZ2UuaWQpO1xuXHRcdGlmICghZnJhbWUpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0dGhpcy5mcmFtZXMuZGVsZXRlKGVkZ2UuaWQpO1xuXHRcdGlmIChmcmFtZS5wYXJlbnQpIHtcblx0XHRcdGFscy5lbnRlcldpdGgoZnJhbWUucGFyZW50KTtcblx0XHR9XG5cdH1cblxuXHRwcml2YXRlIG9uQ3JlYXRlICh7IGVkZ2UgfTogRGl2ZUNyZWF0ZVBheWxvYWQpOiB2b2lkIHtcblx0XHRjb25zdCBjdXJyZW50ID0gYWxzLmdldFN0b3JlKCk7XG5cdFx0aWYgKCFjdXJyZW50KSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXHRcdC8vIENvbnN0cnVjdGlvbnMgYXJlIG9uZS1zaG90IGVkZ2VzIChubyBsZWF2ZSkg4oCUIG5vIGZyYW1lIG9mIHRoZWlyXG5cdFx0Ly8gb3duLCBidXQgdGhlIGNvbnN0cnVjdGVkIGluc3RhbmNlIGlzIERBVEE6IHBpbiBpdCBpbnRvIHRoZSBzY29wZS5cblx0XHRpZiAoZWRnZS5pbnN0YW5jZSAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0XHRjdXJyZW50LnBpblNldC5hZGQoZWRnZS5pbnN0YW5jZSk7XG5cdFx0fVxuXHR9XG59XG4iXX0=
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry provider for dive's edge lifecycle hooks.
|
|
3
|
+
*
|
|
4
|
+
* Where MnemonicaOtelProvider spans CONSTRUCTIONS (mnemonica hooks), this
|
|
5
|
+
* provider spans EVERY WRAPPED CALL (dive hooks): call / construct / method /
|
|
6
|
+
* recontext edges each become a span, parented on the span of the edge's
|
|
7
|
+
* parentId — dive's own trace parentage, not ALS. At unwrapped boundaries
|
|
8
|
+
* (parentId null) the span adopts the currently ACTIVE OTel span as parent,
|
|
9
|
+
* so an
|
|
10
|
+
* HTTP request span adopts the whole dive branch. 'create' edges (mnemonica
|
|
11
|
+
* constructions recorded via recordCreation/recordCreationError) become
|
|
12
|
+
* one-shot spans on the same parentage — the construction HAS completed when
|
|
13
|
+
* the hook fires, so the span starts and ends inside the handler.
|
|
14
|
+
*
|
|
15
|
+
* Async truthfulness: a span does NOT end at the sync close when the wrap
|
|
16
|
+
* produced a tapped promise — it ends at settle, with the chain's outcome.
|
|
17
|
+
* Spans are keyed on edge id; every recorded edge fires leave (and, when
|
|
18
|
+
* async, settle), so the map always drains.
|
|
19
|
+
*/
|
|
20
|
+
import type { Tracer } from '@opentelemetry/api';
|
|
21
|
+
export declare class DiveOtelProvider {
|
|
22
|
+
private tracer;
|
|
23
|
+
private spans;
|
|
24
|
+
private edgeParents;
|
|
25
|
+
private static readonly EDGE_PARENTS_LIMIT;
|
|
26
|
+
private detachers;
|
|
27
|
+
constructor(tracer?: Tracer);
|
|
28
|
+
/**
|
|
29
|
+
* Subscribe to dive's edge lifecycle. Idempotent: attaching twice would
|
|
30
|
+
* double every span. Dive's clear() wipes subscribers — re-attach after it.
|
|
31
|
+
*/
|
|
32
|
+
attach(): void;
|
|
33
|
+
detach(): void;
|
|
34
|
+
private onEnter;
|
|
35
|
+
private onLeave;
|
|
36
|
+
private onSettle;
|
|
37
|
+
private onRecontext;
|
|
38
|
+
private onCreate;
|
|
39
|
+
private findParentSpan;
|
|
40
|
+
private closeSpan;
|
|
41
|
+
/**
|
|
42
|
+
* Cross-surface attributes every span gets, on every hook path:
|
|
43
|
+
* the edge's trace root id (Jaeger link → mnemographica's Live Trace,
|
|
44
|
+
* Wanted #1), the edgeId→traceId publication for the strategy push
|
|
45
|
+
* channel (Wanted #2), and code.filepath/line/column parsed from the
|
|
46
|
+
* callsite name (Wanted #4).
|
|
47
|
+
*/
|
|
48
|
+
private decorateSpan;
|
|
49
|
+
private rootEdgeIdOf;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=dive-otel.provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dive-otel.provider.d.ts","sourceRoot":"","sources":["../../src/providers/dive-otel.provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAQ,MAAM,EAAE,MAAM,oBAAoB,CAAC;AA+CvD,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,CAAC,KAAK,CAA2B;IAKxC,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IACnD,OAAO,CAAC,SAAS,CAAyB;gBAE7B,MAAM,CAAC,EAAE,MAAM;IAI5B;;;OAGG;IACH,MAAM,IAAK,IAAI;IAgCf,MAAM,IAAK,IAAI;IAOf,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,WAAW;IAuBnB,OAAO,CAAC,QAAQ;IAiChB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,SAAS;IAuBjB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAmBpB,OAAO,CAAC,YAAY;CAgBpB"}
|