@atomic-ehr/fhir-canonical-manager 0.0.13 → 0.0.15

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 (170) hide show
  1. package/dist/cache.d.ts +17 -0
  2. package/dist/cache.d.ts.map +1 -0
  3. package/dist/cache.js +50 -0
  4. package/dist/cache.js.map +1 -0
  5. package/dist/cli/index.d.ts.map +1 -1
  6. package/dist/cli/index.js +15 -15
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/init.d.ts.map +1 -1
  9. package/dist/cli/init.js +7 -10
  10. package/dist/cli/init.js.map +1 -1
  11. package/dist/cli/list.d.ts.map +1 -1
  12. package/dist/cli/list.js +14 -10
  13. package/dist/cli/list.js.map +1 -1
  14. package/dist/cli/resolve.js +7 -7
  15. package/dist/cli/resolve.js.map +1 -1
  16. package/dist/cli/search.js +9 -9
  17. package/dist/cli/search.js.map +1 -1
  18. package/dist/cli/searchparam.d.ts.map +1 -1
  19. package/dist/cli/searchparam.js +27 -24
  20. package/dist/cli/searchparam.js.map +1 -1
  21. package/dist/fs/index.d.ts +1 -1
  22. package/dist/fs/index.d.ts.map +1 -1
  23. package/dist/fs/index.js +1 -1
  24. package/dist/fs/index.js.map +1 -1
  25. package/dist/fs/utils.js +2 -2
  26. package/dist/fs/utils.js.map +1 -1
  27. package/dist/index.d.ts +6 -6
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +4 -4
  30. package/dist/index.js.map +1 -1
  31. package/dist/manager/canonical.d.ts +1 -1
  32. package/dist/manager/canonical.d.ts.map +1 -1
  33. package/dist/manager/canonical.js +77 -45
  34. package/dist/manager/canonical.js.map +1 -1
  35. package/dist/manager/index.d.ts +1 -2
  36. package/dist/manager/index.d.ts.map +1 -1
  37. package/dist/manager/index.js +1 -2
  38. package/dist/manager/index.js.map +1 -1
  39. package/dist/package.d.ts +4 -0
  40. package/dist/package.d.ts.map +1 -0
  41. package/dist/package.js +69 -0
  42. package/dist/package.js.map +1 -0
  43. package/dist/{reference/manager.d.ts → reference.d.ts} +8 -3
  44. package/dist/reference.d.ts.map +1 -0
  45. package/dist/{reference/manager.js → reference.js} +13 -4
  46. package/dist/reference.js.map +1 -0
  47. package/dist/resolver.d.ts +4 -0
  48. package/dist/resolver.d.ts.map +1 -0
  49. package/dist/{resolver/context.js → resolver.js} +2 -2
  50. package/dist/resolver.js.map +1 -0
  51. package/dist/scanner/directory.d.ts +2 -2
  52. package/dist/scanner/directory.d.ts.map +1 -1
  53. package/dist/scanner/directory.js +24 -27
  54. package/dist/scanner/directory.js.map +1 -1
  55. package/dist/scanner/index.d.ts +4 -4
  56. package/dist/scanner/index.d.ts.map +1 -1
  57. package/dist/scanner/index.js +4 -4
  58. package/dist/scanner/index.js.map +1 -1
  59. package/dist/scanner/package.d.ts +1 -1
  60. package/dist/scanner/package.d.ts.map +1 -1
  61. package/dist/scanner/package.js +4 -4
  62. package/dist/scanner/package.js.map +1 -1
  63. package/dist/scanner/parser.d.ts +2 -2
  64. package/dist/scanner/parser.d.ts.map +1 -1
  65. package/dist/scanner/parser.js +1 -1
  66. package/dist/scanner/parser.js.map +1 -1
  67. package/dist/scanner/processor.d.ts +2 -2
  68. package/dist/scanner/processor.d.ts.map +1 -1
  69. package/dist/scanner/processor.js +3 -3
  70. package/dist/scanner/processor.js.map +1 -1
  71. package/dist/search/index.d.ts +2 -2
  72. package/dist/search/index.d.ts.map +1 -1
  73. package/dist/search/index.js +2 -2
  74. package/dist/search/index.js.map +1 -1
  75. package/dist/search/smart.d.ts +1 -1
  76. package/dist/search/smart.d.ts.map +1 -1
  77. package/dist/search/smart.js +3 -7
  78. package/dist/search/smart.js.map +1 -1
  79. package/dist/search/terms.js.map +1 -1
  80. package/dist/types/core.d.ts +5 -4
  81. package/dist/types/core.d.ts.map +1 -1
  82. package/dist/types/index.d.ts +2 -2
  83. package/dist/types/index.js +2 -2
  84. package/dist/types/internal.d.ts +7 -4
  85. package/dist/types/internal.d.ts.map +1 -1
  86. package/package.json +12 -2
  87. package/src/cache.ts +59 -0
  88. package/src/cli/index.ts +112 -114
  89. package/src/cli/init.ts +104 -108
  90. package/src/cli/list.ts +84 -77
  91. package/src/cli/resolve.ts +57 -57
  92. package/src/cli/search.ts +72 -72
  93. package/src/cli/searchparam.ts +118 -117
  94. package/src/constants.ts +1 -1
  95. package/src/fs/index.ts +1 -1
  96. package/src/fs/utils.ts +16 -16
  97. package/src/index.ts +9 -12
  98. package/src/manager/canonical.ts +349 -360
  99. package/src/manager/index.ts +1 -3
  100. package/src/package.ts +79 -0
  101. package/src/reference.ts +77 -0
  102. package/src/resolver.ts +38 -0
  103. package/src/scanner/directory.ts +25 -31
  104. package/src/scanner/index.ts +4 -4
  105. package/src/scanner/package.ts +26 -29
  106. package/src/scanner/parser.ts +25 -27
  107. package/src/scanner/processor.ts +51 -55
  108. package/src/search/index.ts +2 -2
  109. package/src/search/smart.ts +41 -48
  110. package/src/search/terms.ts +17 -17
  111. package/src/types/core.ts +102 -89
  112. package/src/types/index.ts +2 -2
  113. package/src/types/internal.ts +36 -34
  114. package/dist/cache/core.d.ts +0 -7
  115. package/dist/cache/core.d.ts.map +0 -1
  116. package/dist/cache/core.js +0 -11
  117. package/dist/cache/core.js.map +0 -1
  118. package/dist/cache/index.d.ts +0 -4
  119. package/dist/cache/index.d.ts.map +0 -1
  120. package/dist/cache/index.js +0 -4
  121. package/dist/cache/index.js.map +0 -1
  122. package/dist/cache/persistence.d.ts +0 -5
  123. package/dist/cache/persistence.d.ts.map +0 -1
  124. package/dist/cache/persistence.js +0 -25
  125. package/dist/cache/persistence.js.map +0 -1
  126. package/dist/cache/validation.d.ts +0 -2
  127. package/dist/cache/validation.d.ts.map +0 -1
  128. package/dist/cache/validation.js +0 -21
  129. package/dist/cache/validation.js.map +0 -1
  130. package/dist/package/detector.d.ts +0 -2
  131. package/dist/package/detector.d.ts.map +0 -1
  132. package/dist/package/detector.js +0 -19
  133. package/dist/package/detector.js.map +0 -1
  134. package/dist/package/index.d.ts +0 -3
  135. package/dist/package/index.d.ts.map +0 -1
  136. package/dist/package/index.js +0 -3
  137. package/dist/package/index.js.map +0 -1
  138. package/dist/package/installer.d.ts +0 -2
  139. package/dist/package/installer.d.ts.map +0 -1
  140. package/dist/package/installer.js +0 -55
  141. package/dist/package/installer.js.map +0 -1
  142. package/dist/reference/index.d.ts +0 -5
  143. package/dist/reference/index.d.ts.map +0 -1
  144. package/dist/reference/index.js +0 -4
  145. package/dist/reference/index.js.map +0 -1
  146. package/dist/reference/manager.d.ts.map +0 -1
  147. package/dist/reference/manager.js.map +0 -1
  148. package/dist/reference/store.d.ts +0 -6
  149. package/dist/reference/store.d.ts.map +0 -1
  150. package/dist/reference/store.js +0 -6
  151. package/dist/reference/store.js.map +0 -1
  152. package/dist/resolver/context.d.ts +0 -4
  153. package/dist/resolver/context.d.ts.map +0 -1
  154. package/dist/resolver/context.js.map +0 -1
  155. package/dist/resolver/index.d.ts +0 -2
  156. package/dist/resolver/index.d.ts.map +0 -1
  157. package/dist/resolver/index.js +0 -2
  158. package/dist/resolver/index.js.map +0 -1
  159. package/src/cache/core.ts +0 -20
  160. package/src/cache/index.ts +0 -7
  161. package/src/cache/persistence.ts +0 -39
  162. package/src/cache/validation.ts +0 -27
  163. package/src/package/detector.ts +0 -24
  164. package/src/package/index.ts +0 -6
  165. package/src/package/installer.ts +0 -77
  166. package/src/reference/index.ts +0 -10
  167. package/src/reference/manager.ts +0 -51
  168. package/src/reference/store.ts +0 -15
  169. package/src/resolver/context.ts +0 -25
  170. package/src/resolver/index.ts +0 -5
@@ -2,369 +2,358 @@
2
2
  * Main CanonicalManager implementation
3
3
  */
4
4
 
5
- import * as path from 'path';
6
- import * as fs from 'fs/promises';
5
+ import * as afs from "node:fs/promises";
6
+ import * as Path from "node:path";
7
+ import { cacheRecordPaths, createCacheRecord, loadCacheRecordFromDisk, saveCacheRecordToDisk } from "../cache.js";
8
+ import { DEFAULT_REGISTRY } from "../constants.js";
9
+ import { ensureDir } from "../fs/index.js";
10
+ import { installPackages } from "../package.js";
11
+ import { resolveWithContext } from "../resolver.js";
12
+ import { scanDirectory } from "../scanner/index.js";
13
+ import { filterBySmartSearch } from "../search/index.js";
7
14
  import type {
8
- Config,
9
- CanonicalManager,
10
- PackageId,
11
- IndexEntry,
12
- Resource,
13
- Reference,
14
- SourceContext,
15
- SearchParameter,
16
- } from '../types/index.js';
17
- import { DEFAULT_REGISTRY } from '../constants.js';
18
- import { ensureDir } from '../fs/index.js';
19
- import { installPackages } from '../package/index.js';
20
- import {
21
- createCache,
22
- saveCacheToDisk,
23
- loadCacheFromDisk,
24
- computePackageLockHash,
25
- type ExtendedCache,
26
- } from '../cache/index.js';
27
- import { scanDirectory } from '../scanner/index.js';
28
- import { resolveWithContext } from '../resolver/index.js';
29
- import { filterBySmartSearch } from '../search/index.js';
15
+ CanonicalManager,
16
+ Config,
17
+ IndexEntry,
18
+ PackageId,
19
+ PackageInfo,
20
+ Reference,
21
+ Resource,
22
+ SearchParameter,
23
+ SourceContext,
24
+ } from "../types/index.js";
30
25
 
31
26
  export const createCanonicalManager = (config: Config): CanonicalManager => {
32
- const { packages = [], workingDir } = config;
33
- // Ensure registry URL ends with /
34
- const registry = config.registry
35
- ? config.registry.endsWith("/")
36
- ? config.registry
37
- : `${config.registry}/`
38
- : DEFAULT_REGISTRY;
39
- const nodeModulesPath = path.join(workingDir, "node_modules");
40
- const cacheDir = path.join(workingDir, ".fcm", "cache");
41
-
42
- let cache = createCache();
43
- let initialized = false;
44
- const searchParamsCache = new Map<string, SearchParameter[]>();
45
-
46
- const ensureInitialized = (): void => {
47
- if (!initialized) {
48
- throw new Error("CanonicalManager not initialized. Call init() first.");
49
- }
50
- };
51
-
52
- const init = async (): Promise<void> => {
53
- if (initialized) return;
54
-
55
- // Ensure directories exist
56
- await ensureDir(workingDir);
57
- await ensureDir(cacheDir);
58
-
59
- // Get current package-lock.json hash
60
- const currentPackageLockHash = await computePackageLockHash(workingDir);
61
-
62
- // Try to load cache from disk
63
- const cachedData = await loadCacheFromDisk(cacheDir);
64
-
65
- // Check if cache is valid (exists and package-lock.json hasn't changed)
66
- const cacheValid =
67
- cachedData &&
68
- cachedData.packageLockHash === currentPackageLockHash &&
69
- currentPackageLockHash !== null;
70
-
71
- if (cacheValid) {
72
- // Restore cache from disk
73
- cache.entries = cachedData.entries;
74
- cache.packages = cachedData.packages;
75
- Object.entries(cachedData.references).forEach(([id, metadata]) => {
76
- cache.referenceManager.set(id, metadata);
77
- });
78
- } else {
79
- // Cache is invalid or doesn't exist - rebuild it
80
- if (cachedData && cachedData.packageLockHash !== currentPackageLockHash) {
81
- console.log("Package dependencies have changed, rebuilding index...");
82
- }
83
-
84
- // Install packages if needed
85
- await installPackages(packages, workingDir, registry);
86
-
87
- // Clear cache before scanning
88
- cache = createCache();
89
-
90
- // Scan installed packages
91
- await scanDirectory(nodeModulesPath, cache);
92
-
93
- // Save cache to disk with current package-lock hash
94
- await saveCacheToDisk(cache, cacheDir, workingDir);
95
- }
96
-
97
- initialized = true;
98
- };
99
-
100
- const destroy = async (): Promise<void> => {
101
- cache.entries = {};
102
- cache.packages = {};
103
- cache.referenceManager.clear();
104
- searchParamsCache.clear();
105
- initialized = false;
106
- };
107
-
108
- const getPackages = async (): Promise<PackageId[]> => {
109
- ensureInitialized();
110
- return Object.values(cache.packages).map((p) => p.id);
111
- };
112
-
113
- const addPackages = async (...newPackages: string[]): Promise<void> => {
114
- if (newPackages.length === 0) return;
115
-
116
- // Check if packages already exists in packages
117
- const packagesToAdd = newPackages.filter((pkg) => !packages.includes(pkg));
118
- if (packagesToAdd.length !== 0) {
119
- // Update config packages
120
- packages.push(...packagesToAdd);
121
- }
122
-
123
- // If not initialized yet, init will handle installation
124
- if(!initialized) {
125
- await init();
126
- return;
27
+ const { packages = [], workingDir } = config;
28
+ // Ensure registry URL ends with /
29
+ let registry = DEFAULT_REGISTRY;
30
+ if (config.registry) {
31
+ registry = config.registry.endsWith("/") ? config.registry : `${config.registry}/`;
127
32
  }
128
33
 
129
- // If it is initialized and there are no new packages, do nothing
130
- if (packagesToAdd.length === 0) {
131
- return;
132
- }
133
-
134
- // Install new packages
135
- await installPackages(packages, workingDir, registry);
136
-
137
- // Re-scan node_modules to update cache
138
- await scanDirectory(nodeModulesPath, cache);
139
-
140
- // Update cache on disk
141
- await saveCacheToDisk(cache, cacheDir, workingDir);
142
- };
143
-
144
- const resolveEntry = async (
145
- canonicalUrl: string,
146
- options?: {
147
- package?: string;
148
- version?: string;
149
- sourceContext?: SourceContext;
150
- },
151
- ): Promise<IndexEntry> => {
152
- ensureInitialized();
153
-
154
- if (options?.sourceContext) {
155
- const contextResolved = await resolveWithContext(
156
- canonicalUrl,
157
- options.sourceContext,
158
- cache,
34
+ const cache = createCacheRecord();
35
+ let initialized = false;
36
+ const searchParamsCache = new Map<string, SearchParameter[]>();
37
+
38
+ const ensureInitialized = (): void => {
39
+ if (!initialized) {
40
+ throw new Error("CanonicalManager not initialized. Call init() first.");
41
+ }
42
+ };
43
+
44
+ const packageRefToPackageMeta = async () => {
45
+ ensureInitialized();
46
+ const { npmRootPackageJsonFile } = cacheRecordPaths(workingDir, packages);
47
+ const rootPackageDeps =
48
+ (
49
+ JSON.parse(await afs.readFile(npmRootPackageJsonFile, "utf8")) as {
50
+ dependencies?: Record<string, string>;
51
+ }
52
+ ).dependencies ?? {};
53
+ const res: Record<string, PackageId> = {};
54
+ for (const pkgRef of packages) {
55
+ if (pkgRef.startsWith("http://") || pkgRef.startsWith("https://")) {
56
+ for (const [depName, depVersion] of Object.entries(rootPackageDeps)) {
57
+ if (depVersion === pkgRef) {
58
+ const packageInfo = cache.packages[depName];
59
+ if (!packageInfo) throw new Error(`Package not found: ${depName}`);
60
+ res[pkgRef] = { name: packageInfo.id.name, version: packageInfo.id.version };
61
+ break;
62
+ }
63
+ }
64
+ } else {
65
+ const [name, version] = pkgRef.split("@");
66
+ if (!name) throw new Error(`Invalid FHIR package meta: ${pkgRef}`);
67
+ res[pkgRef] = { name, version: version ?? "latest" };
68
+ }
69
+ }
70
+ return res;
71
+ };
72
+
73
+ const init = async (): Promise<Record<string, PackageId>> => {
74
+ if (initialized) return packageRefToPackageMeta();
75
+
76
+ await ensureDir(workingDir);
77
+ const { cacheKey, npmPackagePath } = cacheRecordPaths(workingDir, packages);
78
+
79
+ const cachedData = await loadCacheRecordFromDisk(workingDir, cacheKey);
80
+ const isCacheValid = cachedData && cachedData.packageLockHash === cacheKey;
81
+ if (isCacheValid) {
82
+ // Restore cache from disk
83
+ cache.entries = cachedData.entries;
84
+ cache.packages = cachedData.packages;
85
+ Object.entries(cachedData.references).forEach(([id, metadata]) => {
86
+ cache.referenceManager.set(id, metadata);
87
+ });
88
+ } else {
89
+ await installPackages(packages, npmPackagePath, registry);
90
+ await scanDirectory(cache, npmPackagePath);
91
+ await saveCacheRecordToDisk(cache, workingDir, cacheKey);
92
+ }
93
+
94
+ initialized = true;
95
+ return packageRefToPackageMeta();
96
+ };
97
+
98
+ const destroy = async (): Promise<void> => {
99
+ cache.entries = {};
100
+ cache.packages = {};
101
+ cache.referenceManager.clear();
102
+ searchParamsCache.clear();
103
+ initialized = false;
104
+ };
105
+
106
+ const getPackages = async (): Promise<PackageId[]> => {
107
+ ensureInitialized();
108
+ return Object.values(cache.packages).map((p: PackageInfo) => p.id);
109
+ };
110
+
111
+ const addPackages = async (...newPackages: string[]): Promise<Record<string, PackageId>> => {
112
+ if (newPackages.length === 0) return packageRefToPackageMeta();
113
+
114
+ // Check if packages already exists in packages
115
+ const packagesToAdd = newPackages.filter((pkg) => !packages.includes(pkg));
116
+ if (packagesToAdd.length !== 0) {
117
+ packages.push(...packagesToAdd);
118
+ }
119
+
120
+ if (!initialized) {
121
+ await init();
122
+ return packageRefToPackageMeta();
123
+ }
124
+
125
+ if (packagesToAdd.length > 0) {
126
+ // TODO: very expensive, we can just copy/paste old cache to avoid reinstalling packages
127
+ await destroy();
128
+ await init();
129
+ }
130
+ return packageRefToPackageMeta();
131
+ };
132
+
133
+ const resolveEntry = async (
134
+ canonicalUrl: string,
135
+ options?: {
136
+ package?: string;
137
+ version?: string;
138
+ sourceContext?: SourceContext;
139
+ },
140
+ ): Promise<IndexEntry> => {
141
+ ensureInitialized();
142
+
143
+ if (options?.sourceContext) {
144
+ const contextResolved = await resolveWithContext(canonicalUrl, options.sourceContext, cache, resolveEntry);
145
+ if (contextResolved) {
146
+ return contextResolved;
147
+ }
148
+ }
149
+
150
+ const entries = cache.entries[canonicalUrl] || [];
151
+
152
+ if (entries.length === 0) {
153
+ throw new Error(`Cannot resolve canonical URL: ${canonicalUrl}`);
154
+ }
155
+
156
+ let filtered = [...entries];
157
+
158
+ if (options?.package) {
159
+ filtered = filtered.filter((e) => e.package?.name === options.package);
160
+ }
161
+
162
+ if (options?.version) {
163
+ filtered = filtered.filter((e) => e.version === options.version);
164
+ }
165
+
166
+ if (filtered.length === 0) {
167
+ throw new Error(`No matching resource found for ${canonicalUrl} with given options`);
168
+ }
169
+
170
+ const result = filtered[0];
171
+ if (!result) {
172
+ throw new Error(`No matching resource found for ${canonicalUrl}`);
173
+ }
174
+ return result;
175
+ };
176
+
177
+ const resolve = async (
178
+ canonicalUrl: string,
179
+ options?: {
180
+ package?: string;
181
+ version?: string;
182
+ sourceContext?: SourceContext;
183
+ },
184
+ ): Promise<Resource> => {
185
+ const entry = await resolveEntry(canonicalUrl, options);
186
+ return read(entry);
187
+ };
188
+
189
+ const read = async (reference: Reference): Promise<Resource> => {
190
+ ensureInitialized();
191
+
192
+ const metadata = cache.referenceManager.get(reference.id);
193
+ if (!metadata) {
194
+ throw new Error(`Invalid reference ID: ${reference.id}`);
195
+ }
196
+
197
+ try {
198
+ const content = await afs.readFile(metadata.filePath, "utf-8");
199
+ const resource = JSON.parse(content);
200
+
201
+ return {
202
+ ...resource,
203
+ id: reference.id,
204
+ resourceType: reference.resourceType,
205
+ };
206
+ } catch (err) {
207
+ throw new Error(`Failed to read resource: ${err}`);
208
+ }
209
+ };
210
+
211
+ const searchEntries = async (params: {
212
+ kind?: string;
213
+ url?: string;
214
+ type?: string;
215
+ version?: string;
216
+ package?: PackageId;
217
+ }): Promise<IndexEntry[]> => {
218
+ ensureInitialized();
219
+
220
+ let results: IndexEntry[] = [];
221
+
222
+ if (params.url) {
223
+ results = cache.entries[params.url] || [];
224
+ } else {
225
+ for (const entries of Object.values(cache.entries)) {
226
+ results.push(...entries);
227
+ }
228
+ }
229
+
230
+ if (params.kind !== undefined) {
231
+ results = results.filter((e) => e.kind === params.kind);
232
+ }
233
+
234
+ if (params.type !== undefined) {
235
+ results = results.filter((e) => e.type === params.type);
236
+ }
237
+
238
+ if (params.version !== undefined) {
239
+ results = results.filter((e) => e.version === params.version);
240
+ }
241
+
242
+ if (params.package) {
243
+ const pkg = params.package;
244
+ results = results.filter((e) => e.package?.name === pkg.name && e.package?.version === pkg.version);
245
+ }
246
+
247
+ return results;
248
+ };
249
+
250
+ const search = async (params: {
251
+ kind?: string;
252
+ url?: string;
253
+ type?: string;
254
+ version?: string;
255
+ package?: PackageId;
256
+ }): Promise<Resource[]> => {
257
+ const entries = await searchEntries(params);
258
+ const resources = await Promise.all(entries.map((entry) => read(entry)));
259
+ return resources;
260
+ };
261
+
262
+ const smartSearch = async (
263
+ searchTerms: string[],
264
+ filters?: {
265
+ resourceType?: string;
266
+ type?: string;
267
+ kind?: string;
268
+ package?: PackageId;
269
+ },
270
+ ): Promise<IndexEntry[]> => {
271
+ ensureInitialized();
272
+
273
+ // Start with base search using filters
274
+ let results = await searchEntries({
275
+ kind: filters?.kind,
276
+ package: filters?.package,
277
+ });
278
+
279
+ // Apply resourceType filter
280
+ if (filters?.resourceType) {
281
+ results = results.filter((entry) => entry.resourceType === filters.resourceType);
282
+ }
283
+
284
+ // Apply type filter
285
+ if (filters?.type) {
286
+ results = results.filter((entry) => entry.type === filters.type);
287
+ }
288
+
289
+ // Apply smart search filtering
290
+ return filterBySmartSearch(results, searchTerms);
291
+ };
292
+
293
+ const getSearchParametersForResource = async (resourceType: string): Promise<SearchParameter[]> => {
294
+ ensureInitialized();
295
+
296
+ // Check cache first
297
+ if (searchParamsCache.has(resourceType)) {
298
+ const cached = searchParamsCache.get(resourceType);
299
+ if (cached) {
300
+ return cached;
301
+ }
302
+ }
303
+
304
+ // Query all SearchParameter resources
305
+ // Note: We search by resourceType, not type, because for SearchParameter resources,
306
+ // 'type' refers to the search parameter type (token, string, etc.)
307
+ const allEntries = await searchEntries({});
308
+ const searchParamEntries = allEntries.filter((entry) => entry.resourceType === "SearchParameter");
309
+
310
+ const results: SearchParameter[] = [];
311
+
312
+ for (const entry of searchParamEntries) {
313
+ const resource = await read(entry);
314
+
315
+ // Check if this parameter applies to the requested resource
316
+ const bases = resource.base || [];
317
+ if (Array.isArray(bases) && bases.includes(resourceType)) {
318
+ // Return the full original resource - it already contains all fields
319
+ // Cast through unknown to satisfy TypeScript
320
+ results.push(resource as unknown as SearchParameter);
321
+ }
322
+ }
323
+
324
+ // Sort by code for consistent output
325
+ results.sort((a, b) => {
326
+ const codeA = a.code || "";
327
+ const codeB = b.code || "";
328
+ return codeA.localeCompare(codeB);
329
+ });
330
+
331
+ // Cache the results
332
+ searchParamsCache.set(resourceType, results);
333
+
334
+ return results;
335
+ };
336
+
337
+ const packageJson = async (packageName: string) => {
338
+ ensureInitialized();
339
+ const fn = cache.packages[packageName]?.path;
340
+ if (!fn) throw new Error(`Package ${packageName} not found`);
341
+ const packageJSON = JSON.parse(await afs.readFile(Path.join(fn, "package.json"), "utf8"));
342
+ return packageJSON;
343
+ };
344
+
345
+ return {
346
+ init,
347
+ destroy,
348
+ packages: getPackages,
349
+ addPackages,
159
350
  resolveEntry,
160
- );
161
- if (contextResolved) {
162
- return contextResolved;
163
- }
164
- }
165
-
166
- const entries = cache.entries[canonicalUrl] || [];
167
-
168
- if (entries.length === 0) {
169
- throw new Error(`Cannot resolve canonical URL: ${canonicalUrl}`);
170
- }
171
-
172
- let filtered = [...entries];
173
-
174
- if (options?.package) {
175
- filtered = filtered.filter((e) => e.package?.name === options.package);
176
- }
177
-
178
- if (options?.version) {
179
- filtered = filtered.filter((e) => e.version === options.version);
180
- }
181
-
182
- if (filtered.length === 0) {
183
- throw new Error(
184
- `No matching resource found for ${canonicalUrl} with given options`,
185
- );
186
- }
187
-
188
- return filtered[0]!;
189
- };
190
-
191
- const resolve = async (
192
- canonicalUrl: string,
193
- options?: {
194
- package?: string;
195
- version?: string;
196
- sourceContext?: SourceContext;
197
- },
198
- ): Promise<Resource> => {
199
- const entry = await resolveEntry(canonicalUrl, options);
200
- return read(entry);
201
- };
202
-
203
- const read = async (reference: Reference): Promise<Resource> => {
204
- ensureInitialized();
205
-
206
- const metadata = cache.referenceManager.get(reference.id);
207
- if (!metadata) {
208
- throw new Error(`Invalid reference ID: ${reference.id}`);
209
- }
210
-
211
- try {
212
- const content = await fs.readFile(metadata.filePath, "utf-8");
213
- const resource = JSON.parse(content);
214
-
215
- return {
216
- ...resource,
217
- id: reference.id,
218
- resourceType: reference.resourceType,
219
- };
220
- } catch (err) {
221
- throw new Error(`Failed to read resource: ${err}`);
222
- }
223
- };
224
-
225
- const searchEntries = async (params: {
226
- kind?: string;
227
- url?: string;
228
- type?: string;
229
- version?: string;
230
- package?: PackageId;
231
- }): Promise<IndexEntry[]> => {
232
- ensureInitialized();
233
-
234
- let results: IndexEntry[] = [];
235
-
236
- if (params.url) {
237
- results = cache.entries[params.url] || [];
238
- } else {
239
- for (const entries of Object.values(cache.entries)) {
240
- results.push(...entries);
241
- }
242
- }
243
-
244
- if (params.kind !== undefined) {
245
- results = results.filter((e) => e.kind === params.kind);
246
- }
247
-
248
- if (params.type !== undefined) {
249
- results = results.filter((e) => e.type === params.type);
250
- }
251
-
252
- if (params.version !== undefined) {
253
- results = results.filter((e) => e.version === params.version);
254
- }
255
-
256
- if (params.package) {
257
- const pkg = params.package;
258
- results = results.filter(
259
- (e) =>
260
- e.package?.name === pkg.name && e.package?.version === pkg.version,
261
- );
262
- }
263
-
264
- return results;
265
- };
266
-
267
- const search = async (params: {
268
- kind?: string;
269
- url?: string;
270
- type?: string;
271
- version?: string;
272
- package?: PackageId;
273
- }): Promise<Resource[]> => {
274
- const entries = await searchEntries(params);
275
- const resources = await Promise.all(entries.map((entry) => read(entry)));
276
- return resources;
277
- };
278
-
279
- const smartSearch = async (
280
- searchTerms: string[],
281
- filters?: {
282
- resourceType?: string;
283
- type?: string;
284
- kind?: string;
285
- package?: PackageId;
286
- },
287
- ): Promise<IndexEntry[]> => {
288
- ensureInitialized();
289
-
290
- // Start with base search using filters
291
- let results = await searchEntries({
292
- kind: filters?.kind,
293
- package: filters?.package,
294
- });
295
-
296
- // Apply resourceType filter
297
- if (filters?.resourceType) {
298
- results = results.filter(
299
- (entry) => entry.resourceType === filters.resourceType,
300
- );
301
- }
302
-
303
- // Apply type filter
304
- if (filters?.type) {
305
- results = results.filter((entry) => entry.type === filters.type);
306
- }
307
-
308
- // Apply smart search filtering
309
- return filterBySmartSearch(results, searchTerms);
310
- };
311
-
312
- const getSearchParametersForResource = async (
313
- resourceType: string
314
- ): Promise<SearchParameter[]> => {
315
- ensureInitialized();
316
-
317
- // Check cache first
318
- if (searchParamsCache.has(resourceType)) {
319
- return searchParamsCache.get(resourceType)!;
320
- }
321
-
322
- // Query all SearchParameter resources
323
- // Note: We search by resourceType, not type, because for SearchParameter resources,
324
- // 'type' refers to the search parameter type (token, string, etc.)
325
- const allEntries = await searchEntries({});
326
- const searchParamEntries = allEntries.filter(
327
- entry => entry.resourceType === 'SearchParameter'
328
- );
329
-
330
- const results: SearchParameter[] = [];
331
-
332
- for (const entry of searchParamEntries) {
333
- const resource = await read(entry);
334
-
335
- // Check if this parameter applies to the requested resource
336
- const bases = resource.base || [];
337
- if (Array.isArray(bases) && bases.includes(resourceType)) {
338
- // Return the full original resource - it already contains all fields
339
- // Cast through unknown to satisfy TypeScript
340
- results.push(resource as unknown as SearchParameter);
341
- }
342
- }
343
-
344
- // Sort by code for consistent output
345
- results.sort((a, b) => {
346
- const codeA = a.code || '';
347
- const codeB = b.code || '';
348
- return codeA.localeCompare(codeB);
349
- });
350
-
351
- // Cache the results
352
- searchParamsCache.set(resourceType, results);
353
-
354
- return results;
355
- };
356
-
357
- return {
358
- init,
359
- destroy,
360
- packages: getPackages,
361
- addPackages,
362
- resolveEntry,
363
- resolve,
364
- read,
365
- searchEntries,
366
- search,
367
- smartSearch,
368
- getSearchParametersForResource,
369
- };
370
- };
351
+ resolve,
352
+ read,
353
+ searchEntries,
354
+ search,
355
+ smartSearch,
356
+ getSearchParametersForResource,
357
+ packageJson,
358
+ };
359
+ };