@fougere/calls 0.6.0-alpha.0 → 0.8.0-alpha.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/dist/CallRing.d.ts +3 -28
- package/dist/CallRing.d.ts.map +1 -1
- package/dist/CallRing.js +6 -31
- package/dist/CallRing.js.map +1 -1
- package/dist/index.d.ts +2 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -44
- package/dist/index.js.map +1 -1
- package/dist/page.d.ts +1 -21
- package/dist/page.d.ts.map +1 -1
- package/dist/page.js +9 -38
- package/dist/page.js.map +1 -1
- package/dist/panel.d.ts +1 -9
- package/dist/panel.d.ts.map +1 -1
- package/dist/panel.js +4 -12
- package/dist/panel.js.map +1 -1
- package/dist/rings.d.ts +2 -16
- package/dist/rings.d.ts.map +1 -1
- package/dist/rings.js +12 -26
- package/dist/rings.js.map +1 -1
- package/package.json +8 -8
- package/src/CallRing.ts +6 -31
- package/src/index.ts +11 -50
- package/src/page.ts +9 -38
- package/src/panel.ts +4 -12
- package/src/rings.ts +12 -26
package/dist/CallRing.d.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { type CallPage, type CallRecord, type DispatchEvent } from '@fougere/core';
|
|
2
|
-
/**
|
|
3
|
-
* A bounded log of what this process dispatched.
|
|
4
|
-
*
|
|
5
|
-
* The five transitions of one call are folded into one record: `received` opens it,
|
|
6
|
-
* `resolved` names the route, `completed`/`failed` settle the verdict, `settled` closes
|
|
7
|
-
* the duration. They are matched by the identity of the `Call`, which is the same frozen
|
|
8
|
-
* object across all five.
|
|
9
|
-
*/
|
|
2
|
+
/** A bounded log of what this process dispatched. */
|
|
10
3
|
export declare class CallRing {
|
|
11
4
|
private readonly max;
|
|
12
5
|
private readonly frondOf;
|
|
@@ -16,28 +9,10 @@ export declare class CallRing {
|
|
|
16
9
|
private lost;
|
|
17
10
|
private readonly watching;
|
|
18
11
|
constructor(max?: number, frondOf?: (entity: string) => string | undefined);
|
|
19
|
-
/**
|
|
20
|
-
* KNOWN LIMIT — a call this process ORIGINATES carries no trace here.
|
|
21
|
-
*
|
|
22
|
-
* `trace()` writes the traceparent inside the middleware chain, so inside
|
|
23
|
-
* `route.execute(call)`; the observer saw the `Call` at `received`, and that object is
|
|
24
|
-
* frozen. An ARRIVING call is unaffected: its traceparent is already on the invocation
|
|
25
|
-
* the transport handed over, which is why a hosted frond shows traces and its consumer
|
|
26
|
-
* does not.
|
|
27
|
-
*
|
|
28
|
-
* Closing it means `DispatchEvent` carrying the invocation as it ENDED rather than as it
|
|
29
|
-
* arrived — a core change, deliberately not smuggled in here. Until then the Traces view
|
|
30
|
-
* speaks for the side that receives.
|
|
31
|
-
*/
|
|
12
|
+
/** KNOWN LIMIT — a call this process ORIGINATES carries no trace here. */
|
|
32
13
|
record(event: DispatchEvent): void;
|
|
33
14
|
private opened;
|
|
34
|
-
/**
|
|
35
|
-
* Be told when a record settles, and get the unsubscription back.
|
|
36
|
-
*
|
|
37
|
-
* A page that polls is a page that is always a little wrong; this is what lets a door
|
|
38
|
-
* push instead. Told at `settled` and not at `received`, because a record is only
|
|
39
|
-
* complete then — a reader would otherwise redraw the same line four times.
|
|
40
|
-
*/
|
|
15
|
+
/** Be told when a record settles, and get the unsubscription back. */
|
|
41
16
|
watch(told: (record: CallRecord) => void): () => void;
|
|
42
17
|
/** Everything above `cursor`, and what was lost while the reader was away. */
|
|
43
18
|
since(cursor: number, inFlight?: number): CallPage;
|
package/dist/CallRing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CallRing.d.ts","sourceRoot":"","sources":["../src/CallRing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAa/F
|
|
1
|
+
{"version":3,"file":"CallRing.d.ts","sourceRoot":"","sources":["../src/CallRing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAa/F,qDAAqD;AACrD,qBAAa,QAAQ;IAQjB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAR1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoB;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqC;IAC1D,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2C;IAEpE,YACmB,GAAG,SAAM,EACT,OAAO,GAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAA2B,EAChF;IAEJ,0EAA0E;IAC1E,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CA0BjC;IAED,OAAO,CAAC,MAAM;IAsBd,sEAAsE;IACtE,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI,CAIpD;IAED,8EAA8E;IAC9E,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,QAAQ,CAO5C;CACF"}
|
package/dist/CallRing.js
CHANGED
|
@@ -3,20 +3,13 @@ import { RPC_ENTITY } from '@fougere/core';
|
|
|
3
3
|
function refusalOf(error) {
|
|
4
4
|
if (error === null || error === undefined)
|
|
5
5
|
return { message: 'unknown refusal' };
|
|
6
|
-
const
|
|
6
|
+
const thrown = error;
|
|
7
7
|
return {
|
|
8
|
-
...(typeof
|
|
9
|
-
message: typeof
|
|
8
|
+
...(typeof thrown.code === 'string' ? { code: thrown.code } : {}),
|
|
9
|
+
message: typeof thrown.message === 'string' ? thrown.message : String(error),
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
* A bounded log of what this process dispatched.
|
|
14
|
-
*
|
|
15
|
-
* The five transitions of one call are folded into one record: `received` opens it,
|
|
16
|
-
* `resolved` names the route, `completed`/`failed` settle the verdict, `settled` closes
|
|
17
|
-
* the duration. They are matched by the identity of the `Call`, which is the same frozen
|
|
18
|
-
* object across all five.
|
|
19
|
-
*/
|
|
12
|
+
/** A bounded log of what this process dispatched. */
|
|
20
13
|
export class CallRing {
|
|
21
14
|
max;
|
|
22
15
|
frondOf;
|
|
@@ -29,19 +22,7 @@ export class CallRing {
|
|
|
29
22
|
this.max = max;
|
|
30
23
|
this.frondOf = frondOf;
|
|
31
24
|
}
|
|
32
|
-
/**
|
|
33
|
-
* KNOWN LIMIT — a call this process ORIGINATES carries no trace here.
|
|
34
|
-
*
|
|
35
|
-
* `trace()` writes the traceparent inside the middleware chain, so inside
|
|
36
|
-
* `route.execute(call)`; the observer saw the `Call` at `received`, and that object is
|
|
37
|
-
* frozen. An ARRIVING call is unaffected: its traceparent is already on the invocation
|
|
38
|
-
* the transport handed over, which is why a hosted frond shows traces and its consumer
|
|
39
|
-
* does not.
|
|
40
|
-
*
|
|
41
|
-
* Closing it means `DispatchEvent` carrying the invocation as it ENDED rather than as it
|
|
42
|
-
* arrived — a core change, deliberately not smuggled in here. Until then the Traces view
|
|
43
|
-
* speaks for the side that receives.
|
|
44
|
-
*/
|
|
25
|
+
/** KNOWN LIMIT — a call this process ORIGINATES carries no trace here. */
|
|
45
26
|
record(event) {
|
|
46
27
|
// A reader reaches this ring through `rpc`, so recording that would make the panel
|
|
47
28
|
// watch itself — one reader, one line, forever.
|
|
@@ -95,13 +76,7 @@ export class CallRing {
|
|
|
95
76
|
if (this.held.length > this.max)
|
|
96
77
|
this.lost += this.held.splice(0, this.held.length - this.max).length;
|
|
97
78
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Be told when a record settles, and get the unsubscription back.
|
|
100
|
-
*
|
|
101
|
-
* A page that polls is a page that is always a little wrong; this is what lets a door
|
|
102
|
-
* push instead. Told at `settled` and not at `received`, because a record is only
|
|
103
|
-
* complete then — a reader would otherwise redraw the same line four times.
|
|
104
|
-
*/
|
|
79
|
+
/** Be told when a record settles, and get the unsubscription back. */
|
|
105
80
|
watch(told) {
|
|
106
81
|
this.watching.add(told);
|
|
107
82
|
return () => this.watching.delete(told);
|
package/dist/CallRing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CallRing.js","sourceRoot":"","sources":["../src/CallRing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsD,MAAM,eAAe,CAAC;AAE/F,oFAAoF;AACpF,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC;IACjF,MAAM,
|
|
1
|
+
{"version":3,"file":"CallRing.js","sourceRoot":"","sources":["../src/CallRing.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,26 +10,9 @@ export declare const CALLS_OP = "calls";
|
|
|
10
10
|
export interface CallsOptions {
|
|
11
11
|
/** How many calls the ring keeps. Beyond it, the oldest are dropped and counted. */
|
|
12
12
|
max?: number;
|
|
13
|
-
/**
|
|
14
|
-
* Serve the page too, on its own loopback port. `true` takes a free one.
|
|
15
|
-
*
|
|
16
|
-
* Node only — it opens an `http` server, which workerd has not. Without it the extension
|
|
17
|
-
* stays universal and `fougere devtools` is the reader.
|
|
18
|
-
*/
|
|
13
|
+
/** Serve the page too, on its own loopback port. */
|
|
19
14
|
panel?: boolean | number | PanelOptions;
|
|
20
15
|
}
|
|
21
|
-
/**
|
|
22
|
-
* What this process dispatched, kept in a bounded ring and served as an rpc operation.
|
|
23
|
-
*
|
|
24
|
-
* It watches rather than participates: `app.observe` is passive, `DispatchLifecycle`
|
|
25
|
-
* swallows an observer's own failure, and the ring holds no reference to a body. What it
|
|
26
|
-
* sees that a middleware cannot: a call refused BEFORE any handler — an unknown route, an
|
|
27
|
-
* entity hosted elsewhere, a call arriving while the door drains — and the route kind of
|
|
28
|
-
* every call, so a local execution and a hop to another process read the same way.
|
|
29
|
-
*
|
|
30
|
-
* The reader is `fougere devtools`, over `/_fougere/call` like any other consumer. No port
|
|
31
|
-
* is opened here, and an app that never installed this package answers
|
|
32
|
-
* `Unknown rpc operation 'calls'. It serves discover.`
|
|
33
|
-
*/
|
|
16
|
+
/** What this process dispatched, kept in a bounded ring and served as an rpc operation. */
|
|
34
17
|
export declare function calls(options?: CallsOptions): Extension;
|
|
35
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,SAAS,EAA0B,MAAM,eAAe,CAAC;AAGxF,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC3D,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D,sDAAsD;AACtD,eAAO,MAAM,QAAQ,UAAU,CAAC;AAEhC,MAAM,WAAW,YAAY;IAC3B,oFAAoF;IACpF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,SAAS,EAA0B,MAAM,eAAe,CAAC;AAGxF,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC3D,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D,sDAAsD;AACtD,eAAO,MAAM,QAAQ,UAAU,CAAC;AAEhC,MAAM,WAAW,YAAY;IAC3B,oFAAoF;IACpF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,YAAY,CAAC;CACzC;AAsFD,2FAA2F;AAC3F,wBAAgB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,SAAS,CA2D3D"}
|
package/dist/index.js
CHANGED
|
@@ -7,13 +7,7 @@ export { LogRing, QueryRing, ErrorRing } from './rings.js';
|
|
|
7
7
|
export { servePanel } from './panel.js';
|
|
8
8
|
/** The rpc operation this extension answers under. */
|
|
9
9
|
export const CALLS_OP = 'calls';
|
|
10
|
-
/**
|
|
11
|
-
* What this process serves, read from the app itself.
|
|
12
|
-
*
|
|
13
|
-
* Not a second source: `operationsFor` is the same effective model the façade consumes, so
|
|
14
|
-
* the page shows what will actually answer — including the ops nobody has called yet, which
|
|
15
|
-
* is what makes an empty panel useful instead of blank.
|
|
16
|
-
*/
|
|
10
|
+
/** What this process serves, read from the app itself. */
|
|
17
11
|
function servedModel(app) {
|
|
18
12
|
return {
|
|
19
13
|
fronds: app.fronds.map((frond) => ({
|
|
@@ -62,19 +56,12 @@ function hostOf(address) {
|
|
|
62
56
|
}
|
|
63
57
|
}
|
|
64
58
|
function nameOf(schema) {
|
|
65
|
-
const
|
|
66
|
-
if (!
|
|
59
|
+
const named = schema;
|
|
60
|
+
if (!named)
|
|
67
61
|
return null;
|
|
68
|
-
return typeof
|
|
62
|
+
return typeof named.getName === 'function' ? named.getName() : named.name ?? null;
|
|
69
63
|
}
|
|
70
|
-
/**
|
|
71
|
-
* `address -> frond`, resolved once at boot: a call's address does not carry its frond.
|
|
72
|
-
*
|
|
73
|
-
* Indexed on HANDLERS and not on entities, because a frond may hold no entity at all —
|
|
74
|
-
* `demos/observability` has two — and then every one of its calls came back unnamed. The
|
|
75
|
-
* entities are indexed after, and only where a handler left the address free: an entity
|
|
76
|
-
* with no handler is served by `Crud`, which answers under the same address.
|
|
77
|
-
*/
|
|
64
|
+
/** `address -> frond`, resolved once at boot: a call's address does not carry its frond. */
|
|
78
65
|
function frondIndex(app) {
|
|
79
66
|
const byAddress = new Map();
|
|
80
67
|
for (const frond of app.fronds) {
|
|
@@ -85,12 +72,7 @@ function frondIndex(app) {
|
|
|
85
72
|
}
|
|
86
73
|
return (address) => byAddress.get(address);
|
|
87
74
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Subscribe to SQL when there is SQL. Absence is an answer, not a blank tab.
|
|
90
|
-
*
|
|
91
|
-
* `logQueries` is installed in every Kysely this process builds, so the subscription is all
|
|
92
|
-
* that is missing — and it is taken here, in `up(app)`, like the other two.
|
|
93
|
-
*/
|
|
75
|
+
/** Subscribe to SQL when there is SQL. */
|
|
94
76
|
async function queriesFrom(queries) {
|
|
95
77
|
try {
|
|
96
78
|
const { onQuery } = await import('@fougere/adapter-sql');
|
|
@@ -101,29 +83,13 @@ async function queriesFrom(queries) {
|
|
|
101
83
|
}
|
|
102
84
|
}
|
|
103
85
|
function cursorOf(invocation) {
|
|
104
|
-
const
|
|
105
|
-
const since = Number(
|
|
86
|
+
const input = invocation.input;
|
|
87
|
+
const since = Number(input?.since ?? 0);
|
|
106
88
|
return Number.isFinite(since) && since > 0 ? since : 0;
|
|
107
89
|
}
|
|
108
|
-
/**
|
|
109
|
-
* What this process dispatched, kept in a bounded ring and served as an rpc operation.
|
|
110
|
-
*
|
|
111
|
-
* It watches rather than participates: `app.observe` is passive, `DispatchLifecycle`
|
|
112
|
-
* swallows an observer's own failure, and the ring holds no reference to a body. What it
|
|
113
|
-
* sees that a middleware cannot: a call refused BEFORE any handler — an unknown route, an
|
|
114
|
-
* entity hosted elsewhere, a call arriving while the door drains — and the route kind of
|
|
115
|
-
* every call, so a local execution and a hop to another process read the same way.
|
|
116
|
-
*
|
|
117
|
-
* The reader is `fougere devtools`, over `/_fougere/call` like any other consumer. No port
|
|
118
|
-
* is opened here, and an app that never installed this package answers
|
|
119
|
-
* `Unknown rpc operation 'calls'. It serves discover.`
|
|
120
|
-
*/
|
|
90
|
+
/** What this process dispatched, kept in a bounded ring and served as an rpc operation. */
|
|
121
91
|
export function calls(options = {}) {
|
|
122
|
-
/**
|
|
123
|
-
* Per APP, not per extension. A host declares its extensions once, so the same instance
|
|
124
|
-
* goes up on the new app before the old one is released — one shared ring would mix two
|
|
125
|
-
* processes' worth of calls, and the older `down` would erase the newer subscription.
|
|
126
|
-
*/
|
|
92
|
+
/** Per APP, not per extension. */
|
|
127
93
|
const stopping = new WeakMap();
|
|
128
94
|
return {
|
|
129
95
|
name: 'calls',
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAoD,MAAM,eAAe,CAAC;AACxF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAqB,MAAM,YAAY,CAAC;AAG3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,EAAE,UAAU,EAAqB,MAAM,YAAY,CAAC;AAE3D,sDAAsD;AACtD,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/page.d.ts
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The panel, as one file with no dependency.
|
|
3
|
-
*
|
|
4
|
-
* Inline on purpose: this package ships no framework and no build step, and a dev tool that
|
|
5
|
-
* needs `npm install` before it shows anything is a dev tool nobody opens. The page reads
|
|
6
|
-
* its own origin — the door that served it also serves its events — so there is no address
|
|
7
|
-
* to configure and no CORS to arrange.
|
|
8
|
-
*
|
|
9
|
-
* FOUR views, and the number is a decision. Six grew one tab at a time, each with its own
|
|
10
|
-
* empty state and its own caveat, and the result read as noise rather than as an answer.
|
|
11
|
-
* They fold along what a reader actually asks:
|
|
12
|
-
*
|
|
13
|
-
* Calls what happened, and did it hold — the screen that IS the argument
|
|
14
|
-
* Refused what did not, from two sources, since one of them is never a call
|
|
15
|
-
* Activity what the process did along the way — logs and statements are one stream,
|
|
16
|
-
* both chronological, both uncorrelated to a call; two tabs said that twice
|
|
17
|
-
* Served what it would answer if asked, filled before anything is called
|
|
18
|
-
*
|
|
19
|
-
* Traces lost its tab: it spoke for the receiving side only, and a trace already shows in a
|
|
20
|
-
* call's detail. A tab that half-delivers is worse than a field that says what it knows.
|
|
21
|
-
*/
|
|
1
|
+
/** The panel, as one file with no dependency. */
|
|
22
2
|
export declare function page(title: string): string;
|
|
23
3
|
//# sourceMappingURL=page.d.ts.map
|
package/dist/page.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAwf1C"}
|
package/dist/page.js
CHANGED
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The panel, as one file with no dependency.
|
|
3
|
-
*
|
|
4
|
-
* Inline on purpose: this package ships no framework and no build step, and a dev tool that
|
|
5
|
-
* needs `npm install` before it shows anything is a dev tool nobody opens. The page reads
|
|
6
|
-
* its own origin — the door that served it also serves its events — so there is no address
|
|
7
|
-
* to configure and no CORS to arrange.
|
|
8
|
-
*
|
|
9
|
-
* FOUR views, and the number is a decision. Six grew one tab at a time, each with its own
|
|
10
|
-
* empty state and its own caveat, and the result read as noise rather than as an answer.
|
|
11
|
-
* They fold along what a reader actually asks:
|
|
12
|
-
*
|
|
13
|
-
* Calls what happened, and did it hold — the screen that IS the argument
|
|
14
|
-
* Refused what did not, from two sources, since one of them is never a call
|
|
15
|
-
* Activity what the process did along the way — logs and statements are one stream,
|
|
16
|
-
* both chronological, both uncorrelated to a call; two tabs said that twice
|
|
17
|
-
* Served what it would answer if asked, filled before anything is called
|
|
18
|
-
*
|
|
19
|
-
* Traces lost its tab: it spoke for the receiving side only, and a trace already shows in a
|
|
20
|
-
* call's detail. A tab that half-delivers is worse than a field that says what it knows.
|
|
21
|
-
*/
|
|
1
|
+
/** The panel, as one file with no dependency. */
|
|
22
2
|
export function page(title) {
|
|
23
3
|
return `<!doctype html>
|
|
24
4
|
<html lang="en">
|
|
@@ -210,21 +190,21 @@ function pills(options, held, set) {
|
|
|
210
190
|
}
|
|
211
191
|
|
|
212
192
|
function calls() {
|
|
213
|
-
const
|
|
193
|
+
const shown = state.calls.filter((c) => state.filter === 'all' || routeOf(c) === state.filter);
|
|
214
194
|
const bar = pills(['all', 'local', 'remote', 'system'], state.filter, (v) => { state.filter = v; });
|
|
215
195
|
|
|
216
|
-
if (
|
|
196
|
+
if (shown.length === 0) {
|
|
217
197
|
return [bar, empty('Nothing dispatched yet — call an operation and it lands here.')];
|
|
218
198
|
}
|
|
219
199
|
|
|
220
200
|
// The recent window, not the session's high-water mark: one 4-second migration at boot
|
|
221
201
|
// and every bar after it was 2px for good.
|
|
222
|
-
const slowest = Math.max(1, ...
|
|
202
|
+
const slowest = Math.max(1, ...shown.slice(-100).map((c) => c.ms ?? 0));
|
|
223
203
|
|
|
224
204
|
// One frond, one word repeated 400 times, 112px stolen from what the eye scans.
|
|
225
205
|
const many = new Set(state.calls.map((c) => c.frond)).size > 1;
|
|
226
206
|
|
|
227
|
-
const rows =
|
|
207
|
+
const rows = shown.slice(-400).reverse().map((c) => {
|
|
228
208
|
const route = routeOf(c);
|
|
229
209
|
const width = c.ms ? Math.max(2, Math.round((c.ms / slowest) * 56)) : 0;
|
|
230
210
|
// Cleared as it is drawn: the flag marks the first render of a call, and a redraw is
|
|
@@ -355,16 +335,7 @@ function served() {
|
|
|
355
335
|
}
|
|
356
336
|
|
|
357
337
|
// ── Shape ────────────────────────────────────────────────────────────────────
|
|
358
|
-
/**
|
|
359
|
-
* What this process is arranged as — DECLARED and OBSERVED on one picture.
|
|
360
|
-
*
|
|
361
|
-
* Both halves are already on this page: the model says where a call is meant to go, the
|
|
362
|
-
* ring says where it went. Nobody else can draw the pair, because elsewhere the placement
|
|
363
|
-
* is not the developer's to declare — there is only ever one value, so there is nothing to
|
|
364
|
-
* disagree with.
|
|
365
|
-
*
|
|
366
|
-
* One process's view, and it says so: a frond hosted elsewhere is a node with no inside.
|
|
367
|
-
*/
|
|
338
|
+
/** What this process is arranged as — DECLARED and OBSERVED on one picture. */
|
|
368
339
|
function shape() {
|
|
369
340
|
if (!state.model || state.model.fronds.length === 0) return [empty('This process hosts no frond.')];
|
|
370
341
|
|
|
@@ -508,10 +479,10 @@ events.addEventListener('error', () => {
|
|
|
508
479
|
document.getElementById('dot').className = 'dot off';
|
|
509
480
|
});
|
|
510
481
|
events.addEventListener('hello', (m) => {
|
|
511
|
-
const
|
|
512
|
-
state.fronds =
|
|
482
|
+
const payload = JSON.parse(m.data);
|
|
483
|
+
state.fronds = payload.fronds ?? [];
|
|
513
484
|
document.getElementById('where').textContent = state.fronds.length ? ' · ' + state.fronds.join(', ') : '';
|
|
514
|
-
state.calls =
|
|
485
|
+
state.calls = payload.calls;
|
|
515
486
|
draw();
|
|
516
487
|
});
|
|
517
488
|
events.addEventListener('call', (m) => {
|
package/dist/page.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.js","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,MAAM,UAAU,IAAI,CAAC,KAAa;IAChC,OAAO;;;;;SAKA,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA2HJ,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAsXP,CAAC;AACT,CAAC"}
|
package/dist/panel.d.ts
CHANGED
|
@@ -17,14 +17,6 @@ export interface PanelOptions {
|
|
|
17
17
|
/** Told where it is listening, once. */
|
|
18
18
|
announce?: (url: string) => void;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* The panel's own door: the page, and the events that fill it.
|
|
22
|
-
*
|
|
23
|
-
* Its own server and not the app's, for one measured reason: `serve()` from
|
|
24
|
-
* `@fougere/transport-http` answers 404 to everything that is not `POST /_fougere/call`,
|
|
25
|
-
* which is what makes it safe to expose — a page cannot enter it without changing what it
|
|
26
|
-
* is. So the panel binds 127.0.0.1 only, and serves both halves itself, which also means
|
|
27
|
-
* the page reads its own origin and no CORS has to be arranged.
|
|
28
|
-
*/
|
|
20
|
+
/** Serves the panel and returns how to close it. */
|
|
29
21
|
export declare function servePanel(ring: CallRing, options?: PanelOptions): Promise<() => Promise<void>>;
|
|
30
22
|
//# sourceMappingURL=panel.d.ts.map
|
package/dist/panel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../src/panel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,qFAAqF;IACrF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wEAAwE;IACxE,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,MAAM,CAAC;IACxB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAKD
|
|
1
|
+
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../src/panel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,qFAAqF;IACrF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wEAAwE;IACxE,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,MAAM,CAAC;IACxB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAKD,oDAAoD;AACpD,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAsFnG"}
|
package/dist/panel.js
CHANGED
|
@@ -2,15 +2,7 @@ import { createServer } from 'node:http';
|
|
|
2
2
|
import { page } from './page.js';
|
|
3
3
|
/** How often the page is told how many calls are in flight. */
|
|
4
4
|
const BEAT_MS = 1000;
|
|
5
|
-
/**
|
|
6
|
-
* The panel's own door: the page, and the events that fill it.
|
|
7
|
-
*
|
|
8
|
-
* Its own server and not the app's, for one measured reason: `serve()` from
|
|
9
|
-
* `@fougere/transport-http` answers 404 to everything that is not `POST /_fougere/call`,
|
|
10
|
-
* which is what makes it safe to expose — a page cannot enter it without changing what it
|
|
11
|
-
* is. So the panel binds 127.0.0.1 only, and serves both halves itself, which also means
|
|
12
|
-
* the page reads its own origin and no CORS has to be arranged.
|
|
13
|
-
*/
|
|
5
|
+
/** Serves the panel and returns how to close it. */
|
|
14
6
|
export function servePanel(ring, options = {}) {
|
|
15
7
|
const title = options.title ?? 'fougere';
|
|
16
8
|
const readers = new Set();
|
|
@@ -43,8 +35,8 @@ export function servePanel(ring, options = {}) {
|
|
|
43
35
|
});
|
|
44
36
|
// The page is handed the ring as it stands, so a reader that opens late is not
|
|
45
37
|
// looking at an empty screen that suggests a quiet app.
|
|
46
|
-
const
|
|
47
|
-
response.write(`event: hello\ndata: ${JSON.stringify({ calls:
|
|
38
|
+
const backlog = ring.since(0);
|
|
39
|
+
response.write(`event: hello\ndata: ${JSON.stringify({ calls: backlog.calls, fronds: options.fronds ?? [] })}\n\n`);
|
|
48
40
|
readers.add(response);
|
|
49
41
|
request.on('close', () => readers.delete(response));
|
|
50
42
|
return;
|
|
@@ -79,7 +71,7 @@ export function servePanel(ring, options = {}) {
|
|
|
79
71
|
});
|
|
80
72
|
return new Promise((ready, refuse) => {
|
|
81
73
|
server.on('error', refuse);
|
|
82
|
-
// Loopback only. This door has no
|
|
74
|
+
// Loopback only. This door has no validator — `serve()` refuses to start beyond loopback
|
|
83
75
|
// without `verify`, and this one would not know how to refuse at all.
|
|
84
76
|
server.listen(options.port ?? 0, '127.0.0.1', () => {
|
|
85
77
|
const port = server.address().port;
|
package/dist/panel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.js","sourceRoot":"","sources":["../src/panel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AAGtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAqBjC,+DAA+D;AAC/D,MAAM,OAAO,GAAG,IAAI,CAAC;AAErB
|
|
1
|
+
{"version":3,"file":"panel.js","sourceRoot":"","sources":["../src/panel.ts"],"names":[],"mappings":"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"}
|
package/dist/rings.d.ts
CHANGED
|
@@ -54,28 +54,14 @@ declare class Ring<T extends {
|
|
|
54
54
|
dropped: number;
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
-
/**
|
|
58
|
-
* What this process logged.
|
|
59
|
-
*
|
|
60
|
-
* Chronological and nothing more: correlating a line to its call needs an async context,
|
|
61
|
-
* which core's `Ambient` port does not provide (it answers about frames and emission
|
|
62
|
-
* chains) and only `@fougere/observability` has. Aligning by timestamp would be a guess
|
|
63
|
-
* dressed as a fact — the failure mode of every panel that lies.
|
|
64
|
-
*/
|
|
57
|
+
/** What this process logged. */
|
|
65
58
|
export declare class LogRing extends Ring<LogLine> {
|
|
66
59
|
record(line: LogRecord): void;
|
|
67
60
|
}
|
|
68
61
|
export declare class QueryRing extends Ring<QueryLine> {
|
|
69
62
|
record(event: Omit<QueryLine, 'seq'>): void;
|
|
70
63
|
}
|
|
71
|
-
/**
|
|
72
|
-
* What was refused, from TWO sources — and that is the point.
|
|
73
|
-
*
|
|
74
|
-
* Fed by the call flow alone, this screen would miss exactly the failures that matter: a
|
|
75
|
-
* storage that would not open, an export that could not be sent, an extension that fell
|
|
76
|
-
* over. None of those is a dispatch. It is the blind spot Symfony's Headers panel and
|
|
77
|
-
* Django's History panel both have, and the only cure is a second source.
|
|
78
|
-
*/
|
|
64
|
+
/** What was refused, from TWO sources — and that is the point. */
|
|
79
65
|
export declare class ErrorRing extends Ring<ErrorGroup> {
|
|
80
66
|
private readonly byKey;
|
|
81
67
|
/** A refusal that a call carried. `details` reaches here intact, unlike a span's code. */
|
package/dist/rings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rings.d.ts","sourceRoot":"","sources":["../src/rings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAKvF,2FAA2F;AAC3F,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,kFAAkF;IAClF,IAAI,EAAE,UAAU,GAAG,KAAK,CAAC;CAC1B;AAED,6FAA6F;AAC7F,cAAM,IAAI,CAAC,CAAC,SAAS;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE;IAKtB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAJhC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAM;IAClC,SAAS,CAAC,GAAG,SAAK;IAClB,OAAO,CAAC,IAAI,CAAK;IAEjB,YAA6B,GAAG,SAAO,EAAI;IAE3C,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,CAM1C;IAED,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAErE;CACF;AAED
|
|
1
|
+
{"version":3,"file":"rings.d.ts","sourceRoot":"","sources":["../src/rings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAKvF,2FAA2F;AAC3F,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,kFAAkF;IAClF,IAAI,EAAE,UAAU,GAAG,KAAK,CAAC;CAC1B;AAED,6FAA6F;AAC7F,cAAM,IAAI,CAAC,CAAC,SAAS;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE;IAKtB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAJhC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAM;IAClC,SAAS,CAAC,GAAG,SAAK;IAClB,OAAO,CAAC,IAAI,CAAK;IAEjB,YAA6B,GAAG,SAAO,EAAI;IAE3C,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,CAM1C;IAED,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAErE;CACF;AAED,gCAAgC;AAChC,qBAAa,OAAQ,SAAQ,IAAI,CAAC,OAAO,CAAC;IACxC,MAAM,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAS5B;CACF;AAED,qBAAa,SAAU,SAAQ,IAAI,CAAC,SAAS,CAAC;IAC5C,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,IAAI,CAE1C;CACF;AAED,kEAAkE;AAClE,qBAAa,SAAU,SAAQ,IAAI,CAAC,UAAU,CAAC;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IAEvD,0FAA0F;IAC1F,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAYvC;IAED,uFAAuF;IACvF,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAI7B;IAED,OAAO,CAAC,KAAK;CAqBd"}
|
package/dist/rings.js
CHANGED
|
@@ -11,24 +11,17 @@ class Ring {
|
|
|
11
11
|
this.max = max;
|
|
12
12
|
}
|
|
13
13
|
keep(make) {
|
|
14
|
-
const
|
|
15
|
-
this.held.push(
|
|
14
|
+
const record = make(++this.seq);
|
|
15
|
+
this.held.push(record);
|
|
16
16
|
if (this.held.length > this.max)
|
|
17
17
|
this.lost += this.held.splice(0, this.held.length - this.max).length;
|
|
18
|
-
return
|
|
18
|
+
return record;
|
|
19
19
|
}
|
|
20
20
|
since(cursor) {
|
|
21
21
|
return { lines: this.held.filter((one) => one.seq > cursor), cursor: this.seq, dropped: this.lost };
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
25
|
-
* What this process logged.
|
|
26
|
-
*
|
|
27
|
-
* Chronological and nothing more: correlating a line to its call needs an async context,
|
|
28
|
-
* which core's `Ambient` port does not provide (it answers about frames and emission
|
|
29
|
-
* chains) and only `@fougere/observability` has. Aligning by timestamp would be a guess
|
|
30
|
-
* dressed as a fact — the failure mode of every panel that lies.
|
|
31
|
-
*/
|
|
24
|
+
/** What this process logged. */
|
|
32
25
|
export class LogRing extends Ring {
|
|
33
26
|
record(line) {
|
|
34
27
|
this.keep((seq) => ({
|
|
@@ -46,14 +39,7 @@ export class QueryRing extends Ring {
|
|
|
46
39
|
this.keep((seq) => ({ ...event, seq }));
|
|
47
40
|
}
|
|
48
41
|
}
|
|
49
|
-
/**
|
|
50
|
-
* What was refused, from TWO sources — and that is the point.
|
|
51
|
-
*
|
|
52
|
-
* Fed by the call flow alone, this screen would miss exactly the failures that matter: a
|
|
53
|
-
* storage that would not open, an export that could not be sent, an extension that fell
|
|
54
|
-
* over. None of those is a dispatch. It is the blind spot Symfony's Headers panel and
|
|
55
|
-
* Django's History panel both have, and the only cure is a second source.
|
|
56
|
-
*/
|
|
42
|
+
/** What was refused, from TWO sources — and that is the point. */
|
|
57
43
|
export class ErrorRing extends Ring {
|
|
58
44
|
byKey = new Map();
|
|
59
45
|
/** A refusal that a call carried. `details` reaches here intact, unlike a span's code. */
|
|
@@ -77,20 +63,20 @@ export class ErrorRing extends Ring {
|
|
|
77
63
|
}
|
|
78
64
|
group(one) {
|
|
79
65
|
const key = [one.from, one.code, one.entity ?? '', one.operation ?? ''].join(' ');
|
|
80
|
-
const
|
|
66
|
+
const seen = this.byKey.get(key);
|
|
81
67
|
const at = Date.now();
|
|
82
68
|
// Grouped, because a refusal seen forty times is one line with a count — not forty
|
|
83
69
|
// lines that push everything else out of a bounded ring.
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
70
|
+
if (seen) {
|
|
71
|
+
seen.count += 1;
|
|
72
|
+
seen.lastAt = at;
|
|
73
|
+
seen.message = one.message;
|
|
88
74
|
if (one.fields.length > 0)
|
|
89
|
-
|
|
75
|
+
seen.fields = one.fields;
|
|
90
76
|
// Re-numbered so a reader that already saw this group is handed the higher count.
|
|
91
77
|
// Without it a refusal seen forty times reports one, forever: the group is mutated
|
|
92
78
|
// in place, and a cursor asks only for what is above it.
|
|
93
|
-
|
|
79
|
+
seen.seq = ++this.seq;
|
|
94
80
|
return;
|
|
95
81
|
}
|
|
96
82
|
this.byKey.set(key, this.keep((seq) => ({ ...one, key, seq, count: 1, firstAt: at, lastAt: at })));
|
package/dist/rings.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rings.js","sourceRoot":"","sources":["../src/rings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAsC,MAAM,eAAe,CAAC;AAEvF,0EAA0E;AAC1E,MAAM,IAAI,GAAG,GAAG,CAAC;AAyCjB,6FAA6F;AAC7F,MAAM,IAAI;IAKqB,GAAG;IAJb,IAAI,GAAQ,EAAE,CAAC;IACxB,GAAG,GAAG,CAAC,CAAC;IACV,IAAI,GAAG,CAAC,CAAC;IAEjB,YAA6B,GAAG,GAAG,IAAI;mBAAV,GAAG;IAAU,CAAC;IAEjC,IAAI,CAAC,IAAwB;QACrC,MAAM,
|
|
1
|
+
{"version":3,"file":"rings.js","sourceRoot":"","sources":["../src/rings.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fougere/calls",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-alpha.0",
|
|
4
4
|
"description": "Optional call log for Fougere: a bounded ring of every dispatch, served as an rpc operation. Read it with `fougere devtools`.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fougere",
|
|
@@ -30,16 +30,16 @@
|
|
|
30
30
|
"src"
|
|
31
31
|
],
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@fougere/core": "^0.
|
|
34
|
-
"@fougere/adapter-sql": "^0.
|
|
33
|
+
"@fougere/core": "^0.8.0-alpha.0",
|
|
34
|
+
"@fougere/adapter-sql": "^0.8.0-alpha.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"vitest": "^4.1.0",
|
|
38
|
-
"@fougere/
|
|
39
|
-
"@fougere/
|
|
40
|
-
"@fougere/
|
|
41
|
-
"@fougere/
|
|
42
|
-
"@fougere/
|
|
38
|
+
"@fougere/adapter-sql": "0.8.0-alpha.0",
|
|
39
|
+
"@fougere/transport-http": "0.8.0-alpha.0",
|
|
40
|
+
"@fougere/container": "0.8.0-alpha.0",
|
|
41
|
+
"@fougere/schema": "0.8.0-alpha.0",
|
|
42
|
+
"@fougere/core": "0.8.0-alpha.0"
|
|
43
43
|
},
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"access": "public"
|
package/src/CallRing.ts
CHANGED
|
@@ -3,22 +3,15 @@ import { RPC_ENTITY, type CallPage, type CallRecord, type DispatchEvent } from '
|
|
|
3
3
|
/** The message and the code of a refusal, without the stack that carries a body. */
|
|
4
4
|
function refusalOf(error: unknown): CallRecord['refusal'] {
|
|
5
5
|
if (error === null || error === undefined) return { message: 'unknown refusal' };
|
|
6
|
-
const
|
|
6
|
+
const thrown = error as { code?: unknown; message?: unknown };
|
|
7
7
|
|
|
8
8
|
return {
|
|
9
|
-
...(typeof
|
|
10
|
-
message: typeof
|
|
9
|
+
...(typeof thrown.code === 'string' ? { code: thrown.code } : {}),
|
|
10
|
+
message: typeof thrown.message === 'string' ? thrown.message : String(error),
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* A bounded log of what this process dispatched.
|
|
16
|
-
*
|
|
17
|
-
* The five transitions of one call are folded into one record: `received` opens it,
|
|
18
|
-
* `resolved` names the route, `completed`/`failed` settle the verdict, `settled` closes
|
|
19
|
-
* the duration. They are matched by the identity of the `Call`, which is the same frozen
|
|
20
|
-
* object across all five.
|
|
21
|
-
*/
|
|
14
|
+
/** A bounded log of what this process dispatched. */
|
|
22
15
|
export class CallRing {
|
|
23
16
|
private readonly held: CallRecord[] = [];
|
|
24
17
|
private readonly open = new WeakMap<object, CallRecord>();
|
|
@@ -31,19 +24,7 @@ export class CallRing {
|
|
|
31
24
|
private readonly frondOf: (entity: string) => string | undefined = () => undefined,
|
|
32
25
|
) {}
|
|
33
26
|
|
|
34
|
-
/**
|
|
35
|
-
* KNOWN LIMIT — a call this process ORIGINATES carries no trace here.
|
|
36
|
-
*
|
|
37
|
-
* `trace()` writes the traceparent inside the middleware chain, so inside
|
|
38
|
-
* `route.execute(call)`; the observer saw the `Call` at `received`, and that object is
|
|
39
|
-
* frozen. An ARRIVING call is unaffected: its traceparent is already on the invocation
|
|
40
|
-
* the transport handed over, which is why a hosted frond shows traces and its consumer
|
|
41
|
-
* does not.
|
|
42
|
-
*
|
|
43
|
-
* Closing it means `DispatchEvent` carrying the invocation as it ENDED rather than as it
|
|
44
|
-
* arrived — a core change, deliberately not smuggled in here. Until then the Traces view
|
|
45
|
-
* speaks for the side that receives.
|
|
46
|
-
*/
|
|
27
|
+
/** KNOWN LIMIT — a call this process ORIGINATES carries no trace here. */
|
|
47
28
|
record(event: DispatchEvent): void {
|
|
48
29
|
// A reader reaches this ring through `rpc`, so recording that would make the panel
|
|
49
30
|
// watch itself — one reader, one line, forever.
|
|
@@ -94,13 +75,7 @@ export class CallRing {
|
|
|
94
75
|
if (this.held.length > this.max) this.lost += this.held.splice(0, this.held.length - this.max).length;
|
|
95
76
|
}
|
|
96
77
|
|
|
97
|
-
/**
|
|
98
|
-
* Be told when a record settles, and get the unsubscription back.
|
|
99
|
-
*
|
|
100
|
-
* A page that polls is a page that is always a little wrong; this is what lets a door
|
|
101
|
-
* push instead. Told at `settled` and not at `received`, because a record is only
|
|
102
|
-
* complete then — a reader would otherwise redraw the same line four times.
|
|
103
|
-
*/
|
|
78
|
+
/** Be told when a record settles, and get the unsubscription back. */
|
|
104
79
|
watch(told: (record: CallRecord) => void): () => void {
|
|
105
80
|
this.watching.add(told);
|
|
106
81
|
|
package/src/index.ts
CHANGED
|
@@ -15,22 +15,11 @@ export const CALLS_OP = 'calls';
|
|
|
15
15
|
export interface CallsOptions {
|
|
16
16
|
/** How many calls the ring keeps. Beyond it, the oldest are dropped and counted. */
|
|
17
17
|
max?: number;
|
|
18
|
-
/**
|
|
19
|
-
* Serve the page too, on its own loopback port. `true` takes a free one.
|
|
20
|
-
*
|
|
21
|
-
* Node only — it opens an `http` server, which workerd has not. Without it the extension
|
|
22
|
-
* stays universal and `fougere devtools` is the reader.
|
|
23
|
-
*/
|
|
18
|
+
/** Serve the page too, on its own loopback port. */
|
|
24
19
|
panel?: boolean | number | PanelOptions;
|
|
25
20
|
}
|
|
26
21
|
|
|
27
|
-
/**
|
|
28
|
-
* What this process serves, read from the app itself.
|
|
29
|
-
*
|
|
30
|
-
* Not a second source: `operationsFor` is the same effective model the façade consumes, so
|
|
31
|
-
* the page shows what will actually answer — including the ops nobody has called yet, which
|
|
32
|
-
* is what makes an empty panel useful instead of blank.
|
|
33
|
-
*/
|
|
22
|
+
/** What this process serves, read from the app itself. */
|
|
34
23
|
function servedModel(app: App): unknown {
|
|
35
24
|
return {
|
|
36
25
|
fronds: app.fronds.map((frond) => ({
|
|
@@ -80,20 +69,13 @@ function hostOf(address: string): string {
|
|
|
80
69
|
}
|
|
81
70
|
|
|
82
71
|
function nameOf(schema: unknown): string | null {
|
|
83
|
-
const
|
|
84
|
-
if (!
|
|
72
|
+
const named = schema as { getName?: () => string; name?: string } | undefined;
|
|
73
|
+
if (!named) return null;
|
|
85
74
|
|
|
86
|
-
return typeof
|
|
75
|
+
return typeof named.getName === 'function' ? named.getName() : named.name ?? null;
|
|
87
76
|
}
|
|
88
77
|
|
|
89
|
-
/**
|
|
90
|
-
* `address -> frond`, resolved once at boot: a call's address does not carry its frond.
|
|
91
|
-
*
|
|
92
|
-
* Indexed on HANDLERS and not on entities, because a frond may hold no entity at all —
|
|
93
|
-
* `demos/observability` has two — and then every one of its calls came back unnamed. The
|
|
94
|
-
* entities are indexed after, and only where a handler left the address free: an entity
|
|
95
|
-
* with no handler is served by `Crud`, which answers under the same address.
|
|
96
|
-
*/
|
|
78
|
+
/** `address -> frond`, resolved once at boot: a call's address does not carry its frond. */
|
|
97
79
|
function frondIndex(app: App): (address: string) => string | undefined {
|
|
98
80
|
const byAddress = new Map<string, string>();
|
|
99
81
|
for (const frond of app.fronds) {
|
|
@@ -104,12 +86,7 @@ function frondIndex(app: App): (address: string) => string | undefined {
|
|
|
104
86
|
return (address) => byAddress.get(address);
|
|
105
87
|
}
|
|
106
88
|
|
|
107
|
-
/**
|
|
108
|
-
* Subscribe to SQL when there is SQL. Absence is an answer, not a blank tab.
|
|
109
|
-
*
|
|
110
|
-
* `logQueries` is installed in every Kysely this process builds, so the subscription is all
|
|
111
|
-
* that is missing — and it is taken here, in `up(app)`, like the other two.
|
|
112
|
-
*/
|
|
89
|
+
/** Subscribe to SQL when there is SQL. */
|
|
113
90
|
async function queriesFrom(queries: QueryRing): Promise<() => void> {
|
|
114
91
|
try {
|
|
115
92
|
const { onQuery } = await import('@fougere/adapter-sql');
|
|
@@ -120,31 +97,15 @@ async function queriesFrom(queries: QueryRing): Promise<() => void> {
|
|
|
120
97
|
}
|
|
121
98
|
|
|
122
99
|
function cursorOf(invocation: InvocationContext): number {
|
|
123
|
-
const
|
|
124
|
-
const since = Number(
|
|
100
|
+
const input = invocation.input as { since?: unknown } | undefined;
|
|
101
|
+
const since = Number(input?.since ?? 0);
|
|
125
102
|
|
|
126
103
|
return Number.isFinite(since) && since > 0 ? since : 0;
|
|
127
104
|
}
|
|
128
105
|
|
|
129
|
-
/**
|
|
130
|
-
* What this process dispatched, kept in a bounded ring and served as an rpc operation.
|
|
131
|
-
*
|
|
132
|
-
* It watches rather than participates: `app.observe` is passive, `DispatchLifecycle`
|
|
133
|
-
* swallows an observer's own failure, and the ring holds no reference to a body. What it
|
|
134
|
-
* sees that a middleware cannot: a call refused BEFORE any handler — an unknown route, an
|
|
135
|
-
* entity hosted elsewhere, a call arriving while the door drains — and the route kind of
|
|
136
|
-
* every call, so a local execution and a hop to another process read the same way.
|
|
137
|
-
*
|
|
138
|
-
* The reader is `fougere devtools`, over `/_fougere/call` like any other consumer. No port
|
|
139
|
-
* is opened here, and an app that never installed this package answers
|
|
140
|
-
* `Unknown rpc operation 'calls'. It serves discover.`
|
|
141
|
-
*/
|
|
106
|
+
/** What this process dispatched, kept in a bounded ring and served as an rpc operation. */
|
|
142
107
|
export function calls(options: CallsOptions = {}): Extension {
|
|
143
|
-
/**
|
|
144
|
-
* Per APP, not per extension. A host declares its extensions once, so the same instance
|
|
145
|
-
* goes up on the new app before the old one is released — one shared ring would mix two
|
|
146
|
-
* processes' worth of calls, and the older `down` would erase the newer subscription.
|
|
147
|
-
*/
|
|
108
|
+
/** Per APP, not per extension. */
|
|
148
109
|
const stopping = new WeakMap<App, () => void | Promise<void>>();
|
|
149
110
|
|
|
150
111
|
return {
|
package/src/page.ts
CHANGED
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The panel, as one file with no dependency.
|
|
3
|
-
*
|
|
4
|
-
* Inline on purpose: this package ships no framework and no build step, and a dev tool that
|
|
5
|
-
* needs `npm install` before it shows anything is a dev tool nobody opens. The page reads
|
|
6
|
-
* its own origin — the door that served it also serves its events — so there is no address
|
|
7
|
-
* to configure and no CORS to arrange.
|
|
8
|
-
*
|
|
9
|
-
* FOUR views, and the number is a decision. Six grew one tab at a time, each with its own
|
|
10
|
-
* empty state and its own caveat, and the result read as noise rather than as an answer.
|
|
11
|
-
* They fold along what a reader actually asks:
|
|
12
|
-
*
|
|
13
|
-
* Calls what happened, and did it hold — the screen that IS the argument
|
|
14
|
-
* Refused what did not, from two sources, since one of them is never a call
|
|
15
|
-
* Activity what the process did along the way — logs and statements are one stream,
|
|
16
|
-
* both chronological, both uncorrelated to a call; two tabs said that twice
|
|
17
|
-
* Served what it would answer if asked, filled before anything is called
|
|
18
|
-
*
|
|
19
|
-
* Traces lost its tab: it spoke for the receiving side only, and a trace already shows in a
|
|
20
|
-
* call's detail. A tab that half-delivers is worse than a field that says what it knows.
|
|
21
|
-
*/
|
|
1
|
+
/** The panel, as one file with no dependency. */
|
|
22
2
|
export function page(title: string): string {
|
|
23
3
|
return `<!doctype html>
|
|
24
4
|
<html lang="en">
|
|
@@ -210,21 +190,21 @@ function pills(options, held, set) {
|
|
|
210
190
|
}
|
|
211
191
|
|
|
212
192
|
function calls() {
|
|
213
|
-
const
|
|
193
|
+
const shown = state.calls.filter((c) => state.filter === 'all' || routeOf(c) === state.filter);
|
|
214
194
|
const bar = pills(['all', 'local', 'remote', 'system'], state.filter, (v) => { state.filter = v; });
|
|
215
195
|
|
|
216
|
-
if (
|
|
196
|
+
if (shown.length === 0) {
|
|
217
197
|
return [bar, empty('Nothing dispatched yet — call an operation and it lands here.')];
|
|
218
198
|
}
|
|
219
199
|
|
|
220
200
|
// The recent window, not the session's high-water mark: one 4-second migration at boot
|
|
221
201
|
// and every bar after it was 2px for good.
|
|
222
|
-
const slowest = Math.max(1, ...
|
|
202
|
+
const slowest = Math.max(1, ...shown.slice(-100).map((c) => c.ms ?? 0));
|
|
223
203
|
|
|
224
204
|
// One frond, one word repeated 400 times, 112px stolen from what the eye scans.
|
|
225
205
|
const many = new Set(state.calls.map((c) => c.frond)).size > 1;
|
|
226
206
|
|
|
227
|
-
const rows =
|
|
207
|
+
const rows = shown.slice(-400).reverse().map((c) => {
|
|
228
208
|
const route = routeOf(c);
|
|
229
209
|
const width = c.ms ? Math.max(2, Math.round((c.ms / slowest) * 56)) : 0;
|
|
230
210
|
// Cleared as it is drawn: the flag marks the first render of a call, and a redraw is
|
|
@@ -355,16 +335,7 @@ function served() {
|
|
|
355
335
|
}
|
|
356
336
|
|
|
357
337
|
// ── Shape ────────────────────────────────────────────────────────────────────
|
|
358
|
-
/**
|
|
359
|
-
* What this process is arranged as — DECLARED and OBSERVED on one picture.
|
|
360
|
-
*
|
|
361
|
-
* Both halves are already on this page: the model says where a call is meant to go, the
|
|
362
|
-
* ring says where it went. Nobody else can draw the pair, because elsewhere the placement
|
|
363
|
-
* is not the developer's to declare — there is only ever one value, so there is nothing to
|
|
364
|
-
* disagree with.
|
|
365
|
-
*
|
|
366
|
-
* One process's view, and it says so: a frond hosted elsewhere is a node with no inside.
|
|
367
|
-
*/
|
|
338
|
+
/** What this process is arranged as — DECLARED and OBSERVED on one picture. */
|
|
368
339
|
function shape() {
|
|
369
340
|
if (!state.model || state.model.fronds.length === 0) return [empty('This process hosts no frond.')];
|
|
370
341
|
|
|
@@ -508,10 +479,10 @@ events.addEventListener('error', () => {
|
|
|
508
479
|
document.getElementById('dot').className = 'dot off';
|
|
509
480
|
});
|
|
510
481
|
events.addEventListener('hello', (m) => {
|
|
511
|
-
const
|
|
512
|
-
state.fronds =
|
|
482
|
+
const payload = JSON.parse(m.data);
|
|
483
|
+
state.fronds = payload.fronds ?? [];
|
|
513
484
|
document.getElementById('where').textContent = state.fronds.length ? ' · ' + state.fronds.join(', ') : '';
|
|
514
|
-
state.calls =
|
|
485
|
+
state.calls = payload.calls;
|
|
515
486
|
draw();
|
|
516
487
|
});
|
|
517
488
|
events.addEventListener('call', (m) => {
|
package/src/panel.ts
CHANGED
|
@@ -25,15 +25,7 @@ export interface PanelOptions {
|
|
|
25
25
|
/** How often the page is told how many calls are in flight. */
|
|
26
26
|
const BEAT_MS = 1000;
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
* The panel's own door: the page, and the events that fill it.
|
|
30
|
-
*
|
|
31
|
-
* Its own server and not the app's, for one measured reason: `serve()` from
|
|
32
|
-
* `@fougere/transport-http` answers 404 to everything that is not `POST /_fougere/call`,
|
|
33
|
-
* which is what makes it safe to expose — a page cannot enter it without changing what it
|
|
34
|
-
* is. So the panel binds 127.0.0.1 only, and serves both halves itself, which also means
|
|
35
|
-
* the page reads its own origin and no CORS has to be arranged.
|
|
36
|
-
*/
|
|
28
|
+
/** Serves the panel and returns how to close it. */
|
|
37
29
|
export function servePanel(ring: CallRing, options: PanelOptions = {}): Promise<() => Promise<void>> {
|
|
38
30
|
const title = options.title ?? 'fougere';
|
|
39
31
|
const readers = new Set<{ write: (chunk: string) => void; end: () => void }>();
|
|
@@ -64,8 +56,8 @@ export function servePanel(ring: CallRing, options: PanelOptions = {}): Promise<
|
|
|
64
56
|
});
|
|
65
57
|
// The page is handed the ring as it stands, so a reader that opens late is not
|
|
66
58
|
// looking at an empty screen that suggests a quiet app.
|
|
67
|
-
const
|
|
68
|
-
response.write(`event: hello\ndata: ${JSON.stringify({ calls:
|
|
59
|
+
const backlog = ring.since(0);
|
|
60
|
+
response.write(`event: hello\ndata: ${JSON.stringify({ calls: backlog.calls, fronds: options.fronds ?? [] })}\n\n`);
|
|
69
61
|
readers.add(response);
|
|
70
62
|
request.on('close', () => readers.delete(response));
|
|
71
63
|
return;
|
|
@@ -105,7 +97,7 @@ export function servePanel(ring: CallRing, options: PanelOptions = {}): Promise<
|
|
|
105
97
|
|
|
106
98
|
return new Promise((ready, refuse) => {
|
|
107
99
|
server.on('error', refuse);
|
|
108
|
-
// Loopback only. This door has no
|
|
100
|
+
// Loopback only. This door has no validator — `serve()` refuses to start beyond loopback
|
|
109
101
|
// without `verify`, and this one would not know how to refuse at all.
|
|
110
102
|
server.listen(options.port ?? 0, '127.0.0.1', () => {
|
|
111
103
|
const port = (server.address() as { port: number }).port;
|
package/src/rings.ts
CHANGED
|
@@ -51,11 +51,11 @@ class Ring<T extends { seq: number }> {
|
|
|
51
51
|
constructor(private readonly max = KEPT) {}
|
|
52
52
|
|
|
53
53
|
protected keep(make: (seq: number) => T): T {
|
|
54
|
-
const
|
|
55
|
-
this.held.push(
|
|
54
|
+
const record = make(++this.seq);
|
|
55
|
+
this.held.push(record);
|
|
56
56
|
if (this.held.length > this.max) this.lost += this.held.splice(0, this.held.length - this.max).length;
|
|
57
57
|
|
|
58
|
-
return
|
|
58
|
+
return record;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
since(cursor: number): { lines: T[]; cursor: number; dropped: number } {
|
|
@@ -63,14 +63,7 @@ class Ring<T extends { seq: number }> {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/**
|
|
67
|
-
* What this process logged.
|
|
68
|
-
*
|
|
69
|
-
* Chronological and nothing more: correlating a line to its call needs an async context,
|
|
70
|
-
* which core's `Ambient` port does not provide (it answers about frames and emission
|
|
71
|
-
* chains) and only `@fougere/observability` has. Aligning by timestamp would be a guess
|
|
72
|
-
* dressed as a fact — the failure mode of every panel that lies.
|
|
73
|
-
*/
|
|
66
|
+
/** What this process logged. */
|
|
74
67
|
export class LogRing extends Ring<LogLine> {
|
|
75
68
|
record(line: LogRecord): void {
|
|
76
69
|
this.keep((seq) => ({
|
|
@@ -90,14 +83,7 @@ export class QueryRing extends Ring<QueryLine> {
|
|
|
90
83
|
}
|
|
91
84
|
}
|
|
92
85
|
|
|
93
|
-
/**
|
|
94
|
-
* What was refused, from TWO sources — and that is the point.
|
|
95
|
-
*
|
|
96
|
-
* Fed by the call flow alone, this screen would miss exactly the failures that matter: a
|
|
97
|
-
* storage that would not open, an export that could not be sent, an extension that fell
|
|
98
|
-
* over. None of those is a dispatch. It is the blind spot Symfony's Headers panel and
|
|
99
|
-
* Django's History panel both have, and the only cure is a second source.
|
|
100
|
-
*/
|
|
86
|
+
/** What was refused, from TWO sources — and that is the point. */
|
|
101
87
|
export class ErrorRing extends Ring<ErrorGroup> {
|
|
102
88
|
private readonly byKey = new Map<string, ErrorGroup>();
|
|
103
89
|
|
|
@@ -125,20 +111,20 @@ export class ErrorRing extends Ring<ErrorGroup> {
|
|
|
125
111
|
|
|
126
112
|
private group(one: Omit<ErrorGroup, 'seq' | 'key' | 'count' | 'firstAt' | 'lastAt'>): void {
|
|
127
113
|
const key = [one.from, one.code, one.entity ?? '', one.operation ?? ''].join(' ');
|
|
128
|
-
const
|
|
114
|
+
const seen = this.byKey.get(key);
|
|
129
115
|
const at = Date.now();
|
|
130
116
|
|
|
131
117
|
// Grouped, because a refusal seen forty times is one line with a count — not forty
|
|
132
118
|
// lines that push everything else out of a bounded ring.
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (one.fields.length > 0)
|
|
119
|
+
if (seen) {
|
|
120
|
+
seen.count += 1;
|
|
121
|
+
seen.lastAt = at;
|
|
122
|
+
seen.message = one.message;
|
|
123
|
+
if (one.fields.length > 0) seen.fields = one.fields;
|
|
138
124
|
// Re-numbered so a reader that already saw this group is handed the higher count.
|
|
139
125
|
// Without it a refusal seen forty times reports one, forever: the group is mutated
|
|
140
126
|
// in place, and a cursor asks only for what is above it.
|
|
141
|
-
|
|
127
|
+
seen.seq = ++this.seq;
|
|
142
128
|
return;
|
|
143
129
|
}
|
|
144
130
|
|