@webda/debug 4.0.0-beta.3

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 (66) hide show
  1. package/LICENSE +166 -0
  2. package/README.md +95 -0
  3. package/lib/bodycapture.d.ts +66 -0
  4. package/lib/bodycapture.js +162 -0
  5. package/lib/bodycapture.js.map +1 -0
  6. package/lib/debugservice.service.d.ts +166 -0
  7. package/lib/debugservice.service.js +609 -0
  8. package/lib/debugservice.service.js.map +1 -0
  9. package/lib/index.d.ts +7 -0
  10. package/lib/index.js +7 -0
  11. package/lib/index.js.map +1 -0
  12. package/lib/introspection.d.ts +115 -0
  13. package/lib/introspection.js +260 -0
  14. package/lib/introspection.js.map +1 -0
  15. package/lib/logbuffer.d.ts +75 -0
  16. package/lib/logbuffer.js +98 -0
  17. package/lib/logbuffer.js.map +1 -0
  18. package/lib/requestlog.d.ts +181 -0
  19. package/lib/requestlog.js +153 -0
  20. package/lib/requestlog.js.map +1 -0
  21. package/lib/tui/client.d.ts +165 -0
  22. package/lib/tui/client.js +220 -0
  23. package/lib/tui/client.js.map +1 -0
  24. package/lib/tui/panels/config.d.ts +79 -0
  25. package/lib/tui/panels/config.js +291 -0
  26. package/lib/tui/panels/config.js.map +1 -0
  27. package/lib/tui/panels/logs.d.ts +106 -0
  28. package/lib/tui/panels/logs.js +342 -0
  29. package/lib/tui/panels/logs.js.map +1 -0
  30. package/lib/tui/panels/models.d.ts +47 -0
  31. package/lib/tui/panels/models.js +193 -0
  32. package/lib/tui/panels/models.js.map +1 -0
  33. package/lib/tui/panels/operations.d.ts +55 -0
  34. package/lib/tui/panels/operations.js +241 -0
  35. package/lib/tui/panels/operations.js.map +1 -0
  36. package/lib/tui/panels/panel.d.ts +55 -0
  37. package/lib/tui/panels/panel.js +31 -0
  38. package/lib/tui/panels/panel.js.map +1 -0
  39. package/lib/tui/panels/requests.d.ts +156 -0
  40. package/lib/tui/panels/requests.js +512 -0
  41. package/lib/tui/panels/requests.js.map +1 -0
  42. package/lib/tui/panels/routes.d.ts +45 -0
  43. package/lib/tui/panels/routes.js +154 -0
  44. package/lib/tui/panels/routes.js.map +1 -0
  45. package/lib/tui/panels/services.d.ts +49 -0
  46. package/lib/tui/panels/services.js +266 -0
  47. package/lib/tui/panels/services.js.map +1 -0
  48. package/lib/tui/tui.d.ts +58 -0
  49. package/lib/tui/tui.js +236 -0
  50. package/lib/tui/tui.js.map +1 -0
  51. package/package.json +60 -0
  52. package/webui/app.js +177 -0
  53. package/webui/components/logs.js +128 -0
  54. package/webui/components/model-graph.js +292 -0
  55. package/webui/components/models.js +213 -0
  56. package/webui/components/operations.js +315 -0
  57. package/webui/components/requests.js +264 -0
  58. package/webui/components/schema-form.js +268 -0
  59. package/webui/components/services.js +248 -0
  60. package/webui/index.html +22 -0
  61. package/webui/logo.svg +1 -0
  62. package/webui/styles.css +685 -0
  63. package/webui/vendor/htm.js +1 -0
  64. package/webui/vendor/preact-hooks.js +2 -0
  65. package/webui/vendor/preact.js +2 -0
  66. package/webui/webda.png +0 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Information about a registered model.
3
+ */
4
+ export interface ModelInfo {
5
+ /** Fully-qualified model identifier, e.g. "MyApp/Task" */
6
+ id: string;
7
+ /** Plural name used in REST URLs, e.g. "Tasks" */
8
+ plural: string;
9
+ /** List of action names defined on the model */
10
+ actions: string[];
11
+ /** Relation graph (links, queries, maps, binaries) */
12
+ relations: any;
13
+ /** Full raw ModelMetadata object */
14
+ metadata: any;
15
+ }
16
+ /**
17
+ * Information about a running service.
18
+ */
19
+ export interface ServiceInfo {
20
+ /** Service registration name */
21
+ name: string;
22
+ /** Service type string (e.g. "Webda/Router") */
23
+ type: string;
24
+ /** Current lifecycle state (e.g. "running") */
25
+ state: string;
26
+ /** Capabilities advertised by the service */
27
+ capabilities: Record<string, any>;
28
+ /** Service configuration parameters */
29
+ configuration: Record<string, any>;
30
+ }
31
+ /**
32
+ * Information about a registered operation.
33
+ */
34
+ export interface OperationInfo {
35
+ /** Operation identifier, e.g. "Task.Create" */
36
+ id: string;
37
+ /** Input schema identifier. "void" means no input. */
38
+ input: string;
39
+ /** Output schema identifier. "void" means no output. */
40
+ output: string;
41
+ /** Parameters schema identifier, if any */
42
+ parameters?: string;
43
+ /** Additional fields from the operation definition */
44
+ [key: string]: any;
45
+ }
46
+ /**
47
+ * Information about a single route entry.
48
+ */
49
+ export interface RouteInfoEntry {
50
+ /** URL path pattern, e.g. "/tasks/{uuid}" */
51
+ path: string;
52
+ /** HTTP methods accepted by this route, e.g. ["GET","POST"] */
53
+ methods: string[];
54
+ /** Name of the executor service */
55
+ executor: string;
56
+ }
57
+ /**
58
+ * Returns introspection data for all models registered in the application.
59
+ *
60
+ * Each entry contains the model identifier, plural name, action names, relation
61
+ * graph, and the full raw ModelMetadata object.
62
+ *
63
+ * @returns Array of {@link ModelInfo} objects, one per registered model.
64
+ */
65
+ export declare function getModels(): ModelInfo[];
66
+ /**
67
+ * Returns introspection data for a single model by its identifier.
68
+ *
69
+ * @param id - Fully-qualified model identifier, e.g. "MyApp/Task".
70
+ * @returns The matching {@link ModelInfo}, or `undefined` if not found.
71
+ */
72
+ export declare function getModel(id: string): ModelInfo | undefined;
73
+ /**
74
+ * Returns introspection data for all services currently registered with Core.
75
+ *
76
+ * Null / undefined service slots are filtered out before mapping.
77
+ *
78
+ * @returns Array of {@link ServiceInfo} objects, one per registered service.
79
+ */
80
+ export declare function getServices(): ServiceInfo[];
81
+ /**
82
+ * Returns introspection data for all operations registered in the instance storage.
83
+ *
84
+ * The `service` and `method` internal fields are omitted by {@link listOperations};
85
+ * each entry gets a synthetic `id` field added.
86
+ *
87
+ * @returns Array of {@link OperationInfo} objects.
88
+ */
89
+ export declare function getOperations(): OperationInfo[];
90
+ /**
91
+ * Returns introspection data for all routes registered with the Router service.
92
+ *
93
+ * A single path can have multiple route entries (one per executor / method set);
94
+ * each is returned as a separate {@link RouteInfoEntry}.
95
+ *
96
+ * @returns Array of {@link RouteInfoEntry} objects.
97
+ */
98
+ export declare function getRoutes(): RouteInfoEntry[];
99
+ /**
100
+ * Returns the active application configuration object.
101
+ *
102
+ * This is the merged configuration (global + deployment overrides) that the
103
+ * running application was started with.
104
+ *
105
+ * @returns Plain configuration record.
106
+ */
107
+ export declare function getConfig(): Record<string, any>;
108
+ /**
109
+ * Returns project information including package.json data, git info, and deployment details.
110
+ *
111
+ * Adds the current working directory to the result.
112
+ *
113
+ * @returns Project information or a minimal fallback from package.json.
114
+ */
115
+ export declare function getAppInfo(): Record<string, any>;
@@ -0,0 +1,260 @@
1
+ import { useApplication, useCore, useRouter, listFullOperations, useModelMetadata } from "@webda/core";
2
+ /**
3
+ * Returns introspection data for all models registered in the application.
4
+ *
5
+ * Each entry contains the model identifier, plural name, action names, relation
6
+ * graph, and the full raw ModelMetadata object.
7
+ *
8
+ * @returns Array of {@link ModelInfo} objects, one per registered model.
9
+ */
10
+ export function getModels() {
11
+ const app = useApplication();
12
+ const core = useCore();
13
+ const models = app.getModels();
14
+ return Object.entries(models).map(([key, model]) => {
15
+ const metadata = useModelMetadata(model);
16
+ let storeName;
17
+ let storeType;
18
+ try {
19
+ const store = core.getModelStore(model);
20
+ if (store) {
21
+ storeName = store.getName();
22
+ storeType = store.constructor?.name;
23
+ }
24
+ }
25
+ catch {
26
+ // No store assigned
27
+ }
28
+ return {
29
+ id: metadata?.Identifier || key,
30
+ plural: metadata?.Plural || key,
31
+ actions: Object.keys(metadata?.Actions || {}),
32
+ relations: metadata?.Relations || {},
33
+ store: storeName,
34
+ storeType,
35
+ schemas: metadata?.Schemas,
36
+ metadata
37
+ };
38
+ });
39
+ }
40
+ /**
41
+ * Returns introspection data for a single model by its identifier.
42
+ *
43
+ * @param id - Fully-qualified model identifier, e.g. "MyApp/Task".
44
+ * @returns The matching {@link ModelInfo}, or `undefined` if not found.
45
+ */
46
+ export function getModel(id) {
47
+ return getModels().find(m => m.id === id);
48
+ }
49
+ /**
50
+ * Returns introspection data for all services currently registered with Core.
51
+ *
52
+ * Null / undefined service slots are filtered out before mapping.
53
+ *
54
+ * @returns Array of {@link ServiceInfo} objects, one per registered service.
55
+ */
56
+ export function getServices() {
57
+ const core = useCore();
58
+ const app = useApplication();
59
+ const services = core.getServices();
60
+ const config = core.getConfiguration?.() || {};
61
+ return Object.entries(services)
62
+ .filter(([, svc]) => svc != null)
63
+ .map(([name, svc]) => {
64
+ const type = svc.parameters?.type || "unknown";
65
+ // Collect metric definitions and current values from the service's metrics object
66
+ const metricsInfo = [];
67
+ const metricsObj = svc.metrics;
68
+ if (metricsObj && typeof metricsObj === "object") {
69
+ for (const [key, metric] of Object.entries(metricsObj)) {
70
+ if (!metric || typeof metric !== "object")
71
+ continue;
72
+ const m = metric;
73
+ try {
74
+ const values = m.hashMap ? Object.values(m.hashMap).map((v) => ({
75
+ value: v.value,
76
+ labels: v.labels
77
+ })) : [];
78
+ metricsInfo.push({
79
+ name: key,
80
+ fullName: m.name,
81
+ help: m.help,
82
+ type: m.constructor?.name?.toLowerCase() || "unknown",
83
+ labelNames: m.labelNames || [],
84
+ values
85
+ });
86
+ }
87
+ catch {
88
+ // Skip unreadable metrics
89
+ }
90
+ }
91
+ }
92
+ return {
93
+ name,
94
+ type,
95
+ state: svc.getState(),
96
+ capabilities: svc.getCapabilities(),
97
+ configuration: config[name] || {},
98
+ schema: app.getSchema?.(app.completeNamespace(type)) || undefined,
99
+ metrics: metricsInfo.length > 0 ? metricsInfo : undefined
100
+ };
101
+ });
102
+ }
103
+ /**
104
+ * Returns introspection data for all operations registered in the instance storage.
105
+ *
106
+ * The `service` and `method` internal fields are omitted by {@link listOperations};
107
+ * each entry gets a synthetic `id` field added.
108
+ *
109
+ * @returns Array of {@link OperationInfo} objects.
110
+ */
111
+ export function getOperations() {
112
+ const ops = listFullOperations();
113
+ const app = useApplication();
114
+ const core = useCore();
115
+ // Build a map of operation ID → full route URL by scanning the router's openapi metadata
116
+ const operationRoutes = {};
117
+ try {
118
+ const router = useRouter();
119
+ const routes = router.getRoutes();
120
+ for (const [path, infos] of Object.entries(routes)) {
121
+ for (const info of infos) {
122
+ if (!info.openapi)
123
+ continue;
124
+ // One route can serve several verbs (PUT and PATCH), each with its own operation
125
+ for (const [method, methodDef] of Object.entries(info.openapi)) {
126
+ if (methodDef?.operationId) {
127
+ operationRoutes[methodDef.operationId] = { url: path, method: method.toLowerCase() };
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ catch {
134
+ // Router may not be available
135
+ }
136
+ return Object.entries(ops).map(([opId, def]) => {
137
+ const entry = { ...def, id: opId };
138
+ // Resolve input/output schema refs to actual JSON Schema objects
139
+ for (const key of ["input", "output"]) {
140
+ if (entry[key] && entry[key] !== "void") {
141
+ entry[`${key}Schema`] = app.getSchema?.(entry[key]) || undefined;
142
+ }
143
+ }
144
+ // Enrich rest with full URL from router
145
+ const route = operationRoutes[opId];
146
+ if (route) {
147
+ entry.rest = { ...(typeof entry.rest === "object" ? entry.rest : {}), url: route.url, method: entry.rest?.method || route.method };
148
+ }
149
+ // Dedent: find the smallest leading whitespace (skipping the first line which
150
+ // Function.toString() always returns at column 0) and strip it from all lines
151
+ const dedent = (code) => {
152
+ const lines = code.split("\n");
153
+ if (lines.length <= 1)
154
+ return code;
155
+ // Compute min indent from lines 1+ (skip first line and empty lines)
156
+ const indents = lines.slice(1).filter(l => l.trim().length > 0).map(l => (l.match(/^(\s*)/) || ["", ""])[1].length);
157
+ if (indents.length === 0)
158
+ return code;
159
+ const min = Math.min(...indents);
160
+ if (min === 0)
161
+ return code;
162
+ return [lines[0], ...lines.slice(1).map(l => l.slice(min))].join("\n");
163
+ };
164
+ // Resolve implementor info
165
+ try {
166
+ if (def.service) {
167
+ const svc = core.getService(def.service);
168
+ entry.implementor = { type: "service", name: def.service };
169
+ if (svc && def.method) {
170
+ // For model actions, the real code is on the model prototype
171
+ const actionName = def.context?.action?.name;
172
+ const modelClass = def.context?.model;
173
+ if (actionName && modelClass?.prototype?.[actionName]) {
174
+ const fn = modelClass.prototype[actionName];
175
+ const modelId = modelClass.getIdentifier?.() || modelClass.name || def.service;
176
+ entry.implementor = { type: "model", name: modelId, method: actionName };
177
+ entry.implementor.code = dedent((fn.__original || fn).toString());
178
+ }
179
+ else if (typeof svc[def.method] === "function") {
180
+ entry.implementor.method = def.method;
181
+ const fn = svc[def.method];
182
+ entry.implementor.code = dedent((fn.__original || fn).toString());
183
+ }
184
+ }
185
+ }
186
+ else if (def.model) {
187
+ const modelClass = app.getModel(def.model);
188
+ entry.implementor = { type: "model", name: def.model };
189
+ if (modelClass && def.method) {
190
+ const fn = modelClass.prototype?.[def.method] || modelClass[def.method];
191
+ if (typeof fn === "function") {
192
+ entry.implementor.method = def.method;
193
+ entry.implementor.code = dedent((fn.__original || fn).toString());
194
+ }
195
+ }
196
+ }
197
+ }
198
+ catch {
199
+ // Service/model may not be resolvable
200
+ }
201
+ // Strip internal fields not useful for the UI
202
+ delete entry.service;
203
+ delete entry.model;
204
+ delete entry.method;
205
+ delete entry.context;
206
+ delete entry.permissionQuery;
207
+ return entry;
208
+ });
209
+ }
210
+ /**
211
+ * Returns introspection data for all routes registered with the Router service.
212
+ *
213
+ * A single path can have multiple route entries (one per executor / method set);
214
+ * each is returned as a separate {@link RouteInfoEntry}.
215
+ *
216
+ * @returns Array of {@link RouteInfoEntry} objects.
217
+ */
218
+ export function getRoutes() {
219
+ const router = useRouter();
220
+ const routes = router.getRoutes();
221
+ const result = [];
222
+ for (const [path, infos] of Object.entries(routes)) {
223
+ for (const info of infos) {
224
+ result.push({ path, methods: info.methods, executor: info.executor });
225
+ }
226
+ }
227
+ return result;
228
+ }
229
+ /**
230
+ * Returns the active application configuration object.
231
+ *
232
+ * This is the merged configuration (global + deployment overrides) that the
233
+ * running application was started with.
234
+ *
235
+ * @returns Plain configuration record.
236
+ */
237
+ export function getConfig() {
238
+ return useApplication().getConfiguration();
239
+ }
240
+ /**
241
+ * Returns project information including package.json data, git info, and deployment details.
242
+ *
243
+ * Adds the current working directory to the result.
244
+ *
245
+ * @returns Project information or a minimal fallback from package.json.
246
+ */
247
+ export function getAppInfo() {
248
+ const app = useApplication();
249
+ const projectInfo = app.getProjectInfo?.();
250
+ if (projectInfo) {
251
+ return { ...projectInfo, workingDirectory: process.cwd() };
252
+ }
253
+ // Fallback for unpacked apps without full project info
254
+ const pkg = app.getPackageDescription();
255
+ return {
256
+ package: pkg,
257
+ workingDirectory: process.cwd()
258
+ };
259
+ }
260
+ //# sourceMappingURL=introspection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"introspection.js","sourceRoot":"","sources":["../src/introspection.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA8DvG;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC;IAC/B,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACjD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,SAA6B,CAAC;QAClC,IAAI,SAA6B,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YACxC,IAAI,KAAK,EAAE,CAAC;gBACV,SAAS,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;gBAC5B,SAAS,GAAG,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC;YACtC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,oBAAoB;QACtB,CAAC;QACD,OAAO;YACL,EAAE,EAAE,QAAQ,EAAE,UAAU,IAAI,GAAG;YAC/B,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG;YAC/B,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,IAAI,EAAE,CAAC;YAC7C,SAAS,EAAE,QAAQ,EAAE,SAAS,IAAI,EAAE;YACpC,KAAK,EAAE,SAAS;YAChB,SAAS;YACT,OAAO,EAAE,QAAQ,EAAE,OAAO;YAC1B,QAAQ;SACT,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAU;IACjC,OAAO,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,CAAC;IAC/C,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;SAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC;SAChC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE;QACnB,MAAM,IAAI,GAAI,GAAG,CAAC,UAAkB,EAAE,IAAI,IAAI,SAAS,CAAC;QACxD,kFAAkF;QAClF,MAAM,WAAW,GAAU,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAI,GAAW,CAAC,OAAO,CAAC;QACxC,IAAI,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YACjD,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBACvD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;oBAAE,SAAS;gBACpD,MAAM,CAAC,GAAG,MAAa,CAAC;gBACxB,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;wBACnE,KAAK,EAAE,CAAC,CAAC,KAAK;wBACd,MAAM,EAAE,CAAC,CAAC,MAAM;qBACjB,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBACT,WAAW,CAAC,IAAI,CAAC;wBACf,IAAI,EAAE,GAAG;wBACT,QAAQ,EAAE,CAAC,CAAC,IAAI;wBAChB,IAAI,EAAE,CAAC,CAAC,IAAI;wBACZ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,SAAS;wBACrD,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE;wBAC9B,MAAM;qBACP,CAAC,CAAC;gBACL,CAAC;gBAAC,MAAM,CAAC;oBACP,0BAA0B;gBAC5B,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO;YACL,IAAI;YACJ,IAAI;YACJ,KAAK,EAAE,GAAG,CAAC,QAAQ,EAAE;YACrB,YAAY,EAAE,GAAG,CAAC,eAAe,EAAE;YACnC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE;YACjC,MAAM,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,IAAI,SAAS;YACjE,OAAO,EAAE,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;SAC1D,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,yFAAyF;IACzF,MAAM,eAAe,GAAoD,EAAE,CAAC;IAC5E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnD,KAAK,MAAM,IAAI,IAAI,KAAc,EAAE,CAAC;gBAClC,IAAI,CAAC,IAAI,CAAC,OAAO;oBAAE,SAAS;gBAC5B,iFAAiF;gBACjF,KAAK,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAoB,EAAE,CAAC;oBAClF,IAAI,SAAS,EAAE,WAAW,EAAE,CAAC;wBAC3B,eAAe,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;oBACvF,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,8BAA8B;IAChC,CAAC;IACD,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE;QAC7C,MAAM,KAAK,GAAQ,EAAE,GAAG,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACxC,iEAAiE;QACjE,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAU,EAAE,CAAC;YAC/C,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,MAAM,EAAE,CAAC;gBACxC,KAAK,CAAC,GAAG,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,SAAS,CAAC;YACnE,CAAC;QACH,CAAC;QACD,wCAAwC;QACxC,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,IAAI,GAAG,EAAE,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACrI,CAAC;QACD,8EAA8E;QAC9E,8EAA8E;QAC9E,MAAM,MAAM,GAAG,CAAC,IAAY,EAAU,EAAE;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;YACnC,qEAAqE;YACrE,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACpH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACtC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;YACjC,IAAI,GAAG,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzE,CAAC,CAAC;QACF,2BAA2B;QAC3B,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAChB,MAAM,GAAG,GAAQ,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC9C,KAAK,CAAC,WAAW,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;gBAC3D,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;oBACtB,6DAA6D;oBAC7D,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC;oBAC7C,MAAM,UAAU,GAAQ,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC;oBAC3C,IAAI,UAAU,IAAI,UAAU,EAAE,SAAS,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC;wBACtD,MAAM,EAAE,GAAG,UAAU,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;wBAC5C,MAAM,OAAO,GAAG,UAAU,CAAC,aAAa,EAAE,EAAE,IAAI,UAAU,CAAC,IAAI,IAAI,GAAG,CAAC,OAAO,CAAC;wBAC/E,KAAK,CAAC,WAAW,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;wBACzE,KAAK,CAAC,WAAW,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;oBACpE,CAAC;yBAAM,IAAI,OAAO,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC;wBACjD,KAAK,CAAC,WAAW,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;wBACtC,MAAM,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;wBAC3B,KAAK,CAAC,WAAW,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;oBACpE,CAAC;gBACH,CAAC;YACH,CAAC;iBAAM,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;gBACrB,MAAM,UAAU,GAAQ,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAChD,KAAK,CAAC,WAAW,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;gBACvD,IAAI,UAAU,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;oBAC7B,MAAM,EAAE,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;oBACxE,IAAI,OAAO,EAAE,KAAK,UAAU,EAAE,CAAC;wBAC7B,KAAK,CAAC,WAAW,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;wBACtC,KAAK,CAAC,WAAW,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;oBACpE,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;QACD,8CAA8C;QAC9C,OAAO,KAAK,CAAC,OAAO,CAAC;QACrB,OAAO,KAAK,CAAC,KAAK,CAAC;QACnB,OAAO,KAAK,CAAC,MAAM,CAAC;QACpB,OAAO,KAAK,CAAC,OAAO,CAAC;QACrB,OAAO,KAAK,CAAC,eAAe,CAAC;QAC7B,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;IAClC,MAAM,MAAM,GAAqB,EAAE,CAAC;IACpC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,KAAK,MAAM,IAAI,IAAI,KAAc,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,cAAc,EAAE,CAAC,gBAAgB,EAAE,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC;IAC3C,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,EAAE,GAAG,WAAW,EAAE,gBAAgB,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;IAC7D,CAAC;IACD,uDAAuD;IACvD,MAAM,GAAG,GAAG,GAAG,CAAC,qBAAqB,EAAE,CAAC;IACxC,OAAO;QACL,OAAO,EAAE,GAAG;QACZ,gBAAgB,EAAE,OAAO,CAAC,GAAG,EAAE;KAChC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { type WorkerLogLevel } from "@webda/workout";
2
+ /**
3
+ * A single captured log entry.
4
+ */
5
+ export interface LogEntry {
6
+ /** Unique identifier for this entry */
7
+ id: string;
8
+ /** Unix timestamp when the log was emitted */
9
+ timestamp: number;
10
+ /** Severity level */
11
+ level: WorkerLogLevel;
12
+ /** Formatted message string */
13
+ message: string;
14
+ /** Raw arguments from the log call */
15
+ args: any[];
16
+ }
17
+ /**
18
+ * Event emitted when a new log entry is captured.
19
+ */
20
+ export type LogEvent = {
21
+ type: "log";
22
+ id: string;
23
+ timestamp: number;
24
+ level: WorkerLogLevel;
25
+ message: string;
26
+ };
27
+ type LogEventCallback = (event: LogEvent) => void;
28
+ /**
29
+ * Ring buffer that captures application logs from WorkerOutput
30
+ * and notifies subscribers of new entries in real time.
31
+ */
32
+ export declare class LogBuffer {
33
+ private maxSize;
34
+ /** Stored log entries */
35
+ private entries;
36
+ /** Active event subscribers */
37
+ private subscribers;
38
+ /** Listener function attached to WorkerOutput */
39
+ private listener;
40
+ /**
41
+ * Create a new LogBuffer.
42
+ * @param maxSize - Maximum number of entries to retain (oldest are evicted)
43
+ */
44
+ constructor(maxSize?: number);
45
+ /**
46
+ * Start listening to WorkerOutput messages.
47
+ */
48
+ subscribe(): void;
49
+ /**
50
+ * Stop listening to WorkerOutput messages.
51
+ */
52
+ unsubscribe(): void;
53
+ /**
54
+ * Return a shallow copy of all stored entries.
55
+ * @returns array of log entries
56
+ */
57
+ getEntries(): LogEntry[];
58
+ /** Log level severity order — higher index means more severe. */
59
+ private static readonly LOG_LEVELS;
60
+ /**
61
+ * Filter entries by query. If the query matches a log level name,
62
+ * filter by severity (>= that level). Otherwise, do a text search
63
+ * on message and level.
64
+ * @param query - search string or log level name
65
+ * @returns matching entries
66
+ */
67
+ search(query: string): LogEntry[];
68
+ /**
69
+ * Register a callback for new log events.
70
+ * @param callback - function invoked on each new log event
71
+ * @returns unsubscribe function
72
+ */
73
+ onEvent(callback: LogEventCallback): () => void;
74
+ }
75
+ export {};
@@ -0,0 +1,98 @@
1
+ import { useWorkerOutput } from "@webda/workout";
2
+ /**
3
+ * Ring buffer that captures application logs from WorkerOutput
4
+ * and notifies subscribers of new entries in real time.
5
+ */
6
+ export class LogBuffer {
7
+ /**
8
+ * Create a new LogBuffer.
9
+ * @param maxSize - Maximum number of entries to retain (oldest are evicted)
10
+ */
11
+ constructor(maxSize = 2000) {
12
+ this.maxSize = maxSize;
13
+ /** Stored log entries */
14
+ this.entries = [];
15
+ /** Active event subscribers */
16
+ this.subscribers = new Set();
17
+ this.listener = (msg) => {
18
+ if (msg.type !== "log" || !msg.log)
19
+ return;
20
+ const id = Math.random().toString(36).substring(2);
21
+ const entry = {
22
+ id,
23
+ timestamp: msg.timestamp,
24
+ level: msg.log.level,
25
+ message: msg.log.args
26
+ .map((a) => (typeof a === "object" ? JSON.stringify(a) : String(a)))
27
+ .join(" "),
28
+ args: msg.log.args
29
+ };
30
+ if (this.entries.length >= this.maxSize) {
31
+ this.entries.shift();
32
+ }
33
+ this.entries.push(entry);
34
+ const event = {
35
+ type: "log",
36
+ id,
37
+ timestamp: entry.timestamp,
38
+ level: entry.level,
39
+ message: entry.message
40
+ };
41
+ for (const cb of this.subscribers) {
42
+ cb(event);
43
+ }
44
+ };
45
+ }
46
+ /**
47
+ * Start listening to WorkerOutput messages.
48
+ */
49
+ subscribe() {
50
+ useWorkerOutput().on("message", this.listener);
51
+ }
52
+ /**
53
+ * Stop listening to WorkerOutput messages.
54
+ */
55
+ unsubscribe() {
56
+ try {
57
+ useWorkerOutput().removeListener("message", this.listener);
58
+ }
59
+ catch {
60
+ // WorkerOutput may not be available
61
+ }
62
+ }
63
+ /**
64
+ * Return a shallow copy of all stored entries.
65
+ * @returns array of log entries
66
+ */
67
+ getEntries() {
68
+ return [...this.entries];
69
+ }
70
+ /**
71
+ * Filter entries by query. If the query matches a log level name,
72
+ * filter by severity (>= that level). Otherwise, do a text search
73
+ * on message and level.
74
+ * @param query - search string or log level name
75
+ * @returns matching entries
76
+ */
77
+ search(query) {
78
+ const upper = query.toUpperCase();
79
+ const levelIndex = LogBuffer.LOG_LEVELS.indexOf(upper);
80
+ if (levelIndex >= 0) {
81
+ return this.entries.filter(e => LogBuffer.LOG_LEVELS.indexOf(e.level) >= levelIndex);
82
+ }
83
+ const lower = query.toLowerCase();
84
+ return this.entries.filter(e => e.message.toLowerCase().includes(lower) || e.level.toLowerCase().includes(lower));
85
+ }
86
+ /**
87
+ * Register a callback for new log events.
88
+ * @param callback - function invoked on each new log event
89
+ * @returns unsubscribe function
90
+ */
91
+ onEvent(callback) {
92
+ this.subscribers.add(callback);
93
+ return () => this.subscribers.delete(callback);
94
+ }
95
+ }
96
+ /** Log level severity order — higher index means more severe. */
97
+ LogBuffer.LOG_LEVELS = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"];
98
+ //# sourceMappingURL=logbuffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logbuffer.js","sourceRoot":"","sources":["../src/logbuffer.ts"],"names":[],"mappings":"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"}