@api-client/ui 0.5.29 → 0.5.31

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.
@@ -54,9 +54,9 @@ export declare enum IntentFlags {
54
54
  */
55
55
  ByReference = 4
56
56
  }
57
- export interface Intent {
57
+ export interface Intent<T = unknown> {
58
58
  action: string;
59
- data?: unknown;
59
+ data?: T;
60
60
  category?: string[];
61
61
  flags?: number;
62
62
  /**
@@ -68,6 +68,22 @@ export declare enum IntentResult {
68
68
  RESULT_CANCELED = 0,
69
69
  RESULT_OK = 1
70
70
  }
71
+ /**
72
+ * Type for activity constructor with static action property.
73
+ */
74
+ export interface ActivityConstructor {
75
+ new (parent: Application): Activity;
76
+ action?: string;
77
+ }
78
+ /**
79
+ * Enhanced activity registration options.
80
+ */
81
+ export interface ActivityRegistrationOptions {
82
+ /** Whether to override existing registrations */
83
+ override?: boolean;
84
+ /** Optional validation function for intents */
85
+ validateIntent?: (intent: Intent) => boolean;
86
+ }
71
87
  /**
72
88
  * Manages application activities.
73
89
  *
@@ -97,7 +113,13 @@ export declare class ActivityManager {
97
113
  private modalActivityStack;
98
114
  private generateActivityId;
99
115
  constructor(parent: Application);
100
- registerActivity(action: string, activityClass: typeof Activity): void;
116
+ /**
117
+ * Registers an activity class with enhanced options.
118
+ * @param action The action name to register the activity for.
119
+ * @param activityClass The activity class constructor.
120
+ * @param options Optional registration configuration.
121
+ */
122
+ registerActivity(action: string, activityClass: ActivityConstructor, options?: ActivityRegistrationOptions): void;
101
123
  /**
102
124
  * Creates an activity and pushes it into the stack, but it does not initialize any of the lifecycle methods.
103
125
  * It is a way to put an activity at some place of the stack,
@@ -150,13 +172,99 @@ export declare class ActivityManager {
150
172
  * @param activity The activity to bring to the foreground.
151
173
  */
152
174
  private updateCurrentActivity;
175
+ /**
176
+ * Safely executes an async lifecycle method with error handling.
177
+ * @param lifecycleMethod The lifecycle method to execute.
178
+ * @param activity The activity instance.
179
+ * @param methodName The name of the method for error reporting.
180
+ * @returns Promise that resolves when the method completes or rejects with wrapped error.
181
+ */
182
+ private safeExecuteLifecycleMethod;
153
183
  /**
154
184
  * Allows navigating back through the activity stack.
155
185
  * Call `ActivityManager.navigateBack()` when the user performs a back action
156
186
  * (e.g., clicks a back button).
187
+ * @returns Promise that resolves to true if navigation was successful, false if at root.
188
+ */
189
+ navigateBack(): Promise<boolean>;
190
+ /**
191
+ * Navigates to a specific activity in the stack, clearing activities above it.
192
+ * @param action The action of the activity to navigate to.
193
+ * @returns Promise that resolves to true if navigation was successful.
194
+ */
195
+ navigateToActivity(action: string): Promise<boolean>;
196
+ /**
197
+ * Clears the entire activity stack and starts a new root activity.
198
+ * @param intent The intent for the new root activity.
199
+ */
200
+ clearStackAndStart(intent: Intent): Promise<void>;
201
+ /**
202
+ * Creates an intent with better type safety and validation.
203
+ * @param action The action name.
204
+ * @param data Optional intent data.
205
+ * @param options Optional intent configuration.
206
+ * @returns A properly formed intent.
157
207
  */
158
- navigateBack(): Promise<void>;
208
+ createIntent<T = unknown>(action: string, data?: T, options?: {
209
+ category?: string[];
210
+ flags?: number;
211
+ requestCode?: number;
212
+ }): Intent<T>;
159
213
  createRequestCode(): number;
160
214
  findActiveActivity(action: string): Activity | undefined;
215
+ /**
216
+ * Transitions an activity from Created to Resumed state.
217
+ * @param activity The activity to transition.
218
+ * @returns true if successful, false if activity was destroyed during transition.
219
+ */
220
+ private transitionCreatedToResumed;
221
+ /**
222
+ * Transitions an activity from Started to Resumed state.
223
+ * @param activity The activity to transition.
224
+ * @returns true if successful, false if activity was destroyed during transition.
225
+ */
226
+ private transitionStartedToResumed;
227
+ /**
228
+ * Transitions an activity from Initialized to Resumed state.
229
+ * @param activity The activity to transition.
230
+ * @param intent The intent used to create the activity.
231
+ * @returns true if successful, false if activity was destroyed during transition.
232
+ */
233
+ private transitionInitializedToResumed;
234
+ /**
235
+ * Cleans up resources for destroyed activities to prevent memory leaks.
236
+ * @param activity The activity to clean up.
237
+ */
238
+ private cleanupActivity;
239
+ /**
240
+ * Validates that an activity stack is in a consistent state.
241
+ * @param stack The activity stack to validate.
242
+ * @returns Array of validation errors, empty if valid.
243
+ */
244
+ private validateStackConsistency;
245
+ /**
246
+ * Gets diagnostic information about the current state of the activity manager.
247
+ * Useful for debugging and monitoring.
248
+ * @returns Object containing current state information.
249
+ */
250
+ getDiagnostics(): {
251
+ totalActivities: number;
252
+ modalActivities: number;
253
+ currentActivity?: string;
254
+ topActivity?: string;
255
+ stackValidation: string[];
256
+ modalStackValidation: string[];
257
+ };
258
+ /**
259
+ * Gets all registered activity actions.
260
+ * @returns Array of registered action names.
261
+ */
262
+ getRegisteredActions(): string[];
263
+ /**
264
+ * Checks if an action is registered.
265
+ * @param action The action to check.
266
+ * @returns true if the action is registered, false otherwise.
267
+ */
268
+ isActionRegistered(action: string): boolean;
161
269
  }
162
270
  //# sourceMappingURL=ActivityManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActivityManager.d.ts","sourceRoot":"","sources":["../../../src/core/ActivityManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAGnD,oBAAY,iBAAiB;IAC3B;;;OAGG;IACH,WAAW,IAAA;IACX;;;OAGG;IACH,OAAO,IAAA;IACP;;;OAGG;IACH,OAAO,IAAA;IACP;;;OAGG;IACH,OAAO,IAAA;IACP;;;OAGG;IACH,MAAM,IAAA;IACN;;;OAGG;IACH,OAAO,IAAA;IACP;;;OAGG;IACH,SAAS,IAAA;CACV;AAED,oBAAY,WAAW;IACrB;;OAEG;IACH,KAAK,IAAS;IACd;;OAEG;IACH,SAAS,IAAS;IAClB;;;;;OAKG;IACH,WAAW,IAAS;CACrB;AAED,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,oBAAY,YAAY;IACtB,eAAe,IAAA;IACf,SAAS,IAAA;CACV;AA0BD;;;;;;;;;GASG;AACH,qBAAa,eAAe;;IAC1B,OAAO,CAAC,eAAe,CAAC,CAAU;IAElC;;OAEG;IACH,OAAO,CAAC,eAAe,CAAqC;IAE5D;;;OAGG;IACH,OAAO,CAAC,aAAa,CAA2B;IAEhD;;;OAGG;IACH,OAAO,CAAC,kBAAkB,CAA2B;IAErD,OAAO,CAAC,kBAAkB;gBAMd,MAAM,EAAE,WAAW;IAK/B,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,QAAQ,GAAG,IAAI;IAiBtE;;;;;;;;;OASG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOnD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAShC;;;;OAIG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmDlD,OAAO,CAAC,aAAa;IAgBrB;;;;;OAKG;IACG,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhF,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO;IAIxC;;OAEG;IACG,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;YAiCzC,oBAAoB;YAoBpB,wBAAwB;IAYtC,cAAc,IAAI,QAAQ,GAAG,SAAS;IAStC,kBAAkB,IAAI,QAAQ,GAAG,SAAS;IAI1C;;;;;;;OAOG;IACH,WAAW,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,QAAQ,GAAG,IAAI;IAwB9D;;;;;;OAMG;YACW,qBAAqB;IAsEnC;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQnC,iBAAiB,IAAI,MAAM;IAM3B,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;CAGzD"}
1
+ {"version":3,"file":"ActivityManager.d.ts","sourceRoot":"","sources":["../../../src/core/ActivityManager.ts"],"names":[],"mappings":"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"}
@@ -94,10 +94,22 @@ export class ActivityManager {
94
94
  this.#parent = parent;
95
95
  // this.reflectEvent = this.reflectEvent.bind(this)
96
96
  }
97
- registerActivity(action, activityClass) {
98
- if (this.activityClasses.has(action)) {
97
+ /**
98
+ * Registers an activity class with enhanced options.
99
+ * @param action The action name to register the activity for.
100
+ * @param activityClass The activity class constructor.
101
+ * @param options Optional registration configuration.
102
+ */
103
+ registerActivity(action, activityClass, options = {}) {
104
+ const { override = false } = options;
105
+ if (this.activityClasses.has(action) && !override) {
99
106
  // eslint-disable-next-line no-console
100
107
  console.warn(`Activity with action "${action}" already registered.`, new Error().stack);
108
+ return;
109
+ }
110
+ // Validate that the class extends Activity
111
+ if (typeof activityClass !== 'function') {
112
+ throw new Error(`Invalid activity class for action "${action}": must be a constructor function`);
101
113
  }
102
114
  this.activityClasses.set(action, activityClass);
103
115
  }
@@ -245,6 +257,8 @@ export class ActivityManager {
245
257
  stackEntry.activity.lifecycle = ActivityLifecycle.Stopped;
246
258
  await stackEntry.activity.onDestroy();
247
259
  stackEntry.activity.lifecycle = ActivityLifecycle.Destroyed;
260
+ // Clean up resources to prevent memory leaks
261
+ this.cleanupActivity(stackEntry.activity);
248
262
  await this.manageActivityResult(stackEntry.activity, stackEntry.intent);
249
263
  // Resume the previous activity
250
264
  await this.bringLastActivityToFront();
@@ -284,11 +298,11 @@ export class ActivityManager {
284
298
  async bringLastActivityToFront() {
285
299
  if (this.modalActivityStack.length > 0) {
286
300
  const previousEntry = this.modalActivityStack[this.activityStack.length - 1];
287
- await this.updateCurrentActivity(previousEntry.activity, false);
301
+ await this.updateCurrentActivity(previousEntry.activity, false, previousEntry.intent);
288
302
  }
289
303
  else if (this.activityStack.length > 0) {
290
304
  const previousEntry = this.activityStack[this.activityStack.length - 1];
291
- await this.updateCurrentActivity(previousEntry.activity, false);
305
+ await this.updateCurrentActivity(previousEntry.activity, false, previousEntry.intent);
292
306
  }
293
307
  else {
294
308
  this.currentActivity = undefined; // No more activities
@@ -343,7 +357,7 @@ export class ActivityManager {
343
357
  *
344
358
  * @param activity The activity to bring to the foreground.
345
359
  */
346
- async updateCurrentActivity(activity, isModal) {
360
+ async updateCurrentActivity(activity, isModal, intent) {
347
361
  this.setupStyles(activity, this.currentActivity);
348
362
  if (this.currentActivity &&
349
363
  this.currentActivity !== activity &&
@@ -353,61 +367,33 @@ export class ActivityManager {
353
367
  await this.currentActivity.onPause();
354
368
  this.currentActivity.lifecycle = ActivityLifecycle.Paused;
355
369
  }
356
- if (activity.lifecycle === ActivityLifecycle.Paused) {
357
- await activity.onResume();
358
- if (this.isDestroyed(activity)) {
359
- return;
360
- }
361
- activity.lifecycle = ActivityLifecycle.Resumed;
362
- activity.requestUpdate();
363
- }
364
- else if (activity.lifecycle === ActivityLifecycle.Stopped) {
365
- await activity.onRestart();
366
- activity.lifecycle = ActivityLifecycle.Resumed;
367
- }
368
- else if (activity.lifecycle === ActivityLifecycle.Destroyed) {
369
- throw new Error(`Invalid state. The activity is already destroyed.`);
370
- }
371
- else if (activity.lifecycle === ActivityLifecycle.Created) {
372
- await activity.onStart();
373
- if (this.isDestroyed(activity)) {
374
- return;
375
- }
376
- activity.lifecycle = ActivityLifecycle.Started;
377
- await activity.onResume();
378
- if (this.isDestroyed(activity)) {
379
- return;
380
- }
381
- activity.lifecycle = ActivityLifecycle.Resumed;
382
- activity.requestUpdate();
383
- }
384
- else if (activity.lifecycle === ActivityLifecycle.Started) {
385
- await activity.onResume();
386
- if (this.isDestroyed(activity)) {
387
- return;
388
- }
389
- activity.lifecycle = ActivityLifecycle.Resumed;
390
- activity.requestUpdate();
391
- }
392
- else if (activity.lifecycle === ActivityLifecycle.Initialized) {
393
- // TODO: Figure out intent passing here.
394
- await activity.onCreate();
395
- activity.lifecycle = ActivityLifecycle.Created;
396
- if (this.isDestroyed(activity)) {
397
- // the activity finished and the manager already took care of this situation.
398
- return;
399
- }
400
- await activity.onStart();
401
- if (this.isDestroyed(activity)) {
402
- return;
403
- }
404
- activity.lifecycle = ActivityLifecycle.Started;
405
- await activity.onResume();
406
- if (this.isDestroyed(activity)) {
407
- return;
408
- }
409
- activity.lifecycle = ActivityLifecycle.Resumed;
410
- activity.requestUpdate();
370
+ switch (activity.lifecycle) {
371
+ case ActivityLifecycle.Paused:
372
+ if (!(await this.transitionStartedToResumed(activity))) {
373
+ return;
374
+ }
375
+ break;
376
+ case ActivityLifecycle.Stopped:
377
+ await this.safeExecuteLifecycleMethod(() => activity.onRestart(), activity, 'onRestart');
378
+ activity.lifecycle = ActivityLifecycle.Resumed;
379
+ break;
380
+ case ActivityLifecycle.Destroyed:
381
+ throw new Error(`Invalid state. The activity is already destroyed.`);
382
+ case ActivityLifecycle.Created:
383
+ if (!(await this.transitionCreatedToResumed(activity))) {
384
+ return;
385
+ }
386
+ break;
387
+ case ActivityLifecycle.Started:
388
+ if (!(await this.transitionStartedToResumed(activity))) {
389
+ return;
390
+ }
391
+ break;
392
+ case ActivityLifecycle.Initialized:
393
+ if (!(await this.transitionInitializedToResumed(activity, intent))) {
394
+ return;
395
+ }
396
+ break;
411
397
  }
412
398
  if (!isModal) {
413
399
  // With a modal activity, it renders directly to the `<body>` and renders outside
@@ -415,17 +401,104 @@ export class ActivityManager {
415
401
  this.currentActivity = activity;
416
402
  }
417
403
  }
404
+ /**
405
+ * Safely executes an async lifecycle method with error handling.
406
+ * @param lifecycleMethod The lifecycle method to execute.
407
+ * @param activity The activity instance.
408
+ * @param methodName The name of the method for error reporting.
409
+ * @returns Promise that resolves when the method completes or rejects with wrapped error.
410
+ */
411
+ async safeExecuteLifecycleMethod(lifecycleMethod, activity, methodName) {
412
+ try {
413
+ await lifecycleMethod();
414
+ }
415
+ catch (error) {
416
+ const activityName = activity.constructor.name;
417
+ const wrappedError = new Error(`Error in ${activityName}.${methodName}(): ${error instanceof Error ? error.message : String(error)}`);
418
+ wrappedError.cause = error;
419
+ // Allow the activity to handle its own errors, but still propagate
420
+ this.#parent.dispatchEvent(new CustomEvent('activity-lifecycle-error', {
421
+ detail: { activity, methodName, error: wrappedError },
422
+ }));
423
+ throw wrappedError;
424
+ }
425
+ }
418
426
  /**
419
427
  * Allows navigating back through the activity stack.
420
428
  * Call `ActivityManager.navigateBack()` when the user performs a back action
421
429
  * (e.g., clicks a back button).
430
+ * @returns Promise that resolves to true if navigation was successful, false if at root.
422
431
  */
423
432
  async navigateBack() {
433
+ // Handle modal activities first
434
+ if (this.modalActivityStack.length > 0) {
435
+ const topModal = this.modalActivityStack[this.modalActivityStack.length - 1];
436
+ await this.finishActivity(topModal.activity);
437
+ return true;
438
+ }
439
+ // Handle regular activities
424
440
  if (this.activityStack.length > 1 && this.currentActivity) {
425
- // Finish current, which will resume previous
426
441
  await this.finishActivity(this.currentActivity);
442
+ return true;
427
443
  }
428
- // Else do nothing (or display a message, or exit the app)
444
+ // At root, cannot navigate back
445
+ return false;
446
+ }
447
+ /**
448
+ * Navigates to a specific activity in the stack, clearing activities above it.
449
+ * @param action The action of the activity to navigate to.
450
+ * @returns Promise that resolves to true if navigation was successful.
451
+ */
452
+ async navigateToActivity(action) {
453
+ const index = this.activityStack.findIndex((entry) => entry.action === action);
454
+ if (index === -1) {
455
+ return false;
456
+ }
457
+ // Finish all activities above the target
458
+ const activitiesToFinish = this.activityStack.slice(index + 1);
459
+ for (const entry of activitiesToFinish) {
460
+ await this.finishActivity(entry.activity);
461
+ }
462
+ // Bring the target activity to the front
463
+ const targetEntry = this.activityStack[index];
464
+ await this.updateCurrentActivity(targetEntry.activity, false, targetEntry.intent);
465
+ return true;
466
+ }
467
+ /**
468
+ * Clears the entire activity stack and starts a new root activity.
469
+ * @param intent The intent for the new root activity.
470
+ */
471
+ async clearStackAndStart(intent) {
472
+ // Finish all activities
473
+ const allActivities = [...this.activityStack, ...this.modalActivityStack];
474
+ for (const entry of allActivities) {
475
+ if (entry.activity.lifecycle !== ActivityLifecycle.Destroyed) {
476
+ await this.finishActivity(entry.activity);
477
+ }
478
+ }
479
+ // Clear stacks
480
+ this.activityStack.length = 0;
481
+ this.modalActivityStack.length = 0;
482
+ this.currentActivity = undefined;
483
+ // Start new root activity
484
+ await this.startActivity(intent);
485
+ }
486
+ /**
487
+ * Creates an intent with better type safety and validation.
488
+ * @param action The action name.
489
+ * @param data Optional intent data.
490
+ * @param options Optional intent configuration.
491
+ * @returns A properly formed intent.
492
+ */
493
+ createIntent(action, data, options = {}) {
494
+ if (!this.isActionRegistered(action)) {
495
+ throw new Error(`Cannot create intent for unregistered action: ${action}`);
496
+ }
497
+ return {
498
+ action,
499
+ data,
500
+ ...options,
501
+ };
429
502
  }
430
503
  createRequestCode() {
431
504
  const min = 1000;
@@ -435,5 +508,121 @@ export class ActivityManager {
435
508
  findActiveActivity(action) {
436
509
  return this.activityStack.find((entry) => entry.action === action)?.activity;
437
510
  }
511
+ /**
512
+ * Transitions an activity from Created to Resumed state.
513
+ * @param activity The activity to transition.
514
+ * @returns true if successful, false if activity was destroyed during transition.
515
+ */
516
+ async transitionCreatedToResumed(activity) {
517
+ await this.safeExecuteLifecycleMethod(() => activity.onStart(), activity, 'onStart');
518
+ if (this.isDestroyed(activity)) {
519
+ return false;
520
+ }
521
+ activity.lifecycle = ActivityLifecycle.Started;
522
+ await this.safeExecuteLifecycleMethod(() => activity.onResume(), activity, 'onResume');
523
+ if (this.isDestroyed(activity)) {
524
+ return false;
525
+ }
526
+ activity.lifecycle = ActivityLifecycle.Resumed;
527
+ activity.requestUpdate();
528
+ return true;
529
+ }
530
+ /**
531
+ * Transitions an activity from Started to Resumed state.
532
+ * @param activity The activity to transition.
533
+ * @returns true if successful, false if activity was destroyed during transition.
534
+ */
535
+ async transitionStartedToResumed(activity) {
536
+ await this.safeExecuteLifecycleMethod(() => activity.onResume(), activity, 'onResume');
537
+ if (this.isDestroyed(activity)) {
538
+ return false;
539
+ }
540
+ activity.lifecycle = ActivityLifecycle.Resumed;
541
+ activity.requestUpdate();
542
+ return true;
543
+ }
544
+ /**
545
+ * Transitions an activity from Initialized to Resumed state.
546
+ * @param activity The activity to transition.
547
+ * @param intent The intent used to create the activity.
548
+ * @returns true if successful, false if activity was destroyed during transition.
549
+ */
550
+ async transitionInitializedToResumed(activity, intent) {
551
+ await this.safeExecuteLifecycleMethod(() => activity.onCreate(intent), activity, 'onCreate');
552
+ activity.lifecycle = ActivityLifecycle.Created;
553
+ if (this.isDestroyed(activity)) {
554
+ return false;
555
+ }
556
+ return await this.transitionCreatedToResumed(activity);
557
+ }
558
+ /**
559
+ * Cleans up resources for destroyed activities to prevent memory leaks.
560
+ * @param activity The activity to clean up.
561
+ */
562
+ cleanupActivity(activity) {
563
+ // Remove any event listeners that might create memory leaks
564
+ // This changes the entire prototype chain of the activity,
565
+ // effectively making it a new object. We can't do that as the activity will loose some
566
+ // of its properties, so we don't do it for now.
567
+ // if (activity instanceof EventTarget) {
568
+ // // Clear all event listeners by replacing the activity with a new EventTarget
569
+ // // This is a defensive approach since we can't enumerate listeners
570
+ // Object.setPrototypeOf(activity, EventTarget.prototype)
571
+ // }
572
+ // Clear any references that might prevent garbage collection
573
+ activity.renderRoot = undefined;
574
+ }
575
+ /**
576
+ * Validates that an activity stack is in a consistent state.
577
+ * @param stack The activity stack to validate.
578
+ * @returns Array of validation errors, empty if valid.
579
+ */
580
+ validateStackConsistency(stack) {
581
+ const errors = [];
582
+ const seenIds = new Set();
583
+ for (const entry of stack) {
584
+ if (seenIds.has(entry.id)) {
585
+ errors.push(`Duplicate activity ID found: ${entry.id}`);
586
+ }
587
+ seenIds.add(entry.id);
588
+ if (!entry.activity || !entry.id || !entry.action) {
589
+ errors.push(`Invalid stack entry: missing required properties`);
590
+ }
591
+ if (entry.activity.lifecycle === ActivityLifecycle.Destroyed) {
592
+ errors.push(`Destroyed activity found in stack: ${entry.id}`);
593
+ }
594
+ }
595
+ return errors;
596
+ }
597
+ /**
598
+ * Gets diagnostic information about the current state of the activity manager.
599
+ * Useful for debugging and monitoring.
600
+ * @returns Object containing current state information.
601
+ */
602
+ getDiagnostics() {
603
+ return {
604
+ totalActivities: this.activityStack.length,
605
+ modalActivities: this.modalActivityStack.length,
606
+ currentActivity: this.currentActivity?.constructor.name,
607
+ topActivity: this.getTopActivity()?.constructor.name,
608
+ stackValidation: this.validateStackConsistency(this.activityStack),
609
+ modalStackValidation: this.validateStackConsistency(this.modalActivityStack),
610
+ };
611
+ }
612
+ /**
613
+ * Gets all registered activity actions.
614
+ * @returns Array of registered action names.
615
+ */
616
+ getRegisteredActions() {
617
+ return Array.from(this.activityClasses.keys());
618
+ }
619
+ /**
620
+ * Checks if an action is registered.
621
+ * @param action The action to check.
622
+ * @returns true if the action is registered, false otherwise.
623
+ */
624
+ isActionRegistered(action) {
625
+ return this.activityClasses.has(action);
626
+ }
438
627
  }
439
628
  //# sourceMappingURL=ActivityManager.js.map