@fgv/ts-res-ui-components 5.0.0-21 → 5.0.0-22

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 (37) hide show
  1. package/README.md +230 -35
  2. package/dist/ts-res-ui-components.d.ts +283 -19
  3. package/lib/components/orchestrator/ResourceOrchestrator.js +3 -1
  4. package/lib/components/pickers/ResourcePicker/ResourcePickerTree.js +29 -10
  5. package/lib/components/pickers/ResourcePicker/index.js +4 -2
  6. package/lib/components/views/ResolutionView/index.js +1 -1
  7. package/lib/hooks/useResolutionState.js +836 -235
  8. package/lib/namespaces/ResolutionTools.d.ts +2 -1
  9. package/lib/namespaces/ResolutionTools.js +2 -0
  10. package/lib/test/unit/workflows/resolutionWorkflows.test.d.ts +2 -0
  11. package/lib/test/unit/workflows/resourceCreation.test.d.ts +2 -0
  12. package/lib/test/unit/workflows/resultPatternExtensions.test.d.ts +2 -0
  13. package/lib/test/unit/workflows/validation.test.d.ts +2 -0
  14. package/lib/types/index.d.ts +124 -19
  15. package/lib/types/index.js +2 -1
  16. package/lib/utils/resolutionEditing.js +2 -1
  17. package/lib/utils/resourceSelectors.d.ts +146 -0
  18. package/lib/utils/resourceSelectors.js +233 -0
  19. package/lib-commonjs/components/orchestrator/ResourceOrchestrator.js +3 -1
  20. package/lib-commonjs/components/pickers/ResourcePicker/ResourcePickerTree.js +29 -10
  21. package/lib-commonjs/components/pickers/ResourcePicker/index.js +4 -2
  22. package/lib-commonjs/components/views/ResolutionView/index.js +1 -1
  23. package/lib-commonjs/hooks/useResolutionState.js +835 -234
  24. package/lib-commonjs/namespaces/ResolutionTools.js +10 -1
  25. package/lib-commonjs/types/index.js +10 -0
  26. package/lib-commonjs/utils/resolutionEditing.js +2 -1
  27. package/lib-commonjs/utils/resourceSelectors.js +242 -0
  28. package/package.json +7 -7
  29. package/src/components/orchestrator/ResourceOrchestrator.tsx +3 -1
  30. package/src/components/pickers/ResourcePicker/ResourcePickerTree.tsx +30 -10
  31. package/src/components/pickers/ResourcePicker/index.tsx +7 -2
  32. package/src/components/views/ResolutionView/index.tsx +1 -1
  33. package/src/hooks/useResolutionState.ts +1054 -257
  34. package/src/namespaces/ResolutionTools.ts +13 -1
  35. package/src/types/index.ts +131 -20
  36. package/src/utils/resolutionEditing.ts +2 -2
  37. package/src/utils/resourceSelectors.ts +278 -0
@@ -34,7 +34,8 @@ export { QualifierContextControl } from '../components/common/QualifierContextCo
34
34
  export { ResolutionContextOptionsControl } from '../components/common/ResolutionContextOptionsControl';
35
35
  export { useResolutionState } from '../hooks/useResolutionState';
36
36
  export { createResolverWithContext, evaluateConditionsForCandidate, resolveResourceDetailed, getAvailableQualifiers, hasPendingContextChanges, type ResolutionOptions } from '../utils/resolutionUtils';
37
- export type { ResolutionState, ResolutionActions, ResolutionViewProps, ResolutionResult, CandidateInfo, ConditionEvaluationResult, EditedResourceInfo, ResolutionContextOptions, QualifierControlOptions } from '../types';
37
+ export { getPendingAdditionsByType, isPendingAddition, deriveLeafId, deriveFullId, getPendingResourceTypes, getPendingResourceStats, validatePendingResourceKeys } from '../utils/resourceSelectors';
38
+ export type { ResolutionState, ResolutionActions, ResolutionViewProps, ResolutionResult, CandidateInfo, ConditionEvaluationResult, EditedResourceInfo, ResolutionContextOptions, QualifierControlOptions, CreatePendingResourceParams } from '../types';
38
39
  export type { EditableJsonViewProps } from '../components/views/ResolutionView/EditableJsonView';
39
40
  export type { ResolutionContextOptionsControlProps } from '../components/common/ResolutionContextOptionsControl';
40
41
  //# sourceMappingURL=ResolutionTools.d.ts.map
@@ -38,4 +38,6 @@ export { ResolutionContextOptionsControl } from '../components/common/Resolution
38
38
  export { useResolutionState } from '../hooks/useResolutionState';
39
39
  // Export utility functions
40
40
  export { createResolverWithContext, evaluateConditionsForCandidate, resolveResourceDetailed, getAvailableQualifiers, hasPendingContextChanges } from '../utils/resolutionUtils';
41
+ // Export resource selector utility functions for pending resource management
42
+ export { getPendingAdditionsByType, isPendingAddition, deriveLeafId, deriveFullId, getPendingResourceTypes, getPendingResourceStats, validatePendingResourceKeys } from '../utils/resourceSelectors';
41
43
  //# sourceMappingURL=ResolutionTools.js.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=resolutionWorkflows.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=resourceCreation.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=resultPatternExtensions.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=validation.test.d.ts.map
@@ -411,6 +411,58 @@ export interface ResolutionState {
411
411
  /** Whether there are pending resource additions or deletions */
412
412
  hasPendingResourceChanges: boolean;
413
413
  }
414
+ /**
415
+ * Parameters for creating a pending resource atomically.
416
+ *
417
+ * @example
418
+ * ```typescript
419
+ * const params: CreatePendingResourceParams = {
420
+ * id: 'platform.languages.az-AZ',
421
+ * resourceTypeName: 'json',
422
+ * json: { text: 'Welcome', locale: 'az-AZ' }
423
+ * };
424
+ * ```
425
+ *
426
+ * @public
427
+ */
428
+ export interface CreatePendingResourceParams {
429
+ /** Full resource ID (e.g., 'platform.languages.az-AZ') - must be unique */
430
+ id: string;
431
+ /** Name of the resource type to use for validation and template creation */
432
+ resourceTypeName: string;
433
+ /** JSON content for the resource candidate. If undefined, the resource type's base template will be used. */
434
+ json?: JsonValue;
435
+ }
436
+ /**
437
+ * Parameters for starting a new resource with optional pre-seeding.
438
+ *
439
+ * @example
440
+ * ```typescript
441
+ * // Basic start with just type
442
+ * const basicParams: StartNewResourceParams = {
443
+ * defaultTypeName: 'json'
444
+ * };
445
+ *
446
+ * // Pre-seeded start with full data
447
+ * const preSeededParams: StartNewResourceParams = {
448
+ * id: 'user.welcome',
449
+ * resourceTypeName: 'json',
450
+ * json: { text: 'Welcome!' }
451
+ * };
452
+ * ```
453
+ *
454
+ * @public
455
+ */
456
+ export interface StartNewResourceParams {
457
+ /** Resource type to use (optional - will use first available if not provided) */
458
+ defaultTypeName?: string;
459
+ /** Pre-seed with specific ID (optional) */
460
+ id?: string;
461
+ /** Pre-seed with specific resource type name (optional) */
462
+ resourceTypeName?: string;
463
+ /** Pre-seed with specific JSON content (optional) */
464
+ json?: JsonValue;
465
+ }
414
466
  /**
415
467
  * Actions available for managing resource resolution testing and editing.
416
468
  * Provides methods for context management, resource selection, and value editing.
@@ -419,41 +471,70 @@ export interface ResolutionState {
419
471
  */
420
472
  export interface ResolutionActions {
421
473
  /** Update a context value for resolution testing */
422
- updateContextValue: (qualifierName: string, value: string | undefined) => void;
474
+ updateContextValue: (qualifierName: string, value: string | undefined) => Result<void>;
423
475
  /** Apply pending context changes to the resolver (with optional host-managed values) */
424
- applyContext: (hostManagedValues?: Record<string, string | undefined>) => void;
476
+ applyContext: (hostManagedValues?: Record<string, string | undefined>) => Result<void>;
425
477
  /** Select a resource for detailed resolution testing */
426
- selectResource: (resourceId: string) => void;
478
+ selectResource: (resourceId: string) => Result<void>;
427
479
  /** Change how resolution results are displayed */
428
480
  setViewMode: (mode: 'composed' | 'best' | 'all' | 'raw') => void;
429
481
  /** Clear the resolution cache to force fresh resolution */
430
- resetCache: () => void;
482
+ resetCache: () => Result<void>;
431
483
  /** Save an edit to a resource value */
432
- saveEdit: (resourceId: string, editedValue: JsonValue, originalValue?: JsonValue) => void;
484
+ saveEdit: (resourceId: string, editedValue: JsonValue, originalValue?: JsonValue) => Result<void>;
433
485
  /** Get the edited value for a resource, if any */
434
486
  getEditedValue: (resourceId: string) => JsonValue | undefined;
435
487
  /** Check if a resource has been edited */
436
488
  hasEdit: (resourceId: string) => boolean;
437
489
  /** Clear all pending edits */
438
- clearEdits: () => void;
490
+ clearEdits: () => Result<{
491
+ clearedCount: number;
492
+ }>;
439
493
  /** Discard all pending edits */
440
- discardEdits: () => void;
441
- /** Start creating a new resource */
442
- startNewResource: (defaultTypeName?: string) => void;
494
+ discardEdits: () => Result<{
495
+ discardedCount: number;
496
+ }>;
497
+ /** Create a pending resource atomically with validation */
498
+ createPendingResource: (params: CreatePendingResourceParams) => Result<void>;
499
+ /** Start creating a new resource (enhanced with optional pre-seeding) */
500
+ startNewResource: (params?: StartNewResourceParams) => Result<{
501
+ draft: ResolutionState['newResourceDraft'];
502
+ diagnostics: string[];
503
+ }>;
443
504
  /** Update the resource ID for the new resource being created */
444
- updateNewResourceId: (id: string) => void;
505
+ updateNewResourceId: (id: string) => Result<{
506
+ draft: ResolutionState['newResourceDraft'];
507
+ diagnostics: string[];
508
+ }>;
445
509
  /** Select a resource type for the new resource */
446
- selectResourceType: (type: string) => void;
510
+ selectResourceType: (type: string) => Result<{
511
+ draft: ResolutionState['newResourceDraft'];
512
+ diagnostics: string[];
513
+ }>;
514
+ /** Update the JSON content for the new resource being created */
515
+ updateNewResourceJson: (json: JsonValue) => Result<{
516
+ draft: ResolutionState['newResourceDraft'];
517
+ diagnostics: string[];
518
+ }>;
447
519
  /** Add the new resource to pending resources (not applied yet) */
448
- saveNewResourceAsPending: () => void;
520
+ saveNewResourceAsPending: () => Result<{
521
+ pendingResources: Map<string, ResourceJson.Json.ILooseResourceDecl>;
522
+ diagnostics: string[];
523
+ }>;
449
524
  /** Cancel the new resource creation */
450
525
  cancelNewResource: () => void;
451
526
  /** Remove a pending resource */
452
- removePendingResource: (resourceId: string) => void;
527
+ removePendingResource: (resourceId: string) => Result<void>;
453
528
  /** Mark an existing resource for deletion */
454
529
  markResourceForDeletion: (resourceId: string) => void;
455
530
  /** Apply all pending resource additions and deletions */
456
- applyPendingResources: () => Promise<void>;
531
+ applyPendingResources: () => Promise<Result<{
532
+ appliedCount: number;
533
+ existingResourceEditCount: number;
534
+ pendingResourceEditCount: number;
535
+ newResourceCount: number;
536
+ deletionCount: number;
537
+ }>>;
457
538
  /** Discard all pending resource changes */
458
539
  discardPendingResources: () => void;
459
540
  }
@@ -1176,14 +1257,37 @@ export interface OrchestratorActions {
1176
1257
  hasResourceEdit: (resourceId: string) => boolean;
1177
1258
  clearResourceEdits: () => void;
1178
1259
  discardResourceEdits: () => void;
1179
- startNewResource: (defaultTypeName?: string) => void;
1180
- updateNewResourceId: (id: string) => void;
1181
- selectResourceType: (type: string) => void;
1182
- saveNewResourceAsPending: () => void;
1260
+ createPendingResource: (params: CreatePendingResourceParams) => Result<void>;
1261
+ startNewResource: (params?: StartNewResourceParams) => Result<{
1262
+ draft: ResolutionState['newResourceDraft'];
1263
+ diagnostics: string[];
1264
+ }>;
1265
+ updateNewResourceId: (id: string) => Result<{
1266
+ draft: ResolutionState['newResourceDraft'];
1267
+ diagnostics: string[];
1268
+ }>;
1269
+ selectResourceType: (type: string) => Result<{
1270
+ draft: ResolutionState['newResourceDraft'];
1271
+ diagnostics: string[];
1272
+ }>;
1273
+ updateNewResourceJson: (json: JsonValue) => Result<{
1274
+ draft: ResolutionState['newResourceDraft'];
1275
+ diagnostics: string[];
1276
+ }>;
1277
+ saveNewResourceAsPending: () => Result<{
1278
+ pendingResources: Map<string, ResourceJson.Json.ILooseResourceDecl>;
1279
+ diagnostics: string[];
1280
+ }>;
1183
1281
  cancelNewResource: () => void;
1184
1282
  removePendingResource: (resourceId: string) => void;
1185
1283
  markResourceForDeletion: (resourceId: string) => void;
1186
- applyPendingResources: () => Promise<void>;
1284
+ applyPendingResources: () => Promise<Result<{
1285
+ appliedCount: number;
1286
+ existingResourceEditCount: number;
1287
+ pendingResourceEditCount: number;
1288
+ newResourceCount: number;
1289
+ deletionCount: number;
1290
+ }>>;
1187
1291
  discardPendingResources: () => void;
1188
1292
  selectResource: (resourceId: string | null) => void;
1189
1293
  addMessage: (type: Message['type'], message: string) => void;
@@ -1192,4 +1296,5 @@ export interface OrchestratorActions {
1192
1296
  }
1193
1297
  export type { Result } from '@fgv/ts-utils';
1194
1298
  export type { JsonValue } from '@fgv/ts-json-base';
1299
+ export { getPendingAdditionsByType, isPendingAddition, deriveLeafId, deriveFullId, getPendingResourceTypes, getPendingResourceStats, validatePendingResourceKeys } from '../utils/resourceSelectors';
1195
1300
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,3 @@
1
- export {};
1
+ // Resource selector utility functions
2
+ export { getPendingAdditionsByType, isPendingAddition, deriveLeafId, deriveFullId, getPendingResourceTypes, getPendingResourceStats, validatePendingResourceKeys } from '../utils/resourceSelectors';
2
3
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,7 @@
1
1
  import { succeed, fail } from '@fgv/ts-utils';
2
2
  import { Runtime } from '@fgv/ts-res';
3
3
  import { Diff } from '@fgv/ts-json';
4
+ import { isJsonObject } from '@fgv/ts-json-base';
4
5
  /**
5
6
  * Validates an edited resource JSON value
6
7
  */
@@ -67,7 +68,7 @@ export function computeResourceDelta(baseValue, resolvedValue, editedValue) {
67
68
  * Recursively adds null values to delta for all properties in the deleted object
68
69
  */
69
70
  function addDeletionsAsNull(deleted, delta) {
70
- if (typeof deleted === 'object' && deleted !== null && !Array.isArray(deleted)) {
71
+ if (isJsonObject(deleted)) {
71
72
  const deletedObj = deleted;
72
73
  for (const key in deletedObj) {
73
74
  if (deletedObj.hasOwnProperty(key)) {
@@ -0,0 +1,146 @@
1
+ import { ResourceJson } from '@fgv/ts-res';
2
+ import { Result } from '@fgv/ts-utils';
3
+ /**
4
+ * Helper functions for working with pending resources and resource IDs.
5
+ * These utilities provide standardized ways to work with resource identification
6
+ * and selection patterns commonly needed by host applications.
7
+ *
8
+ * @public
9
+ */
10
+ /**
11
+ * Gets pending resources filtered by resource type.
12
+ *
13
+ * @param pendingResources - Map of pending resources (keys are guaranteed to be full resource IDs)
14
+ * @param resourceType - Resource type to filter by (e.g., 'json', 'string')
15
+ * @returns Array of pending resources of the specified type
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const jsonResources = getPendingAdditionsByType(pendingResources, 'json');
20
+ * const languageConfigs = getPendingAdditionsByType(pendingResources, 'languageConfig');
21
+ * ```
22
+ *
23
+ * @public
24
+ */
25
+ export declare function getPendingAdditionsByType(pendingResources: Map<string, ResourceJson.Json.ILooseResourceDecl>, resourceType: string): Array<{
26
+ id: string;
27
+ resource: ResourceJson.Json.ILooseResourceDecl;
28
+ }>;
29
+ /**
30
+ * Checks if a resource ID corresponds to a pending addition.
31
+ *
32
+ * @param resourceId - Resource ID to check (should be full resource ID)
33
+ * @param pendingResources - Map of pending resources
34
+ * @returns True if the resource ID exists in pending resources
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * if (isPendingAddition('platform.languages.az-AZ', pendingResources)) {
39
+ * console.log('This is a pending resource');
40
+ * }
41
+ * ```
42
+ *
43
+ * @public
44
+ */
45
+ export declare function isPendingAddition(resourceId: string, pendingResources: Map<string, ResourceJson.Json.ILooseResourceDecl>): boolean;
46
+ /**
47
+ * Derives a leaf ID from a full resource ID.
48
+ * Extracts the last segment after the final dot.
49
+ *
50
+ * @param fullResourceId - Full resource ID (e.g., 'platform.languages.az-AZ')
51
+ * @returns Result containing the leaf ID (e.g., 'az-AZ') or error if invalid format
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const leafResult = deriveLeafId('platform.languages.az-AZ');
56
+ * if (leafResult.isSuccess()) {
57
+ * console.log(leafResult.value); // 'az-AZ'
58
+ * }
59
+ *
60
+ * const invalidResult = deriveLeafId('invalid');
61
+ * if (invalidResult.isFailure()) {
62
+ * console.log(invalidResult.message); // 'Resource ID must contain at least one dot'
63
+ * }
64
+ * ```
65
+ *
66
+ * @public
67
+ */
68
+ export declare function deriveLeafId(fullResourceId: string): Result<string>;
69
+ /**
70
+ * Constructs a full resource ID from a root path and leaf ID.
71
+ *
72
+ * @param rootPath - Root path (e.g., 'platform.languages')
73
+ * @param leafId - Leaf identifier (e.g., 'az-AZ')
74
+ * @returns Result containing the full resource ID or error if invalid inputs
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const fullIdResult = deriveFullId('platform.languages', 'az-AZ');
79
+ * if (fullIdResult.isSuccess()) {
80
+ * console.log(fullIdResult.value); // 'platform.languages.az-AZ'
81
+ * }
82
+ *
83
+ * const invalidResult = deriveFullId('', 'az-AZ');
84
+ * if (invalidResult.isFailure()) {
85
+ * console.log(invalidResult.message); // 'Root path cannot be empty'
86
+ * }
87
+ * ```
88
+ *
89
+ * @public
90
+ */
91
+ export declare function deriveFullId(rootPath: string, leafId: string): Result<string>;
92
+ /**
93
+ * Gets all unique resource types from pending resources.
94
+ *
95
+ * @param pendingResources - Map of pending resources
96
+ * @returns Array of unique resource type names
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const types = getPendingResourceTypes(pendingResources);
101
+ * console.log(types); // ['json', 'string', 'languageConfig']
102
+ * ```
103
+ *
104
+ * @public
105
+ */
106
+ export declare function getPendingResourceTypes(pendingResources: Map<string, ResourceJson.Json.ILooseResourceDecl>): string[];
107
+ /**
108
+ * Gets statistics about pending resources.
109
+ * Provides summary information useful for UI displays.
110
+ *
111
+ * @param pendingResources - Map of pending resources
112
+ * @returns Statistics object with counts and breakdowns
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * const stats = getPendingResourceStats(pendingResources);
117
+ * console.log(`${stats.totalCount} pending resources`);
118
+ * console.log(`Types: ${Object.keys(stats.byType).join(', ')}`);
119
+ * ```
120
+ *
121
+ * @public
122
+ */
123
+ export declare function getPendingResourceStats(pendingResources: Map<string, ResourceJson.Json.ILooseResourceDecl>): {
124
+ totalCount: number;
125
+ byType: Record<string, number>;
126
+ resourceIds: string[];
127
+ };
128
+ /**
129
+ * Validates that all keys in a pending resources map are properly formatted as full resource IDs.
130
+ * This is a diagnostic function to ensure the pending resource key invariant is maintained.
131
+ *
132
+ * @param pendingResources - Map of pending resources to validate
133
+ * @returns Result indicating whether all keys are valid full resource IDs, or details about any issues found
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const validation = validatePendingResourceKeys(pendingResources);
138
+ * if (validation.isFailure()) {
139
+ * console.error('Pending resource key validation failed:', validation.message);
140
+ * }
141
+ * ```
142
+ *
143
+ * @public
144
+ */
145
+ export declare function validatePendingResourceKeys(pendingResources: Map<string, ResourceJson.Json.ILooseResourceDecl>): Result<void>;
146
+ //# sourceMappingURL=resourceSelectors.d.ts.map
@@ -0,0 +1,233 @@
1
+ import { succeed, fail } from '@fgv/ts-utils';
2
+ /**
3
+ * Helper functions for working with pending resources and resource IDs.
4
+ * These utilities provide standardized ways to work with resource identification
5
+ * and selection patterns commonly needed by host applications.
6
+ *
7
+ * @public
8
+ */
9
+ /**
10
+ * Gets pending resources filtered by resource type.
11
+ *
12
+ * @param pendingResources - Map of pending resources (keys are guaranteed to be full resource IDs)
13
+ * @param resourceType - Resource type to filter by (e.g., 'json', 'string')
14
+ * @returns Array of pending resources of the specified type
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const jsonResources = getPendingAdditionsByType(pendingResources, 'json');
19
+ * const languageConfigs = getPendingAdditionsByType(pendingResources, 'languageConfig');
20
+ * ```
21
+ *
22
+ * @public
23
+ */
24
+ export function getPendingAdditionsByType(pendingResources, resourceType) {
25
+ const results = [];
26
+ pendingResources.forEach((resource, id) => {
27
+ if (resource.resourceTypeName === resourceType) {
28
+ results.push({ id, resource });
29
+ }
30
+ });
31
+ return results;
32
+ }
33
+ /**
34
+ * Checks if a resource ID corresponds to a pending addition.
35
+ *
36
+ * @param resourceId - Resource ID to check (should be full resource ID)
37
+ * @param pendingResources - Map of pending resources
38
+ * @returns True if the resource ID exists in pending resources
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * if (isPendingAddition('platform.languages.az-AZ', pendingResources)) {
43
+ * console.log('This is a pending resource');
44
+ * }
45
+ * ```
46
+ *
47
+ * @public
48
+ */
49
+ export function isPendingAddition(resourceId, pendingResources) {
50
+ return pendingResources.has(resourceId);
51
+ }
52
+ /**
53
+ * Derives a leaf ID from a full resource ID.
54
+ * Extracts the last segment after the final dot.
55
+ *
56
+ * @param fullResourceId - Full resource ID (e.g., 'platform.languages.az-AZ')
57
+ * @returns Result containing the leaf ID (e.g., 'az-AZ') or error if invalid format
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const leafResult = deriveLeafId('platform.languages.az-AZ');
62
+ * if (leafResult.isSuccess()) {
63
+ * console.log(leafResult.value); // 'az-AZ'
64
+ * }
65
+ *
66
+ * const invalidResult = deriveLeafId('invalid');
67
+ * if (invalidResult.isFailure()) {
68
+ * console.log(invalidResult.message); // 'Resource ID must contain at least one dot'
69
+ * }
70
+ * ```
71
+ *
72
+ * @public
73
+ */
74
+ export function deriveLeafId(fullResourceId) {
75
+ if (!fullResourceId || fullResourceId.trim().length === 0) {
76
+ return fail('Resource ID cannot be empty');
77
+ }
78
+ const parts = fullResourceId.split('.');
79
+ if (parts.length < 2) {
80
+ return fail('Resource ID must contain at least one dot separator (e.g., "platform.languages.az-AZ")');
81
+ }
82
+ const leafId = parts[parts.length - 1];
83
+ if (!leafId || leafId.trim().length === 0) {
84
+ return fail('Leaf ID cannot be empty (resource ID cannot end with a dot)');
85
+ }
86
+ return succeed(leafId);
87
+ }
88
+ /**
89
+ * Constructs a full resource ID from a root path and leaf ID.
90
+ *
91
+ * @param rootPath - Root path (e.g., 'platform.languages')
92
+ * @param leafId - Leaf identifier (e.g., 'az-AZ')
93
+ * @returns Result containing the full resource ID or error if invalid inputs
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const fullIdResult = deriveFullId('platform.languages', 'az-AZ');
98
+ * if (fullIdResult.isSuccess()) {
99
+ * console.log(fullIdResult.value); // 'platform.languages.az-AZ'
100
+ * }
101
+ *
102
+ * const invalidResult = deriveFullId('', 'az-AZ');
103
+ * if (invalidResult.isFailure()) {
104
+ * console.log(invalidResult.message); // 'Root path cannot be empty'
105
+ * }
106
+ * ```
107
+ *
108
+ * @public
109
+ */
110
+ export function deriveFullId(rootPath, leafId) {
111
+ if (!rootPath || rootPath.trim().length === 0) {
112
+ return fail('Root path cannot be empty');
113
+ }
114
+ if (!leafId || leafId.trim().length === 0) {
115
+ return fail('Leaf ID cannot be empty');
116
+ }
117
+ // Remove leading/trailing dots from root path
118
+ const cleanRootPath = rootPath.replace(/^\.+|\.+$/g, '');
119
+ if (cleanRootPath.length === 0) {
120
+ return fail('Root path cannot consist only of dots');
121
+ }
122
+ // Remove leading/trailing dots from leaf ID
123
+ const cleanLeafId = leafId.replace(/^\.+|\.+$/g, '');
124
+ if (cleanLeafId.length === 0) {
125
+ return fail('Leaf ID cannot consist only of dots');
126
+ }
127
+ const fullId = `${cleanRootPath}.${cleanLeafId}`;
128
+ return succeed(fullId);
129
+ }
130
+ /**
131
+ * Gets all unique resource types from pending resources.
132
+ *
133
+ * @param pendingResources - Map of pending resources
134
+ * @returns Array of unique resource type names
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * const types = getPendingResourceTypes(pendingResources);
139
+ * console.log(types); // ['json', 'string', 'languageConfig']
140
+ * ```
141
+ *
142
+ * @public
143
+ */
144
+ export function getPendingResourceTypes(pendingResources) {
145
+ const types = new Set();
146
+ pendingResources.forEach((resource) => {
147
+ if (resource.resourceTypeName) {
148
+ types.add(resource.resourceTypeName);
149
+ }
150
+ });
151
+ return Array.from(types).sort();
152
+ }
153
+ /**
154
+ * Gets statistics about pending resources.
155
+ * Provides summary information useful for UI displays.
156
+ *
157
+ * @param pendingResources - Map of pending resources
158
+ * @returns Statistics object with counts and breakdowns
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * const stats = getPendingResourceStats(pendingResources);
163
+ * console.log(`${stats.totalCount} pending resources`);
164
+ * console.log(`Types: ${Object.keys(stats.byType).join(', ')}`);
165
+ * ```
166
+ *
167
+ * @public
168
+ */
169
+ export function getPendingResourceStats(pendingResources) {
170
+ const stats = {
171
+ totalCount: pendingResources.size,
172
+ byType: {},
173
+ resourceIds: Array.from(pendingResources.keys()).sort()
174
+ };
175
+ pendingResources.forEach((resource) => {
176
+ if (resource.resourceTypeName) {
177
+ stats.byType[resource.resourceTypeName] = (stats.byType[resource.resourceTypeName] || 0) + 1;
178
+ }
179
+ });
180
+ return stats;
181
+ }
182
+ /**
183
+ * Validates that all keys in a pending resources map are properly formatted as full resource IDs.
184
+ * This is a diagnostic function to ensure the pending resource key invariant is maintained.
185
+ *
186
+ * @param pendingResources - Map of pending resources to validate
187
+ * @returns Result indicating whether all keys are valid full resource IDs, or details about any issues found
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * const validation = validatePendingResourceKeys(pendingResources);
192
+ * if (validation.isFailure()) {
193
+ * console.error('Pending resource key validation failed:', validation.message);
194
+ * }
195
+ * ```
196
+ *
197
+ * @public
198
+ */
199
+ export function validatePendingResourceKeys(pendingResources) {
200
+ const issues = [];
201
+ pendingResources.forEach((resource, key) => {
202
+ // Check for empty keys
203
+ if (!key || key.trim().length === 0) {
204
+ issues.push('Found empty resource ID key');
205
+ return;
206
+ }
207
+ // Check for temporary IDs (should not be in pending resources)
208
+ if (key.startsWith('new-resource-')) {
209
+ issues.push(`Found temporary ID key: ${key}`);
210
+ }
211
+ // Check for minimum structure (at least one dot)
212
+ if (!key.includes('.')) {
213
+ issues.push(`Resource ID '${key}' appears to be a leaf ID rather than a full resource ID`);
214
+ }
215
+ // Check for trailing dots
216
+ if (key.endsWith('.')) {
217
+ issues.push(`Resource ID '${key}' ends with a dot`);
218
+ }
219
+ // Check for leading dots
220
+ if (key.startsWith('.')) {
221
+ issues.push(`Resource ID '${key}' starts with a dot`);
222
+ }
223
+ // Check for double dots
224
+ if (key.includes('..')) {
225
+ issues.push(`Resource ID '${key}' contains consecutive dots`);
226
+ }
227
+ });
228
+ if (issues.length > 0) {
229
+ return fail(`Pending resource key validation failed: ${issues.join('; ')}`);
230
+ }
231
+ return succeed(undefined);
232
+ }
233
+ //# sourceMappingURL=resourceSelectors.js.map
@@ -315,10 +315,12 @@ const ResourceOrchestrator = ({ children, initialConfiguration, qualifierTypeFac
315
315
  clearResourceEdits: resolutionData.actions.clearEdits,
316
316
  // Edits applied through unified applyPendingResources
317
317
  discardResourceEdits: resolutionData.actions.discardEdits,
318
- // Resource creation actions
318
+ // Resource creation actions (enhanced with atomic API)
319
+ createPendingResource: resolutionData.actions.createPendingResource,
319
320
  startNewResource: resolutionData.actions.startNewResource,
320
321
  updateNewResourceId: resolutionData.actions.updateNewResourceId,
321
322
  selectResourceType: resolutionData.actions.selectResourceType,
323
+ updateNewResourceJson: resolutionData.actions.updateNewResourceJson,
322
324
  saveNewResourceAsPending: resolutionData.actions.saveNewResourceAsPending,
323
325
  cancelNewResource: resolutionData.actions.cancelNewResource,
324
326
  removePendingResource: resolutionData.actions.removePendingResource,