eventmodeler 0.4.2 → 0.4.4

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.
@@ -7,6 +7,13 @@ export interface SearchResult {
7
7
  type: string;
8
8
  id: string;
9
9
  name: string;
10
+ chapterId?: string;
11
+ chapterName?: string;
12
+ sliceId?: string;
13
+ sliceName?: string;
14
+ sliceStatus?: string;
15
+ parentChapterId?: string;
16
+ parentChapterName?: string;
10
17
  }
11
18
  export interface SearchResponse {
12
19
  query: string;
@@ -11,16 +11,7 @@ import { getValidAccessToken } from '../../lib/auth.js';
11
11
  */
12
12
  async function handleHttpError(response, path) {
13
13
  const text = await response.text().catch(() => '');
14
- let errorDetail = '';
15
- if (text.trim()) {
16
- try {
17
- const parsed = JSON.parse(text);
18
- errorDetail = parsed.error ?? parsed.message ?? '';
19
- }
20
- catch {
21
- // Response body isn't JSON (could be HTML error page, etc.)
22
- }
23
- }
14
+ const errorDetail = extractErrorDetail(text);
24
15
  switch (response.status) {
25
16
  case 401:
26
17
  throw new Error(`Authentication required. Run 'eventmodeler login' to authenticate.` +
@@ -34,6 +25,73 @@ async function handleHttpError(response, path) {
34
25
  throw new Error(errorDetail || `HTTP ${response.status}: ${response.statusText}`);
35
26
  }
36
27
  }
28
+ const GENERIC_ERROR_MESSAGES = new Set([
29
+ 'internal server error',
30
+ 'bad request',
31
+ 'unauthorized',
32
+ 'forbidden',
33
+ 'not found',
34
+ ]);
35
+ function isGenericErrorMessage(value) {
36
+ return GENERIC_ERROR_MESSAGES.has(value.trim().toLowerCase());
37
+ }
38
+ function toStringValue(value) {
39
+ return typeof value === 'string' ? value.trim() : '';
40
+ }
41
+ function collectCandidateMessages(node, out) {
42
+ if (!node || typeof node !== 'object')
43
+ return;
44
+ const obj = node;
45
+ // Prefer richer fields before generic "error".
46
+ for (const key of ['message', 'detail', 'reason', 'description', 'title', 'error']) {
47
+ const value = toStringValue(obj[key]);
48
+ if (value)
49
+ out.push(value);
50
+ }
51
+ const errors = obj.errors;
52
+ if (Array.isArray(errors)) {
53
+ for (const entry of errors) {
54
+ if (typeof entry === 'string') {
55
+ const value = entry.trim();
56
+ if (value)
57
+ out.push(value);
58
+ }
59
+ else if (entry && typeof entry === 'object') {
60
+ collectCandidateMessages(entry, out);
61
+ }
62
+ }
63
+ }
64
+ const cause = obj.cause;
65
+ if (cause && typeof cause === 'object') {
66
+ collectCandidateMessages(cause, out);
67
+ }
68
+ }
69
+ function extractErrorDetail(text) {
70
+ const body = text.trim();
71
+ if (!body)
72
+ return '';
73
+ try {
74
+ const parsed = JSON.parse(body);
75
+ if (typeof parsed === 'string') {
76
+ return parsed.trim();
77
+ }
78
+ const candidates = [];
79
+ collectCandidateMessages(parsed, candidates);
80
+ if (candidates.length > 0) {
81
+ const specific = candidates.find((value) => !isGenericErrorMessage(value));
82
+ return specific ?? candidates[0];
83
+ }
84
+ }
85
+ catch {
86
+ // Not JSON. Fall back to plain text below.
87
+ }
88
+ // Avoid dumping entire HTML pages from reverse proxies.
89
+ if (body.startsWith('<')) {
90
+ const title = body.match(/<title>([^<]+)<\/title>/i)?.[1]?.trim();
91
+ return title && !isGenericErrorMessage(title) ? title : '';
92
+ }
93
+ return body;
94
+ }
37
95
  async function cliGet(path, params) {
38
96
  const backendUrl = getBackendUrl();
39
97
  const accessToken = await getValidAccessToken();
package/dist/index.js CHANGED
@@ -1510,10 +1510,21 @@ EXAMPLES:
1510
1510
  }
1511
1511
  else {
1512
1512
  const esc = (s) => s.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1513
+ const attr = (name, value) => value ? ` ${name}="${esc(value)}"` : '';
1513
1514
  console.log(`<search-results query="${esc(subcommand)}" count="${response.results.length}">`);
1514
1515
  for (const r of response.results) {
1515
1516
  const tag = r.type === 'readmodel' ? 'read-model' : r.type;
1516
- console.log(` <${tag} name="${esc(r.name)}"/>`);
1517
+ console.log(` <${tag}` +
1518
+ `${attr('id', r.id)}` +
1519
+ `${attr('name', r.name)}` +
1520
+ `${attr('chapter-id', r.chapterId)}` +
1521
+ `${attr('chapter-name', r.chapterName)}` +
1522
+ `${attr('slice-id', r.sliceId)}` +
1523
+ `${attr('slice-name', r.sliceName)}` +
1524
+ `${attr('slice-status', r.sliceStatus)}` +
1525
+ `${attr('parent-chapter-id', r.parentChapterId)}` +
1526
+ `${attr('parent-chapter-name', r.parentChapterName)}` +
1527
+ '/>');
1517
1528
  }
1518
1529
  console.log('</search-results>');
1519
1530
  }
@@ -4,8 +4,8 @@ import { getValidAccessToken } from './auth.js';
4
4
  * Handle HTTP error responses with user-friendly messages
5
5
  */
6
6
  async function handleHttpError(response) {
7
- const errorData = await response.json().catch(() => ({}));
8
- const errorDetail = errorData.error ?? errorData.message ?? '';
7
+ const text = await response.text().catch(() => '');
8
+ const errorDetail = extractErrorDetail(text);
9
9
  switch (response.status) {
10
10
  case 401:
11
11
  throw new Error(`Authentication required. Run 'eventmodeler login' to authenticate.` +
@@ -19,6 +19,73 @@ async function handleHttpError(response) {
19
19
  throw new Error(errorDetail || `HTTP ${response.status}: ${response.statusText}`);
20
20
  }
21
21
  }
22
+ const GENERIC_ERROR_MESSAGES = new Set([
23
+ 'internal server error',
24
+ 'bad request',
25
+ 'unauthorized',
26
+ 'forbidden',
27
+ 'not found',
28
+ ]);
29
+ function isGenericErrorMessage(value) {
30
+ return GENERIC_ERROR_MESSAGES.has(value.trim().toLowerCase());
31
+ }
32
+ function toStringValue(value) {
33
+ return typeof value === 'string' ? value.trim() : '';
34
+ }
35
+ function collectCandidateMessages(node, out) {
36
+ if (!node || typeof node !== 'object')
37
+ return;
38
+ const obj = node;
39
+ // Prefer specific fields before generic "error".
40
+ for (const key of ['message', 'detail', 'reason', 'description', 'title', 'error']) {
41
+ const value = toStringValue(obj[key]);
42
+ if (value)
43
+ out.push(value);
44
+ }
45
+ const errors = obj.errors;
46
+ if (Array.isArray(errors)) {
47
+ for (const entry of errors) {
48
+ if (typeof entry === 'string') {
49
+ const value = entry.trim();
50
+ if (value)
51
+ out.push(value);
52
+ }
53
+ else if (entry && typeof entry === 'object') {
54
+ collectCandidateMessages(entry, out);
55
+ }
56
+ }
57
+ }
58
+ const cause = obj.cause;
59
+ if (cause && typeof cause === 'object') {
60
+ collectCandidateMessages(cause, out);
61
+ }
62
+ }
63
+ function extractErrorDetail(text) {
64
+ const body = text.trim();
65
+ if (!body)
66
+ return '';
67
+ try {
68
+ const parsed = JSON.parse(body);
69
+ if (typeof parsed === 'string') {
70
+ return parsed.trim();
71
+ }
72
+ const candidates = [];
73
+ collectCandidateMessages(parsed, candidates);
74
+ if (candidates.length > 0) {
75
+ const specific = candidates.find((value) => !isGenericErrorMessage(value));
76
+ return specific ?? candidates[0];
77
+ }
78
+ }
79
+ catch {
80
+ // Not JSON. Fall back to plain text below.
81
+ }
82
+ // Avoid dumping full HTML error pages.
83
+ if (body.startsWith('<')) {
84
+ const title = body.match(/<title>([^<]+)<\/title>/i)?.[1]?.trim();
85
+ return title && !isGenericErrorMessage(title) ? title : '';
86
+ }
87
+ return body;
88
+ }
22
89
  /**
23
90
  * Create a cloud client for interacting with the Axon backend.
24
91
  */
@@ -78,16 +78,6 @@ function applyEvent(model, event) {
78
78
  }
79
79
  break;
80
80
  }
81
- case 'CommandStickyDuplicated':
82
- model.commands.set(d.commandStickyId, {
83
- id: d.commandStickyId,
84
- name: d.name,
85
- fields: d.fields,
86
- position: d.position,
87
- width: d.width,
88
- height: d.height,
89
- });
90
- break;
91
81
  // Event sticky events
92
82
  case 'EventStickyPlaced':
93
83
  model.events.set(d.eventStickyId, {
@@ -449,16 +439,6 @@ function applyEvent(model, event) {
449
439
  }
450
440
  break;
451
441
  }
452
- case 'ProcessorDuplicated':
453
- model.processors.set(d.processorId, {
454
- id: d.processorId,
455
- name: d.name,
456
- fields: d.fields,
457
- position: d.position,
458
- width: d.width,
459
- height: d.height,
460
- });
461
- break;
462
442
  // Slice events
463
443
  case 'SlicePlaced':
464
444
  model.slices.set(d.sliceId, {
@@ -548,16 +528,6 @@ function applyEvent(model, event) {
548
528
  }
549
529
  break;
550
530
  }
551
- case 'SliceDuplicated':
552
- model.slices.set(d.sliceId, {
553
- id: d.sliceId,
554
- name: d.name,
555
- status: d.status,
556
- position: d.position,
557
- size: d.size,
558
- nodeIds: [],
559
- });
560
- break;
561
531
  // Aggregate events
562
532
  case 'AggregatePlaced':
563
533
  model.aggregates.set(d.aggregateId, {
@@ -756,92 +726,6 @@ function applyEvent(model, event) {
756
726
  flow.fieldMappings = [];
757
727
  break;
758
728
  }
759
- // Quick place events - create node and flow atomically
760
- case 'CommandQuickPlacedFromScreen':
761
- model.commands.set(d.commandStickyId, {
762
- id: d.commandStickyId,
763
- name: d.name,
764
- fields: [],
765
- position: d.position,
766
- width: d.width,
767
- height: d.height,
768
- });
769
- model.flows.set(d.flowId, {
770
- id: d.flowId,
771
- flowType: 'ScreenToCommand',
772
- sourceId: d.screenId,
773
- targetId: d.commandStickyId,
774
- fieldMappings: [],
775
- });
776
- break;
777
- case 'EventQuickPlacedFromCommand':
778
- model.events.set(d.eventStickyId, {
779
- id: d.eventStickyId,
780
- name: d.name,
781
- fields: [],
782
- position: d.position,
783
- width: d.width,
784
- height: d.height,
785
- });
786
- model.flows.set(d.flowId, {
787
- id: d.flowId,
788
- flowType: 'CommandToEvent',
789
- sourceId: d.commandStickyId,
790
- targetId: d.eventStickyId,
791
- fieldMappings: [],
792
- });
793
- break;
794
- case 'ReadModelQuickPlacedFromEvent':
795
- model.readModels.set(d.readModelStickyId, {
796
- id: d.readModelStickyId,
797
- name: d.name,
798
- fields: [],
799
- position: d.position,
800
- width: d.width,
801
- height: d.height,
802
- });
803
- model.flows.set(d.flowId, {
804
- id: d.flowId,
805
- flowType: 'EventToReadModel',
806
- sourceId: d.eventStickyId,
807
- targetId: d.readModelStickyId,
808
- fieldMappings: [],
809
- });
810
- break;
811
- case 'ScreenQuickPlacedFromReadModel':
812
- model.screens.set(d.screenId, {
813
- id: d.screenId,
814
- name: d.name,
815
- fields: [],
816
- position: d.position,
817
- width: d.width,
818
- height: d.height,
819
- });
820
- model.flows.set(d.flowId, {
821
- id: d.flowId,
822
- flowType: 'ReadModelToScreen',
823
- sourceId: d.readModelStickyId,
824
- targetId: d.screenId,
825
- fieldMappings: [],
826
- });
827
- break;
828
- case 'CommandQuickPlacedFromProcessor':
829
- model.commands.set(d.commandStickyId, {
830
- id: d.commandStickyId,
831
- name: d.name,
832
- fields: [],
833
- position: d.position,
834
- width: d.width,
835
- height: d.height,
836
- });
837
- model.flows.set(d.flowId, {
838
- id: d.flowId,
839
- flowType: 'ProcessorToCommand',
840
- sourceId: d.processorId,
841
- targetId: d.commandStickyId,
842
- fieldMappings: [],
843
- });
844
- break;
845
729
  // Field propagation events
846
730
  case 'FieldsPropagatedFromCommandToEvent':
847
731
  case 'FieldsPropagatedFromEventToCommand':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eventmodeler",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "CLI tool for interacting with Event Model files - query, update, and export event models from the terminal",
5
5
  "type": "module",
6
6
  "bin": {