@resourcexjs/core 2.13.0 → 2.14.0

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/dist/index.d.ts CHANGED
@@ -415,6 +415,21 @@ interface SourceLoader {
415
415
  * @throws ResourceXError if loading fails
416
416
  */
417
417
  load(source: string): Promise<RXS>;
418
+ /**
419
+ * Check if cached content for this source is still fresh.
420
+ *
421
+ * Each loader implements its own strategy:
422
+ * - FolderSourceLoader: compare file mtime against cachedAt
423
+ * - GitHubSourceLoader: not implemented (always stale)
424
+ *
425
+ * Loaders that don't implement this are treated as always stale,
426
+ * causing a full reload on every ingest.
427
+ *
428
+ * @param source - Source path or identifier
429
+ * @param cachedAt - When the resource was last cached
430
+ * @returns true if cache is still fresh, false if stale
431
+ */
432
+ isFresh?(source: string, cachedAt: Date): Promise<boolean>;
418
433
  }
419
434
  /**
420
435
  * Default ResourceLoader implementation for loading resources from folders.
@@ -458,6 +473,15 @@ declare class FolderSourceLoader implements SourceLoader {
458
473
  canLoad(source: string): Promise<boolean>;
459
474
  load(source: string): Promise<RXS>;
460
475
  /**
476
+ * Check if cached content is still fresh by comparing file mtimes.
477
+ * Returns true only if no file in the directory has been modified since cachedAt.
478
+ */
479
+ isFresh(source: string, cachedAt: Date): Promise<boolean>;
480
+ /**
481
+ * Get the most recent mtime across all files in a folder (recursive).
482
+ */
483
+ private getMaxMtime;
484
+ /**
461
485
  * Recursively read all files in a folder.
462
486
  */
463
487
  private readFolderFiles;
@@ -560,6 +584,17 @@ declare class SourceLoaderChain {
560
584
  * @throws ResourceXError if no loader matches
561
585
  */
562
586
  load(source: string): Promise<RXS>;
587
+ /**
588
+ * Check if cached content for a source is still fresh.
589
+ *
590
+ * Delegates to the matching loader's isFresh method.
591
+ * Returns false if the loader doesn't implement isFresh (always reload).
592
+ *
593
+ * @param source - Source path or identifier
594
+ * @param cachedAt - When the resource was last cached
595
+ * @returns true if cache is still fresh
596
+ */
597
+ isFresh(source: string, cachedAt: Date): Promise<boolean>;
563
598
  }
564
599
  /**
565
600
  * Configuration for resolveSource.
@@ -906,6 +941,11 @@ declare class CASRegistry implements Registry {
906
941
  remove(rxi: RXI): Promise<void>;
907
942
  list(options?: SearchOptions): Promise<RXI[]>;
908
943
  /**
944
+ * Get stored manifest metadata without extracting file contents.
945
+ * Useful for freshness checks (accessing updatedAt) without the cost of blob retrieval.
946
+ */
947
+ getStoredManifest(rxi: RXI): Promise<StoredRXM | null>;
948
+ /**
909
949
  * Clear cached resources (resources with registry).
910
950
  * @param registry - If provided, only clear resources from this registry
911
951
  */
package/dist/index.js CHANGED
@@ -15008,6 +15008,31 @@ class FolderSourceLoader {
15008
15008
  const files = await this.readFolderFiles(source);
15009
15009
  return { source, files };
15010
15010
  }
15011
+ async isFresh(source, cachedAt) {
15012
+ try {
15013
+ const maxMtime = await this.getMaxMtime(source);
15014
+ return maxMtime <= cachedAt;
15015
+ } catch {
15016
+ return false;
15017
+ }
15018
+ }
15019
+ async getMaxMtime(folderPath) {
15020
+ let max = new Date(0);
15021
+ const entries = await readdir2(folderPath, { withFileTypes: true });
15022
+ for (const entry of entries) {
15023
+ const fullPath = join2(folderPath, entry.name);
15024
+ if (entry.isFile()) {
15025
+ const stats = await stat2(fullPath);
15026
+ if (stats.mtime > max)
15027
+ max = stats.mtime;
15028
+ } else if (entry.isDirectory()) {
15029
+ const subMax = await this.getMaxMtime(fullPath);
15030
+ if (subMax > max)
15031
+ max = subMax;
15032
+ }
15033
+ }
15034
+ return max;
15035
+ }
15011
15036
  async readFolderFiles(folderPath, basePath = folderPath) {
15012
15037
  const files = {};
15013
15038
  const entries = await readdir2(folderPath, { withFileTypes: true });
@@ -15129,6 +15154,17 @@ class SourceLoaderChain {
15129
15154
  }
15130
15155
  throw new ResourceXError(`Cannot load source: ${source}`);
15131
15156
  }
15157
+ async isFresh(source, cachedAt) {
15158
+ for (const loader of this.loaders) {
15159
+ if (await loader.canLoad(source)) {
15160
+ if (loader.isFresh) {
15161
+ return loader.isFresh(source, cachedAt);
15162
+ }
15163
+ return false;
15164
+ }
15165
+ }
15166
+ return false;
15167
+ }
15132
15168
  }
15133
15169
 
15134
15170
  // src/loader/resolveSource.ts
@@ -15337,6 +15373,10 @@ class CASRegistry {
15337
15373
  tag: m.tag
15338
15374
  }));
15339
15375
  }
15376
+ async getStoredManifest(rxi) {
15377
+ const tag = await this.resolveTag(rxi.name, rxi.tag ?? "latest", rxi.registry);
15378
+ return this.rxmStore.get(rxi.name, tag, rxi.registry);
15379
+ }
15340
15380
  async clearCache(registry2) {
15341
15381
  if (registry2) {
15342
15382
  await this.rxmStore.deleteByRegistry(registry2);
@@ -15864,4 +15904,4 @@ export {
15864
15904
  CASRegistry
15865
15905
  };
15866
15906
 
15867
- //# debugId=8AF33945E518861764756E2164756E21
15907
+ //# debugId=B122DF8CA052DA8364756E2164756E21