@canon-protocol/sdk 8.0.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/canons/Canon.d.ts +2 -0
- package/dist/canons/Canon.js +2 -0
- package/dist/canons/DefinedCanon.d.ts +5 -0
- package/dist/canons/DefinedCanon.js +4 -0
- package/dist/canons/EmbeddedCanon.d.ts +3 -0
- package/dist/canons/EmbeddedCanon.js +3 -0
- package/dist/canons/ReferenceCanon.d.ts +6 -0
- package/dist/canons/ReferenceCanon.js +10 -0
- package/dist/canons/SubjectCanon.d.ts +6 -0
- package/dist/canons/SubjectCanon.js +6 -0
- package/dist/canons/index.d.ts +5 -0
- package/dist/canons/index.js +5 -0
- package/dist/ctl/CtlCanonUri.d.ts +28 -0
- package/dist/ctl/CtlCanonUri.js +230 -0
- package/dist/ctl/CtlGraphResolver.d.ts +14 -0
- package/dist/ctl/CtlGraphResolver.js +411 -0
- package/dist/ctl/CtlMarkdownRenderer.d.ts +17 -0
- package/dist/ctl/CtlMarkdownRenderer.js +262 -0
- package/dist/ctl/CtlParser.d.ts +10 -0
- package/dist/ctl/CtlParser.js +331 -0
- package/dist/ctl/CtlValidator.d.ts +12 -0
- package/dist/ctl/CtlValidator.js +207 -0
- package/dist/ctl/ResourceTypeClassifier.d.ts +21 -0
- package/dist/ctl/ResourceTypeClassifier.js +126 -0
- package/dist/ctl/index.d.ts +12 -0
- package/dist/ctl/index.js +9 -0
- package/dist/filtering/GitIgnoreFilter.d.ts +7 -0
- package/dist/filtering/GitIgnoreFilter.js +32 -0
- package/dist/filtering/IGitIgnoreFilter.d.ts +3 -0
- package/dist/filtering/IGitIgnoreFilter.js +1 -0
- package/dist/filtering/index.d.ts +2 -0
- package/dist/filtering/index.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +8 -0
- package/dist/parsing/CanonObjectParser.d.ts +21 -0
- package/dist/parsing/CanonObjectParser.js +242 -0
- package/dist/parsing/CanonParser.d.ts +21 -0
- package/dist/parsing/CanonParser.js +457 -0
- package/dist/parsing/ICanonObjectParser.d.ts +7 -0
- package/dist/parsing/ICanonObjectParser.js +1 -0
- package/dist/parsing/PropertyMetadata.d.ts +7 -0
- package/dist/parsing/PropertyMetadata.js +7 -0
- package/dist/parsing/index.d.ts +6 -0
- package/dist/parsing/index.js +3 -0
- package/dist/repositories/CompositeCanonDocumentRepository.d.ts +28 -0
- package/dist/repositories/CompositeCanonDocumentRepository.js +125 -0
- package/dist/repositories/FileSystemCanonDocumentRepository.d.ts +28 -0
- package/dist/repositories/FileSystemCanonDocumentRepository.js +295 -0
- package/dist/repositories/HttpCanonDocumentRepository.d.ts +24 -0
- package/dist/repositories/HttpCanonDocumentRepository.js +79 -0
- package/dist/repositories/InMemoryCanonDocumentRepository.d.ts +23 -0
- package/dist/repositories/InMemoryCanonDocumentRepository.js +149 -0
- package/dist/repositories/PublisherConfig.d.ts +12 -0
- package/dist/repositories/PublisherConfig.js +62 -0
- package/dist/repositories/PublisherIndex.d.ts +20 -0
- package/dist/repositories/PublisherIndex.js +127 -0
- package/dist/repositories/RepositoryFactory.d.ts +6 -0
- package/dist/repositories/RepositoryFactory.js +13 -0
- package/dist/repositories/index.d.ts +9 -0
- package/dist/repositories/index.js +7 -0
- package/dist/resolution/CanonUri.d.ts +10 -0
- package/dist/resolution/CanonUri.js +55 -0
- package/dist/resolution/CanonUriBuilder.d.ts +12 -0
- package/dist/resolution/CanonUriBuilder.js +51 -0
- package/dist/resolution/ResourceResolutionResult.d.ts +9 -0
- package/dist/resolution/ResourceResolutionResult.js +1 -0
- package/dist/resolution/ResourceResolver.d.ts +27 -0
- package/dist/resolution/ResourceResolver.js +266 -0
- package/dist/resolution/TypeResolver.d.ts +15 -0
- package/dist/resolution/TypeResolver.js +110 -0
- package/dist/resolution/index.d.ts +6 -0
- package/dist/resolution/index.js +4 -0
- package/dist/statements/BooleanStatement.d.ts +3 -0
- package/dist/statements/BooleanStatement.js +3 -0
- package/dist/statements/EmbeddedStatement.d.ts +4 -0
- package/dist/statements/EmbeddedStatement.js +3 -0
- package/dist/statements/IStatement.d.ts +2 -0
- package/dist/statements/IStatement.js +1 -0
- package/dist/statements/ListStatement.d.ts +4 -0
- package/dist/statements/ListStatement.js +3 -0
- package/dist/statements/NumberStatement.d.ts +4 -0
- package/dist/statements/NumberStatement.js +10 -0
- package/dist/statements/ReferenceStatement.d.ts +5 -0
- package/dist/statements/ReferenceStatement.js +10 -0
- package/dist/statements/ScalarStatement.d.ts +3 -0
- package/dist/statements/ScalarStatement.js +3 -0
- package/dist/statements/Statement.d.ts +6 -0
- package/dist/statements/Statement.js +4 -0
- package/dist/statements/StringStatement.d.ts +4 -0
- package/dist/statements/StringStatement.js +10 -0
- package/dist/statements/index.d.ts +9 -0
- package/dist/statements/index.js +8 -0
- package/dist/validation/CanonObjectValidator.d.ts +21 -0
- package/dist/validation/CanonObjectValidator.js +150 -0
- package/dist/validation/ICanonObjectValidator.d.ts +7 -0
- package/dist/validation/ICanonObjectValidator.js +1 -0
- package/dist/validation/OntologyValidationError.d.ts +15 -0
- package/dist/validation/OntologyValidationError.js +25 -0
- package/dist/validation/OntologyValidationResult.d.ts +7 -0
- package/dist/validation/OntologyValidationResult.js +8 -0
- package/dist/validation/ValidationContext.d.ts +6 -0
- package/dist/validation/ValidationContext.js +10 -0
- package/dist/validation/ValidationSeverity.d.ts +4 -0
- package/dist/validation/ValidationSeverity.js +5 -0
- package/dist/validation/index.d.ts +10 -0
- package/dist/validation/index.js +7 -0
- package/dist/validation/rules/document/EmbeddedCanonNoExplicitTypeRule.d.ts +8 -0
- package/dist/validation/rules/document/EmbeddedCanonNoExplicitTypeRule.js +53 -0
- package/dist/validation/rules/document/IDocumentValidationRule.d.ts +6 -0
- package/dist/validation/rules/document/IDocumentValidationRule.js +1 -0
- package/dist/validation/rules/document/NamespacePrefixRule.d.ts +10 -0
- package/dist/validation/rules/document/NamespacePrefixRule.js +51 -0
- package/dist/validation/rules/document/PropertyTypeSpecificityRule.d.ts +11 -0
- package/dist/validation/rules/document/PropertyTypeSpecificityRule.js +89 -0
- package/dist/validation/rules/document/ResourceNamingRule.d.ts +12 -0
- package/dist/validation/rules/document/ResourceNamingRule.js +79 -0
- package/dist/validation/rules/document/SubjectCanonTypeRequiredRule.d.ts +7 -0
- package/dist/validation/rules/document/SubjectCanonTypeRequiredRule.js +33 -0
- package/dist/validation/rules/document/index.d.ts +6 -0
- package/dist/validation/rules/document/index.js +5 -0
- package/dist/validation/rules/normalizeToStringList.d.ts +1 -0
- package/dist/validation/rules/normalizeToStringList.js +9 -0
- package/dist/validation/rules/repository/AmbiguousReferenceRule.d.ts +14 -0
- package/dist/validation/rules/repository/AmbiguousReferenceRule.js +155 -0
- package/dist/validation/rules/repository/ClassDefinitionRule.d.ts +12 -0
- package/dist/validation/rules/repository/ClassDefinitionRule.js +205 -0
- package/dist/validation/rules/repository/ClassHierarchyCycleRule.d.ts +11 -0
- package/dist/validation/rules/repository/ClassHierarchyCycleRule.js +98 -0
- package/dist/validation/rules/repository/DefinitionPropertyReferenceRule.d.ts +13 -0
- package/dist/validation/rules/repository/DefinitionPropertyReferenceRule.js +162 -0
- package/dist/validation/rules/repository/IRepositoryValidationRule.d.ts +7 -0
- package/dist/validation/rules/repository/IRepositoryValidationRule.js +1 -0
- package/dist/validation/rules/repository/ImportExistenceRule.d.ts +12 -0
- package/dist/validation/rules/repository/ImportExistenceRule.js +124 -0
- package/dist/validation/rules/repository/InstancePropertyReferenceRule.d.ts +13 -0
- package/dist/validation/rules/repository/InstancePropertyReferenceRule.js +161 -0
- package/dist/validation/rules/repository/NamespaceImportCycleRule.d.ts +11 -0
- package/dist/validation/rules/repository/NamespaceImportCycleRule.js +85 -0
- package/dist/validation/rules/repository/ObjectPropertyImportRule.d.ts +9 -0
- package/dist/validation/rules/repository/ObjectPropertyImportRule.js +113 -0
- package/dist/validation/rules/repository/ObjectPropertyValueValidationRule.d.ts +12 -0
- package/dist/validation/rules/repository/ObjectPropertyValueValidationRule.js +281 -0
- package/dist/validation/rules/repository/PropertyDomainRule.d.ts +14 -0
- package/dist/validation/rules/repository/PropertyDomainRule.js +221 -0
- package/dist/validation/rules/repository/PropertyHierarchyCycleRule.d.ts +11 -0
- package/dist/validation/rules/repository/PropertyHierarchyCycleRule.js +104 -0
- package/dist/validation/rules/repository/PropertyRangeReferenceRule.d.ts +13 -0
- package/dist/validation/rules/repository/PropertyRangeReferenceRule.js +184 -0
- package/dist/validation/rules/repository/PropertyRangeRequiredRule.d.ts +8 -0
- package/dist/validation/rules/repository/PropertyRangeRequiredRule.js +40 -0
- package/dist/validation/rules/repository/PropertyValueTypeRule.d.ts +11 -0
- package/dist/validation/rules/repository/PropertyValueTypeRule.js +171 -0
- package/dist/validation/rules/repository/SubClassOfReferenceRule.d.ts +11 -0
- package/dist/validation/rules/repository/SubClassOfReferenceRule.js +133 -0
- package/dist/validation/rules/repository/SubPropertyOfReferenceRule.d.ts +11 -0
- package/dist/validation/rules/repository/SubPropertyOfReferenceRule.js +133 -0
- package/dist/validation/rules/repository/TypeAmbiguityRule.d.ts +10 -0
- package/dist/validation/rules/repository/TypeAmbiguityRule.js +104 -0
- package/dist/validation/rules/repository/UnresolvedReferenceRule.d.ts +11 -0
- package/dist/validation/rules/repository/UnresolvedReferenceRule.js +91 -0
- package/dist/validation/rules/repository/XsdImportRule.d.ts +11 -0
- package/dist/validation/rules/repository/XsdImportRule.js +125 -0
- package/dist/validation/rules/repository/index.d.ts +20 -0
- package/dist/validation/rules/repository/index.js +19 -0
- package/package.json +82 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { CanonParser } from '../parsing/CanonParser.js';
|
|
4
|
+
export class FileSystemCanonDocumentRepository {
|
|
5
|
+
rootPath;
|
|
6
|
+
recursive;
|
|
7
|
+
parser;
|
|
8
|
+
documentCache;
|
|
9
|
+
filePathsByIdentifier;
|
|
10
|
+
cacheInitialized = false;
|
|
11
|
+
cacheInitPromise = null;
|
|
12
|
+
parsingErrors = new Map();
|
|
13
|
+
constructor(rootPath, recursive = true, parser) {
|
|
14
|
+
if (!rootPath || rootPath.trim().length === 0) {
|
|
15
|
+
throw new Error('Root path cannot be null or empty');
|
|
16
|
+
}
|
|
17
|
+
this.rootPath = path.resolve(rootPath);
|
|
18
|
+
this.recursive = recursive;
|
|
19
|
+
this.parser = parser ?? new CanonParser();
|
|
20
|
+
this.documentCache = new Map();
|
|
21
|
+
this.filePathsByIdentifier = new Map();
|
|
22
|
+
}
|
|
23
|
+
async getAllDocumentsAsync() {
|
|
24
|
+
await this.ensureCacheInitialized();
|
|
25
|
+
const uniqueDocuments = new Set(this.documentCache.values());
|
|
26
|
+
return Array.from(uniqueDocuments);
|
|
27
|
+
}
|
|
28
|
+
async getDocumentAsync(identifier) {
|
|
29
|
+
await this.ensureCacheInitialized();
|
|
30
|
+
let document = this.documentCache.get(identifier);
|
|
31
|
+
if (document) {
|
|
32
|
+
return document;
|
|
33
|
+
}
|
|
34
|
+
const fullPath = path.join(this.rootPath, identifier);
|
|
35
|
+
document = this.documentCache.get(fullPath);
|
|
36
|
+
if (document) {
|
|
37
|
+
return document;
|
|
38
|
+
}
|
|
39
|
+
for (const ext of ['.can.yml', '.yml', '.yaml']) {
|
|
40
|
+
if (!identifier.endsWith(ext)) {
|
|
41
|
+
const withExt = identifier + ext;
|
|
42
|
+
document = this.documentCache.get(withExt);
|
|
43
|
+
if (document) {
|
|
44
|
+
return document;
|
|
45
|
+
}
|
|
46
|
+
const fullWithExt = path.join(this.rootPath, withExt);
|
|
47
|
+
document = this.documentCache.get(fullWithExt);
|
|
48
|
+
if (document) {
|
|
49
|
+
return document;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
async getDocumentsByNamespaceAsync(publisher, package_) {
|
|
56
|
+
await this.ensureCacheInitialized();
|
|
57
|
+
const uniqueDocuments = new Set(this.documentCache.values());
|
|
58
|
+
return Array.from(uniqueDocuments).filter(doc => doc.metadata.namespace_?.toString().includes(publisher) &&
|
|
59
|
+
doc.metadata.namespace_?.toString().includes(package_));
|
|
60
|
+
}
|
|
61
|
+
async getHighestCompatibleVersionAsync(publisher, import_) {
|
|
62
|
+
await this.ensureCacheInitialized();
|
|
63
|
+
const uniqueDocuments = new Set(this.documentCache.values());
|
|
64
|
+
const candidates = Array.from(uniqueDocuments).filter(doc => {
|
|
65
|
+
const ns = doc.metadata.namespace_?.toString() || '';
|
|
66
|
+
return ns.includes(publisher) && ns.includes(import_.packageName);
|
|
67
|
+
});
|
|
68
|
+
if (candidates.length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return candidates[0] || null;
|
|
72
|
+
}
|
|
73
|
+
async saveDocumentAsync(document, identifier) {
|
|
74
|
+
let filePath;
|
|
75
|
+
const ns = document.metadata.namespace_?.toString();
|
|
76
|
+
if (ns && ns.includes('@') && ns.includes('/')) {
|
|
77
|
+
const parts = ns.split('/');
|
|
78
|
+
const publisher = parts[0];
|
|
79
|
+
const packageVersion = parts[1];
|
|
80
|
+
const fileName = packageVersion.replace('@', '@') + '.can.yml';
|
|
81
|
+
filePath = path.join(this.rootPath, publisher, fileName);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
filePath = path.join(this.rootPath, identifier);
|
|
85
|
+
if (!filePath.endsWith('.yml') && !filePath.endsWith('.yaml') && !filePath.endsWith('.can.yml')) {
|
|
86
|
+
filePath += '.can.yml';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const directory = path.dirname(filePath);
|
|
90
|
+
await fs.mkdir(directory, { recursive: true });
|
|
91
|
+
const yaml = this.parser.save(document);
|
|
92
|
+
await fs.writeFile(filePath, yaml, 'utf8');
|
|
93
|
+
if (ns) {
|
|
94
|
+
this.documentCache.set(ns, document);
|
|
95
|
+
}
|
|
96
|
+
this.documentCache.set(filePath, document);
|
|
97
|
+
const relativePath = path.relative(this.rootPath, filePath);
|
|
98
|
+
this.documentCache.set(relativePath, document);
|
|
99
|
+
}
|
|
100
|
+
async deleteDocumentAsync(identifier) {
|
|
101
|
+
const document = await this.getDocumentAsync(identifier);
|
|
102
|
+
if (!document) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const filePathToDelete = this.filePathsByIdentifier.get(identifier);
|
|
106
|
+
if (!filePathToDelete) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await fs.unlink(filePathToDelete);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
}
|
|
114
|
+
const ns = document.metadata.namespace_?.toString();
|
|
115
|
+
if (ns) {
|
|
116
|
+
this.documentCache.delete(ns);
|
|
117
|
+
}
|
|
118
|
+
this.documentCache.delete(identifier);
|
|
119
|
+
this.documentCache.delete(filePathToDelete);
|
|
120
|
+
const relativePath = path.relative(this.rootPath, filePathToDelete);
|
|
121
|
+
this.documentCache.delete(relativePath);
|
|
122
|
+
}
|
|
123
|
+
async clearNamespaceAsync(publisher, package_) {
|
|
124
|
+
await this.ensureCacheInitialized();
|
|
125
|
+
const documentsToRemove = [];
|
|
126
|
+
for (const [key, doc] of this.documentCache.entries()) {
|
|
127
|
+
const ns = doc.metadata.namespace_?.toString() || '';
|
|
128
|
+
if (ns.includes(publisher) && ns.includes(package_)) {
|
|
129
|
+
documentsToRemove.push([key, doc]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const publisherDir = path.join(this.rootPath, publisher);
|
|
133
|
+
try {
|
|
134
|
+
const files = await fs.readdir(publisherDir);
|
|
135
|
+
for (const file of files) {
|
|
136
|
+
if (file.includes(package_) && file.endsWith('.can.yml')) {
|
|
137
|
+
await fs.unlink(path.join(publisherDir, file));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
}
|
|
143
|
+
for (const [key] of documentsToRemove) {
|
|
144
|
+
this.documentCache.delete(key);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async getAllDocumentReferencesAsync() {
|
|
148
|
+
await this.ensureCacheInitialized();
|
|
149
|
+
const filePaths = new Set(this.filePathsByIdentifier.values());
|
|
150
|
+
const references = [];
|
|
151
|
+
for (const filePath of filePaths) {
|
|
152
|
+
const uri = `file://${filePath}`;
|
|
153
|
+
let namespaceIdentifier;
|
|
154
|
+
for (const [key, doc] of this.documentCache.entries()) {
|
|
155
|
+
const mappedPath = this.filePathsByIdentifier.get(key);
|
|
156
|
+
if (mappedPath === filePath && doc.metadata.namespace_) {
|
|
157
|
+
namespaceIdentifier = doc.metadata.namespace_.toString();
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const identifier = namespaceIdentifier ?? path.relative(this.rootPath, filePath);
|
|
162
|
+
const hasParseError = this.parsingErrors.has(filePath);
|
|
163
|
+
references.push({
|
|
164
|
+
identifier,
|
|
165
|
+
uri,
|
|
166
|
+
hasParseError
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return references;
|
|
170
|
+
}
|
|
171
|
+
async getDocumentContentAsync(identifier) {
|
|
172
|
+
await this.ensureCacheInitialized();
|
|
173
|
+
let filePath = this.filePathsByIdentifier.get(identifier);
|
|
174
|
+
if (!filePath) {
|
|
175
|
+
try {
|
|
176
|
+
await fs.access(identifier);
|
|
177
|
+
filePath = identifier;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
const fullPath = path.join(this.rootPath, identifier);
|
|
181
|
+
try {
|
|
182
|
+
await fs.access(fullPath);
|
|
183
|
+
filePath = fullPath;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
return await fs.readFile(filePath, 'utf8');
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async getDocumentUriAsync(identifier) {
|
|
198
|
+
await this.ensureCacheInitialized();
|
|
199
|
+
let filePath = this.filePathsByIdentifier.get(identifier);
|
|
200
|
+
if (!filePath) {
|
|
201
|
+
try {
|
|
202
|
+
await fs.access(identifier);
|
|
203
|
+
filePath = identifier;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
const fullPath = path.join(this.rootPath, identifier);
|
|
207
|
+
try {
|
|
208
|
+
await fs.access(fullPath);
|
|
209
|
+
filePath = fullPath;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return `file://${filePath}`;
|
|
217
|
+
}
|
|
218
|
+
async refreshAsync() {
|
|
219
|
+
this.documentCache.clear();
|
|
220
|
+
this.filePathsByIdentifier.clear();
|
|
221
|
+
this.parsingErrors.clear();
|
|
222
|
+
this.cacheInitialized = false;
|
|
223
|
+
this.cacheInitPromise = null;
|
|
224
|
+
await this.ensureCacheInitialized();
|
|
225
|
+
}
|
|
226
|
+
async ensureCacheInitialized() {
|
|
227
|
+
if (this.cacheInitialized) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (this.cacheInitPromise) {
|
|
231
|
+
await this.cacheInitPromise;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
this.cacheInitPromise = this.loadDocuments();
|
|
235
|
+
await this.cacheInitPromise;
|
|
236
|
+
this.cacheInitialized = true;
|
|
237
|
+
}
|
|
238
|
+
async loadDocuments() {
|
|
239
|
+
try {
|
|
240
|
+
await fs.mkdir(this.rootPath, { recursive: true });
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
}
|
|
244
|
+
const yamlFiles = await this.findYamlFiles(this.rootPath, this.recursive);
|
|
245
|
+
const loadTasks = yamlFiles.map(async (filePath) => {
|
|
246
|
+
try {
|
|
247
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
248
|
+
const document = this.parser.parse(content);
|
|
249
|
+
return { filePath, document, error: null };
|
|
250
|
+
}
|
|
251
|
+
catch (ex) {
|
|
252
|
+
return { filePath, document: null, error: ex instanceof Error ? ex.message : String(ex) };
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
const results = await Promise.all(loadTasks);
|
|
256
|
+
for (const { filePath, document, error } of results) {
|
|
257
|
+
const relativePath = path.relative(this.rootPath, filePath);
|
|
258
|
+
if (document && document.metadata.namespace_) {
|
|
259
|
+
const namespaceKey = document.metadata.namespace_.toString();
|
|
260
|
+
this.documentCache.set(namespaceKey, document);
|
|
261
|
+
this.documentCache.set(filePath, document);
|
|
262
|
+
this.documentCache.set(relativePath, document);
|
|
263
|
+
this.filePathsByIdentifier.set(namespaceKey, filePath);
|
|
264
|
+
this.filePathsByIdentifier.set(filePath, filePath);
|
|
265
|
+
this.filePathsByIdentifier.set(relativePath, filePath);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
this.filePathsByIdentifier.set(filePath, filePath);
|
|
269
|
+
this.filePathsByIdentifier.set(relativePath, filePath);
|
|
270
|
+
}
|
|
271
|
+
if (error) {
|
|
272
|
+
this.parsingErrors.set(filePath, error);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async findYamlFiles(dir, recursive) {
|
|
277
|
+
const files = [];
|
|
278
|
+
try {
|
|
279
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
280
|
+
for (const entry of entries) {
|
|
281
|
+
const fullPath = path.join(dir, entry.name);
|
|
282
|
+
if (entry.isDirectory() && recursive) {
|
|
283
|
+
const subFiles = await this.findYamlFiles(fullPath, recursive);
|
|
284
|
+
files.push(...subFiles);
|
|
285
|
+
}
|
|
286
|
+
else if (entry.isFile() && entry.name.endsWith('.can.yml')) {
|
|
287
|
+
files.push(fullPath);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
}
|
|
293
|
+
return files;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ICanonDocumentRepository } from '@canon-protocol/types/document/models';
|
|
2
|
+
import type { CanonDocument, Import, DocumentReference } from '@canon-protocol/types/document/models/types';
|
|
3
|
+
export declare class HttpCanonDocumentRepository implements ICanonDocumentRepository {
|
|
4
|
+
private readonly parser;
|
|
5
|
+
private readonly publisherIndex;
|
|
6
|
+
private readonly documents;
|
|
7
|
+
private readonly contentCache;
|
|
8
|
+
private readonly onFetch;
|
|
9
|
+
private readonly getFromCache;
|
|
10
|
+
constructor(options?: {
|
|
11
|
+
onFetch?: (publisher: string, packageName: string, version: string, content: string) => void;
|
|
12
|
+
getFromCache?: (publisher: string, packageName: string, version: string) => string | null;
|
|
13
|
+
});
|
|
14
|
+
getHighestCompatibleVersionAsync(publisher: string, import_: Import): Promise<CanonDocument | null>;
|
|
15
|
+
getAllDocumentsAsync(): Promise<CanonDocument[]>;
|
|
16
|
+
getDocumentAsync(identifier: string): Promise<CanonDocument | null>;
|
|
17
|
+
getDocumentsByNamespaceAsync(publisher: string, package_: string): Promise<CanonDocument[]>;
|
|
18
|
+
saveDocumentAsync(_document: CanonDocument, _identifier: string): Promise<void>;
|
|
19
|
+
deleteDocumentAsync(_identifier: string): Promise<void>;
|
|
20
|
+
clearNamespaceAsync(_publisher: string, _package: string): Promise<void>;
|
|
21
|
+
getAllDocumentReferencesAsync(): Promise<DocumentReference[]>;
|
|
22
|
+
getDocumentContentAsync(identifier: string): Promise<string | null>;
|
|
23
|
+
getDocumentUriAsync(identifier: string): Promise<string | null>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CanonParser } from '../parsing/CanonParser.js';
|
|
2
|
+
import { PublisherIndex } from './PublisherIndex.js';
|
|
3
|
+
export class HttpCanonDocumentRepository {
|
|
4
|
+
parser = new CanonParser();
|
|
5
|
+
publisherIndex = new PublisherIndex();
|
|
6
|
+
documents = new Map();
|
|
7
|
+
contentCache = new Map();
|
|
8
|
+
onFetch;
|
|
9
|
+
getFromCache;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.onFetch = options?.onFetch;
|
|
12
|
+
this.getFromCache = options?.getFromCache;
|
|
13
|
+
}
|
|
14
|
+
async getHighestCompatibleVersionAsync(publisher, import_) {
|
|
15
|
+
const version = await this.publisherIndex.resolveVersion(publisher, import_);
|
|
16
|
+
if (!version)
|
|
17
|
+
return null;
|
|
18
|
+
const key = `${publisher}/${import_.packageName}@${version}`;
|
|
19
|
+
const cached = this.documents.get(key);
|
|
20
|
+
if (cached)
|
|
21
|
+
return cached;
|
|
22
|
+
if (this.getFromCache) {
|
|
23
|
+
const cachedContent = this.getFromCache(publisher, import_.packageName, version);
|
|
24
|
+
if (cachedContent) {
|
|
25
|
+
const doc = this.parser.parse(cachedContent);
|
|
26
|
+
this.documents.set(key, doc);
|
|
27
|
+
this.contentCache.set(key, cachedContent);
|
|
28
|
+
return doc;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const url = await this.publisherIndex.getPackageUrl(publisher, import_.packageName, version);
|
|
32
|
+
const response = await fetch(url);
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(`Failed to fetch Canon package: ${url} (${response.status} ${response.statusText})`);
|
|
35
|
+
}
|
|
36
|
+
const content = await response.text();
|
|
37
|
+
if (this.onFetch) {
|
|
38
|
+
this.onFetch(publisher, import_.packageName, version, content);
|
|
39
|
+
}
|
|
40
|
+
const doc = this.parser.parse(content);
|
|
41
|
+
this.documents.set(key, doc);
|
|
42
|
+
this.contentCache.set(key, content);
|
|
43
|
+
return doc;
|
|
44
|
+
}
|
|
45
|
+
async getAllDocumentsAsync() {
|
|
46
|
+
return Array.from(this.documents.values());
|
|
47
|
+
}
|
|
48
|
+
async getDocumentAsync(identifier) {
|
|
49
|
+
return this.documents.get(identifier) ?? null;
|
|
50
|
+
}
|
|
51
|
+
async getDocumentsByNamespaceAsync(publisher, package_) {
|
|
52
|
+
return Array.from(this.documents.values()).filter(doc => {
|
|
53
|
+
const ns = doc.metadata.namespace_;
|
|
54
|
+
return ns && ns.publisher === publisher && ns.package_ === package_;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async saveDocumentAsync(_document, _identifier) {
|
|
58
|
+
throw new Error('HttpCanonDocumentRepository is read-only');
|
|
59
|
+
}
|
|
60
|
+
async deleteDocumentAsync(_identifier) {
|
|
61
|
+
throw new Error('HttpCanonDocumentRepository is read-only');
|
|
62
|
+
}
|
|
63
|
+
async clearNamespaceAsync(_publisher, _package) {
|
|
64
|
+
throw new Error('HttpCanonDocumentRepository is read-only');
|
|
65
|
+
}
|
|
66
|
+
async getAllDocumentReferencesAsync() {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
async getDocumentContentAsync(identifier) {
|
|
70
|
+
return this.contentCache.get(identifier) ?? null;
|
|
71
|
+
}
|
|
72
|
+
async getDocumentUriAsync(identifier) {
|
|
73
|
+
const match = identifier.match(/^(.+?)\/(.+?)@(.+)$/);
|
|
74
|
+
if (!match)
|
|
75
|
+
return null;
|
|
76
|
+
const [, publisher, packageName, version] = match;
|
|
77
|
+
return this.publisherIndex.getPackageUrl(publisher, packageName, version);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ICanonDocumentRepository } from '@canon-protocol/types/document/models';
|
|
2
|
+
import type { CanonDocument, DocumentReference, Import } from '@canon-protocol/types/document/models/types';
|
|
3
|
+
import type { ICanonParser } from '@canon-protocol/types/document/parsing';
|
|
4
|
+
export declare class InMemoryCanonDocumentRepository implements ICanonDocumentRepository {
|
|
5
|
+
private readonly documents;
|
|
6
|
+
private readonly documentContents;
|
|
7
|
+
private readonly parser;
|
|
8
|
+
constructor(parser: ICanonParser);
|
|
9
|
+
getAllDocumentsAsync(): Promise<CanonDocument[]>;
|
|
10
|
+
getDocumentAsync(identifier: string): Promise<CanonDocument | null>;
|
|
11
|
+
getDocumentsByNamespaceAsync(publisher: string, package_: string): Promise<CanonDocument[]>;
|
|
12
|
+
getHighestCompatibleVersionAsync(publisher: string, import_: Import): Promise<CanonDocument | null>;
|
|
13
|
+
saveDocumentAsync(document: CanonDocument, identifier: string): Promise<void>;
|
|
14
|
+
deleteDocumentAsync(identifier: string): Promise<void>;
|
|
15
|
+
clearNamespaceAsync(publisher: string, package_: string): Promise<void>;
|
|
16
|
+
getAllDocumentReferencesAsync(): Promise<DocumentReference[]>;
|
|
17
|
+
getDocumentContentAsync(identifier: string): Promise<string | null>;
|
|
18
|
+
getDocumentUriAsync(identifier: string): Promise<string | null>;
|
|
19
|
+
private versionsEqual;
|
|
20
|
+
private compareVersions;
|
|
21
|
+
private isCompatibleVersion;
|
|
22
|
+
private isMajorCompatible;
|
|
23
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export class InMemoryCanonDocumentRepository {
|
|
2
|
+
documents = new Map();
|
|
3
|
+
documentContents = new Map();
|
|
4
|
+
parser;
|
|
5
|
+
constructor(parser) {
|
|
6
|
+
this.parser = parser;
|
|
7
|
+
}
|
|
8
|
+
async getAllDocumentsAsync() {
|
|
9
|
+
return Array.from(this.documents.values());
|
|
10
|
+
}
|
|
11
|
+
async getDocumentAsync(identifier) {
|
|
12
|
+
return this.documents.get(identifier) ?? null;
|
|
13
|
+
}
|
|
14
|
+
async getDocumentsByNamespaceAsync(publisher, package_) {
|
|
15
|
+
const results = [];
|
|
16
|
+
for (const doc of this.documents.values()) {
|
|
17
|
+
if (doc.metadata?.namespace_?.publisher === publisher &&
|
|
18
|
+
doc.metadata?.namespace_?.package_ === package_) {
|
|
19
|
+
results.push(doc);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return results;
|
|
23
|
+
}
|
|
24
|
+
async getHighestCompatibleVersionAsync(publisher, import_) {
|
|
25
|
+
const candidates = [];
|
|
26
|
+
for (const doc of this.documents.values()) {
|
|
27
|
+
if (doc.metadata?.namespace_?.publisher === publisher &&
|
|
28
|
+
doc.metadata?.namespace_?.package_ === import_.packageName &&
|
|
29
|
+
doc.metadata?.namespace_?.version !== undefined) {
|
|
30
|
+
candidates.push(doc);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (candidates.length === 0) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const compatible = candidates.filter(doc => {
|
|
37
|
+
const docVersion = doc.metadata.namespace_.version;
|
|
38
|
+
if (!docVersion)
|
|
39
|
+
return false;
|
|
40
|
+
switch (import_.versionOperator) {
|
|
41
|
+
case 0: // VersionOperator.Exact
|
|
42
|
+
return this.versionsEqual(docVersion, import_.version);
|
|
43
|
+
case 1: // VersionOperator.Compatible (~)
|
|
44
|
+
return this.isCompatibleVersion(docVersion, import_.version);
|
|
45
|
+
case 2: // VersionOperator.Major (^)
|
|
46
|
+
return this.isMajorCompatible(docVersion, import_.version);
|
|
47
|
+
case 3: // VersionOperator.Any (>=)
|
|
48
|
+
return this.compareVersions(docVersion, import_.minVersion) >= 0;
|
|
49
|
+
default:
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
if (compatible.length === 0) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
compatible.sort((a, b) => {
|
|
57
|
+
const versionA = a.metadata.namespace_.version;
|
|
58
|
+
const versionB = b.metadata.namespace_.version;
|
|
59
|
+
if (!versionA && !versionB)
|
|
60
|
+
return 0;
|
|
61
|
+
if (!versionA)
|
|
62
|
+
return 1;
|
|
63
|
+
if (!versionB)
|
|
64
|
+
return -1;
|
|
65
|
+
return this.compareVersions(versionB, versionA); // Descending order
|
|
66
|
+
});
|
|
67
|
+
return compatible[0];
|
|
68
|
+
}
|
|
69
|
+
async saveDocumentAsync(document, identifier) {
|
|
70
|
+
this.documents.set(identifier, document);
|
|
71
|
+
const content = this.parser.save(document);
|
|
72
|
+
this.documentContents.set(identifier, content);
|
|
73
|
+
}
|
|
74
|
+
async deleteDocumentAsync(identifier) {
|
|
75
|
+
this.documents.delete(identifier);
|
|
76
|
+
this.documentContents.delete(identifier);
|
|
77
|
+
}
|
|
78
|
+
async clearNamespaceAsync(publisher, package_) {
|
|
79
|
+
const keysToRemove = [];
|
|
80
|
+
for (const [key, doc] of this.documents.entries()) {
|
|
81
|
+
if (doc.metadata?.namespace_?.publisher === publisher &&
|
|
82
|
+
doc.metadata?.namespace_?.package_ === package_) {
|
|
83
|
+
keysToRemove.push(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const key of keysToRemove) {
|
|
87
|
+
this.documents.delete(key);
|
|
88
|
+
this.documentContents.delete(key);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async getAllDocumentReferencesAsync() {
|
|
92
|
+
const references = [];
|
|
93
|
+
for (const doc of this.documents.values()) {
|
|
94
|
+
if (doc.metadata?.namespace_) {
|
|
95
|
+
references.push({
|
|
96
|
+
identifier: doc.metadata.namespace_.toString(),
|
|
97
|
+
uri: `canon://${doc.metadata.namespace_}`,
|
|
98
|
+
hasParseError: false // In-memory documents are always valid
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return references;
|
|
103
|
+
}
|
|
104
|
+
async getDocumentContentAsync(identifier) {
|
|
105
|
+
const storedContent = this.documentContents.get(identifier);
|
|
106
|
+
if (storedContent !== undefined) {
|
|
107
|
+
return storedContent;
|
|
108
|
+
}
|
|
109
|
+
const document = this.documents.get(identifier);
|
|
110
|
+
if (document !== undefined) {
|
|
111
|
+
return this.parser.save(document);
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
async getDocumentUriAsync(identifier) {
|
|
116
|
+
const document = this.documents.get(identifier);
|
|
117
|
+
if (!document) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
if (document.metadata?.namespace_) {
|
|
121
|
+
return `canon://${document.metadata.namespace_}`;
|
|
122
|
+
}
|
|
123
|
+
return `canon://${identifier}`;
|
|
124
|
+
}
|
|
125
|
+
versionsEqual(a, b) {
|
|
126
|
+
return a.major === b.major && a.minor === b.minor && a.patch === b.patch;
|
|
127
|
+
}
|
|
128
|
+
compareVersions(a, b) {
|
|
129
|
+
if (a.major !== b.major)
|
|
130
|
+
return a.major - b.major;
|
|
131
|
+
if (a.minor !== b.minor)
|
|
132
|
+
return a.minor - b.minor;
|
|
133
|
+
return a.patch - b.patch;
|
|
134
|
+
}
|
|
135
|
+
isCompatibleVersion(docVersion, requiredVersion) {
|
|
136
|
+
return this.compareVersions(docVersion, requiredVersion) >= 0 &&
|
|
137
|
+
docVersion.major === requiredVersion.major &&
|
|
138
|
+
docVersion.minor === requiredVersion.minor;
|
|
139
|
+
}
|
|
140
|
+
isMajorCompatible(docVersion, requiredVersion) {
|
|
141
|
+
if (requiredVersion.major === 0) {
|
|
142
|
+
return this.compareVersions(docVersion, requiredVersion) >= 0 &&
|
|
143
|
+
docVersion.major === 0 &&
|
|
144
|
+
docVersion.minor === requiredVersion.minor;
|
|
145
|
+
}
|
|
146
|
+
return this.compareVersions(docVersion, requiredVersion) >= 0 &&
|
|
147
|
+
docVersion.major === requiredVersion.major;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface PublisherConfig {
|
|
2
|
+
version: number;
|
|
3
|
+
index?: string;
|
|
4
|
+
package?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class PublisherConfigResolver {
|
|
7
|
+
private readonly cache;
|
|
8
|
+
getConfig(publisher: string): Promise<PublisherConfig>;
|
|
9
|
+
private fetchConfig;
|
|
10
|
+
resolveIndexUrl(publisher: string, config: PublisherConfig): string;
|
|
11
|
+
resolvePackageUrl(publisher: string, packageName: string, version: string, config: PublisherConfig): string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const DEFAULT_INDEX_TEMPLATE = 'https://{publisher}/index.txt';
|
|
2
|
+
const DEFAULT_PACKAGE_TEMPLATE = 'https://{publisher}/{package}/{version}.can.yml';
|
|
3
|
+
const DEFAULT_CONFIG = { version: 1 };
|
|
4
|
+
export class PublisherConfigResolver {
|
|
5
|
+
cache = new Map();
|
|
6
|
+
async getConfig(publisher) {
|
|
7
|
+
const cached = this.cache.get(publisher);
|
|
8
|
+
if (cached)
|
|
9
|
+
return cached;
|
|
10
|
+
const config = await this.fetchConfig(publisher);
|
|
11
|
+
this.cache.set(publisher, config);
|
|
12
|
+
return config;
|
|
13
|
+
}
|
|
14
|
+
async fetchConfig(publisher) {
|
|
15
|
+
const url = `https://${publisher}/.well-known/canon.json`;
|
|
16
|
+
let response;
|
|
17
|
+
try {
|
|
18
|
+
response = await fetch(url);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Network error (DNS failure, unreachable, etc.) — fall back to defaults
|
|
22
|
+
return DEFAULT_CONFIG;
|
|
23
|
+
}
|
|
24
|
+
if (response.status === 404) {
|
|
25
|
+
return DEFAULT_CONFIG;
|
|
26
|
+
}
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new Error(`Failed to fetch publisher config: ${url} (${response.status} ${response.statusText})`);
|
|
29
|
+
}
|
|
30
|
+
const json = await response.json();
|
|
31
|
+
if (typeof json.version !== 'number') {
|
|
32
|
+
throw new Error(`Invalid publisher config at ${url}: missing or invalid "version" field`);
|
|
33
|
+
}
|
|
34
|
+
const config = { version: json.version };
|
|
35
|
+
if (typeof json.index === 'string') {
|
|
36
|
+
config.index = json.index;
|
|
37
|
+
}
|
|
38
|
+
if (typeof json.package === 'string') {
|
|
39
|
+
config.package = json.package;
|
|
40
|
+
}
|
|
41
|
+
return config;
|
|
42
|
+
}
|
|
43
|
+
resolveIndexUrl(publisher, config) {
|
|
44
|
+
const template = config.index ?? DEFAULT_INDEX_TEMPLATE;
|
|
45
|
+
return resolveTemplate(template, { publisher });
|
|
46
|
+
}
|
|
47
|
+
resolvePackageUrl(publisher, packageName, version, config) {
|
|
48
|
+
const template = config.package ?? DEFAULT_PACKAGE_TEMPLATE;
|
|
49
|
+
return resolveTemplate(template, { publisher, package: packageName, version });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function resolveTemplate(template, vars) {
|
|
53
|
+
let result = template;
|
|
54
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
55
|
+
result = result.replaceAll(`{${key}}`, value);
|
|
56
|
+
}
|
|
57
|
+
const unresolved = result.match(/\{[a-z]+\}/);
|
|
58
|
+
if (unresolved) {
|
|
59
|
+
throw new Error(`Unresolved variable ${unresolved[0]} in URL template: ${template}`);
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Import } from '@canon-protocol/types/document/models/types';
|
|
2
|
+
import { PublisherConfigResolver } from './PublisherConfig.js';
|
|
3
|
+
export declare class PublisherIndex {
|
|
4
|
+
private readonly indexCache;
|
|
5
|
+
private readonly configResolver;
|
|
6
|
+
constructor(configResolver?: PublisherConfigResolver);
|
|
7
|
+
resolveVersion(publisher: string, import_: Import): Promise<string | null>;
|
|
8
|
+
getHighestVersion(publisher: string, packageName: string): Promise<string | null>;
|
|
9
|
+
getPackageUrl(publisher: string, packageName: string, version: string): Promise<string>;
|
|
10
|
+
private getPackageVersions;
|
|
11
|
+
private fetchIndex;
|
|
12
|
+
static parseIndex(text: string): Map<string, string[]>;
|
|
13
|
+
private isVersionCompatible;
|
|
14
|
+
static parseVersion(str: string): {
|
|
15
|
+
major: number;
|
|
16
|
+
minor: number;
|
|
17
|
+
patch: number;
|
|
18
|
+
} | null;
|
|
19
|
+
private compareVersionStrings;
|
|
20
|
+
}
|