@resourcexjs/core 2.14.0 → 2.15.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 +20 -66
- package/dist/index.js +56 -61
- package/dist/index.js.map +15 -15
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -72,18 +72,21 @@ interface RXI {
|
|
|
72
72
|
readonly path?: string;
|
|
73
73
|
/** Resource name */
|
|
74
74
|
readonly name: string;
|
|
75
|
-
/** Tag (
|
|
75
|
+
/** Tag (mutable pointer). Defaults to "latest" if not specified. */
|
|
76
76
|
readonly tag: string;
|
|
77
|
+
/** Content digest (immutable hash, e.g., "sha256:abc123"). */
|
|
78
|
+
readonly digest?: string;
|
|
77
79
|
}
|
|
78
80
|
/**
|
|
79
81
|
* Format RXI to locator string.
|
|
80
82
|
*
|
|
81
|
-
* Docker-style format: [registry/][path/]name[:tag]
|
|
83
|
+
* Docker-style format: [registry/][path/]name[:tag][@digest]
|
|
82
84
|
*
|
|
83
85
|
* Examples:
|
|
84
86
|
* - { name: "hello", tag: "latest" } → "hello" (omit :latest)
|
|
85
87
|
* - { name: "hello", tag: "1.0.0" } → "hello:1.0.0"
|
|
86
|
-
* - {
|
|
88
|
+
* - { name: "hello", tag: "latest", digest: "sha256:abc" } → "hello@sha256:abc"
|
|
89
|
+
* - { name: "hello", tag: "beta", digest: "sha256:abc" } → "hello:beta@sha256:abc"
|
|
87
90
|
*
|
|
88
91
|
* @param rxi - Resource identifier
|
|
89
92
|
* @returns Locator string
|
|
@@ -113,9 +116,11 @@ interface RXMDefinition {
|
|
|
113
116
|
}
|
|
114
117
|
/**
|
|
115
118
|
* RXM Archive — packaging metadata.
|
|
116
|
-
* Placeholder for future fields (digest, size, md5, etc.)
|
|
117
119
|
*/
|
|
118
|
-
|
|
120
|
+
interface RXMArchive {
|
|
121
|
+
/** Deterministic content digest computed from file-level digests. Format: sha256:<hex> */
|
|
122
|
+
readonly digest?: string;
|
|
123
|
+
}
|
|
119
124
|
/**
|
|
120
125
|
* File entry with metadata.
|
|
121
126
|
*/
|
|
@@ -242,7 +247,7 @@ interface TypeDetectionResult {
|
|
|
242
247
|
readonly type: string;
|
|
243
248
|
/** Detected resource name */
|
|
244
249
|
readonly name: string;
|
|
245
|
-
/** Tag
|
|
250
|
+
/** Tag (defaults to "latest" if not provided) */
|
|
246
251
|
readonly tag?: string;
|
|
247
252
|
/** Description extracted from content */
|
|
248
253
|
readonly description?: string;
|
|
@@ -415,21 +420,6 @@ interface SourceLoader {
|
|
|
415
420
|
* @throws ResourceXError if loading fails
|
|
416
421
|
*/
|
|
417
422
|
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>;
|
|
433
423
|
}
|
|
434
424
|
/**
|
|
435
425
|
* Default ResourceLoader implementation for loading resources from folders.
|
|
@@ -446,8 +436,8 @@ interface SourceLoader {
|
|
|
446
436
|
* {
|
|
447
437
|
* "name": "resource-name", // required
|
|
448
438
|
* "type": "text", // required
|
|
449
|
-
* "
|
|
450
|
-
* "
|
|
439
|
+
* "tag": "1.0.0", // optional, defaults to "latest"
|
|
440
|
+
* "registry": "localhost", // optional
|
|
451
441
|
* "path": "optional/path" // optional
|
|
452
442
|
* }
|
|
453
443
|
* ```
|
|
@@ -473,15 +463,6 @@ declare class FolderSourceLoader implements SourceLoader {
|
|
|
473
463
|
canLoad(source: string): Promise<boolean>;
|
|
474
464
|
load(source: string): Promise<RXS>;
|
|
475
465
|
/**
|
|
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
|
-
/**
|
|
485
466
|
* Recursively read all files in a folder.
|
|
486
467
|
*/
|
|
487
468
|
private readFolderFiles;
|
|
@@ -584,17 +565,6 @@ declare class SourceLoaderChain {
|
|
|
584
565
|
* @throws ResourceXError if no loader matches
|
|
585
566
|
*/
|
|
586
567
|
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>;
|
|
598
568
|
}
|
|
599
569
|
/**
|
|
600
570
|
* Configuration for resolveSource.
|
|
@@ -684,6 +654,7 @@ interface StoredRXM {
|
|
|
684
654
|
readonly license?: string;
|
|
685
655
|
readonly keywords?: string[];
|
|
686
656
|
readonly repository?: string;
|
|
657
|
+
readonly digest?: string;
|
|
687
658
|
readonly files: Record<string, string>;
|
|
688
659
|
readonly createdAt?: Date;
|
|
689
660
|
readonly updatedAt?: Date;
|
|
@@ -778,19 +749,6 @@ interface ProviderStores {
|
|
|
778
749
|
rxmStore: RXMStore;
|
|
779
750
|
}
|
|
780
751
|
/**
|
|
781
|
-
* Resource loader interface for loading from directories/archives.
|
|
782
|
-
*/
|
|
783
|
-
interface ResourceLoader2 {
|
|
784
|
-
/**
|
|
785
|
-
* Check if this loader can handle the given source.
|
|
786
|
-
*/
|
|
787
|
-
canLoad(source: string): boolean | Promise<boolean>;
|
|
788
|
-
/**
|
|
789
|
-
* Load resource from source.
|
|
790
|
-
*/
|
|
791
|
-
load(source: string): Promise<unknown>;
|
|
792
|
-
}
|
|
793
|
-
/**
|
|
794
752
|
* Platform-specific defaults resolved from environment variables and config files.
|
|
795
753
|
*/
|
|
796
754
|
interface ProviderDefaults {
|
|
@@ -823,11 +781,6 @@ interface ResourceXProvider {
|
|
|
823
781
|
*/
|
|
824
782
|
createStores(config: ProviderConfig): ProviderStores;
|
|
825
783
|
/**
|
|
826
|
-
* Create resource loader (optional).
|
|
827
|
-
* Not all platforms support loading from filesystem.
|
|
828
|
-
*/
|
|
829
|
-
createLoader?(config: ProviderConfig): ResourceLoader2;
|
|
830
|
-
/**
|
|
831
784
|
* Create source loader for auto-detection pipeline (optional).
|
|
832
785
|
*/
|
|
833
786
|
createSourceLoader?(config: ProviderConfig): SourceLoader;
|
|
@@ -915,8 +868,9 @@ interface Registry {
|
|
|
915
868
|
get(rxi: RXI): Promise<RXR>;
|
|
916
869
|
/**
|
|
917
870
|
* Store resource.
|
|
871
|
+
* @returns The stored manifest (with computed digest).
|
|
918
872
|
*/
|
|
919
|
-
put(rxr: RXR): Promise<
|
|
873
|
+
put(rxr: RXR): Promise<RXM>;
|
|
920
874
|
/**
|
|
921
875
|
* Check if resource exists.
|
|
922
876
|
*/
|
|
@@ -936,7 +890,7 @@ declare class CASRegistry implements Registry {
|
|
|
936
890
|
constructor(rxaStore: RXAStore, rxmStore: RXMStore);
|
|
937
891
|
private resolveTag;
|
|
938
892
|
get(rxi: RXI): Promise<RXR>;
|
|
939
|
-
put(rxr: RXR): Promise<
|
|
893
|
+
put(rxr: RXR): Promise<RXM>;
|
|
940
894
|
has(rxi: RXI): Promise<boolean>;
|
|
941
895
|
remove(rxi: RXI): Promise<void>;
|
|
942
896
|
list(options?: SearchOptions): Promise<RXI[]>;
|
|
@@ -994,7 +948,7 @@ declare class LinkedRegistry implements Registry {
|
|
|
994
948
|
* Put is not typically used for LinkedRegistry.
|
|
995
949
|
* Use link() instead to create symlinks.
|
|
996
950
|
*/
|
|
997
|
-
put(_rxr: RXR): Promise<
|
|
951
|
+
put(_rxr: RXR): Promise<RXM>;
|
|
998
952
|
has(rxi: RXI): Promise<boolean>;
|
|
999
953
|
remove(rxi: RXI): Promise<void>;
|
|
1000
954
|
list(options?: SearchOptions): Promise<RXI[]>;
|
|
@@ -1030,7 +984,7 @@ declare abstract class RegistryMiddleware implements Registry {
|
|
|
1030
984
|
protected readonly inner: Registry;
|
|
1031
985
|
constructor(inner: Registry);
|
|
1032
986
|
get(rxi: RXI): Promise<RXR>;
|
|
1033
|
-
put(rxr: RXR): Promise<
|
|
987
|
+
put(rxr: RXR): Promise<RXM>;
|
|
1034
988
|
has(rxi: RXI): Promise<boolean>;
|
|
1035
989
|
remove(rxi: RXI): Promise<void>;
|
|
1036
990
|
list(options?: SearchOptions): Promise<RXI[]>;
|
|
@@ -1129,7 +1083,7 @@ interface ResolveContext {
|
|
|
1129
1083
|
path?: string
|
|
1130
1084
|
name: string
|
|
1131
1085
|
type: string
|
|
1132
|
-
|
|
1086
|
+
tag: string
|
|
1133
1087
|
};
|
|
1134
1088
|
/**
|
|
1135
1089
|
* Extracted files from archive.
|
package/dist/index.js
CHANGED
|
@@ -14597,7 +14597,6 @@ var RXDSchema = exports_external.object({
|
|
|
14597
14597
|
name: exports_external.string().min(1).max(128),
|
|
14598
14598
|
type: exports_external.string().min(1).max(64),
|
|
14599
14599
|
tag: exports_external.string().max(64).optional(),
|
|
14600
|
-
version: exports_external.string().max(64).optional(),
|
|
14601
14600
|
registry: exports_external.string().max(256).optional(),
|
|
14602
14601
|
path: exports_external.string().max(256).optional(),
|
|
14603
14602
|
description: exports_external.string().max(1024).optional(),
|
|
@@ -14619,7 +14618,7 @@ function define(input) {
|
|
|
14619
14618
|
const rxd = Object.assign(Object.create(null), {
|
|
14620
14619
|
name: validated.name,
|
|
14621
14620
|
type: validated.type,
|
|
14622
|
-
tag: validated.tag ??
|
|
14621
|
+
tag: validated.tag ?? undefined,
|
|
14623
14622
|
registry: validated.registry,
|
|
14624
14623
|
path: validated.path,
|
|
14625
14624
|
description: validated.description,
|
|
@@ -14659,6 +14658,9 @@ function format(rxi) {
|
|
|
14659
14658
|
if (rxi.tag && rxi.tag !== "latest") {
|
|
14660
14659
|
result += `:${rxi.tag}`;
|
|
14661
14660
|
}
|
|
14661
|
+
if (rxi.digest) {
|
|
14662
|
+
result += `@${rxi.digest}`;
|
|
14663
|
+
}
|
|
14662
14664
|
return result;
|
|
14663
14665
|
}
|
|
14664
14666
|
// src/model/locate.ts
|
|
@@ -14737,15 +14739,25 @@ function parse5(locator) {
|
|
|
14737
14739
|
throw new LocatorError("Locator must be a non-empty string", locator);
|
|
14738
14740
|
}
|
|
14739
14741
|
validateLocatorSecurity(locator);
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
+
let digest;
|
|
14743
|
+
let locatorWithoutDigest = locator;
|
|
14744
|
+
const atIndex = locator.indexOf("@");
|
|
14745
|
+
if (atIndex !== -1) {
|
|
14746
|
+
if (atIndex === 0) {
|
|
14747
|
+
throw new LocatorError("Invalid locator format. Name is required before @", locator);
|
|
14748
|
+
}
|
|
14749
|
+
digest = locator.substring(atIndex + 1);
|
|
14750
|
+
locatorWithoutDigest = locator.substring(0, atIndex);
|
|
14751
|
+
if (!digest || digest.includes("@")) {
|
|
14752
|
+
throw new LocatorError("Invalid digest format after @", locator);
|
|
14753
|
+
}
|
|
14742
14754
|
}
|
|
14743
|
-
const lastSlashIndex =
|
|
14755
|
+
const lastSlashIndex = locatorWithoutDigest.lastIndexOf("/");
|
|
14744
14756
|
let beforeSlash = "";
|
|
14745
|
-
let afterSlash =
|
|
14757
|
+
let afterSlash = locatorWithoutDigest;
|
|
14746
14758
|
if (lastSlashIndex !== -1) {
|
|
14747
|
-
beforeSlash =
|
|
14748
|
-
afterSlash =
|
|
14759
|
+
beforeSlash = locatorWithoutDigest.substring(0, lastSlashIndex);
|
|
14760
|
+
afterSlash = locatorWithoutDigest.substring(lastSlashIndex + 1);
|
|
14749
14761
|
}
|
|
14750
14762
|
const colonIndex = afterSlash.lastIndexOf(":");
|
|
14751
14763
|
let name;
|
|
@@ -14768,7 +14780,8 @@ function parse5(locator) {
|
|
|
14768
14780
|
registry: undefined,
|
|
14769
14781
|
path: undefined,
|
|
14770
14782
|
name,
|
|
14771
|
-
tag
|
|
14783
|
+
tag,
|
|
14784
|
+
digest
|
|
14772
14785
|
};
|
|
14773
14786
|
}
|
|
14774
14787
|
const parts = beforeSlash.split("/");
|
|
@@ -14779,14 +14792,16 @@ function parse5(locator) {
|
|
|
14779
14792
|
registry: registry2,
|
|
14780
14793
|
path,
|
|
14781
14794
|
name,
|
|
14782
|
-
tag
|
|
14795
|
+
tag,
|
|
14796
|
+
digest
|
|
14783
14797
|
};
|
|
14784
14798
|
}
|
|
14785
14799
|
return {
|
|
14786
14800
|
registry: undefined,
|
|
14787
14801
|
path: beforeSlash,
|
|
14788
14802
|
name,
|
|
14789
|
-
tag
|
|
14803
|
+
tag,
|
|
14804
|
+
digest
|
|
14790
14805
|
};
|
|
14791
14806
|
}
|
|
14792
14807
|
// src/model/resource.ts
|
|
@@ -14864,7 +14879,7 @@ class ResourceJsonDetector {
|
|
|
14864
14879
|
return {
|
|
14865
14880
|
type: json2.type,
|
|
14866
14881
|
name: json2.name,
|
|
14867
|
-
tag: json2.tag
|
|
14882
|
+
tag: json2.tag,
|
|
14868
14883
|
description: json2.description,
|
|
14869
14884
|
registry: json2.registry,
|
|
14870
14885
|
path: json2.path,
|
|
@@ -15008,31 +15023,6 @@ class FolderSourceLoader {
|
|
|
15008
15023
|
const files = await this.readFolderFiles(source);
|
|
15009
15024
|
return { source, files };
|
|
15010
15025
|
}
|
|
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
|
-
}
|
|
15036
15026
|
async readFolderFiles(folderPath, basePath = folderPath) {
|
|
15037
15027
|
const files = {};
|
|
15038
15028
|
const entries = await readdir2(folderPath, { withFileTypes: true });
|
|
@@ -15154,17 +15144,6 @@ class SourceLoaderChain {
|
|
|
15154
15144
|
}
|
|
15155
15145
|
throw new ResourceXError(`Cannot load source: ${source}`);
|
|
15156
15146
|
}
|
|
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
|
-
}
|
|
15168
15147
|
}
|
|
15169
15148
|
|
|
15170
15149
|
// src/loader/resolveSource.ts
|
|
@@ -15278,6 +15257,22 @@ function withRegistryValidation(registry2, trustedRegistry) {
|
|
|
15278
15257
|
}
|
|
15279
15258
|
var DomainValidation = RegistryValidation;
|
|
15280
15259
|
var withDomainValidation = withRegistryValidation;
|
|
15260
|
+
// src/registry/store/digest.ts
|
|
15261
|
+
import { createHash } from "node:crypto";
|
|
15262
|
+
function computeDigest(data) {
|
|
15263
|
+
const hash2 = createHash("sha256").update(data).digest("hex");
|
|
15264
|
+
return `sha256:${hash2}`;
|
|
15265
|
+
}
|
|
15266
|
+
function computeArchiveDigest(files) {
|
|
15267
|
+
const entries = Object.keys(files).sort().map((name) => `${name}:${files[name]}`).join(`
|
|
15268
|
+
`);
|
|
15269
|
+
const hash2 = createHash("sha256").update(entries).digest("hex");
|
|
15270
|
+
return `sha256:${hash2}`;
|
|
15271
|
+
}
|
|
15272
|
+
function isValidDigest(digest) {
|
|
15273
|
+
return /^sha256:[a-f0-9]{64}$/.test(digest);
|
|
15274
|
+
}
|
|
15275
|
+
|
|
15281
15276
|
// src/registry/registries/CASRegistry.ts
|
|
15282
15277
|
class CASRegistry {
|
|
15283
15278
|
rxaStore;
|
|
@@ -15320,7 +15315,9 @@ class CASRegistry {
|
|
|
15320
15315
|
keywords: storedRxm.keywords,
|
|
15321
15316
|
repository: storedRxm.repository
|
|
15322
15317
|
},
|
|
15323
|
-
archive: {
|
|
15318
|
+
archive: {
|
|
15319
|
+
digest: storedRxm.digest ?? computeArchiveDigest(storedRxm.files)
|
|
15320
|
+
},
|
|
15324
15321
|
source: {}
|
|
15325
15322
|
};
|
|
15326
15323
|
const rxa = await archive(files);
|
|
@@ -15330,9 +15327,10 @@ class CASRegistry {
|
|
|
15330
15327
|
const files = await extract(rxr.archive);
|
|
15331
15328
|
const fileDigests = {};
|
|
15332
15329
|
for (const [filename, content] of Object.entries(files)) {
|
|
15333
|
-
const
|
|
15334
|
-
fileDigests[filename] =
|
|
15330
|
+
const digest2 = await this.rxaStore.put(content);
|
|
15331
|
+
fileDigests[filename] = digest2;
|
|
15335
15332
|
}
|
|
15333
|
+
const digest = computeArchiveDigest(fileDigests);
|
|
15336
15334
|
const storedRxm = {
|
|
15337
15335
|
registry: rxr.manifest.definition.registry,
|
|
15338
15336
|
path: rxr.manifest.definition.path,
|
|
@@ -15344,12 +15342,18 @@ class CASRegistry {
|
|
|
15344
15342
|
license: rxr.manifest.definition.license,
|
|
15345
15343
|
keywords: rxr.manifest.definition.keywords,
|
|
15346
15344
|
repository: rxr.manifest.definition.repository,
|
|
15345
|
+
digest,
|
|
15347
15346
|
files: fileDigests,
|
|
15348
15347
|
createdAt: new Date,
|
|
15349
15348
|
updatedAt: new Date
|
|
15350
15349
|
};
|
|
15351
15350
|
await this.rxmStore.put(storedRxm);
|
|
15352
15351
|
await this.rxmStore.setLatest(rxr.manifest.definition.name, rxr.manifest.definition.tag, rxr.manifest.definition.registry);
|
|
15352
|
+
return {
|
|
15353
|
+
definition: rxr.manifest.definition,
|
|
15354
|
+
archive: { digest },
|
|
15355
|
+
source: rxr.manifest.source
|
|
15356
|
+
};
|
|
15353
15357
|
}
|
|
15354
15358
|
async has(rxi) {
|
|
15355
15359
|
const tag = await this.resolveTag(rxi.name, rxi.tag ?? "latest", rxi.registry);
|
|
@@ -15548,15 +15552,6 @@ class LinkedRegistry {
|
|
|
15548
15552
|
}
|
|
15549
15553
|
}
|
|
15550
15554
|
}
|
|
15551
|
-
// src/registry/store/digest.ts
|
|
15552
|
-
import { createHash } from "node:crypto";
|
|
15553
|
-
function computeDigest(data) {
|
|
15554
|
-
const hash2 = createHash("sha256").update(data).digest("hex");
|
|
15555
|
-
return `sha256:${hash2}`;
|
|
15556
|
-
}
|
|
15557
|
-
function isValidDigest(digest) {
|
|
15558
|
-
return /^sha256:[a-f0-9]{64}$/.test(digest);
|
|
15559
|
-
}
|
|
15560
15555
|
// src/registry/store/MemoryRXAStore.ts
|
|
15561
15556
|
class MemoryRXAStore {
|
|
15562
15557
|
blobs = new Map;
|
|
@@ -15904,4 +15899,4 @@ export {
|
|
|
15904
15899
|
CASRegistry
|
|
15905
15900
|
};
|
|
15906
15901
|
|
|
15907
|
-
//# debugId=
|
|
15902
|
+
//# debugId=2D991EC1BFB2AAA764756E2164756E21
|