@dudousxd/nestjs-telescope-inertia-watcher 1.7.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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +46 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/inertia-content.d.ts +80 -0
- package/dist/inertia-content.d.ts.map +1 -0
- package/dist/inertia-content.js +63 -0
- package/dist/inertia-content.js.map +1 -0
- package/dist/inertia.watcher.d.ts +30 -0
- package/dist/inertia.watcher.d.ts.map +1 -0
- package/dist/inertia.watcher.js +75 -0
- package/dist/inertia.watcher.js.map +1 -0
- package/package.json +53 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @dudousxd/nestjs-telescope-inertia-watcher
|
|
2
|
+
|
|
3
|
+
## 1.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`b35cc6b`](https://github.com/DavideCarvalho/nestjs-telescope/commit/b35cc6b1508e98f75f2ed78b8d08b93569f47089) - Add `@dudousxd/nestjs-telescope-inertia-watcher`: an `InertiaWatcher` that
|
|
8
|
+
subscribes to the `nestjs-inertia:render` diagnostics channel and records one
|
|
9
|
+
`inertia` entry per Inertia render (component, resolved props, deferred/merge
|
|
10
|
+
classification, partial-reload decision, asset version + 409 version-mismatch,
|
|
11
|
+
history flags, page size), correlated to the request batch. Adds the
|
|
12
|
+
`EntryType.Inertia` constant to core. Fully decoupled from `nestjs-inertia` —
|
|
13
|
+
the channel name and payload shape (`v: 1`) are the only contract; malformed or
|
|
14
|
+
wrong-version messages are dropped.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- [`10c76f1`](https://github.com/DavideCarvalho/nestjs-telescope/commit/10c76f1c0d9c1034e5bffca2d9775cd16e553200) - Inertia panel polish: log on record failure (was silently swallowed) and warn once on
|
|
19
|
+
an unsupported diagnostic version; collapse `InertiaContent` to
|
|
20
|
+
`Omit<InertiaRenderDiagnostic, …>` with a destructure-rest copy; make the
|
|
21
|
+
`isInertiaDiagnostic` guard honest about what it proves; render the Inertia badge in
|
|
22
|
+
the entries table (parity with the cache badge); data-drive the props rows. Adds a
|
|
23
|
+
committed cross-repo wire-contract fixture (`inertia-render.v1.json`) so producer
|
|
24
|
+
drift fails a test.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Davi Carvalho
|
|
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,46 @@
|
|
|
1
|
+
# @dudousxd/nestjs-telescope-inertia-watcher
|
|
2
|
+
|
|
3
|
+
Inertia.js render watcher for [`@dudousxd/nestjs-telescope`](../../README.md).
|
|
4
|
+
Subscribes to the `nestjs-inertia:render` diagnostics channel that
|
|
5
|
+
[`@dudousxd/nestjs-inertia`](https://github.com/DavideCarvalho/nestjs-inertia)
|
|
6
|
+
publishes once per response, and records one `inertia` entry per render
|
|
7
|
+
(rendered component, resolved props, deferred/merge classification, the
|
|
8
|
+
partial-reload decision, asset version + 409 version-mismatch, history flags and
|
|
9
|
+
page size), correlated to the request that produced it.
|
|
10
|
+
|
|
11
|
+
The bridge is Node's core `node:diagnostics_channel` — telescope **subscribes**,
|
|
12
|
+
inertia **publishes**. The two libraries stay fully decoupled: this package does
|
|
13
|
+
NOT depend on `nestjs-inertia`. The channel name (`nestjs-inertia:render`) and
|
|
14
|
+
the payload shape (`v: 1`) are the only contract; malformed or wrong-version
|
|
15
|
+
messages are dropped, never thrown.
|
|
16
|
+
|
|
17
|
+
When this watcher isn't installed, inertia's `channel.hasSubscribers` stays
|
|
18
|
+
`false` and it publishes nothing — so the integration costs ~zero when off.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
pnpm add @dudousxd/nestjs-telescope-inertia-watcher
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { TelescopeModule } from '@dudousxd/nestjs-telescope';
|
|
30
|
+
import { InertiaWatcher } from '@dudousxd/nestjs-telescope-inertia-watcher';
|
|
31
|
+
|
|
32
|
+
TelescopeModule.forRoot({
|
|
33
|
+
watchers: [new InertiaWatcher()],
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
On bootstrap the watcher subscribes to the channel (flipping inertia's
|
|
38
|
+
`hasSubscribers` to `true`), so events start flowing. Each captured entry has
|
|
39
|
+
type `inertia` and `InertiaContent`. The heavy `resolvedProps` field is passed
|
|
40
|
+
to the Recorder by reference, so its `redactBounded` clips + masks it with the
|
|
41
|
+
same budget every other entry's content gets (secret keys → `[REDACTED]`,
|
|
42
|
+
oversized trees truncated).
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
MIT © Davi Carvalho
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type RecordInput } from '@dudousxd/nestjs-telescope';
|
|
2
|
+
/**
|
|
3
|
+
* The persisted content of one captured Inertia render: the wire
|
|
4
|
+
* `InertiaRenderDiagnostic` (below) minus the transport-only `v` and the
|
|
5
|
+
* always-true `isInertia` discriminator. Defined as an `Omit` so it can never
|
|
6
|
+
* drift from the wire shape. The heavy `resolvedProps` field is passed THROUGH
|
|
7
|
+
* to the Recorder by reference so its `redactBounded` clips + masks it with the
|
|
8
|
+
* standard budget — we do nothing special for size/secrets here.
|
|
9
|
+
*/
|
|
10
|
+
export type InertiaContent = Omit<InertiaRenderDiagnostic, 'v' | 'isInertia'>;
|
|
11
|
+
/**
|
|
12
|
+
* The wire payload published by `@dudousxd/nestjs-inertia` on the
|
|
13
|
+
* `nestjs-inertia:render` channel. This shape is the source of truth in
|
|
14
|
+
* nestjs-inertia's `src/diagnostics.ts` (`InertiaRenderDiagnostic`, spec §1.3);
|
|
15
|
+
* it is duplicated here STRUCTURALLY (never imported) so the two libraries stay
|
|
16
|
+
* decoupled. Validate with `isInertiaDiagnostic` before trusting it.
|
|
17
|
+
*/
|
|
18
|
+
export interface InertiaRenderDiagnostic {
|
|
19
|
+
v: number;
|
|
20
|
+
component: string;
|
|
21
|
+
url: string;
|
|
22
|
+
method: string;
|
|
23
|
+
isInertia: boolean;
|
|
24
|
+
isPartial: boolean;
|
|
25
|
+
partial: {
|
|
26
|
+
only: string[];
|
|
27
|
+
except: string[];
|
|
28
|
+
reset: string[];
|
|
29
|
+
resetOnce: string[];
|
|
30
|
+
};
|
|
31
|
+
props: {
|
|
32
|
+
sharedKeys: string[];
|
|
33
|
+
finalKeys: string[];
|
|
34
|
+
deferred: Record<string, string[]>;
|
|
35
|
+
merge: string[];
|
|
36
|
+
deepMerge: string[];
|
|
37
|
+
matchPropsOn: Record<string, string>;
|
|
38
|
+
optionalKeys: string[];
|
|
39
|
+
onceKeys: string[];
|
|
40
|
+
excludedKeys: string[];
|
|
41
|
+
};
|
|
42
|
+
resolvedProps: unknown;
|
|
43
|
+
assetVersion: string;
|
|
44
|
+
versionMismatch: boolean;
|
|
45
|
+
clientVersion: string | null;
|
|
46
|
+
encryptHistory: boolean;
|
|
47
|
+
clearHistory: boolean;
|
|
48
|
+
statusCode: number;
|
|
49
|
+
pageBytes: number;
|
|
50
|
+
ssr: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* True when a message is shaped like our diagnostic — it carries a numeric `v`
|
|
54
|
+
* and the load-bearing render fields — REGARDLESS of version. Lets the watcher
|
|
55
|
+
* tell "Inertia-shaped but unsupported `v`" (warn once) apart from ordinary
|
|
56
|
+
* non-Inertia channel noise (drop silently). Never throws.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isInertiaShaped(msg: unknown): msg is {
|
|
59
|
+
v: number;
|
|
60
|
+
} & Partial<InertiaRenderDiagnostic>;
|
|
61
|
+
/**
|
|
62
|
+
* Defensive structural validation of an untrusted channel message: Inertia-shaped
|
|
63
|
+
* AND a supported `v === 1`. The watcher records only these (fail-safe across
|
|
64
|
+
* producer version bumps). Narrowed return type — this proves the version and the
|
|
65
|
+
* load-bearing fields it actually checks, not the whole payload; the UI re-reads
|
|
66
|
+
* every other field defensively. Never throws.
|
|
67
|
+
*/
|
|
68
|
+
export declare function isInertiaDiagnostic(msg: unknown): msg is {
|
|
69
|
+
v: 1;
|
|
70
|
+
} & Partial<InertiaRenderDiagnostic>;
|
|
71
|
+
/**
|
|
72
|
+
* Map a validated diagnostic to a `RecordInput`. `familyHash` groups all renders
|
|
73
|
+
* of a component (`inertia:<component>`), mirroring `event:<name>` / `redis:<CMD>`.
|
|
74
|
+
* `durationMs` is null — render time belongs to the sibling `request` entry. The
|
|
75
|
+
* content is the wire payload minus `v`/`isInertia` via destructure-rest, so the
|
|
76
|
+
* heavy `resolvedProps` is carried BY REFERENCE (no clone/stringify) for the
|
|
77
|
+
* Recorder's `redactBounded` to traverse, clip, and mask.
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildInertiaContent(d: InertiaRenderDiagnostic): RecordInput<InertiaContent>;
|
|
80
|
+
//# sourceMappingURL=inertia-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inertia-content.d.ts","sourceRoot":"","sources":["../src/inertia-content.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,uBAAuB,EAAE,GAAG,GAAG,WAAW,CAAC,CAAC;AAE9E;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACtC,CAAC,EAAE,MAAM,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACpF,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACnC,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,YAAY,EAAE,MAAM,EAAE,CAAC;KACxB,CAAC;IACF,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,OAAO,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,OAAO,GACX,GAAG,IAAI;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAazD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,GACX,GAAG,IAAI;IAAE,CAAC,EAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAEpD;AAYD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,uBAAuB,GAAG,WAAW,CAAC,cAAc,CAAC,CAS3F"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// packages/inertia-watcher/src/inertia-content.ts
|
|
2
|
+
import { EntryType } from '@dudousxd/nestjs-telescope';
|
|
3
|
+
/**
|
|
4
|
+
* True when a message is shaped like our diagnostic — it carries a numeric `v`
|
|
5
|
+
* and the load-bearing render fields — REGARDLESS of version. Lets the watcher
|
|
6
|
+
* tell "Inertia-shaped but unsupported `v`" (warn once) apart from ordinary
|
|
7
|
+
* non-Inertia channel noise (drop silently). Never throws.
|
|
8
|
+
*/
|
|
9
|
+
export function isInertiaShaped(msg) {
|
|
10
|
+
if (typeof msg !== 'object' || msg === null)
|
|
11
|
+
return false;
|
|
12
|
+
const m = msg;
|
|
13
|
+
return (typeof m.v === 'number' &&
|
|
14
|
+
typeof m.component === 'string' &&
|
|
15
|
+
typeof m.url === 'string' &&
|
|
16
|
+
typeof m.method === 'string' &&
|
|
17
|
+
typeof m.props === 'object' &&
|
|
18
|
+
m.props !== null &&
|
|
19
|
+
typeof m.partial === 'object' &&
|
|
20
|
+
m.partial !== null);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Defensive structural validation of an untrusted channel message: Inertia-shaped
|
|
24
|
+
* AND a supported `v === 1`. The watcher records only these (fail-safe across
|
|
25
|
+
* producer version bumps). Narrowed return type — this proves the version and the
|
|
26
|
+
* load-bearing fields it actually checks, not the whole payload; the UI re-reads
|
|
27
|
+
* every other field defensively. Never throws.
|
|
28
|
+
*/
|
|
29
|
+
export function isInertiaDiagnostic(msg) {
|
|
30
|
+
return isInertiaShaped(msg) && msg.v === 1;
|
|
31
|
+
}
|
|
32
|
+
/** Build the tag list for one render: base tags + partial/mismatch/deferred/merge flags. */
|
|
33
|
+
function buildTags(d) {
|
|
34
|
+
const tags = ['inertia', `inertia:component:${d.component}`];
|
|
35
|
+
if (d.isPartial)
|
|
36
|
+
tags.push('inertia:partial');
|
|
37
|
+
if (d.versionMismatch)
|
|
38
|
+
tags.push('inertia:version-mismatch');
|
|
39
|
+
if (Object.keys(d.props.deferred ?? {}).length)
|
|
40
|
+
tags.push('inertia:deferred');
|
|
41
|
+
if ((d.props.merge?.length ?? 0) || (d.props.deepMerge?.length ?? 0))
|
|
42
|
+
tags.push('inertia:merge');
|
|
43
|
+
return tags;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Map a validated diagnostic to a `RecordInput`. `familyHash` groups all renders
|
|
47
|
+
* of a component (`inertia:<component>`), mirroring `event:<name>` / `redis:<CMD>`.
|
|
48
|
+
* `durationMs` is null — render time belongs to the sibling `request` entry. The
|
|
49
|
+
* content is the wire payload minus `v`/`isInertia` via destructure-rest, so the
|
|
50
|
+
* heavy `resolvedProps` is carried BY REFERENCE (no clone/stringify) for the
|
|
51
|
+
* Recorder's `redactBounded` to traverse, clip, and mask.
|
|
52
|
+
*/
|
|
53
|
+
export function buildInertiaContent(d) {
|
|
54
|
+
const { v, isInertia, ...content } = d;
|
|
55
|
+
return {
|
|
56
|
+
type: EntryType.Inertia,
|
|
57
|
+
familyHash: `inertia:${d.component}`,
|
|
58
|
+
durationMs: null,
|
|
59
|
+
tags: buildTags(d),
|
|
60
|
+
content,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=inertia-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inertia-content.js","sourceRoot":"","sources":["../src/inertia-content.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,OAAO,EAAE,SAAS,EAAoB,MAAM,4BAA4B,CAAC;AAiDzE;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,GAAY;IAEZ,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,OAAO,CACL,OAAO,CAAC,CAAC,CAAC,KAAK,QAAQ;QACvB,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ;QAC/B,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ;QACzB,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAC5B,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAC3B,CAAC,CAAC,KAAK,KAAK,IAAI;QAChB,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ;QAC7B,CAAC,CAAC,OAAO,KAAK,IAAI,CACnB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAAY;IAEZ,OAAO,eAAe,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AAC7C,CAAC;AAED,4FAA4F;AAC5F,SAAS,SAAS,CAAC,CAA0B;IAC3C,MAAM,IAAI,GAAG,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;IAC7D,IAAI,CAAC,CAAC,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC9C,IAAI,CAAC,CAAC,eAAe;QAAE,IAAI,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC7D,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM;QAAE,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC9E,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAC;QAAE,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACjG,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAA0B;IAC5D,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,OAAO,EAAE,GAAG,CAAC,CAAC;IACvC,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,OAAO;QACvB,UAAU,EAAE,WAAW,CAAC,CAAC,SAAS,EAAE;QACpC,UAAU,EAAE,IAAI;QAChB,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;QAClB,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Watcher, type WatcherContext } from '@dudousxd/nestjs-telescope';
|
|
2
|
+
/**
|
|
3
|
+
* Records every Inertia response published on the `nestjs-inertia:render`
|
|
4
|
+
* diagnostics channel as one `inertia` entry, correlated to the active
|
|
5
|
+
* request/job batch.
|
|
6
|
+
*
|
|
7
|
+
* ## How it works
|
|
8
|
+
* On `register` the watcher subscribes a listener to the channel. `inertia`
|
|
9
|
+
* publishes synchronously inside `InertiaService.render()`, so the listener runs
|
|
10
|
+
* on the same call stack / async context as the render — `ctx.record(...)` lands
|
|
11
|
+
* in the request's ALS batch (no batch is opened here, no request-id plumbing).
|
|
12
|
+
* Subscribing also flips inertia's `channel.hasSubscribers` to `true`, which is
|
|
13
|
+
* what makes the producer start building + publishing payloads at all.
|
|
14
|
+
*
|
|
15
|
+
* ## Resilience
|
|
16
|
+
* Each message is structurally validated (`isInertiaDiagnostic`, `v === 1`);
|
|
17
|
+
* malformed or wrong-version payloads are dropped. All recording is wrapped so a
|
|
18
|
+
* telescope error can never break a render. Registration is idempotent.
|
|
19
|
+
*/
|
|
20
|
+
export declare class InertiaWatcher implements Watcher {
|
|
21
|
+
readonly type: "inertia";
|
|
22
|
+
private registered;
|
|
23
|
+
private onMessage;
|
|
24
|
+
register(ctx: WatcherContext): void;
|
|
25
|
+
/** Unsubscribe the listener. Safe to call when never registered. */
|
|
26
|
+
cleanup(): void;
|
|
27
|
+
/** Validate + record, swallowing any failure so a render can never break. */
|
|
28
|
+
private safeRecord;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=inertia.watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inertia.watcher.d.ts","sourceRoot":"","sources":["../src/inertia.watcher.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAiB1F;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,cAAe,YAAW,OAAO;IAC5C,QAAQ,CAAC,IAAI,YAAqB;IAClC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAyC;IAE1D,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI;IASnC,oEAAoE;IACpE,OAAO,IAAI,IAAI;IAQf,6EAA6E;IAC7E,OAAO,CAAC,UAAU;CAmBnB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// packages/inertia-watcher/src/inertia.watcher.ts
|
|
2
|
+
import diagnostics_channel from 'node:diagnostics_channel';
|
|
3
|
+
import { EntryType } from '@dudousxd/nestjs-telescope';
|
|
4
|
+
import { buildInertiaContent, isInertiaDiagnostic, isInertiaShaped } from './inertia-content.js';
|
|
5
|
+
/**
|
|
6
|
+
* The channel name is the cross-repo contract with `@dudousxd/nestjs-inertia`
|
|
7
|
+
* (exported there as `INERTIA_DIAG_CHANNEL`). Hardcoded here — telescope must NOT
|
|
8
|
+
* import inertia at runtime. Keep this string byte-identical on both sides.
|
|
9
|
+
*/
|
|
10
|
+
const INERTIA_CHANNEL = 'nestjs-inertia:render';
|
|
11
|
+
/**
|
|
12
|
+
* One-time guard so an unsupported producer version (`v !== 1`) is surfaced once
|
|
13
|
+
* per process instead of every render — distinct from ordinary non-Inertia noise,
|
|
14
|
+
* which stays silent.
|
|
15
|
+
*/
|
|
16
|
+
let warnedUnsupportedVersion = false;
|
|
17
|
+
/**
|
|
18
|
+
* Records every Inertia response published on the `nestjs-inertia:render`
|
|
19
|
+
* diagnostics channel as one `inertia` entry, correlated to the active
|
|
20
|
+
* request/job batch.
|
|
21
|
+
*
|
|
22
|
+
* ## How it works
|
|
23
|
+
* On `register` the watcher subscribes a listener to the channel. `inertia`
|
|
24
|
+
* publishes synchronously inside `InertiaService.render()`, so the listener runs
|
|
25
|
+
* on the same call stack / async context as the render — `ctx.record(...)` lands
|
|
26
|
+
* in the request's ALS batch (no batch is opened here, no request-id plumbing).
|
|
27
|
+
* Subscribing also flips inertia's `channel.hasSubscribers` to `true`, which is
|
|
28
|
+
* what makes the producer start building + publishing payloads at all.
|
|
29
|
+
*
|
|
30
|
+
* ## Resilience
|
|
31
|
+
* Each message is structurally validated (`isInertiaDiagnostic`, `v === 1`);
|
|
32
|
+
* malformed or wrong-version payloads are dropped. All recording is wrapped so a
|
|
33
|
+
* telescope error can never break a render. Registration is idempotent.
|
|
34
|
+
*/
|
|
35
|
+
export class InertiaWatcher {
|
|
36
|
+
type = EntryType.Inertia;
|
|
37
|
+
registered = false;
|
|
38
|
+
onMessage = null;
|
|
39
|
+
register(ctx) {
|
|
40
|
+
if (this.registered)
|
|
41
|
+
return;
|
|
42
|
+
this.registered = true;
|
|
43
|
+
const channel = diagnostics_channel.channel(INERTIA_CHANNEL);
|
|
44
|
+
this.onMessage = (msg) => this.safeRecord(ctx, msg);
|
|
45
|
+
channel.subscribe(this.onMessage);
|
|
46
|
+
}
|
|
47
|
+
/** Unsubscribe the listener. Safe to call when never registered. */
|
|
48
|
+
cleanup() {
|
|
49
|
+
if (this.onMessage) {
|
|
50
|
+
diagnostics_channel.channel(INERTIA_CHANNEL).unsubscribe(this.onMessage);
|
|
51
|
+
this.onMessage = null;
|
|
52
|
+
}
|
|
53
|
+
this.registered = false;
|
|
54
|
+
}
|
|
55
|
+
/** Validate + record, swallowing any failure so a render can never break. */
|
|
56
|
+
safeRecord(ctx, msg) {
|
|
57
|
+
try {
|
|
58
|
+
if (!isInertiaDiagnostic(msg)) {
|
|
59
|
+
// Inertia-shaped but an unsupported producer version: warn once so the
|
|
60
|
+
// drift is visible. Genuine non-Inertia messages fall through silently.
|
|
61
|
+
if (!warnedUnsupportedVersion && isInertiaShaped(msg)) {
|
|
62
|
+
warnedUnsupportedVersion = true;
|
|
63
|
+
console.warn(`InertiaWatcher: dropping unsupported diagnostic version v=${msg.v} (expected 1) — upgrade @dudousxd/nestjs-telescope to match @dudousxd/nestjs-inertia`);
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
ctx.record(buildInertiaContent(msg));
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
// NOT rethrown — telescope must never break an Inertia render.
|
|
71
|
+
console.error('InertiaWatcher: failed to record inertia render:', err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=inertia.watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inertia.watcher.js","sourceRoot":"","sources":["../src/inertia.watcher.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,OAAO,mBAAmB,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAqC,MAAM,4BAA4B,CAAC;AAC1F,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEjG;;;;GAIG;AACH,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD;;;;GAIG;AACH,IAAI,wBAAwB,GAAG,KAAK,CAAC;AAErC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,cAAc;IAChB,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC;IAC1B,UAAU,GAAG,KAAK,CAAC;IACnB,SAAS,GAAoC,IAAI,CAAC;IAE1D,QAAQ,CAAC,GAAmB;QAC1B,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO;QAC5B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QAEvB,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAC7D,IAAI,CAAC,SAAS,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACpD,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACpC,CAAC;IAED,oEAAoE;IACpE,OAAO;QACL,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,mBAAmB,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACzE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED,6EAA6E;IACrE,UAAU,CAAC,GAAmB,EAAE,GAAY;QAClD,IAAI,CAAC;YACH,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,uEAAuE;gBACvE,wEAAwE;gBACxE,IAAI,CAAC,wBAAwB,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;oBACtD,wBAAwB,GAAG,IAAI,CAAC;oBAChC,OAAO,CAAC,IAAI,CACV,6DAA6D,GAAG,CAAC,CAAC,sFAAsF,CACzJ,CAAC;gBACJ,CAAC;gBACD,OAAO;YACT,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,mBAAmB,CAAC,GAAgD,CAAC,CAAC,CAAC;QACpF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,+DAA+D;YAC/D,OAAO,CAAC,KAAK,CAAC,kDAAkD,EAAE,GAAG,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dudousxd/nestjs-telescope-inertia-watcher",
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Inertia.js render watcher for @dudousxd/nestjs-telescope.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/DavideCarvalho/nestjs-telescope.git",
|
|
9
|
+
"directory": "packages/inertia-watcher"
|
|
10
|
+
},
|
|
11
|
+
"author": "Davi Carvalho <davi@goflip.ai>",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/",
|
|
24
|
+
"README.md",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@dudousxd/nestjs-telescope": "^1.7.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^20.0.0",
|
|
32
|
+
"reflect-metadata": "^0.2.2",
|
|
33
|
+
"typescript": "^5.4.0",
|
|
34
|
+
"vitest": "^3.0.0",
|
|
35
|
+
"@dudousxd/nestjs-telescope": "1.7.0"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"nestjs",
|
|
42
|
+
"telescope",
|
|
43
|
+
"inertia",
|
|
44
|
+
"inertia.js",
|
|
45
|
+
"observability"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.json",
|
|
49
|
+
"test": "vitest run --passWithNoTests",
|
|
50
|
+
"test:watch": "vitest",
|
|
51
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
52
|
+
}
|
|
53
|
+
}
|