@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.
- package/README.md +230 -35
- package/dist/ts-res-ui-components.d.ts +283 -19
- package/lib/components/orchestrator/ResourceOrchestrator.js +3 -1
- package/lib/components/pickers/ResourcePicker/ResourcePickerTree.js +29 -10
- package/lib/components/pickers/ResourcePicker/index.js +4 -2
- package/lib/components/views/ResolutionView/index.js +1 -1
- package/lib/hooks/useResolutionState.js +836 -235
- package/lib/namespaces/ResolutionTools.d.ts +2 -1
- package/lib/namespaces/ResolutionTools.js +2 -0
- package/lib/test/unit/workflows/resolutionWorkflows.test.d.ts +2 -0
- package/lib/test/unit/workflows/resourceCreation.test.d.ts +2 -0
- package/lib/test/unit/workflows/resultPatternExtensions.test.d.ts +2 -0
- package/lib/test/unit/workflows/validation.test.d.ts +2 -0
- package/lib/types/index.d.ts +124 -19
- package/lib/types/index.js +2 -1
- package/lib/utils/resolutionEditing.js +2 -1
- package/lib/utils/resourceSelectors.d.ts +146 -0
- package/lib/utils/resourceSelectors.js +233 -0
- package/lib-commonjs/components/orchestrator/ResourceOrchestrator.js +3 -1
- package/lib-commonjs/components/pickers/ResourcePicker/ResourcePickerTree.js +29 -10
- package/lib-commonjs/components/pickers/ResourcePicker/index.js +4 -2
- package/lib-commonjs/components/views/ResolutionView/index.js +1 -1
- package/lib-commonjs/hooks/useResolutionState.js +835 -234
- package/lib-commonjs/namespaces/ResolutionTools.js +10 -1
- package/lib-commonjs/types/index.js +10 -0
- package/lib-commonjs/utils/resolutionEditing.js +2 -1
- package/lib-commonjs/utils/resourceSelectors.js +242 -0
- package/package.json +7 -7
- package/src/components/orchestrator/ResourceOrchestrator.tsx +3 -1
- package/src/components/pickers/ResourcePicker/ResourcePickerTree.tsx +30 -10
- package/src/components/pickers/ResourcePicker/index.tsx +7 -2
- package/src/components/views/ResolutionView/index.tsx +1 -1
- package/src/hooks/useResolutionState.ts +1054 -257
- package/src/namespaces/ResolutionTools.ts +13 -1
- package/src/types/index.ts +131 -20
- package/src/utils/resolutionEditing.ts +2 -2
- 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
|
|
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
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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: () =>
|
|
490
|
+
clearEdits: () => Result<{
|
|
491
|
+
clearedCount: number;
|
|
492
|
+
}>;
|
|
439
493
|
/** Discard all pending edits */
|
|
440
|
-
discardEdits: () =>
|
|
441
|
-
|
|
442
|
-
|
|
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) =>
|
|
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) =>
|
|
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: () =>
|
|
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<
|
|
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
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
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<
|
|
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
|
package/lib/types/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
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 (
|
|
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,
|