@hydranium/glsp-client-theia 1.0.0-next.32 → 1.0.0-next.321

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.
Files changed (87) hide show
  1. package/README.md +60 -98
  2. package/lib/browser/client-contribution.d.ts +87 -25
  3. package/lib/browser/client-contribution.d.ts.map +1 -1
  4. package/lib/browser/client-contribution.js +233 -53
  5. package/lib/browser/client-contribution.js.map +1 -1
  6. package/lib/browser/diagram-only-marker-manager.d.ts.map +1 -1
  7. package/lib/browser/diagram-only-marker-manager.js.map +1 -1
  8. package/lib/browser/diagram-widget.d.ts +43 -25
  9. package/lib/browser/diagram-widget.d.ts.map +1 -1
  10. package/lib/browser/diagram-widget.js +76 -26
  11. package/lib/browser/diagram-widget.js.map +1 -1
  12. package/lib/browser/glsp-client-theia-module.d.ts +12 -5
  13. package/lib/browser/glsp-client-theia-module.d.ts.map +1 -1
  14. package/lib/browser/glsp-client-theia-module.js +27 -12
  15. package/lib/browser/glsp-client-theia-module.js.map +1 -1
  16. package/lib/browser/glsp-diagram-manager.d.ts +40 -1
  17. package/lib/browser/glsp-diagram-manager.d.ts.map +1 -1
  18. package/lib/browser/glsp-diagram-manager.js +100 -0
  19. package/lib/browser/glsp-diagram-manager.js.map +1 -1
  20. package/lib/browser/glsp-message-service.d.ts.map +1 -1
  21. package/lib/browser/glsp-message-service.js.map +1 -1
  22. package/lib/browser/glsp-saveable.d.ts +67 -0
  23. package/lib/browser/glsp-saveable.d.ts.map +1 -0
  24. package/lib/browser/glsp-saveable.js +110 -0
  25. package/lib/browser/glsp-saveable.js.map +1 -0
  26. package/lib/browser/glsp-theia-frontend-module.d.ts.map +1 -1
  27. package/lib/browser/glsp-theia-frontend-module.js +4 -1
  28. package/lib/browser/glsp-theia-frontend-module.js.map +1 -1
  29. package/lib/browser/hydranium-glsp-diagram-configuration.d.ts +3 -1
  30. package/lib/browser/hydranium-glsp-diagram-configuration.d.ts.map +1 -1
  31. package/lib/browser/hydranium-glsp-diagram-configuration.js +3 -1
  32. package/lib/browser/hydranium-glsp-diagram-configuration.js.map +1 -1
  33. package/lib/browser/index.d.ts +4 -3
  34. package/lib/browser/index.d.ts.map +1 -1
  35. package/lib/browser/index.js +7 -5
  36. package/lib/browser/index.js.map +1 -1
  37. package/lib/browser/theia-diagram-loader.d.ts +25 -0
  38. package/lib/browser/theia-diagram-loader.d.ts.map +1 -0
  39. package/lib/browser/theia-diagram-loader.js +42 -0
  40. package/lib/browser/theia-diagram-loader.js.map +1 -0
  41. package/lib/browser/window-session.d.ts +57 -0
  42. package/lib/browser/window-session.d.ts.map +1 -0
  43. package/lib/browser/window-session.js +102 -0
  44. package/lib/browser/window-session.js.map +1 -0
  45. package/lib/node/connection-container-module.js.map +1 -1
  46. package/lib/node/glsp-server-connection-handler.d.ts +11 -20
  47. package/lib/node/glsp-server-connection-handler.d.ts.map +1 -1
  48. package/lib/node/glsp-server-connection-handler.js +12 -15
  49. package/lib/node/glsp-server-connection-handler.js.map +1 -1
  50. package/package.json +37 -46
  51. package/src/browser/client-contribution.ts +254 -55
  52. package/src/browser/diagram-widget.ts +80 -28
  53. package/src/browser/glsp-client-theia-module.ts +34 -13
  54. package/src/browser/glsp-diagram-manager.ts +112 -3
  55. package/src/browser/glsp-saveable.ts +111 -0
  56. package/src/browser/glsp-theia-frontend-module.ts +4 -1
  57. package/src/browser/hydranium-glsp-diagram-configuration.ts +3 -1
  58. package/src/browser/index.ts +12 -5
  59. package/src/browser/theia-diagram-loader.ts +31 -0
  60. package/src/browser/window-session.ts +114 -0
  61. package/src/node/glsp-server-connection-handler.ts +12 -24
  62. package/style/diagram-loading.css +2 -5
  63. package/lib/browser/action-dispatcher.d.ts +0 -85
  64. package/lib/browser/action-dispatcher.d.ts.map +0 -1
  65. package/lib/browser/action-dispatcher.js +0 -238
  66. package/lib/browser/action-dispatcher.js.map +0 -1
  67. package/lib/browser/diagram-loader.d.ts +0 -123
  68. package/lib/browser/diagram-loader.d.ts.map +0 -1
  69. package/lib/browser/diagram-loader.js +0 -153
  70. package/lib/browser/diagram-loader.js.map +0 -1
  71. package/lib/browser/hidden-bounds-updater.d.ts +0 -122
  72. package/lib/browser/hidden-bounds-updater.d.ts.map +0 -1
  73. package/lib/browser/hidden-bounds-updater.js +0 -207
  74. package/lib/browser/hidden-bounds-updater.js.map +0 -1
  75. package/lib/testing/bind-recorder.d.ts +0 -67
  76. package/lib/testing/bind-recorder.d.ts.map +0 -1
  77. package/lib/testing/bind-recorder.js +0 -72
  78. package/lib/testing/bind-recorder.js.map +0 -1
  79. package/lib/testing/index.d.ts +0 -10
  80. package/lib/testing/index.d.ts.map +0 -1
  81. package/lib/testing/index.js +0 -30
  82. package/lib/testing/index.js.map +0 -1
  83. package/src/browser/action-dispatcher.ts +0 -257
  84. package/src/browser/diagram-loader.ts +0 -173
  85. package/src/browser/hidden-bounds-updater.ts +0 -212
  86. package/src/testing/bind-recorder.ts +0 -126
  87. package/src/testing/index.ts +0 -15
@@ -1,67 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the MIT License which is available in the project root.
6
- *
7
- * SPDX-License-Identifier: MIT
8
- ********************************************************************************/
9
- import type { interfaces } from 'inversify';
10
- /**
11
- * One captured `bind(...)` or `rebind(...)` call on a {@link BindRecorder}.
12
- *
13
- * `chain` holds the follow-up methods in invocation order, one string per call,
14
- * rendered as `name(arg, …)`. Arguments are stringified inline so assertions
15
- * can compare structure without re-resolving service identifiers.
16
- */
17
- export interface BindRecorderEntry {
18
- readonly kind: 'bind' | 'rebind';
19
- readonly token: unknown;
20
- readonly chain: string[];
21
- }
22
- /**
23
- * A spy set of Inversify binding hooks that records every call and its chain.
24
- * Hand them to a module-factory under test — they satisfy Inversify's
25
- * `interfaces.Bind` / `interfaces.Rebind` / `interfaces.Unbind` /
26
- * `interfaces.IsBound` shapes at the call site but never resolve real services.
27
- *
28
- * Together they match GLSP's `BindingContext`, so a factory taking the whole
29
- * context is called as `factory(recorder, options)` — the recorder *is*
30
- * structurally a context.
31
- *
32
- * Use {@link captured} to assert which tokens were touched and with what
33
- * fluent chain. {@link find} is a small ergonomic helper for the common
34
- * lookup pattern.
35
- */
36
- export interface BindRecorder {
37
- readonly bind: interfaces.Bind;
38
- readonly rebind: interfaces.Rebind;
39
- readonly unbind: interfaces.Unbind;
40
- readonly isBound: interfaces.IsBound;
41
- readonly captured: readonly BindRecorderEntry[];
42
- /** Convenience lookup — returns the first entry matching both `kind` and `token`, or `undefined`. */
43
- find(kind: 'bind' | 'rebind', token: unknown): BindRecorderEntry | undefined;
44
- }
45
- /** Options for {@link makeBindRecorder}. */
46
- export interface BindRecorderOptions {
47
- /**
48
- * What `isBound` answers. Defaults to `true` for every token, matching
49
- * production: a head registers its own module *after* the GLSP default
50
- * modules, so the tokens a framework module rebinds are already bound. Pass a
51
- * list to model a container where only those tokens are bound — e.g. to
52
- * exercise a factory's bind-instead-of-rebind fallback.
53
- */
54
- readonly boundTokens?: readonly unknown[];
55
- }
56
- /**
57
- * Build a {@link BindRecorder} for testing Inversify module factories without a
58
- * real container: hand it to the factory, then assert over {@link BindRecorder.find}.
59
- *
60
- * The `bind` / `rebind` functions return a chained proxy that ignores its
61
- * arguments structurally but records every method invocation as a string.
62
- * Stringification: functions → `<className>`, symbols → `Symbol(desc).toString()`,
63
- * everything else → `String(value)`. This is enough to assert which tokens are
64
- * involved without dragging real service resolution into the test.
65
- */
66
- export declare function makeBindRecorder(options?: BindRecorderOptions): BindRecorder;
67
- //# sourceMappingURL=bind-recorder.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bind-recorder.d.ts","sourceRoot":"","sources":["../../src/testing/bind-recorder.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAChD,qGAAqG;IACrG,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,KAAK,EAAE,OAAO,GAAG,iBAAiB,GAAG,SAAS,CAAC;CAC/E;AAED,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC5C;AAuBD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,mBAAwB,GAAG,YAAY,CAkChF"}
@@ -1,72 +0,0 @@
1
- "use strict";
2
- /********************************************************************************
3
- * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
4
- *
5
- * This program and the accompanying materials are made available under the
6
- * terms of the MIT License which is available in the project root.
7
- *
8
- * SPDX-License-Identifier: MIT
9
- ********************************************************************************/
10
- Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.makeBindRecorder = makeBindRecorder;
12
- /** Methods on Inversify's binding builder that the recorder traps verbatim. */
13
- const RECORDED_CHAIN_METHODS = [
14
- 'toSelf',
15
- 'to',
16
- 'toService',
17
- 'toConstantValue',
18
- 'toDynamicValue',
19
- 'toFactory',
20
- 'toFunction',
21
- 'toAutoFactory',
22
- 'toProvider',
23
- 'inSingletonScope',
24
- 'inTransientScope',
25
- 'inRequestScope',
26
- 'whenTargetNamed',
27
- 'whenTargetTagged',
28
- 'whenInjectedInto',
29
- 'onActivation',
30
- 'onDeactivation'
31
- ];
32
- /**
33
- * Build a {@link BindRecorder} for testing Inversify module factories without a
34
- * real container: hand it to the factory, then assert over {@link BindRecorder.find}.
35
- *
36
- * The `bind` / `rebind` functions return a chained proxy that ignores its
37
- * arguments structurally but records every method invocation as a string.
38
- * Stringification: functions → `<className>`, symbols → `Symbol(desc).toString()`,
39
- * everything else → `String(value)`. This is enough to assert which tokens are
40
- * involved without dragging real service resolution into the test.
41
- */
42
- function makeBindRecorder(options = {}) {
43
- const captured = [];
44
- const stringifyArg = (value) => {
45
- if (typeof value === 'function') {
46
- return `<${value.name || 'fn'}>`;
47
- }
48
- if (typeof value === 'symbol') {
49
- return value.toString();
50
- }
51
- return String(value);
52
- };
53
- const beginEntry = (kind, token) => {
54
- const entry = { kind, token, chain: [] };
55
- captured.push(entry);
56
- const proxy = {};
57
- for (const method of RECORDED_CHAIN_METHODS) {
58
- proxy[method] = (...args) => {
59
- entry.chain.push(`${method}(${args.map(stringifyArg).join(', ')})`);
60
- return proxy;
61
- };
62
- }
63
- return proxy;
64
- };
65
- const bind = ((token) => beginEntry('bind', token));
66
- const rebind = ((token) => beginEntry('rebind', token));
67
- const unbind = (() => undefined);
68
- const isBound = ((token) => options.boundTokens?.includes(token) ?? true);
69
- const find = (kind, token) => captured.find(entry => entry.kind === kind && entry.token === token);
70
- return { bind, rebind, unbind, isBound, captured, find };
71
- }
72
- //# sourceMappingURL=bind-recorder.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bind-recorder.js","sourceRoot":"","sources":["../../src/testing/bind-recorder.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;AAoFlF,4CAkCC;AAjED,+EAA+E;AAC/E,MAAM,sBAAsB,GAAG;IAC5B,QAAQ;IACR,IAAI;IACJ,WAAW;IACX,iBAAiB;IACjB,gBAAgB;IAChB,WAAW;IACX,YAAY;IACZ,eAAe;IACf,YAAY;IACZ,kBAAkB;IAClB,kBAAkB;IAClB,gBAAgB;IAChB,iBAAiB;IACjB,kBAAkB;IAClB,kBAAkB;IAClB,cAAc;IACd,gBAAgB;CACT,CAAC;AAEX;;;;;;;;;GASG;AACH,SAAgB,gBAAgB,CAAC,UAA+B,EAAE;IAC/D,MAAM,QAAQ,GAAwB,EAAE,CAAC;IAEzC,MAAM,YAAY,GAAG,CAAC,KAAc,EAAU,EAAE;QAC7C,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;YAC/B,OAAO,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,GAAG,CAAC;QACpC,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC3B,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,CAAC,IAAuB,EAAE,KAAc,EAAW,EAAE;QACrE,MAAM,KAAK,GAAsB,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QAC5D,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,MAAM,KAAK,GAAoD,EAAE,CAAC;QAClE,KAAK,MAAM,MAAM,IAAI,sBAAsB,EAAE,CAAC;YAC3C,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAe,EAAW,EAAE;gBAC7C,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACpE,OAAO,KAAK,CAAC;YAChB,CAAC,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC;IAChB,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAoB,CAAC;IAChF,MAAM,MAAM,GAAG,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAsB,CAAC;IACtF,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,SAAS,CAAiC,CAAC;IACjE,MAAM,OAAO,GAAG,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAuB,CAAC;IACzG,MAAM,IAAI,GAAG,CAAC,IAAuB,EAAE,KAAc,EAAiC,EAAE,CACrF,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;IAExE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC5D,CAAC"}
@@ -1,10 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the MIT License which is available in the project root.
6
- *
7
- * SPDX-License-Identifier: MIT
8
- ********************************************************************************/
9
- export * from './bind-recorder';
10
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAOlF,cAAc,iBAAiB,CAAC"}
@@ -1,30 +0,0 @@
1
- "use strict";
2
- /********************************************************************************
3
- * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
4
- *
5
- * This program and the accompanying materials are made available under the
6
- * terms of the MIT License which is available in the project root.
7
- *
8
- * SPDX-License-Identifier: MIT
9
- ********************************************************************************/
10
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- var desc = Object.getOwnPropertyDescriptor(m, k);
13
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
- desc = { enumerable: true, get: function() { return m[k]; } };
15
- }
16
- Object.defineProperty(o, k2, desc);
17
- }) : (function(o, m, k, k2) {
18
- if (k2 === undefined) k2 = k;
19
- o[k2] = m[k];
20
- }));
21
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
22
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
23
- };
24
- Object.defineProperty(exports, "__esModule", { value: true });
25
- // Subpath barrel for `@hydranium/glsp-client-theia/testing` — the
26
- // GLSP-module-specific Inversify test double. The cross-head doubles
27
- // (`makeStubOutputChannelManager`, `makeStubInversifyContext`) live in
28
- // `@hydranium/client-theia/testing`, alongside the logger they double for.
29
- __exportStar(require("./bind-recorder"), exports);
30
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;;;;;AAElF,kEAAkE;AAClE,qEAAqE;AACrE,uEAAuE;AACvE,2EAA2E;AAE3E,kDAAgC"}
@@ -1,257 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the MIT License which is available in the project root.
6
- *
7
- * SPDX-License-Identifier: MIT
8
- ********************************************************************************/
9
-
10
- import {
11
- type Action,
12
- ComputedBoundsAction,
13
- GLSPActionDispatcher,
14
- GLSPModelSource,
15
- LocalComputedBoundsAction,
16
- MessageAction,
17
- RejectAction,
18
- RequestAction,
19
- RequestBoundsAction,
20
- RequestContextActions,
21
- RequestModelAction,
22
- RequestTypeHintsAction,
23
- ResponseAction,
24
- ServerAction,
25
- SetDirtyStateAction,
26
- SetEditModeAction,
27
- SetMarkersAction,
28
- SetModelAction,
29
- SetTypeHintsAction,
30
- StatusAction,
31
- UpdateModelAction
32
- } from '@eclipse-glsp/client';
33
- import { ChannelLogger, ChannelTracer } from '@hydranium/client-theia/lib/browser';
34
- import { type Tracer } from '@hydranium/protocol';
35
- import { inject, injectable, unmanaged } from '@theia/core/shared/inversify';
36
-
37
- /** Default subset of action kinds the framework dispatcher will log: the GLSP
38
- * traffic an observer reading the Output channel typically wants to see. All
39
- * are standard `@eclipse-glsp/client` kinds; adopters with custom action kinds
40
- * extend via `extraLoggedKinds`. */
41
- export const HYDRANIUM_DEFAULT_LOGGED_KINDS: ReadonlySet<string> = new Set<string>([
42
- SetModelAction.KIND,
43
- UpdateModelAction.KIND,
44
- RequestBoundsAction.KIND,
45
- ComputedBoundsAction.KIND,
46
- SetDirtyStateAction.KIND,
47
- SetEditModeAction.KIND,
48
- SetMarkersAction.KIND,
49
- SetTypeHintsAction.KIND,
50
- StatusAction.KIND,
51
- MessageAction.KIND,
52
- RejectAction.KIND,
53
- RequestContextActions.KIND,
54
- RequestModelAction.KIND,
55
- RequestTypeHintsAction.KIND
56
- ]);
57
-
58
- /** Default request → response kind pairs for GLSP 'flow' actions that do NOT
59
- * carry a requestId/responseId — so the dispatcher matches by kind (FIFO).
60
- * Id-based matching still wins when a requestId is present. */
61
- export const HYDRANIUM_DEFAULT_KIND_PAIRS: ReadonlyMap<string, string> = new Map<string, string>([
62
- [RequestModelAction.KIND, SetModelAction.KIND],
63
- [RequestTypeHintsAction.KIND, SetTypeHintsAction.KIND],
64
- [RequestBoundsAction.KIND, ComputedBoundsAction.KIND]
65
- ]);
66
-
67
- /** Additive options. Adopters with custom action kinds pass extras here; the
68
- * framework defaults always apply (you cannot un-log a kind the framework
69
- * considers worth logging — subclass + override `dispatch` if you must). */
70
- export interface HydraniumGlspActionDispatcherOptions {
71
- readonly extraLoggedKinds?: Iterable<string>;
72
- readonly extraKindPairs?: Iterable<readonly [string, string]>;
73
- /** Replaces the default `summarize` body entirely. Use for adopter-domain
74
- * action kinds where the framework default returns ''. */
75
- readonly summarize?: (action: Action) => string;
76
- }
77
-
78
- /**
79
- * Wraps every dispatch so action traffic appears in the framework Output
80
- * channel, interleaved with server log lines. Instrumenting the dispatcher
81
- * (rather than per-kind handlers) catches ResponseActions that bypass the
82
- * handler pipeline.
83
- *
84
- * The framework defaults cover the standard GLSP kinds worth logging, not every
85
- * kind the protocol defines. Adopters with custom kinds either pass
86
- * `extraLoggedKinds` / `extraKindPairs` to the constructor (via a
87
- * `toDynamicValue` binding) or subclass and override.
88
- */
89
- @injectable()
90
- export class HydraniumGlspActionDispatcher extends GLSPActionDispatcher {
91
- @inject(ChannelLogger) protected readonly channel!: ChannelLogger;
92
- @inject(ChannelTracer) protected readonly tracer!: Tracer;
93
-
94
- protected readonly loggedKinds: ReadonlySet<string>;
95
- protected readonly kindPairs: ReadonlyMap<string, string>;
96
- protected readonly kindPairsReverse: ReadonlyMap<string, string>;
97
-
98
- protected pairCounter = 0;
99
- /** Pending requests keyed by requestId (for actions that carry one). */
100
- protected pendingById = new Map<string, { id: number; start: number }>();
101
- /** FIFO queue per response kind, used for `flow` pairs without requestId/responseId. */
102
- protected pendingByKind = new Map<string, Array<{ id: number; start: number }>>();
103
-
104
- constructor(@unmanaged() options?: HydraniumGlspActionDispatcherOptions) {
105
- super();
106
- const extraLogged = options?.extraLoggedKinds ? Array.from(options.extraLoggedKinds) : [];
107
- this.loggedKinds = new Set<string>([...HYDRANIUM_DEFAULT_LOGGED_KINDS, ...extraLogged]);
108
- const extraPairs = options?.extraKindPairs ? Array.from(options.extraKindPairs) : [];
109
- const pairs = new Map<string, string>([...HYDRANIUM_DEFAULT_KIND_PAIRS, ...extraPairs]);
110
- this.kindPairs = pairs;
111
- this.kindPairsReverse = new Map<string, string>(Array.from(pairs, ([req, res]) => [res, req]));
112
- if (options?.summarize) {
113
- this.summarize = options.summarize;
114
- }
115
- }
116
-
117
- override initialize(): Promise<void> {
118
- // `initialize()` is called many times; super is idempotent, but our callback registration
119
- // isn't. Register the timing log only on the first call (when super.initialized is unset).
120
- if (!this.initialized) {
121
- this.registerInitTiming();
122
- }
123
- return super.initialize();
124
- }
125
-
126
- /**
127
- * Register the one-shot 'model initialized' timing line. Instrumentation
128
- * only — guarded so a tracer failure degrades logging rather than aborting
129
- * diagram init. `initialize()` runs inside `DiagramLoader.load()`, whose
130
- * caller (`GLSPDiagramWidget.onAfterAttach`) neither awaits nor catches the
131
- * returned promise; an error thrown here would therefore escape as an
132
- * uncaught rejection that blanks the diagram with no server-side signal.
133
- */
134
- protected registerInitTiming(): void {
135
- try {
136
- this.tracer.time('Model initialized', () => this.onceModelInitialized(), 'info', { logAfterMs: 0 });
137
- } catch (err) {
138
- this.channel.error('Failed to register model-initialized timing instrumentation', err);
139
- }
140
- }
141
-
142
- override dispatch(action: Action): Promise<void> {
143
- if (this.loggedKinds.has(action.kind)) {
144
- this.logAction(action);
145
- }
146
- return super.dispatch(action);
147
- }
148
-
149
- protected logAction(action: Action): void {
150
- const { verb, direction } = this.classify(action);
151
- const summary = this.summarize(action);
152
- const timing = this.trackPair(action);
153
- const parts = [`${verb} action '${action.kind}'`, `[${direction}]`];
154
- if (summary) {
155
- parts.push(`[${summary}]`);
156
- }
157
- if (timing) {
158
- parts.push(timing);
159
- }
160
- this.channel.trace(parts.join(' '));
161
- }
162
-
163
- protected classify(action: Action): { verb: string; direction: string } {
164
- // GLSP marks client-produced computedBounds as ServerAction to suppress forwarding; stays local.
165
- if (LocalComputedBoundsAction.is(action)) {
166
- return { verb: 'Process', direction: 'client → client' };
167
- }
168
- // Marked by GLSPModelSource.messageReceived — arrived from the server.
169
- if (ServerAction.is(action)) {
170
- return { verb: 'Receive', direction: 'server → client' };
171
- }
172
- // GLSPModelSource is registered as handler for every server-handled kind (configureServeActions),
173
- // so its presence means the action will be forwarded to the server.
174
- const handlers = this.actionHandlerRegistry.get(action.kind);
175
- if (handlers.some(handler => handler instanceof GLSPModelSource)) {
176
- return { verb: 'Send', direction: 'client → server' };
177
- }
178
- return { verb: 'Process', direction: 'client → client' };
179
- }
180
-
181
- /** Emit `[request #N]` on the request side, `[response #N, Xms]` on the matching response side.
182
- * Match first by requestId/responseId, then by known kind pairs (FIFO). */
183
- protected trackPair(action: Action): string {
184
- // Response side.
185
- if (ResponseAction.hasValidResponseId(action)) {
186
- const entry = this.pendingById.get(action.responseId);
187
- if (entry) {
188
- this.pendingById.delete(action.responseId);
189
- return this.formatResponse(entry);
190
- }
191
- }
192
- if (this.kindPairsReverse.has(action.kind)) {
193
- const queue = this.pendingByKind.get(action.kind);
194
- const entry = queue?.shift();
195
- if (entry) {
196
- return this.formatResponse(entry);
197
- }
198
- }
199
- // Request side.
200
- if (RequestAction.is(action) && action.requestId) {
201
- const entry = { id: ++this.pairCounter, start: performance.now() };
202
- this.pendingById.set(action.requestId, entry);
203
- return `[request #${entry.id}]`;
204
- }
205
- if (this.kindPairs.has(action.kind)) {
206
- const responseKind = this.kindPairs.get(action.kind)!;
207
- const entry = { id: ++this.pairCounter, start: performance.now() };
208
- const queue = this.pendingByKind.get(responseKind) ?? [];
209
- queue.push(entry);
210
- this.pendingByKind.set(responseKind, queue);
211
- return `[request #${entry.id}]`;
212
- }
213
- return '';
214
- }
215
-
216
- protected formatResponse(entry: { id: number; start: number }): string {
217
- const elapsed = Math.round(performance.now() - entry.start);
218
- return `[response #${entry.id}, ${elapsed}ms]`;
219
- }
220
-
221
- protected summarize(action: Action): string {
222
- if (SetModelAction.is(action) || UpdateModelAction.is(action)) {
223
- const root = action.newRoot;
224
- return `rootType=${root?.type ?? 'none'} children=${root?.children?.length ?? 0}`;
225
- }
226
- if (RequestBoundsAction.is(action)) {
227
- return `rootType=${action.newRoot.type} children=${action.newRoot.children?.length ?? 0}`;
228
- }
229
- if (ComputedBoundsAction.is(action)) {
230
- return `bounds=${action.bounds.length}`;
231
- }
232
- if (SetDirtyStateAction.is(action)) {
233
- return `isDirty=${action.isDirty} reason=${action.reason ?? 'n/a'}`;
234
- }
235
- if (SetEditModeAction.is(action)) {
236
- return `editMode=${action.editMode}`;
237
- }
238
- if (SetMarkersAction.is(action)) {
239
- return `markers=${action.markers.length}`;
240
- }
241
- if (StatusAction.is(action) || MessageAction.is(action)) {
242
- // severity=NONE with empty message is the conventional 'clear' signal.
243
- const clear = action.severity === 'NONE' && !action.message ? ' (clear)' : '';
244
- return `severity=${action.severity} message="${action.message.slice(0, 160)}"${clear}`;
245
- }
246
- if (RejectAction.is(action)) {
247
- return `reason="${action.message.slice(0, 160)}"`;
248
- }
249
- if (SetTypeHintsAction.is(action)) {
250
- return `shapes=${action.shapeHints.length} edges=${action.edgeHints.length}`;
251
- }
252
- if (RequestContextActions.is(action)) {
253
- return `contextId=${action.contextId}`;
254
- }
255
- return '';
256
- }
257
- }
@@ -1,173 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the MIT License which is available in the project root.
6
- *
7
- * SPDX-License-Identifier: MIT
8
- ********************************************************************************/
9
-
10
- import { DiagramLoader, type DiagramLoadingOptions, StatusAction } from '@eclipse-glsp/client';
11
- import { ChannelLogger } from '@hydranium/client-theia/lib/browser';
12
- import { Deferred } from '@hydranium/protocol';
13
- import { inject, injectable } from '@theia/core/shared/inversify';
14
-
15
- /**
16
- * How a diagram load ended. A discriminated union rather than a boolean or a
17
- * string: a consumer showing a pending indicator has to distinguish "not settled
18
- * yet" (`undefined`) from either terminal state, and the failed arm has to carry
19
- * enough for that consumer to decide whether *it* is now the only thing that can
20
- * report the failure — see {@link DiagramLoadFailure.surfaced}.
21
- */
22
- export type DiagramLoadOutcome = DiagramLoadSuccess | DiagramLoadFailure;
23
-
24
- export interface DiagramLoadSuccess {
25
- readonly status: 'loaded';
26
- }
27
-
28
- export interface DiagramLoadFailure {
29
- readonly status: 'failed';
30
- /** The thrown value, verbatim. `unknown` because a rejection can be anything. */
31
- readonly error: unknown;
32
- /**
33
- * Whether the `severity: 'ERROR'` {@link StatusAction} reached the action
34
- * dispatcher — i.e. whether GLSP's `StatusOverlay` is now displaying this
35
- * failure.
36
- *
37
- * `false` means the report itself failed, which happens when the action
38
- * dispatcher is the thing that could not initialize. A consumer that covers
39
- * the canvas must then render the failure itself: it is the only surface
40
- * left, and removing its overlay would leave a blank diagram whose only
41
- * explanation is a line in the Output channel — the exact failure mode this
42
- * loader exists to prevent.
43
- */
44
- readonly surfaced: boolean;
45
- }
46
-
47
- /**
48
- * Diagram loader that never lets a load failure vanish.
49
- *
50
- * `DiagramLoader.load()` orchestrates the full diagram-init path — action
51
- * dispatcher init, GLSP-client connect + server `initialize`, the first
52
- * `RequestModelAction`, and the model-initialization constraint. Of these only
53
- * the `IDiagramStartup` hooks are wrapped by the base loader (it logs them to
54
- * `console.error`); the dispatcher-init / connection / request-model steps are
55
- * not. The widget that drives loading
56
- * (`@eclipse-glsp/theia-integration`'s `GLSPDiagramWidget.onAfterAttach`)
57
- * calls `load()` *without awaiting or catching it*, so any rejection becomes an
58
- * uncaught promise rejection visible only in the browser devtools console —
59
- * nothing reaches the e2e output, the server log, or a Theia log sink, and the
60
- * diagram just stays blank.
61
- *
62
- * This subclass closes that gap: it catches every load failure, routes it to
63
- * the framework Output channel (the same {@link ChannelLogger} sink the server
64
- * log and action traffic share, so it is discoverable where a developer already
65
- * looks), and surfaces a persistent error to the user via a `severity: 'ERROR'`
66
- * {@link StatusAction} on the diagram's status overlay. It deliberately does
67
- * **not** rethrow — the caller drops the promise, so rethrowing would only
68
- * reproduce the uncaught rejection this exists to prevent.
69
- *
70
- * It also publishes the load's terminal state ({@link loadOutcome} /
71
- * {@link onceLoadSettled}) so a pending indicator has one authority to key on.
72
- * `HydraniumGlspDiagramWidget` uses it to cover the canvas while loading; see
73
- * {@link settle} for why the signal cannot be derived from the error
74
- * `StatusAction` instead.
75
- *
76
- * Bound by `createGlspClientTheiaModule` via `rebind(DiagramLoader)`.
77
- */
78
- @injectable()
79
- export class HydraniumDiagramLoader extends DiagramLoader {
80
- @inject(ChannelLogger) protected readonly channel!: ChannelLogger;
81
-
82
- protected readonly settled = new Deferred<DiagramLoadOutcome>();
83
- protected outcome?: DiagramLoadOutcome;
84
-
85
- /** Terminal state of the load, or `undefined` while it is still in flight.
86
- * The synchronous companion of {@link onceLoadSettled}, for a consumer that
87
- * must decide something in the same tick (e.g. whether to show an indicator
88
- * at all when re-attaching an already-loaded diagram). */
89
- get loadOutcome(): DiagramLoadOutcome | undefined {
90
- return this.outcome;
91
- }
92
-
93
- /** Resolves once the load reaches a terminal state — on success **and** on
94
- * failure, so a caller awaiting it cannot hang. Never rejects. */
95
- onceLoadSettled(): Promise<DiagramLoadOutcome> {
96
- return this.settled.promise;
97
- }
98
-
99
- override async load(options?: DiagramLoadingOptions): Promise<void> {
100
- try {
101
- await super.load(options);
102
- this.settle({ status: 'loaded' });
103
- } catch (err) {
104
- // Report first, settle second, and carry whether the report landed: a
105
- // consumer that uncovers the canvas on settle then finds the error
106
- // already on the status overlay — or learns that it has to render the
107
- // failure itself because nothing else can.
108
- const surfaced = await this.reportLoadFailure(err);
109
- this.settle({ status: 'failed', error: err, surfaced });
110
- }
111
- }
112
-
113
- /**
114
- * Record the terminal state and release {@link onceLoadSettled}. Idempotent —
115
- * the first outcome wins, so a reload cannot flip a settled indicator.
116
- *
117
- * This runs unconditionally after {@link reportLoadFailure}, *including* when
118
- * the status dispatch inside it threw. That is the load-bearing property: when
119
- * the action dispatcher is itself the thing that failed to initialize — the
120
- * exact failure this class exists for — no `StatusAction` ever lands, so a
121
- * consumer observing actions would wait forever. Settling here means a pending
122
- * indicator always comes down, and `surfaced: false` tells that consumer it is
123
- * now the only thing that can report the failure.
124
- */
125
- protected settle(outcome: DiagramLoadOutcome): void {
126
- if (this.outcome !== undefined) {
127
- return;
128
- }
129
- this.outcome = outcome;
130
- this.settled.resolve(outcome);
131
- }
132
-
133
- /**
134
- * Route a diagram-load failure to the Output channel and the status overlay.
135
- * The status dispatch is itself guarded: when the action dispatcher is the
136
- * thing that failed to initialize, dispatching may throw too, and a failure
137
- * to *report* the failure must not re-escape as an uncaught rejection.
138
- *
139
- * @returns whether the `StatusAction` reached the dispatcher — i.e. whether the
140
- * user can see this failure on the diagram. The channel line always happens, so
141
- * the return value is specifically about the *user-visible* report; a `false`
142
- * makes {@link DiagramLoadFailure.surfaced} false and hands the job to whoever
143
- * is covering the canvas.
144
- */
145
- protected async reportLoadFailure(err: unknown): Promise<boolean> {
146
- const label = this.loadFailureLabel(err);
147
- this.channel.error(label, err);
148
- try {
149
- await this.actionDispatcher.dispatch(StatusAction.create(label, { severity: 'ERROR' }));
150
- return true;
151
- } catch (statusErr) {
152
- this.channel.error('Failed to surface the diagram-load failure on the status overlay', statusErr);
153
- return false;
154
- }
155
- }
156
-
157
- /**
158
- * The sentence a failed load is reported with, on every surface that reports
159
- * it, and the only seam an adopter rewords or localizes it at. Public
160
- * because the canvas overlay is one of those surfaces and lives in another
161
- * class: it renders this failure exactly when the `StatusAction` did not
162
- * land, so a second copy of the sentence there could only ever drift unseen.
163
- */
164
- loadFailureLabel(err: unknown): string {
165
- return `Diagram failed to load: ${this.formatError(err)}`;
166
- }
167
-
168
- /** Render a thrown value as a message. Shared so the channel line, the status
169
- * overlay and any consumer rendering {@link DiagramLoadFailure.error} agree. */
170
- protected formatError(err: unknown): string {
171
- return err instanceof Error ? err.message : String(err);
172
- }
173
- }