@resourcexjs/core 2.5.4 → 2.5.6
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/README.md +87 -8
- package/dist/index.d.ts +727 -1
- package/dist/index.js +680 -12
- package/dist/index.js.map +27 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ class DefinitionError extends ResourceXError {
|
|
|
35
35
|
this.name = "DefinitionError";
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
// src/
|
|
38
|
+
// src/model/define.ts
|
|
39
39
|
function define(input) {
|
|
40
40
|
if (input === null || typeof input !== "object") {
|
|
41
41
|
throw new DefinitionError("definition must be an object");
|
|
@@ -66,7 +66,7 @@ function define(input) {
|
|
|
66
66
|
};
|
|
67
67
|
return rxd;
|
|
68
68
|
}
|
|
69
|
-
// src/
|
|
69
|
+
// src/model/manifest.ts
|
|
70
70
|
function manifest(rxd) {
|
|
71
71
|
return {
|
|
72
72
|
registry: rxd.registry,
|
|
@@ -76,7 +76,7 @@ function manifest(rxd) {
|
|
|
76
76
|
tag: rxd.tag ?? "latest"
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
// src/
|
|
79
|
+
// src/model/archive.ts
|
|
80
80
|
import { gzip } from "node:zlib";
|
|
81
81
|
import { promisify } from "node:util";
|
|
82
82
|
|
|
@@ -1057,7 +1057,7 @@ async function unpackTar(archive, options = {}) {
|
|
|
1057
1057
|
return results;
|
|
1058
1058
|
}
|
|
1059
1059
|
|
|
1060
|
-
// src/
|
|
1060
|
+
// src/model/archive.ts
|
|
1061
1061
|
var gzipAsync = promisify(gzip);
|
|
1062
1062
|
|
|
1063
1063
|
class RXAImpl {
|
|
@@ -1089,7 +1089,7 @@ async function archive(files) {
|
|
|
1089
1089
|
const gzipBuffer = await gzipAsync(Buffer.from(tarBuffer));
|
|
1090
1090
|
return new RXAImpl(gzipBuffer);
|
|
1091
1091
|
}
|
|
1092
|
-
// src/
|
|
1092
|
+
// src/model/locate.ts
|
|
1093
1093
|
function locate(rxm) {
|
|
1094
1094
|
return {
|
|
1095
1095
|
registry: rxm.registry,
|
|
@@ -1098,7 +1098,7 @@ function locate(rxm) {
|
|
|
1098
1098
|
tag: rxm.tag
|
|
1099
1099
|
};
|
|
1100
1100
|
}
|
|
1101
|
-
// src/
|
|
1101
|
+
// src/model/resource.ts
|
|
1102
1102
|
function resource(rxm, rxa) {
|
|
1103
1103
|
const rxl = locate(rxm);
|
|
1104
1104
|
return {
|
|
@@ -1107,7 +1107,7 @@ function resource(rxm, rxa) {
|
|
|
1107
1107
|
archive: rxa
|
|
1108
1108
|
};
|
|
1109
1109
|
}
|
|
1110
|
-
// src/
|
|
1110
|
+
// src/model/extract.ts
|
|
1111
1111
|
import { gunzip } from "node:zlib";
|
|
1112
1112
|
import { promisify as promisify2 } from "node:util";
|
|
1113
1113
|
var gunzipAsync = promisify2(gunzip);
|
|
@@ -1123,7 +1123,7 @@ async function extract(rxa) {
|
|
|
1123
1123
|
}
|
|
1124
1124
|
return files;
|
|
1125
1125
|
}
|
|
1126
|
-
// src/
|
|
1126
|
+
// src/model/format.ts
|
|
1127
1127
|
function format(rxl) {
|
|
1128
1128
|
let result = "";
|
|
1129
1129
|
if (rxl.registry) {
|
|
@@ -1138,7 +1138,7 @@ function format(rxl) {
|
|
|
1138
1138
|
}
|
|
1139
1139
|
return result;
|
|
1140
1140
|
}
|
|
1141
|
-
// src/
|
|
1141
|
+
// src/model/parse.ts
|
|
1142
1142
|
function looksLikeRegistry(str) {
|
|
1143
1143
|
if (str.includes(":") && !str.includes("/")) {
|
|
1144
1144
|
return true;
|
|
@@ -1207,7 +1207,7 @@ function parse(locator) {
|
|
|
1207
1207
|
tag
|
|
1208
1208
|
};
|
|
1209
1209
|
}
|
|
1210
|
-
// src/
|
|
1210
|
+
// src/model/wrap.ts
|
|
1211
1211
|
class RXAImpl2 {
|
|
1212
1212
|
_buffer;
|
|
1213
1213
|
constructor(buffer) {
|
|
@@ -1229,21 +1229,689 @@ class RXAImpl2 {
|
|
|
1229
1229
|
function wrap(buffer) {
|
|
1230
1230
|
return new RXAImpl2(buffer);
|
|
1231
1231
|
}
|
|
1232
|
+
// src/type/bundler.ts
|
|
1233
|
+
import { readFile } from "node:fs/promises";
|
|
1234
|
+
import { resolve, isAbsolute } from "node:path";
|
|
1235
|
+
async function bundleResourceType(sourcePath, basePath) {
|
|
1236
|
+
const fullPath = isAbsolute(sourcePath) ? sourcePath : resolve(basePath ?? process.cwd(), sourcePath);
|
|
1237
|
+
const source = await readFile(fullPath, "utf-8");
|
|
1238
|
+
const result = await Bun.build({
|
|
1239
|
+
stdin: {
|
|
1240
|
+
contents: source,
|
|
1241
|
+
resolveDir: resolve(fullPath, ".."),
|
|
1242
|
+
loader: "ts"
|
|
1243
|
+
},
|
|
1244
|
+
target: "bun",
|
|
1245
|
+
format: "esm",
|
|
1246
|
+
minify: false
|
|
1247
|
+
});
|
|
1248
|
+
if (!result.success) {
|
|
1249
|
+
const errors = result.logs.map((log) => log.message).join(`
|
|
1250
|
+
`);
|
|
1251
|
+
throw new Error(`Failed to bundle ${sourcePath}: ${errors}`);
|
|
1252
|
+
}
|
|
1253
|
+
const bundledCode = await result.outputs[0].text();
|
|
1254
|
+
const tempModule = await import(fullPath);
|
|
1255
|
+
const typeSource = tempModule.default;
|
|
1256
|
+
if (!typeSource.name) {
|
|
1257
|
+
throw new Error(`Resource type at ${sourcePath} must have a name`);
|
|
1258
|
+
}
|
|
1259
|
+
if (typeof typeSource.resolve !== "function") {
|
|
1260
|
+
throw new Error(`Resource type at ${sourcePath} must have a resolve function`);
|
|
1261
|
+
}
|
|
1262
|
+
return {
|
|
1263
|
+
name: typeSource.name,
|
|
1264
|
+
aliases: typeSource.aliases,
|
|
1265
|
+
description: typeSource.description ?? "",
|
|
1266
|
+
schema: typeSource.schema,
|
|
1267
|
+
code: bundledCode
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
// src/type/errors.ts
|
|
1271
|
+
class ResourceTypeError extends ResourceXError {
|
|
1272
|
+
constructor(message) {
|
|
1273
|
+
super(message);
|
|
1274
|
+
this.name = "ResourceTypeError";
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
// src/type/builtinTypes.ts
|
|
1278
|
+
var textType = {
|
|
1279
|
+
name: "text",
|
|
1280
|
+
aliases: ["txt", "plaintext"],
|
|
1281
|
+
description: "Plain text content",
|
|
1282
|
+
code: `// @resolver: text_type_default
|
|
1283
|
+
// src/builtins/text.type.ts
|
|
1284
|
+
var text_type_default = {
|
|
1285
|
+
name: "text",
|
|
1286
|
+
aliases: ["txt", "plaintext"],
|
|
1287
|
+
description: "Plain text content",
|
|
1288
|
+
async resolve(ctx) {
|
|
1289
|
+
const content = ctx.files["content"];
|
|
1290
|
+
return new TextDecoder().decode(content);
|
|
1291
|
+
}
|
|
1292
|
+
};`
|
|
1293
|
+
};
|
|
1294
|
+
var jsonType = {
|
|
1295
|
+
name: "json",
|
|
1296
|
+
aliases: ["config", "manifest"],
|
|
1297
|
+
description: "JSON content",
|
|
1298
|
+
code: `// @resolver: json_type_default
|
|
1299
|
+
// src/builtins/json.type.ts
|
|
1300
|
+
var json_type_default = {
|
|
1301
|
+
name: "json",
|
|
1302
|
+
aliases: ["config", "manifest"],
|
|
1303
|
+
description: "JSON content",
|
|
1304
|
+
async resolve(ctx) {
|
|
1305
|
+
const content = ctx.files["content"];
|
|
1306
|
+
return JSON.parse(new TextDecoder().decode(content));
|
|
1307
|
+
}
|
|
1308
|
+
};`
|
|
1309
|
+
};
|
|
1310
|
+
var binaryType = {
|
|
1311
|
+
name: "binary",
|
|
1312
|
+
aliases: ["bin", "blob", "raw"],
|
|
1313
|
+
description: "Binary content",
|
|
1314
|
+
code: `// @resolver: binary_type_default
|
|
1315
|
+
// src/builtins/binary.type.ts
|
|
1316
|
+
var binary_type_default = {
|
|
1317
|
+
name: "binary",
|
|
1318
|
+
aliases: ["bin", "blob", "raw"],
|
|
1319
|
+
description: "Binary content",
|
|
1320
|
+
async resolve(ctx) {
|
|
1321
|
+
return ctx.files["content"];
|
|
1322
|
+
}
|
|
1323
|
+
};`
|
|
1324
|
+
};
|
|
1325
|
+
var builtinTypes = [textType, jsonType, binaryType];
|
|
1326
|
+
|
|
1327
|
+
// src/type/TypeHandlerChain.ts
|
|
1328
|
+
class TypeHandlerChain {
|
|
1329
|
+
handlers = new Map;
|
|
1330
|
+
constructor() {
|
|
1331
|
+
for (const type of builtinTypes) {
|
|
1332
|
+
this.registerInternal(type);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
static create() {
|
|
1336
|
+
return new TypeHandlerChain;
|
|
1337
|
+
}
|
|
1338
|
+
registerInternal(type) {
|
|
1339
|
+
this.handlers.set(type.name, type);
|
|
1340
|
+
if (type.aliases) {
|
|
1341
|
+
for (const alias of type.aliases) {
|
|
1342
|
+
this.handlers.set(alias, type);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
register(type) {
|
|
1347
|
+
if (this.handlers.has(type.name)) {
|
|
1348
|
+
throw new ResourceTypeError(`Type '${type.name}' is already registered`);
|
|
1349
|
+
}
|
|
1350
|
+
this.handlers.set(type.name, type);
|
|
1351
|
+
if (type.aliases) {
|
|
1352
|
+
for (const alias of type.aliases) {
|
|
1353
|
+
if (this.handlers.has(alias)) {
|
|
1354
|
+
throw new ResourceTypeError(`Alias '${alias}' conflicts with existing type or alias`);
|
|
1355
|
+
}
|
|
1356
|
+
this.handlers.set(alias, type);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
canHandle(typeName) {
|
|
1361
|
+
return this.handlers.has(typeName);
|
|
1362
|
+
}
|
|
1363
|
+
getHandler(typeName) {
|
|
1364
|
+
const handler = this.handlers.get(typeName);
|
|
1365
|
+
if (!handler) {
|
|
1366
|
+
throw new ResourceTypeError(`Unsupported resource type: ${typeName}`);
|
|
1367
|
+
}
|
|
1368
|
+
return handler;
|
|
1369
|
+
}
|
|
1370
|
+
getHandlerOrUndefined(typeName) {
|
|
1371
|
+
return this.handlers.get(typeName);
|
|
1372
|
+
}
|
|
1373
|
+
getSupportedTypes() {
|
|
1374
|
+
return Array.from(this.handlers.keys());
|
|
1375
|
+
}
|
|
1376
|
+
clear() {
|
|
1377
|
+
this.handlers.clear();
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
// src/loader/FolderLoader.ts
|
|
1381
|
+
import { join, relative } from "node:path";
|
|
1382
|
+
import { stat, readFile as readFile2, readdir } from "node:fs/promises";
|
|
1383
|
+
class FolderLoader {
|
|
1384
|
+
async canLoad(source) {
|
|
1385
|
+
try {
|
|
1386
|
+
const stats = await stat(source);
|
|
1387
|
+
if (!stats.isDirectory()) {
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
const manifestPath = join(source, "resource.json");
|
|
1391
|
+
const manifestStats = await stat(manifestPath);
|
|
1392
|
+
return manifestStats.isFile();
|
|
1393
|
+
} catch {
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
async load(folderPath) {
|
|
1398
|
+
const resourceJsonPath = join(folderPath, "resource.json");
|
|
1399
|
+
let resourceJson;
|
|
1400
|
+
try {
|
|
1401
|
+
resourceJson = await readFile2(resourceJsonPath, "utf-8");
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
throw new ResourceXError(`Failed to read resource.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
1404
|
+
}
|
|
1405
|
+
let json;
|
|
1406
|
+
try {
|
|
1407
|
+
json = JSON.parse(resourceJson);
|
|
1408
|
+
} catch (error) {
|
|
1409
|
+
throw new ResourceXError(`Invalid JSON in resource.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
1410
|
+
}
|
|
1411
|
+
const rxd = define(json);
|
|
1412
|
+
const files = await this.readFolderFiles(folderPath);
|
|
1413
|
+
if (Object.keys(files).length === 0) {
|
|
1414
|
+
throw new ResourceXError("No content files found in resource folder");
|
|
1415
|
+
}
|
|
1416
|
+
const rxm = manifest(rxd);
|
|
1417
|
+
const rxa = await archive(files);
|
|
1418
|
+
return resource(rxm, rxa);
|
|
1419
|
+
}
|
|
1420
|
+
async readFolderFiles(folderPath, basePath = folderPath) {
|
|
1421
|
+
const files = {};
|
|
1422
|
+
const entries = await readdir(folderPath, { withFileTypes: true });
|
|
1423
|
+
for (const entry of entries) {
|
|
1424
|
+
const fullPath = join(folderPath, entry.name);
|
|
1425
|
+
const relativePath = relative(basePath, fullPath);
|
|
1426
|
+
if (relativePath === "resource.json") {
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
if (entry.isFile()) {
|
|
1430
|
+
files[relativePath] = await readFile2(fullPath);
|
|
1431
|
+
} else if (entry.isDirectory()) {
|
|
1432
|
+
const subFiles = await this.readFolderFiles(fullPath, basePath);
|
|
1433
|
+
Object.assign(files, subFiles);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return files;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
// src/loader/loadResource.ts
|
|
1440
|
+
async function loadResource(source, config) {
|
|
1441
|
+
const loader = config?.loader ?? new FolderLoader;
|
|
1442
|
+
const canLoad = await loader.canLoad(source);
|
|
1443
|
+
if (!canLoad) {
|
|
1444
|
+
throw new ResourceXError(`Cannot load resource from: ${source}`);
|
|
1445
|
+
}
|
|
1446
|
+
return loader.load(source);
|
|
1447
|
+
}
|
|
1448
|
+
// src/registry/errors.ts
|
|
1449
|
+
class RegistryError extends ResourceXError {
|
|
1450
|
+
constructor(message, options) {
|
|
1451
|
+
super(message, options);
|
|
1452
|
+
this.name = "RegistryError";
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// src/registry/registries/CASRegistry.ts
|
|
1457
|
+
class CASRegistry {
|
|
1458
|
+
rxaStore;
|
|
1459
|
+
rxmStore;
|
|
1460
|
+
constructor(rxaStore, rxmStore) {
|
|
1461
|
+
this.rxaStore = rxaStore;
|
|
1462
|
+
this.rxmStore = rxmStore;
|
|
1463
|
+
}
|
|
1464
|
+
async get(rxl) {
|
|
1465
|
+
const tag = rxl.tag ?? "latest";
|
|
1466
|
+
const storedRxm = await this.rxmStore.get(rxl.name, tag, rxl.registry);
|
|
1467
|
+
if (!storedRxm) {
|
|
1468
|
+
throw new RegistryError(`Resource not found: ${format(rxl)}`);
|
|
1469
|
+
}
|
|
1470
|
+
const files = {};
|
|
1471
|
+
for (const [filename, digest] of Object.entries(storedRxm.files)) {
|
|
1472
|
+
files[filename] = await this.rxaStore.get(digest);
|
|
1473
|
+
}
|
|
1474
|
+
const rxm = {
|
|
1475
|
+
registry: storedRxm.registry,
|
|
1476
|
+
path: storedRxm.path,
|
|
1477
|
+
name: storedRxm.name,
|
|
1478
|
+
type: storedRxm.type,
|
|
1479
|
+
tag: storedRxm.tag,
|
|
1480
|
+
files: Object.keys(storedRxm.files)
|
|
1481
|
+
};
|
|
1482
|
+
const rxa = await archive(files);
|
|
1483
|
+
return resource(rxm, rxa);
|
|
1484
|
+
}
|
|
1485
|
+
async put(rxr) {
|
|
1486
|
+
const files = await extract(rxr.archive);
|
|
1487
|
+
const fileDigests = {};
|
|
1488
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
1489
|
+
const digest = await this.rxaStore.put(content);
|
|
1490
|
+
fileDigests[filename] = digest;
|
|
1491
|
+
}
|
|
1492
|
+
const storedRxm = {
|
|
1493
|
+
registry: rxr.manifest.registry,
|
|
1494
|
+
path: rxr.manifest.path,
|
|
1495
|
+
name: rxr.manifest.name,
|
|
1496
|
+
type: rxr.manifest.type,
|
|
1497
|
+
tag: rxr.manifest.tag,
|
|
1498
|
+
files: fileDigests,
|
|
1499
|
+
createdAt: new Date,
|
|
1500
|
+
updatedAt: new Date
|
|
1501
|
+
};
|
|
1502
|
+
await this.rxmStore.put(storedRxm);
|
|
1503
|
+
}
|
|
1504
|
+
async has(rxl) {
|
|
1505
|
+
const tag = rxl.tag ?? "latest";
|
|
1506
|
+
return this.rxmStore.has(rxl.name, tag, rxl.registry);
|
|
1507
|
+
}
|
|
1508
|
+
async remove(rxl) {
|
|
1509
|
+
const tag = rxl.tag ?? "latest";
|
|
1510
|
+
await this.rxmStore.delete(rxl.name, tag, rxl.registry);
|
|
1511
|
+
}
|
|
1512
|
+
async list(options) {
|
|
1513
|
+
const { query, limit, offset = 0 } = options ?? {};
|
|
1514
|
+
const manifests = await this.rxmStore.search({
|
|
1515
|
+
query,
|
|
1516
|
+
limit,
|
|
1517
|
+
offset
|
|
1518
|
+
});
|
|
1519
|
+
return manifests.map((m) => ({
|
|
1520
|
+
registry: m.registry,
|
|
1521
|
+
path: m.path,
|
|
1522
|
+
name: m.name,
|
|
1523
|
+
tag: m.tag
|
|
1524
|
+
}));
|
|
1525
|
+
}
|
|
1526
|
+
async clearCache(registry) {
|
|
1527
|
+
if (registry) {
|
|
1528
|
+
await this.rxmStore.deleteByRegistry(registry);
|
|
1529
|
+
} else {
|
|
1530
|
+
const cached = await this.rxmStore.search({ registry: undefined });
|
|
1531
|
+
for (const m of cached) {
|
|
1532
|
+
if (m.registry) {
|
|
1533
|
+
await this.rxmStore.delete(m.name, m.tag, m.registry);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
async gc() {
|
|
1539
|
+
const referenced = new Set;
|
|
1540
|
+
const allManifests = await this.rxmStore.search({});
|
|
1541
|
+
for (const m of allManifests) {
|
|
1542
|
+
for (const digest of Object.values(m.files)) {
|
|
1543
|
+
referenced.add(digest);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
let deleted = 0;
|
|
1547
|
+
const allDigests = await this.rxaStore.list();
|
|
1548
|
+
for (const digest of allDigests) {
|
|
1549
|
+
if (!referenced.has(digest)) {
|
|
1550
|
+
await this.rxaStore.delete(digest);
|
|
1551
|
+
deleted++;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
return deleted;
|
|
1555
|
+
}
|
|
1556
|
+
async hasBlob(digest) {
|
|
1557
|
+
return this.rxaStore.has(digest);
|
|
1558
|
+
}
|
|
1559
|
+
async getBlob(digest) {
|
|
1560
|
+
return this.rxaStore.get(digest);
|
|
1561
|
+
}
|
|
1562
|
+
async putBlob(data) {
|
|
1563
|
+
return this.rxaStore.put(data);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
// src/registry/registries/LinkedRegistry.ts
|
|
1567
|
+
import { join as join2, resolve as resolvePath } from "node:path";
|
|
1568
|
+
import { symlink, lstat, readlink, rm, mkdir, readdir as readdir2 } from "node:fs/promises";
|
|
1569
|
+
class LinkedRegistry {
|
|
1570
|
+
basePath;
|
|
1571
|
+
constructor(basePath) {
|
|
1572
|
+
this.basePath = basePath;
|
|
1573
|
+
}
|
|
1574
|
+
buildLinkPath(rxl) {
|
|
1575
|
+
const registry = rxl.registry ?? "localhost";
|
|
1576
|
+
const tag = rxl.tag ?? "latest";
|
|
1577
|
+
let linkPath = join2(this.basePath, registry);
|
|
1578
|
+
if (rxl.path) {
|
|
1579
|
+
linkPath = join2(linkPath, rxl.path);
|
|
1580
|
+
}
|
|
1581
|
+
return join2(linkPath, rxl.name, tag);
|
|
1582
|
+
}
|
|
1583
|
+
async isSymlink(path) {
|
|
1584
|
+
try {
|
|
1585
|
+
const stats = await lstat(path);
|
|
1586
|
+
return stats.isSymbolicLink();
|
|
1587
|
+
} catch {
|
|
1588
|
+
return false;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
async get(rxl) {
|
|
1592
|
+
const linkPath = this.buildLinkPath(rxl);
|
|
1593
|
+
if (!await this.isSymlink(linkPath)) {
|
|
1594
|
+
throw new RegistryError(`Linked resource not found: ${format(rxl)}`);
|
|
1595
|
+
}
|
|
1596
|
+
const targetPath = await readlink(linkPath);
|
|
1597
|
+
return loadResource(targetPath);
|
|
1598
|
+
}
|
|
1599
|
+
async put(_rxr) {
|
|
1600
|
+
throw new RegistryError("LinkedRegistry does not support put(). Use link() instead.");
|
|
1601
|
+
}
|
|
1602
|
+
async has(rxl) {
|
|
1603
|
+
const linkPath = this.buildLinkPath(rxl);
|
|
1604
|
+
return this.isSymlink(linkPath);
|
|
1605
|
+
}
|
|
1606
|
+
async remove(rxl) {
|
|
1607
|
+
const linkPath = this.buildLinkPath(rxl);
|
|
1608
|
+
if (await this.isSymlink(linkPath)) {
|
|
1609
|
+
await rm(linkPath);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
async list(options) {
|
|
1613
|
+
const { query, limit, offset = 0 } = options ?? {};
|
|
1614
|
+
const locators = [];
|
|
1615
|
+
try {
|
|
1616
|
+
await this.scanSymlinks(this.basePath, "", locators);
|
|
1617
|
+
} catch {
|
|
1618
|
+
return [];
|
|
1619
|
+
}
|
|
1620
|
+
let filtered = locators;
|
|
1621
|
+
if (query) {
|
|
1622
|
+
const lowerQuery = query.toLowerCase();
|
|
1623
|
+
filtered = locators.filter((rxl) => {
|
|
1624
|
+
const searchText = `${rxl.registry ?? ""} ${rxl.path ?? ""} ${rxl.name}`.toLowerCase();
|
|
1625
|
+
return searchText.includes(lowerQuery);
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
let result = filtered.slice(offset);
|
|
1629
|
+
if (limit !== undefined) {
|
|
1630
|
+
result = result.slice(0, limit);
|
|
1631
|
+
}
|
|
1632
|
+
return result;
|
|
1633
|
+
}
|
|
1634
|
+
async link(devPath) {
|
|
1635
|
+
const rxr = await loadResource(devPath);
|
|
1636
|
+
const linkPath = this.buildLinkPath(rxr.locator);
|
|
1637
|
+
try {
|
|
1638
|
+
const stats = await lstat(linkPath);
|
|
1639
|
+
if (stats.isSymbolicLink() || stats.isDirectory()) {
|
|
1640
|
+
await rm(linkPath, { recursive: true });
|
|
1641
|
+
}
|
|
1642
|
+
} catch {}
|
|
1643
|
+
const parentPath = join2(linkPath, "..");
|
|
1644
|
+
await mkdir(parentPath, { recursive: true });
|
|
1645
|
+
const absolutePath = resolvePath(devPath);
|
|
1646
|
+
await symlink(absolutePath, linkPath);
|
|
1647
|
+
return rxr.locator;
|
|
1648
|
+
}
|
|
1649
|
+
async unlink(rxl) {
|
|
1650
|
+
return this.remove(rxl);
|
|
1651
|
+
}
|
|
1652
|
+
async scanSymlinks(dirPath, relativePath, locators) {
|
|
1653
|
+
let entries;
|
|
1654
|
+
try {
|
|
1655
|
+
entries = await readdir2(dirPath);
|
|
1656
|
+
} catch {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
for (const entry of entries) {
|
|
1660
|
+
const fullPath = join2(dirPath, entry);
|
|
1661
|
+
const relPath = relativePath ? `${relativePath}/${entry}` : entry;
|
|
1662
|
+
try {
|
|
1663
|
+
const stats = await lstat(fullPath);
|
|
1664
|
+
if (stats.isSymbolicLink()) {
|
|
1665
|
+
const rxl = this.parsePathToRXL(relPath);
|
|
1666
|
+
if (rxl) {
|
|
1667
|
+
locators.push(rxl);
|
|
1668
|
+
}
|
|
1669
|
+
} else if (stats.isDirectory()) {
|
|
1670
|
+
await this.scanSymlinks(fullPath, relPath, locators);
|
|
1671
|
+
}
|
|
1672
|
+
} catch {}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
parsePathToRXL(relPath) {
|
|
1676
|
+
const parts = relPath.split("/");
|
|
1677
|
+
if (parts.length < 3) {
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
const tag = parts.pop();
|
|
1681
|
+
const name = parts.pop();
|
|
1682
|
+
const registry = parts.shift();
|
|
1683
|
+
const path = parts.length > 0 ? parts.join("/") : undefined;
|
|
1684
|
+
let locatorStr = registry;
|
|
1685
|
+
if (path)
|
|
1686
|
+
locatorStr += `/${path}`;
|
|
1687
|
+
locatorStr += `/${name}`;
|
|
1688
|
+
if (tag !== "latest")
|
|
1689
|
+
locatorStr += `:${tag}`;
|
|
1690
|
+
try {
|
|
1691
|
+
return parse(locatorStr);
|
|
1692
|
+
} catch {
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
// src/registry/store/digest.ts
|
|
1698
|
+
import { createHash } from "node:crypto";
|
|
1699
|
+
function computeDigest(data) {
|
|
1700
|
+
const hash = createHash("sha256").update(data).digest("hex");
|
|
1701
|
+
return `sha256:${hash}`;
|
|
1702
|
+
}
|
|
1703
|
+
function isValidDigest(digest) {
|
|
1704
|
+
return /^sha256:[a-f0-9]{64}$/.test(digest);
|
|
1705
|
+
}
|
|
1706
|
+
// src/registry/store/MemoryRXAStore.ts
|
|
1707
|
+
class MemoryRXAStore {
|
|
1708
|
+
blobs = new Map;
|
|
1709
|
+
async get(digest) {
|
|
1710
|
+
const blob = this.blobs.get(digest);
|
|
1711
|
+
if (!blob) {
|
|
1712
|
+
throw new RegistryError(`Blob not found: ${digest}`);
|
|
1713
|
+
}
|
|
1714
|
+
return blob;
|
|
1715
|
+
}
|
|
1716
|
+
async put(data) {
|
|
1717
|
+
const digest = computeDigest(data);
|
|
1718
|
+
if (!this.blobs.has(digest)) {
|
|
1719
|
+
this.blobs.set(digest, data);
|
|
1720
|
+
}
|
|
1721
|
+
return digest;
|
|
1722
|
+
}
|
|
1723
|
+
async has(digest) {
|
|
1724
|
+
return this.blobs.has(digest);
|
|
1725
|
+
}
|
|
1726
|
+
async delete(digest) {
|
|
1727
|
+
this.blobs.delete(digest);
|
|
1728
|
+
}
|
|
1729
|
+
async list() {
|
|
1730
|
+
return Array.from(this.blobs.keys());
|
|
1731
|
+
}
|
|
1732
|
+
clear() {
|
|
1733
|
+
this.blobs.clear();
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
// src/registry/store/MemoryRXMStore.ts
|
|
1737
|
+
class MemoryRXMStore {
|
|
1738
|
+
manifests = new Map;
|
|
1739
|
+
buildKey(name, tag, registry) {
|
|
1740
|
+
return registry ? `${registry}/${name}:${tag}` : `${name}:${tag}`;
|
|
1741
|
+
}
|
|
1742
|
+
async get(name, tag, registry) {
|
|
1743
|
+
const key = this.buildKey(name, tag, registry);
|
|
1744
|
+
return this.manifests.get(key) ?? null;
|
|
1745
|
+
}
|
|
1746
|
+
async put(manifest2) {
|
|
1747
|
+
const key = this.buildKey(manifest2.name, manifest2.tag, manifest2.registry);
|
|
1748
|
+
this.manifests.set(key, {
|
|
1749
|
+
...manifest2,
|
|
1750
|
+
updatedAt: new Date
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
async has(name, tag, registry) {
|
|
1754
|
+
const key = this.buildKey(name, tag, registry);
|
|
1755
|
+
return this.manifests.has(key);
|
|
1756
|
+
}
|
|
1757
|
+
async delete(name, tag, registry) {
|
|
1758
|
+
const key = this.buildKey(name, tag, registry);
|
|
1759
|
+
this.manifests.delete(key);
|
|
1760
|
+
}
|
|
1761
|
+
async listTags(name, registry) {
|
|
1762
|
+
const tags = [];
|
|
1763
|
+
for (const m of this.manifests.values()) {
|
|
1764
|
+
if (m.name === name && m.registry === registry) {
|
|
1765
|
+
tags.push(m.tag);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
return tags;
|
|
1769
|
+
}
|
|
1770
|
+
async listNames(registry, query) {
|
|
1771
|
+
const names = new Set;
|
|
1772
|
+
for (const m of this.manifests.values()) {
|
|
1773
|
+
if (registry !== undefined && m.registry !== registry)
|
|
1774
|
+
continue;
|
|
1775
|
+
if (query && !m.name.toLowerCase().includes(query.toLowerCase()))
|
|
1776
|
+
continue;
|
|
1777
|
+
names.add(m.name);
|
|
1778
|
+
}
|
|
1779
|
+
return Array.from(names);
|
|
1780
|
+
}
|
|
1781
|
+
async search(options) {
|
|
1782
|
+
const { registry, query, limit, offset = 0 } = options ?? {};
|
|
1783
|
+
let results = Array.from(this.manifests.values());
|
|
1784
|
+
if (registry !== undefined) {
|
|
1785
|
+
if (registry === null) {
|
|
1786
|
+
results = results.filter((m) => !m.registry);
|
|
1787
|
+
} else {
|
|
1788
|
+
results = results.filter((m) => m.registry === registry);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
if (query) {
|
|
1792
|
+
const lowerQuery = query.toLowerCase();
|
|
1793
|
+
results = results.filter((m) => m.name.toLowerCase().includes(lowerQuery));
|
|
1794
|
+
}
|
|
1795
|
+
results = results.slice(offset);
|
|
1796
|
+
if (limit !== undefined) {
|
|
1797
|
+
results = results.slice(0, limit);
|
|
1798
|
+
}
|
|
1799
|
+
return results;
|
|
1800
|
+
}
|
|
1801
|
+
async deleteByRegistry(registry) {
|
|
1802
|
+
for (const [key, m] of this.manifests.entries()) {
|
|
1803
|
+
if (m.registry === registry) {
|
|
1804
|
+
this.manifests.delete(key);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
clear() {
|
|
1809
|
+
this.manifests.clear();
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
// src/registry/discovery.ts
|
|
1813
|
+
async function discoverRegistry(domain) {
|
|
1814
|
+
const wellKnownUrl = `https://${domain}/.well-known/resourcex`;
|
|
1815
|
+
try {
|
|
1816
|
+
const response = await fetch(wellKnownUrl);
|
|
1817
|
+
if (!response.ok) {
|
|
1818
|
+
throw new RegistryError(`Well-known discovery failed for ${domain}: ${response.statusText}`);
|
|
1819
|
+
}
|
|
1820
|
+
const data = await response.json();
|
|
1821
|
+
if (!data.registries || !Array.isArray(data.registries) || data.registries.length === 0) {
|
|
1822
|
+
throw new RegistryError(`Invalid well-known response for ${domain}: missing or empty registries`);
|
|
1823
|
+
}
|
|
1824
|
+
return {
|
|
1825
|
+
domain,
|
|
1826
|
+
registries: data.registries
|
|
1827
|
+
};
|
|
1828
|
+
} catch (error) {
|
|
1829
|
+
if (error instanceof RegistryError) {
|
|
1830
|
+
throw error;
|
|
1831
|
+
}
|
|
1832
|
+
throw new RegistryError(`Failed to discover registry for ${domain}: ${error.message}`);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
// src/registry/middleware/RegistryMiddleware.ts
|
|
1836
|
+
class RegistryMiddleware {
|
|
1837
|
+
inner;
|
|
1838
|
+
constructor(inner) {
|
|
1839
|
+
this.inner = inner;
|
|
1840
|
+
}
|
|
1841
|
+
get(rxl) {
|
|
1842
|
+
return this.inner.get(rxl);
|
|
1843
|
+
}
|
|
1844
|
+
put(rxr) {
|
|
1845
|
+
return this.inner.put(rxr);
|
|
1846
|
+
}
|
|
1847
|
+
has(rxl) {
|
|
1848
|
+
return this.inner.has(rxl);
|
|
1849
|
+
}
|
|
1850
|
+
remove(rxl) {
|
|
1851
|
+
return this.inner.remove(rxl);
|
|
1852
|
+
}
|
|
1853
|
+
list(options) {
|
|
1854
|
+
return this.inner.list(options);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
// src/registry/middleware/DomainValidation.ts
|
|
1858
|
+
class RegistryValidation extends RegistryMiddleware {
|
|
1859
|
+
trustedRegistry;
|
|
1860
|
+
constructor(inner, trustedRegistry) {
|
|
1861
|
+
super(inner);
|
|
1862
|
+
this.trustedRegistry = trustedRegistry;
|
|
1863
|
+
}
|
|
1864
|
+
validateRegistry(rxr) {
|
|
1865
|
+
if (rxr.manifest.registry !== this.trustedRegistry) {
|
|
1866
|
+
throw new RegistryError(`Untrusted registry: resource claims "${rxr.manifest.registry}" but registry only trusts "${this.trustedRegistry}"`);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
async get(rxl) {
|
|
1870
|
+
const rxr = await this.inner.get(rxl);
|
|
1871
|
+
this.validateRegistry(rxr);
|
|
1872
|
+
return rxr;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
function withRegistryValidation(registry, trustedRegistry) {
|
|
1876
|
+
return new RegistryValidation(registry, trustedRegistry);
|
|
1877
|
+
}
|
|
1878
|
+
var DomainValidation = RegistryValidation;
|
|
1879
|
+
var withDomainValidation = withRegistryValidation;
|
|
1232
1880
|
export {
|
|
1233
1881
|
wrap,
|
|
1882
|
+
withDomainValidation,
|
|
1883
|
+
textType,
|
|
1234
1884
|
resource,
|
|
1235
1885
|
parse,
|
|
1236
1886
|
manifest,
|
|
1237
1887
|
locate,
|
|
1888
|
+
loadResource,
|
|
1889
|
+
jsonType,
|
|
1890
|
+
isValidDigest,
|
|
1238
1891
|
format,
|
|
1239
1892
|
extract,
|
|
1893
|
+
discoverRegistry,
|
|
1240
1894
|
define,
|
|
1895
|
+
computeDigest,
|
|
1896
|
+
bundleResourceType,
|
|
1897
|
+
builtinTypes,
|
|
1898
|
+
binaryType,
|
|
1241
1899
|
archive,
|
|
1900
|
+
TypeHandlerChain,
|
|
1242
1901
|
ResourceXError,
|
|
1902
|
+
ResourceTypeError,
|
|
1903
|
+
RegistryMiddleware,
|
|
1904
|
+
RegistryError,
|
|
1905
|
+
MemoryRXMStore,
|
|
1906
|
+
MemoryRXAStore,
|
|
1243
1907
|
ManifestError,
|
|
1244
1908
|
LocatorError,
|
|
1909
|
+
LinkedRegistry,
|
|
1910
|
+
FolderLoader,
|
|
1911
|
+
DomainValidation,
|
|
1245
1912
|
DefinitionError,
|
|
1246
|
-
ContentError
|
|
1913
|
+
ContentError,
|
|
1914
|
+
CASRegistry
|
|
1247
1915
|
};
|
|
1248
1916
|
|
|
1249
|
-
//# debugId=
|
|
1917
|
+
//# debugId=E62BE3673C4E081E64756E2164756E21
|