@fnndsc/chili 3.2.5

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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/dist/chefs/chefs.d.ts +15 -0
  4. package/dist/chefs/chefs.js +195 -0
  5. package/dist/commands/compute/fields.d.ts +6 -0
  6. package/dist/commands/compute/fields.js +13 -0
  7. package/dist/commands/compute/list.d.ts +19 -0
  8. package/dist/commands/compute/list.js +20 -0
  9. package/dist/commands/connect/login.d.ts +16 -0
  10. package/dist/commands/connect/login.js +18 -0
  11. package/dist/commands/connect/logout.d.ts +6 -0
  12. package/dist/commands/connect/logout.js +16 -0
  13. package/dist/commands/feed/comments.d.ts +41 -0
  14. package/dist/commands/feed/comments.js +45 -0
  15. package/dist/commands/feed/create.d.ts +13 -0
  16. package/dist/commands/feed/create.js +40 -0
  17. package/dist/commands/feed/note.d.ts +21 -0
  18. package/dist/commands/feed/note.js +24 -0
  19. package/dist/commands/feeds/delete.d.ts +14 -0
  20. package/dist/commands/feeds/delete.js +34 -0
  21. package/dist/commands/feeds/fields.d.ts +6 -0
  22. package/dist/commands/feeds/fields.js +17 -0
  23. package/dist/commands/feeds/list.d.ts +17 -0
  24. package/dist/commands/feeds/list.js +38 -0
  25. package/dist/commands/feeds/share.d.ts +17 -0
  26. package/dist/commands/feeds/share.js +19 -0
  27. package/dist/commands/file/view.d.ts +8 -0
  28. package/dist/commands/file/view.js +25 -0
  29. package/dist/commands/files/delete.d.ts +16 -0
  30. package/dist/commands/files/delete.js +36 -0
  31. package/dist/commands/files/fields.d.ts +7 -0
  32. package/dist/commands/files/fields.js +18 -0
  33. package/dist/commands/files/list.d.ts +11 -0
  34. package/dist/commands/files/list.js +23 -0
  35. package/dist/commands/fs/cat.d.ts +15 -0
  36. package/dist/commands/fs/cat.js +26 -0
  37. package/dist/commands/fs/cp.d.ts +17 -0
  38. package/dist/commands/fs/cp.js +26 -0
  39. package/dist/commands/fs/create.d.ts +15 -0
  40. package/dist/commands/fs/create.js +83 -0
  41. package/dist/commands/fs/download.d.ts +25 -0
  42. package/dist/commands/fs/download.js +235 -0
  43. package/dist/commands/fs/edit.d.ts +16 -0
  44. package/dist/commands/fs/edit.js +25 -0
  45. package/dist/commands/fs/ls.d.ts +18 -0
  46. package/dist/commands/fs/ls.js +142 -0
  47. package/dist/commands/fs/mkdir.d.ts +8 -0
  48. package/dist/commands/fs/mkdir.js +21 -0
  49. package/dist/commands/fs/mv.d.ts +13 -0
  50. package/dist/commands/fs/mv.js +73 -0
  51. package/dist/commands/fs/rm.d.ts +24 -0
  52. package/dist/commands/fs/rm.js +108 -0
  53. package/dist/commands/fs/touch.d.ts +16 -0
  54. package/dist/commands/fs/touch.js +62 -0
  55. package/dist/commands/fs/upload.d.ts +55 -0
  56. package/dist/commands/fs/upload.js +221 -0
  57. package/dist/commands/groups/fields.d.ts +6 -0
  58. package/dist/commands/groups/fields.js +13 -0
  59. package/dist/commands/groups/list.d.ts +16 -0
  60. package/dist/commands/groups/list.js +26 -0
  61. package/dist/commands/man/doc.d.ts +16 -0
  62. package/dist/commands/man/doc.js +32 -0
  63. package/dist/commands/man/topics.d.ts +6 -0
  64. package/dist/commands/man/topics.js +27 -0
  65. package/dist/commands/pipeline/fields.d.ts +6 -0
  66. package/dist/commands/pipeline/fields.js +13 -0
  67. package/dist/commands/pipeline/run.d.ts +18 -0
  68. package/dist/commands/pipeline/run.js +33 -0
  69. package/dist/commands/plugin/readme.d.ts +14 -0
  70. package/dist/commands/plugin/readme.js +49 -0
  71. package/dist/commands/plugin/run.d.ts +10 -0
  72. package/dist/commands/plugin/run.js +29 -0
  73. package/dist/commands/plugin/search.d.ts +7 -0
  74. package/dist/commands/plugin/search.js +18 -0
  75. package/dist/commands/plugininstances/fields.d.ts +6 -0
  76. package/dist/commands/plugininstances/fields.js +13 -0
  77. package/dist/commands/plugininstances/list.d.ts +16 -0
  78. package/dist/commands/plugininstances/list.js +26 -0
  79. package/dist/commands/pluginmetas/fields.d.ts +6 -0
  80. package/dist/commands/pluginmetas/fields.js +13 -0
  81. package/dist/commands/pluginmetas/list.d.ts +16 -0
  82. package/dist/commands/pluginmetas/list.js +26 -0
  83. package/dist/commands/plugins/add.d.ts +34 -0
  84. package/dist/commands/plugins/add.js +374 -0
  85. package/dist/commands/plugins/delete.d.ts +14 -0
  86. package/dist/commands/plugins/delete.js +34 -0
  87. package/dist/commands/plugins/fields.d.ts +6 -0
  88. package/dist/commands/plugins/fields.js +17 -0
  89. package/dist/commands/plugins/list.d.ts +17 -0
  90. package/dist/commands/plugins/list.js +40 -0
  91. package/dist/commands/plugins/overview.d.ts +6 -0
  92. package/dist/commands/plugins/overview.js +17 -0
  93. package/dist/commands/store/list.d.ts +23 -0
  94. package/dist/commands/store/list.js +48 -0
  95. package/dist/commands/tags/fields.d.ts +6 -0
  96. package/dist/commands/tags/fields.js +13 -0
  97. package/dist/commands/tags/list.d.ts +16 -0
  98. package/dist/commands/tags/list.js +26 -0
  99. package/dist/commands/workflows/fields.d.ts +6 -0
  100. package/dist/commands/workflows/fields.js +13 -0
  101. package/dist/commands/workflows/list.d.ts +16 -0
  102. package/dist/commands/workflows/list.js +26 -0
  103. package/dist/config/colorConfig.d.ts +56 -0
  104. package/dist/config/colorConfig.js +161 -0
  105. package/dist/connect/connectHandler.d.ts +15 -0
  106. package/dist/connect/connectHandler.js +48 -0
  107. package/dist/context/contextCommand.d.ts +13 -0
  108. package/dist/context/contextCommand.js +193 -0
  109. package/dist/controllers/baseController.d.ts +43 -0
  110. package/dist/controllers/baseController.js +67 -0
  111. package/dist/controllers/feedController.d.ts +35 -0
  112. package/dist/controllers/feedController.js +53 -0
  113. package/dist/controllers/fileController.d.ts +56 -0
  114. package/dist/controllers/fileController.js +93 -0
  115. package/dist/controllers/pluginContextController.d.ts +24 -0
  116. package/dist/controllers/pluginContextController.js +49 -0
  117. package/dist/controllers/pluginController.d.ts +70 -0
  118. package/dist/controllers/pluginController.js +98 -0
  119. package/dist/controllers/pluginMetaController.d.ts +41 -0
  120. package/dist/controllers/pluginMetaController.js +52 -0
  121. package/dist/feeds/feedHandler.d.ts +64 -0
  122. package/dist/feeds/feedHandler.js +203 -0
  123. package/dist/filesystem/fileGroupHandler.d.ts +92 -0
  124. package/dist/filesystem/fileGroupHandler.js +318 -0
  125. package/dist/filesystem/filesystemHandler.d.ts +12 -0
  126. package/dist/filesystem/filesystemHandler.js +135 -0
  127. package/dist/filesystem/inodeCommand.d.ts +12 -0
  128. package/dist/filesystem/inodeCommand.js +37 -0
  129. package/dist/handlers/baseGroupHandler.d.ts +103 -0
  130. package/dist/handlers/baseGroupHandler.js +342 -0
  131. package/dist/index.d.ts +7 -0
  132. package/dist/index.js +239 -0
  133. package/dist/lfs/lfs.d.ts +25 -0
  134. package/dist/lfs/lfs.js +74 -0
  135. package/dist/man/man.d.ts +15 -0
  136. package/dist/man/man.js +31 -0
  137. package/dist/man/renderer.d.ts +26 -0
  138. package/dist/man/renderer.js +187 -0
  139. package/dist/models/feed.d.ts +18 -0
  140. package/dist/models/feed.js +8 -0
  141. package/dist/models/listing.d.ts +33 -0
  142. package/dist/models/listing.js +9 -0
  143. package/dist/models/plugin.d.ts +27 -0
  144. package/dist/models/plugin.js +1 -0
  145. package/dist/models/resource.d.ts +39 -0
  146. package/dist/models/resource.js +9 -0
  147. package/dist/pacs/pacsQueryHandler.d.ts +55 -0
  148. package/dist/pacs/pacsQueryHandler.js +196 -0
  149. package/dist/pacs/pacsQueryPayload.d.ts +19 -0
  150. package/dist/pacs/pacsQueryPayload.js +44 -0
  151. package/dist/pacs/pacsResultRender.d.ts +20 -0
  152. package/dist/pacs/pacsResultRender.js +141 -0
  153. package/dist/pacs/pacsRetrieveHandler.d.ts +87 -0
  154. package/dist/pacs/pacsRetrieveHandler.js +272 -0
  155. package/dist/pacs/pacsServerHandler.d.ts +15 -0
  156. package/dist/pacs/pacsServerHandler.js +32 -0
  157. package/dist/path/pathCommand.d.ts +64 -0
  158. package/dist/path/pathCommand.js +805 -0
  159. package/dist/path/pathMapper.d.ts +189 -0
  160. package/dist/path/pathMapper.js +382 -0
  161. package/dist/plugins/pluginGroupHandler.d.ts +42 -0
  162. package/dist/plugins/pluginGroupHandler.js +90 -0
  163. package/dist/plugins/pluginHandler.d.ts +87 -0
  164. package/dist/plugins/pluginHandler.js +303 -0
  165. package/dist/plugins/pluginMetaHandler.d.ts +21 -0
  166. package/dist/plugins/pluginMetaHandler.js +27 -0
  167. package/dist/screen/screen.d.ts +214 -0
  168. package/dist/screen/screen.js +478 -0
  169. package/dist/utils/admin_prompt.d.ts +42 -0
  170. package/dist/utils/admin_prompt.js +105 -0
  171. package/dist/utils/cli.d.ts +67 -0
  172. package/dist/utils/cli.js +112 -0
  173. package/dist/utils/docker.d.ts +74 -0
  174. package/dist/utils/docker.js +155 -0
  175. package/dist/utils/input_format.d.ts +77 -0
  176. package/dist/utils/input_format.js +135 -0
  177. package/dist/utils/sort.d.ts +29 -0
  178. package/dist/utils/sort.js +63 -0
  179. package/dist/utils/ui.d.ts +7 -0
  180. package/dist/utils/ui.js +26 -0
  181. package/dist/utils.d.ts +15 -0
  182. package/dist/utils.js +14 -0
  183. package/dist/views/compute.d.ts +17 -0
  184. package/dist/views/compute.js +40 -0
  185. package/dist/views/connect.d.ts +13 -0
  186. package/dist/views/connect.js +35 -0
  187. package/dist/views/feed.d.ts +44 -0
  188. package/dist/views/feed.js +110 -0
  189. package/dist/views/file.d.ts +25 -0
  190. package/dist/views/file.js +71 -0
  191. package/dist/views/fs.d.ts +51 -0
  192. package/dist/views/fs.js +105 -0
  193. package/dist/views/ls.d.ts +37 -0
  194. package/dist/views/ls.js +190 -0
  195. package/dist/views/plugin.d.ts +28 -0
  196. package/dist/views/plugin.js +71 -0
  197. package/dist/views/pluginParameters.d.ts +17 -0
  198. package/dist/views/pluginParameters.js +79 -0
  199. package/docs/00_intro.adoc +25 -0
  200. package/docs/01_setup.adoc +63 -0
  201. package/docs/02_context.adoc +128 -0
  202. package/docs/03_searchable.adoc +134 -0
  203. package/docs/10_everyday_examples.adoc +9 -0
  204. package/docs/11_upload_data.adoc +146 -0
  205. package/docs/12_create_a_feed.adoc +9 -0
  206. package/docs/13_run_a_plugin.adoc +271 -0
  207. package/docs/20_plugins.adoc +16 -0
  208. package/docs/21_pluginReadme.adoc +123 -0
  209. package/docs/30_filesystem.adoc +93 -0
  210. package/docs/31_fileops.adoc +51 -0
  211. package/docs/32_file_content.adoc +471 -0
  212. package/docs/_architecture.adoc +48 -0
  213. package/docs/_deepContext.adoc +84 -0
  214. package/docs/_roadmap.adoc +42 -0
  215. package/docs/_searchable.adoc +187 -0
  216. package/docs/_state.adoc +68 -0
  217. package/docs/pacs.adoc +101 -0
  218. package/docs/pathMapper.md +246 -0
  219. package/package.json +92 -0
@@ -0,0 +1,189 @@
1
+ /**
2
+ * @file Singleton path mapper for logical-to-physical ChRIS path resolution.
3
+ *
4
+ * Maintains a cached mapping of logical paths to physical locations,
5
+ * enabling incremental resolution by reusing cached path prefixes.
6
+ *
7
+ * Example:
8
+ * - First call: '/home/user/public/feed_4' → Full resolution, cache prefix
9
+ * - Second call: '/home/user/public/feed_5' → Reuse '/home/user/public', resolve only 'feed_5'
10
+ *
11
+ * This eliminates redundant tree walking and API calls for paths with common prefixes.
12
+ *
13
+ * @module
14
+ */
15
+ import { Result } from '@fnndsc/cumin';
16
+ /**
17
+ * Statistics for cache performance monitoring.
18
+ */
19
+ export interface CacheStats {
20
+ hits: number;
21
+ misses: number;
22
+ size: number;
23
+ hitRate: number;
24
+ }
25
+ /**
26
+ * Singleton PathMapper class for logical-to-physical path resolution.
27
+ *
28
+ * Maintains hierarchical cache of path mappings to minimize repeated
29
+ * link resolution operations.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const mapper = PathMapper.instance_get();
34
+ *
35
+ * // First resolution
36
+ * const result1 = await mapper.logical_toPhysical('/home/user/public/feed_4');
37
+ * // → Resolves /home/user/public → /SHARED, then feed_4
38
+ * // → Caches intermediate mappings
39
+ *
40
+ * // Second resolution - prefix reuse
41
+ * const result2 = await mapper.logical_toPhysical('/home/user/public/feed_5');
42
+ * // → Reuses cached '/home/user/public' → '/SHARED'
43
+ * // → Resolves only 'feed_5'
44
+ * ```
45
+ */
46
+ export declare class PathMapper {
47
+ private static instance;
48
+ private cache;
49
+ private readonly defaultTTL;
50
+ private stats;
51
+ /**
52
+ * Private constructor enforces singleton pattern.
53
+ */
54
+ private constructor();
55
+ /**
56
+ * Gets the singleton PathMapper instance.
57
+ *
58
+ * @returns The singleton PathMapper instance.
59
+ */
60
+ static instance_get(): PathMapper;
61
+ /**
62
+ * Resets the singleton instance (for testing).
63
+ *
64
+ * @internal
65
+ */
66
+ static instance_reset(): void;
67
+ /**
68
+ * Resolves a logical path to its physical location.
69
+ *
70
+ * Uses cached path prefixes when available, resolving only the
71
+ * uncached suffix to minimize API calls.
72
+ *
73
+ * @param logicalPath - The logical path to resolve.
74
+ * @returns Result containing the physical path, or Err on failure.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const mapper = PathMapper.instance_get();
79
+ *
80
+ * // First call: full resolution
81
+ * const result1 = await mapper.logical_toPhysical('/home/user/public/feed_4');
82
+ * // → Resolves /home/user/public → /SHARED, then feed_4
83
+ * // → Caches: '/home/user/public' → '/SHARED'
84
+ * // → Caches: '/home/user/public/feed_4' → '/SHARED/feed_4'
85
+ *
86
+ * // Second call: prefix reuse
87
+ * const result2 = await mapper.logical_toPhysical('/home/user/public/feed_5');
88
+ * // → Reuses cached '/home/user/public' → '/SHARED'
89
+ * // → Resolves only 'feed_5'
90
+ * ```
91
+ */
92
+ logical_toPhysical(logicalPath: string): Promise<Result<string>>;
93
+ /**
94
+ * Finds the longest cached prefix of a logical path.
95
+ *
96
+ * Walks up the path hierarchy to find the deepest cached mapping,
97
+ * returning the cached physical prefix and remaining suffix.
98
+ *
99
+ * @param logicalPath - The full logical path.
100
+ * @returns Object with cached prefix and remaining suffix.
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * // Cache contains: '/home/user/public' → '/SHARED'
105
+ * const result = mapper.longestCachedPrefix_find('/home/user/public/feed_4/files');
106
+ * // Returns:
107
+ * // {
108
+ * // prefixLogical: '/home/user/public',
109
+ * // prefixPhysical: '/SHARED',
110
+ * // suffix: 'feed_4/files'
111
+ * // }
112
+ * ```
113
+ */
114
+ private longestCachedPrefix_find;
115
+ /**
116
+ * Resolves a path suffix by checking each component for links.
117
+ *
118
+ * Walks the suffix path, checking each component to see if it's a link.
119
+ * Caches intermediate mappings along the way for future prefix reuse.
120
+ *
121
+ * @param suffix - The path suffix to resolve (e.g., 'feed_4/files').
122
+ * @param physicalBase - The physical base path to build upon.
123
+ * @param logicalBase - The logical base path (for caching intermediate results).
124
+ * @returns Result containing the resolved physical path.
125
+ */
126
+ private suffix_resolve;
127
+ /**
128
+ * Checks if a path is a link and returns its target.
129
+ *
130
+ * Optimizes resolution by checking ListCache first. If the parent directory
131
+ * is cached, we can determine if the candidate is a link without an API call.
132
+ * Falls back to API (fetching all links in parent) if not cached.
133
+ *
134
+ * @param candidatePhysical - The physical path to check.
135
+ * @param candidateLogical - The logical path (used for cache lookup).
136
+ * @returns The link target if it's a link, null otherwise.
137
+ */
138
+ private link_checkAndResolve;
139
+ /**
140
+ * Gets a cached mapping if it exists and hasn't expired.
141
+ *
142
+ * @param logicalPath - The logical path to look up.
143
+ * @returns The cached physical path, or null if not cached or expired.
144
+ */
145
+ private cache_get;
146
+ /**
147
+ * Caches a logical-to-physical path mapping.
148
+ *
149
+ * @param logicalPath - The logical path.
150
+ * @param physicalPath - The corresponding physical path.
151
+ * @param ttl - Optional TTL in milliseconds (defaults to 30s).
152
+ */
153
+ private cache_set;
154
+ /**
155
+ * Invalidates all cached mappings with a given logical path prefix.
156
+ *
157
+ * Call this when links are created/deleted/modified.
158
+ *
159
+ * @param logicalPathPrefix - The logical path prefix to invalidate.
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * // Link changed: /home/user/public → /SHARED becomes → /PUBLIC
164
+ * mapper.cache_invalidate('/home/user/public');
165
+ * // Invalidates:
166
+ * // - /home/user/public
167
+ * // - /home/user/public/feed_4
168
+ * // - /home/user/public/feed_5
169
+ * // - ... etc
170
+ * ```
171
+ */
172
+ cache_invalidate(logicalPathPrefix: string): void;
173
+ /**
174
+ * Clears all cached mappings.
175
+ */
176
+ cache_clear(): void;
177
+ /**
178
+ * Gets cache statistics.
179
+ *
180
+ * @returns Cache statistics including hit rate and size.
181
+ */
182
+ stats_get(): CacheStats;
183
+ }
184
+ /**
185
+ * Convenience function to get the singleton PathMapper instance.
186
+ *
187
+ * @returns The singleton PathMapper instance.
188
+ */
189
+ export declare function pathMapper_get(): PathMapper;
@@ -0,0 +1,382 @@
1
+ /**
2
+ * @file Singleton path mapper for logical-to-physical ChRIS path resolution.
3
+ *
4
+ * Maintains a cached mapping of logical paths to physical locations,
5
+ * enabling incremental resolution by reusing cached path prefixes.
6
+ *
7
+ * Example:
8
+ * - First call: '/home/user/public/feed_4' → Full resolution, cache prefix
9
+ * - Second call: '/home/user/public/feed_5' → Reuse '/home/user/public', resolve only 'feed_5'
10
+ *
11
+ * This eliminates redundant tree walking and API calls for paths with common prefixes.
12
+ *
13
+ * @module
14
+ */
15
+ import { Ok, Err, errorStack } from '@fnndsc/cumin';
16
+ import { files_listAll } from '@fnndsc/salsa';
17
+ import * as path from 'path';
18
+ import { listCache_get } from '@fnndsc/cumin';
19
+ /**
20
+ * Singleton PathMapper class for logical-to-physical path resolution.
21
+ *
22
+ * Maintains hierarchical cache of path mappings to minimize repeated
23
+ * link resolution operations.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const mapper = PathMapper.instance_get();
28
+ *
29
+ * // First resolution
30
+ * const result1 = await mapper.logical_toPhysical('/home/user/public/feed_4');
31
+ * // → Resolves /home/user/public → /SHARED, then feed_4
32
+ * // → Caches intermediate mappings
33
+ *
34
+ * // Second resolution - prefix reuse
35
+ * const result2 = await mapper.logical_toPhysical('/home/user/public/feed_5');
36
+ * // → Reuses cached '/home/user/public' → '/SHARED'
37
+ * // → Resolves only 'feed_5'
38
+ * ```
39
+ */
40
+ export class PathMapper {
41
+ /**
42
+ * Private constructor enforces singleton pattern.
43
+ */
44
+ constructor() {
45
+ this.cache = new Map();
46
+ this.defaultTTL = 30000; // 30 seconds
47
+ this.stats = { hits: 0, misses: 0 };
48
+ }
49
+ /**
50
+ * Gets the singleton PathMapper instance.
51
+ *
52
+ * @returns The singleton PathMapper instance.
53
+ */
54
+ static instance_get() {
55
+ if (!PathMapper.instance) {
56
+ PathMapper.instance = new PathMapper();
57
+ }
58
+ return PathMapper.instance;
59
+ }
60
+ /**
61
+ * Resets the singleton instance (for testing).
62
+ *
63
+ * @internal
64
+ */
65
+ static instance_reset() {
66
+ PathMapper.instance = null;
67
+ }
68
+ /**
69
+ * Resolves a logical path to its physical location.
70
+ *
71
+ * Uses cached path prefixes when available, resolving only the
72
+ * uncached suffix to minimize API calls.
73
+ *
74
+ * @param logicalPath - The logical path to resolve.
75
+ * @returns Result containing the physical path, or Err on failure.
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * const mapper = PathMapper.instance_get();
80
+ *
81
+ * // First call: full resolution
82
+ * const result1 = await mapper.logical_toPhysical('/home/user/public/feed_4');
83
+ * // → Resolves /home/user/public → /SHARED, then feed_4
84
+ * // → Caches: '/home/user/public' → '/SHARED'
85
+ * // → Caches: '/home/user/public/feed_4' → '/SHARED/feed_4'
86
+ *
87
+ * // Second call: prefix reuse
88
+ * const result2 = await mapper.logical_toPhysical('/home/user/public/feed_5');
89
+ * // → Reuses cached '/home/user/public' → '/SHARED'
90
+ * // → Resolves only 'feed_5'
91
+ * ```
92
+ */
93
+ async logical_toPhysical(logicalPath) {
94
+ // 1. Validate input
95
+ if (!logicalPath || typeof logicalPath !== 'string') {
96
+ errorStack.stack_push('error', 'Invalid path: path must be a non-empty string');
97
+ return Err();
98
+ }
99
+ // 2. Normalize path
100
+ const normalizedPath = logicalPath.startsWith('/')
101
+ ? logicalPath
102
+ : `/${logicalPath}`;
103
+ // 3. Check exact cache hit
104
+ const exactMatch = this.cache_get(normalizedPath);
105
+ if (exactMatch !== null) {
106
+ this.stats.hits++;
107
+ return Ok(exactMatch);
108
+ }
109
+ // 4. Find longest cached prefix
110
+ const prefixMatch = this.longestCachedPrefix_find(normalizedPath);
111
+ // 5. Resolve suffix incrementally
112
+ const resolvedPhysical = await this.suffix_resolve(prefixMatch.suffix, prefixMatch.prefixPhysical, prefixMatch.prefixLogical);
113
+ if (!resolvedPhysical.ok) {
114
+ this.stats.misses++;
115
+ return Err();
116
+ }
117
+ // 6. Cache the full resolution
118
+ this.cache_set(normalizedPath, resolvedPhysical.value);
119
+ this.stats.misses++;
120
+ return Ok(resolvedPhysical.value);
121
+ }
122
+ /**
123
+ * Finds the longest cached prefix of a logical path.
124
+ *
125
+ * Walks up the path hierarchy to find the deepest cached mapping,
126
+ * returning the cached physical prefix and remaining suffix.
127
+ *
128
+ * @param logicalPath - The full logical path.
129
+ * @returns Object with cached prefix and remaining suffix.
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * // Cache contains: '/home/user/public' → '/SHARED'
134
+ * const result = mapper.longestCachedPrefix_find('/home/user/public/feed_4/files');
135
+ * // Returns:
136
+ * // {
137
+ * // prefixLogical: '/home/user/public',
138
+ * // prefixPhysical: '/SHARED',
139
+ * // suffix: 'feed_4/files'
140
+ * // }
141
+ * ```
142
+ */
143
+ longestCachedPrefix_find(logicalPath) {
144
+ const parts = logicalPath.split('/').filter((p) => p.length > 0);
145
+ // Walk down from full path to root
146
+ for (let i = parts.length; i >= 0; i--) {
147
+ const candidatePrefix = i === 0
148
+ ? '/'
149
+ : '/' + parts.slice(0, i).join('/');
150
+ const cached = this.cache_get(candidatePrefix);
151
+ if (cached !== null) {
152
+ const suffixParts = parts.slice(i);
153
+ const suffix = suffixParts.join('/');
154
+ return {
155
+ prefixLogical: candidatePrefix,
156
+ prefixPhysical: cached,
157
+ suffix: suffix
158
+ };
159
+ }
160
+ }
161
+ // No cached prefix found, start from root
162
+ return {
163
+ prefixLogical: '/',
164
+ prefixPhysical: '/',
165
+ suffix: parts.join('/')
166
+ };
167
+ }
168
+ /**
169
+ * Resolves a path suffix by checking each component for links.
170
+ *
171
+ * Walks the suffix path, checking each component to see if it's a link.
172
+ * Caches intermediate mappings along the way for future prefix reuse.
173
+ *
174
+ * @param suffix - The path suffix to resolve (e.g., 'feed_4/files').
175
+ * @param physicalBase - The physical base path to build upon.
176
+ * @param logicalBase - The logical base path (for caching intermediate results).
177
+ * @returns Result containing the resolved physical path.
178
+ */
179
+ async suffix_resolve(suffix, physicalBase, logicalBase) {
180
+ if (!suffix || suffix.length === 0) {
181
+ return Ok(physicalBase);
182
+ }
183
+ const parts = suffix.split('/').filter((p) => p.length > 0);
184
+ let physicalCurrent = physicalBase;
185
+ let logicalCurrent = logicalBase;
186
+ for (let i = 0; i < parts.length; i++) {
187
+ const part = parts[i];
188
+ const candidatePhysical = physicalCurrent === '/'
189
+ ? `/${part}`
190
+ : `${physicalCurrent}/${part}`;
191
+ const candidateLogical = logicalCurrent === '/'
192
+ ? `/${part}`
193
+ : `${logicalCurrent}/${part}`;
194
+ try {
195
+ const linkTarget = await this.link_checkAndResolve(candidatePhysical, candidateLogical);
196
+ if (linkTarget) {
197
+ // It's a link! Jump to target
198
+ physicalCurrent = linkTarget;
199
+ logicalCurrent = candidateLogical;
200
+ // Cache this intermediate mapping
201
+ this.cache_set(candidateLogical, linkTarget);
202
+ }
203
+ else {
204
+ // Not a link, continue building path
205
+ physicalCurrent = candidatePhysical;
206
+ logicalCurrent = candidateLogical;
207
+ // Cache this intermediate mapping
208
+ this.cache_set(candidateLogical, candidatePhysical);
209
+ }
210
+ }
211
+ catch (error) {
212
+ // Link resolution failed - log warning but continue
213
+ const msg = error instanceof Error ? error.message : String(error);
214
+ errorStack.stack_push('warning', `Failed to check if '${candidatePhysical}' is a link: ${msg}. Treating as regular path.`);
215
+ // Assume it's not a link and continue
216
+ physicalCurrent = candidatePhysical;
217
+ logicalCurrent = candidateLogical;
218
+ this.cache_set(candidateLogical, candidatePhysical);
219
+ }
220
+ }
221
+ return Ok(physicalCurrent);
222
+ }
223
+ /**
224
+ * Checks if a path is a link and returns its target.
225
+ *
226
+ * Optimizes resolution by checking ListCache first. If the parent directory
227
+ * is cached, we can determine if the candidate is a link without an API call.
228
+ * Falls back to API (fetching all links in parent) if not cached.
229
+ *
230
+ * @param candidatePhysical - The physical path to check.
231
+ * @param candidateLogical - The logical path (used for cache lookup).
232
+ * @returns The link target if it's a link, null otherwise.
233
+ */
234
+ async link_checkAndResolve(candidatePhysical, candidateLogical) {
235
+ // 1. Fast Path: Check ListCache using logical path
236
+ // If we have the parent directory cached, we can check if the item is a link there.
237
+ const logicalParent = path.dirname(candidateLogical);
238
+ const itemName = path.basename(candidateLogical);
239
+ const listCache = listCache_get();
240
+ const cached = listCache.cache_get(logicalParent);
241
+ if (cached && cached.data && Array.isArray(cached.data)) {
242
+ // Look for the item in the cached listing
243
+ const item = cached.data.find((i) => i.name === itemName);
244
+ if (item) {
245
+ // Item found in cache!
246
+ if (item.type === 'link' && item.target) {
247
+ return item.target;
248
+ }
249
+ // Found but not a link (or has no target) -> return null
250
+ return null;
251
+ }
252
+ // Item not found in cache.
253
+ // It might be hidden or newly created. Fall back to API.
254
+ }
255
+ // 2. Slow Path: Check API using physical path
256
+ const parts = candidatePhysical.split('/');
257
+ parts.pop(); // Remove filename
258
+ const parentDir = parts.join('/') || '/';
259
+ const normalizedCandidate = candidatePhysical.startsWith('/')
260
+ ? candidatePhysical
261
+ : `/${candidatePhysical}`;
262
+ try {
263
+ const fetchOpts = { limit: 1000, offset: 0 };
264
+ const linksResult = await files_listAll(fetchOpts, 'links', parentDir);
265
+ if (linksResult && linksResult.tableData) {
266
+ for (const linkRaw of linksResult.tableData) {
267
+ const linkFname = linkRaw.fname || '';
268
+ const linkPath = linkRaw.path || '';
269
+ const normalizedLinkFname = linkFname.startsWith('/')
270
+ ? linkFname
271
+ : `/${linkFname}`;
272
+ if (normalizedLinkFname.endsWith('.chrislink')) {
273
+ const logicalPath = normalizedLinkFname.slice(0, -10);
274
+ if (logicalPath === normalizedCandidate) {
275
+ const target = linkPath.startsWith('/') ? linkPath : `/${linkPath}`;
276
+ return target;
277
+ }
278
+ }
279
+ }
280
+ }
281
+ return null;
282
+ }
283
+ catch (error) {
284
+ // Re-throw to be handled by caller
285
+ throw error;
286
+ }
287
+ }
288
+ /**
289
+ * Gets a cached mapping if it exists and hasn't expired.
290
+ *
291
+ * @param logicalPath - The logical path to look up.
292
+ * @returns The cached physical path, or null if not cached or expired.
293
+ */
294
+ cache_get(logicalPath) {
295
+ const cached = this.cache.get(logicalPath);
296
+ if (!cached) {
297
+ return null;
298
+ }
299
+ const now = Date.now();
300
+ const age = now - cached.timestamp;
301
+ if (age > cached.ttl) {
302
+ // Expired, remove from cache
303
+ this.cache.delete(logicalPath);
304
+ return null;
305
+ }
306
+ return cached.physicalPath;
307
+ }
308
+ /**
309
+ * Caches a logical-to-physical path mapping.
310
+ *
311
+ * @param logicalPath - The logical path.
312
+ * @param physicalPath - The corresponding physical path.
313
+ * @param ttl - Optional TTL in milliseconds (defaults to 30s).
314
+ */
315
+ cache_set(logicalPath, physicalPath, ttl = this.defaultTTL) {
316
+ this.cache.set(logicalPath, {
317
+ physicalPath,
318
+ timestamp: Date.now(),
319
+ ttl
320
+ });
321
+ }
322
+ /**
323
+ * Invalidates all cached mappings with a given logical path prefix.
324
+ *
325
+ * Call this when links are created/deleted/modified.
326
+ *
327
+ * @param logicalPathPrefix - The logical path prefix to invalidate.
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * // Link changed: /home/user/public → /SHARED becomes → /PUBLIC
332
+ * mapper.cache_invalidate('/home/user/public');
333
+ * // Invalidates:
334
+ * // - /home/user/public
335
+ * // - /home/user/public/feed_4
336
+ * // - /home/user/public/feed_5
337
+ * // - ... etc
338
+ * ```
339
+ */
340
+ cache_invalidate(logicalPathPrefix) {
341
+ const keysToDelete = [];
342
+ for (const [key] of this.cache) {
343
+ if (key === logicalPathPrefix || key.startsWith(logicalPathPrefix + '/')) {
344
+ keysToDelete.push(key);
345
+ }
346
+ }
347
+ for (const key of keysToDelete) {
348
+ this.cache.delete(key);
349
+ }
350
+ }
351
+ /**
352
+ * Clears all cached mappings.
353
+ */
354
+ cache_clear() {
355
+ this.cache.clear();
356
+ this.stats = { hits: 0, misses: 0 };
357
+ }
358
+ /**
359
+ * Gets cache statistics.
360
+ *
361
+ * @returns Cache statistics including hit rate and size.
362
+ */
363
+ stats_get() {
364
+ const total = this.stats.hits + this.stats.misses;
365
+ const hitRate = total === 0 ? 0 : this.stats.hits / total;
366
+ return {
367
+ hits: this.stats.hits,
368
+ misses: this.stats.misses,
369
+ size: this.cache.size,
370
+ hitRate
371
+ };
372
+ }
373
+ }
374
+ PathMapper.instance = null;
375
+ /**
376
+ * Convenience function to get the singleton PathMapper instance.
377
+ *
378
+ * @returns The singleton PathMapper instance.
379
+ */
380
+ export function pathMapper_get() {
381
+ return PathMapper.instance_get();
382
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @file Handler for the plugin context command group.
3
+ *
4
+ * @module
5
+ */
6
+ import { Command } from "commander";
7
+ import { CLIoptions } from "../utils/cli.js";
8
+ /**
9
+ * Handles commands related to groups of plugin contexts (computes, instances, parameters).
10
+ */
11
+ export declare class PluginContextGroupHandler {
12
+ private baseGroupHandler;
13
+ private controller;
14
+ readonly assetName: string;
15
+ private constructor();
16
+ /**
17
+ * Factory method to create a new PluginContextGroupHandler instance.
18
+ *
19
+ * @param assetName - The type of plugin context to handle ('computesofplugin', 'instancesofplugin', 'parametersofplugin').
20
+ * @param id - Optional plugin ID. Defaults to current ChRIS plugin context.
21
+ * @returns A Promise resolving to a new PluginContextGroupHandler instance.
22
+ * @throws InitializationError if an unsupported asset type is provided.
23
+ */
24
+ static handler_create(assetName: string, id?: number | null): Promise<PluginContextGroupHandler>;
25
+ /**
26
+ * Lists plugin parameters in a "man page" style format.
27
+ * This is a specialized view for parameters that differs from the standard table view.
28
+ *
29
+ * @param options - CLI options for filtering.
30
+ */
31
+ parameters_listMan(options: CLIoptions): Promise<void>;
32
+ /**
33
+ * Lists available fields for the current plugin context resource.
34
+ */
35
+ parameters_fieldsList(): Promise<void>;
36
+ /**
37
+ * Sets up the Commander.js commands for plugin context group operations.
38
+ *
39
+ * @param program - The Commander.js program instance.
40
+ */
41
+ pluginContextGroupCommand_setup(program: Command): void;
42
+ }