@resourcexjs/registry 1.6.0 → 1.7.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 +81 -13
- package/dist/index.js +156 -517
- package/dist/index.js.map +7 -6
- package/package.json +3 -4
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
function isRemoteConfig(config) {
|
|
3
|
+
return config !== undefined && "endpoint" in config;
|
|
4
|
+
}
|
|
1
5
|
// ../core/dist/index.js
|
|
2
6
|
import { gzip, gunzip } from "node:zlib";
|
|
3
7
|
import { promisify } from "node:util";
|
|
@@ -156,8 +160,10 @@ class RegistryError extends ResourceXError {
|
|
|
156
160
|
this.name = "RegistryError";
|
|
157
161
|
}
|
|
158
162
|
}
|
|
159
|
-
// src/
|
|
163
|
+
// src/LocalRegistry.ts
|
|
160
164
|
import { homedir } from "node:os";
|
|
165
|
+
import { join } from "node:path";
|
|
166
|
+
import { readFile, writeFile, mkdir, rm, stat, readdir } from "node:fs/promises";
|
|
161
167
|
|
|
162
168
|
// ../type/dist/index.js
|
|
163
169
|
import { gzip as gzip2, gunzip as gunzip2 } from "node:zlib";
|
|
@@ -1459,487 +1465,13 @@ class TypeHandlerChain {
|
|
|
1459
1465
|
}
|
|
1460
1466
|
}
|
|
1461
1467
|
|
|
1462
|
-
//
|
|
1463
|
-
import { readFile, writeFile, readdir, mkdir, rm, access, stat } from "node:fs/promises";
|
|
1464
|
-
import { resolve, dirname, join } from "node:path";
|
|
1465
|
-
|
|
1466
|
-
class ARPError extends Error {
|
|
1467
|
-
constructor(message, options) {
|
|
1468
|
-
super(message, options);
|
|
1469
|
-
this.name = "ARPError";
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
class ParseError extends ARPError {
|
|
1474
|
-
url;
|
|
1475
|
-
constructor(message, url) {
|
|
1476
|
-
super(message);
|
|
1477
|
-
this.url = url;
|
|
1478
|
-
this.name = "ParseError";
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
class TransportError extends ARPError {
|
|
1483
|
-
transport;
|
|
1484
|
-
constructor(message, transport, options) {
|
|
1485
|
-
super(message, options);
|
|
1486
|
-
this.transport = transport;
|
|
1487
|
-
this.name = "TransportError";
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
class SemanticError extends ARPError {
|
|
1492
|
-
semantic;
|
|
1493
|
-
constructor(message, semantic, options) {
|
|
1494
|
-
super(message, options);
|
|
1495
|
-
this.semantic = semantic;
|
|
1496
|
-
this.name = "SemanticError";
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
class ARL {
|
|
1501
|
-
semantic;
|
|
1502
|
-
transport;
|
|
1503
|
-
location;
|
|
1504
|
-
resolver;
|
|
1505
|
-
constructor(semantic, transport, location, resolver) {
|
|
1506
|
-
this.semantic = semantic;
|
|
1507
|
-
this.transport = transport;
|
|
1508
|
-
this.location = location;
|
|
1509
|
-
this.resolver = resolver;
|
|
1510
|
-
}
|
|
1511
|
-
createContext(params) {
|
|
1512
|
-
return {
|
|
1513
|
-
url: this.toString(),
|
|
1514
|
-
semantic: this.semantic,
|
|
1515
|
-
transport: this.transport,
|
|
1516
|
-
location: this.location,
|
|
1517
|
-
timestamp: new Date,
|
|
1518
|
-
params
|
|
1519
|
-
};
|
|
1520
|
-
}
|
|
1521
|
-
async resolve(params) {
|
|
1522
|
-
const transport = this.resolver.getTransportHandler(this.transport);
|
|
1523
|
-
const semantic = this.resolver.getSemanticHandler(this.semantic);
|
|
1524
|
-
const context = this.createContext(params);
|
|
1525
|
-
return semantic.resolve(transport, this.location, context);
|
|
1526
|
-
}
|
|
1527
|
-
async deposit(data, params) {
|
|
1528
|
-
const transport = this.resolver.getTransportHandler(this.transport);
|
|
1529
|
-
const semantic = this.resolver.getSemanticHandler(this.semantic);
|
|
1530
|
-
const context = this.createContext(params);
|
|
1531
|
-
if (!semantic.deposit) {
|
|
1532
|
-
throw new SemanticError(`Semantic "${semantic.name}" does not support deposit operation`, this.semantic);
|
|
1533
|
-
}
|
|
1534
|
-
await semantic.deposit(transport, this.location, data, context);
|
|
1535
|
-
}
|
|
1536
|
-
async exists() {
|
|
1537
|
-
const transport = this.resolver.getTransportHandler(this.transport);
|
|
1538
|
-
const semantic = this.resolver.getSemanticHandler(this.semantic);
|
|
1539
|
-
const context = this.createContext();
|
|
1540
|
-
if (semantic.exists) {
|
|
1541
|
-
return semantic.exists(transport, this.location, context);
|
|
1542
|
-
}
|
|
1543
|
-
return transport.exists(this.location);
|
|
1544
|
-
}
|
|
1545
|
-
async delete() {
|
|
1546
|
-
const transport = this.resolver.getTransportHandler(this.transport);
|
|
1547
|
-
const semantic = this.resolver.getSemanticHandler(this.semantic);
|
|
1548
|
-
const context = this.createContext();
|
|
1549
|
-
if (semantic.delete) {
|
|
1550
|
-
return semantic.delete(transport, this.location, context);
|
|
1551
|
-
}
|
|
1552
|
-
await transport.delete(this.location);
|
|
1553
|
-
}
|
|
1554
|
-
toString() {
|
|
1555
|
-
return `arp:${this.semantic}:${this.transport}://${this.location}`;
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
class FileTransportHandler {
|
|
1560
|
-
name = "file";
|
|
1561
|
-
resolvePath(location) {
|
|
1562
|
-
return resolve(process.cwd(), location);
|
|
1563
|
-
}
|
|
1564
|
-
async get(location, params) {
|
|
1565
|
-
const filePath = this.resolvePath(location);
|
|
1566
|
-
try {
|
|
1567
|
-
const stats = await stat(filePath);
|
|
1568
|
-
if (stats.isDirectory()) {
|
|
1569
|
-
return this.getDirectory(filePath, stats, params);
|
|
1570
|
-
} else {
|
|
1571
|
-
return this.getFile(filePath, stats);
|
|
1572
|
-
}
|
|
1573
|
-
} catch (error) {
|
|
1574
|
-
const err = error;
|
|
1575
|
-
throw new TransportError(`File get error: ${err.code} - ${filePath}`, this.name, {
|
|
1576
|
-
cause: err
|
|
1577
|
-
});
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
async getFile(filePath, stats) {
|
|
1581
|
-
const content = await readFile(filePath);
|
|
1582
|
-
return {
|
|
1583
|
-
content,
|
|
1584
|
-
metadata: {
|
|
1585
|
-
type: "file",
|
|
1586
|
-
size: Number(stats.size),
|
|
1587
|
-
modifiedAt: stats.mtime
|
|
1588
|
-
}
|
|
1589
|
-
};
|
|
1590
|
-
}
|
|
1591
|
-
async getDirectory(dirPath, stats, params) {
|
|
1592
|
-
const recursive = params?.recursive === "true";
|
|
1593
|
-
const pattern = params?.pattern;
|
|
1594
|
-
let entries;
|
|
1595
|
-
if (recursive) {
|
|
1596
|
-
entries = await this.listRecursive(dirPath, dirPath);
|
|
1597
|
-
} else {
|
|
1598
|
-
entries = await readdir(dirPath);
|
|
1599
|
-
}
|
|
1600
|
-
if (pattern) {
|
|
1601
|
-
entries = this.filterByPattern(entries, pattern);
|
|
1602
|
-
}
|
|
1603
|
-
const content = Buffer.from(JSON.stringify(entries));
|
|
1604
|
-
return {
|
|
1605
|
-
content,
|
|
1606
|
-
metadata: {
|
|
1607
|
-
type: "directory",
|
|
1608
|
-
modifiedAt: stats.mtime
|
|
1609
|
-
}
|
|
1610
|
-
};
|
|
1611
|
-
}
|
|
1612
|
-
async listRecursive(basePath, currentPath) {
|
|
1613
|
-
const entries = await readdir(currentPath, { withFileTypes: true });
|
|
1614
|
-
const results = [];
|
|
1615
|
-
for (const entry of entries) {
|
|
1616
|
-
const fullPath = join(currentPath, entry.name);
|
|
1617
|
-
const relativePath = fullPath.substring(basePath.length + 1);
|
|
1618
|
-
if (entry.isDirectory()) {
|
|
1619
|
-
const subEntries = await this.listRecursive(basePath, fullPath);
|
|
1620
|
-
results.push(...subEntries);
|
|
1621
|
-
} else {
|
|
1622
|
-
results.push(relativePath);
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
return results;
|
|
1626
|
-
}
|
|
1627
|
-
filterByPattern(entries, pattern) {
|
|
1628
|
-
const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1629
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
1630
|
-
return entries.filter((entry) => {
|
|
1631
|
-
const filename = entry.split("/").pop() || entry;
|
|
1632
|
-
return regex.test(filename);
|
|
1633
|
-
});
|
|
1634
|
-
}
|
|
1635
|
-
async set(location, content, _params) {
|
|
1636
|
-
const filePath = this.resolvePath(location);
|
|
1637
|
-
try {
|
|
1638
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
1639
|
-
await writeFile(filePath, content);
|
|
1640
|
-
} catch (error) {
|
|
1641
|
-
const err = error;
|
|
1642
|
-
throw new TransportError(`File set error: ${err.code} - ${filePath}`, this.name, {
|
|
1643
|
-
cause: err
|
|
1644
|
-
});
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
async exists(location) {
|
|
1648
|
-
const filePath = this.resolvePath(location);
|
|
1649
|
-
try {
|
|
1650
|
-
await access(filePath);
|
|
1651
|
-
return true;
|
|
1652
|
-
} catch {
|
|
1653
|
-
return false;
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
async delete(location) {
|
|
1657
|
-
const filePath = this.resolvePath(location);
|
|
1658
|
-
try {
|
|
1659
|
-
await rm(filePath, { recursive: true });
|
|
1660
|
-
} catch (error) {
|
|
1661
|
-
const err = error;
|
|
1662
|
-
if (err.code === "ENOENT") {
|
|
1663
|
-
return;
|
|
1664
|
-
}
|
|
1665
|
-
throw new TransportError(`File delete error: ${err.code} - ${filePath}`, this.name, {
|
|
1666
|
-
cause: err
|
|
1667
|
-
});
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
var fileTransport = new FileTransportHandler;
|
|
1672
|
-
|
|
1673
|
-
class HttpTransportHandler {
|
|
1674
|
-
name;
|
|
1675
|
-
protocol;
|
|
1676
|
-
constructor(protocol = "https") {
|
|
1677
|
-
this.protocol = protocol;
|
|
1678
|
-
this.name = protocol;
|
|
1679
|
-
}
|
|
1680
|
-
async get(location, params) {
|
|
1681
|
-
const url = this.buildUrl(location, params);
|
|
1682
|
-
try {
|
|
1683
|
-
const response = await fetch(url);
|
|
1684
|
-
if (!response.ok) {
|
|
1685
|
-
throw new TransportError(`HTTP ${response.status}: ${response.statusText} - ${url}`, this.name);
|
|
1686
|
-
}
|
|
1687
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
1688
|
-
const content = Buffer.from(arrayBuffer);
|
|
1689
|
-
const contentType = response.headers.get("content-type");
|
|
1690
|
-
const contentLength = response.headers.get("content-length");
|
|
1691
|
-
const lastModified = response.headers.get("last-modified");
|
|
1692
|
-
return {
|
|
1693
|
-
content,
|
|
1694
|
-
metadata: {
|
|
1695
|
-
type: "file",
|
|
1696
|
-
size: contentLength ? parseInt(contentLength, 10) : content.length,
|
|
1697
|
-
modifiedAt: lastModified ? new Date(lastModified) : undefined,
|
|
1698
|
-
contentType
|
|
1699
|
-
}
|
|
1700
|
-
};
|
|
1701
|
-
} catch (error) {
|
|
1702
|
-
if (error instanceof TransportError) {
|
|
1703
|
-
throw error;
|
|
1704
|
-
}
|
|
1705
|
-
throw new TransportError(`Network error: ${url}`, this.name, {
|
|
1706
|
-
cause: error
|
|
1707
|
-
});
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
buildUrl(location, params) {
|
|
1711
|
-
const url = new URL(`${this.protocol}://${location}`);
|
|
1712
|
-
if (params) {
|
|
1713
|
-
for (const [key, value] of Object.entries(params)) {
|
|
1714
|
-
url.searchParams.set(key, value);
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
return url.toString();
|
|
1718
|
-
}
|
|
1719
|
-
async set(_location, _content, _params) {
|
|
1720
|
-
throw new TransportError("HTTP transport is read-only, set not supported", this.name);
|
|
1721
|
-
}
|
|
1722
|
-
async exists(location) {
|
|
1723
|
-
const url = `${this.protocol}://${location}`;
|
|
1724
|
-
try {
|
|
1725
|
-
const response = await fetch(url, { method: "HEAD" });
|
|
1726
|
-
return response.ok;
|
|
1727
|
-
} catch {
|
|
1728
|
-
return false;
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
async delete(_location) {
|
|
1732
|
-
throw new TransportError("HTTP transport is read-only, delete not supported", this.name);
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
var httpsTransport = new HttpTransportHandler("https");
|
|
1736
|
-
var httpTransport = new HttpTransportHandler("http");
|
|
1737
|
-
|
|
1738
|
-
class TextSemanticHandler {
|
|
1739
|
-
name = "text";
|
|
1740
|
-
async resolve(transport, location, context) {
|
|
1741
|
-
const result = await transport.get(location, context.params);
|
|
1742
|
-
if (result.metadata?.type === "directory") {
|
|
1743
|
-
const meta2 = {
|
|
1744
|
-
url: context.url,
|
|
1745
|
-
semantic: context.semantic,
|
|
1746
|
-
transport: context.transport,
|
|
1747
|
-
location: context.location,
|
|
1748
|
-
size: result.content.length,
|
|
1749
|
-
encoding: "utf-8",
|
|
1750
|
-
mimeType: "application/json",
|
|
1751
|
-
resolvedAt: context.timestamp.toISOString(),
|
|
1752
|
-
type: "directory"
|
|
1753
|
-
};
|
|
1754
|
-
return {
|
|
1755
|
-
type: "text",
|
|
1756
|
-
content: result.content.toString("utf-8"),
|
|
1757
|
-
meta: meta2
|
|
1758
|
-
};
|
|
1759
|
-
}
|
|
1760
|
-
const text = result.content.toString("utf-8");
|
|
1761
|
-
const meta = {
|
|
1762
|
-
url: context.url,
|
|
1763
|
-
semantic: context.semantic,
|
|
1764
|
-
transport: context.transport,
|
|
1765
|
-
location: context.location,
|
|
1766
|
-
size: result.metadata?.size ?? result.content.length,
|
|
1767
|
-
encoding: "utf-8",
|
|
1768
|
-
mimeType: "text/plain",
|
|
1769
|
-
resolvedAt: context.timestamp.toISOString(),
|
|
1770
|
-
type: "file"
|
|
1771
|
-
};
|
|
1772
|
-
return {
|
|
1773
|
-
type: "text",
|
|
1774
|
-
content: text,
|
|
1775
|
-
meta
|
|
1776
|
-
};
|
|
1777
|
-
}
|
|
1778
|
-
async deposit(transport, location, data, context) {
|
|
1779
|
-
const buffer = Buffer.from(data, "utf-8");
|
|
1780
|
-
try {
|
|
1781
|
-
await transport.set(location, buffer, context.params);
|
|
1782
|
-
} catch (error) {
|
|
1783
|
-
throw new SemanticError(`Failed to deposit text to "${location}": ${error.message}`, this.name, { cause: error });
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
async exists(transport, location, _context) {
|
|
1787
|
-
return transport.exists(location);
|
|
1788
|
-
}
|
|
1789
|
-
async delete(transport, location, _context) {
|
|
1790
|
-
try {
|
|
1791
|
-
await transport.delete(location);
|
|
1792
|
-
} catch (error) {
|
|
1793
|
-
throw new SemanticError(`Failed to delete "${location}": ${error.message}`, this.name, { cause: error });
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
var textSemantic = new TextSemanticHandler;
|
|
1798
|
-
function toBuffer(data) {
|
|
1799
|
-
if (Buffer.isBuffer(data)) {
|
|
1800
|
-
return data;
|
|
1801
|
-
}
|
|
1802
|
-
if (data instanceof Uint8Array) {
|
|
1803
|
-
return Buffer.from(data);
|
|
1804
|
-
}
|
|
1805
|
-
if (data instanceof ArrayBuffer) {
|
|
1806
|
-
return Buffer.from(data);
|
|
1807
|
-
}
|
|
1808
|
-
if (Array.isArray(data)) {
|
|
1809
|
-
return Buffer.from(data);
|
|
1810
|
-
}
|
|
1811
|
-
throw new SemanticError(`Unsupported binary input type`, "binary");
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
class BinarySemanticHandler {
|
|
1815
|
-
name = "binary";
|
|
1816
|
-
async resolve(transport, location, context) {
|
|
1817
|
-
const result = await transport.get(location, context.params);
|
|
1818
|
-
const meta = {
|
|
1819
|
-
url: context.url,
|
|
1820
|
-
semantic: context.semantic,
|
|
1821
|
-
transport: context.transport,
|
|
1822
|
-
location: context.location,
|
|
1823
|
-
size: result.metadata?.size ?? result.content.length,
|
|
1824
|
-
resolvedAt: context.timestamp.toISOString(),
|
|
1825
|
-
type: result.metadata?.type
|
|
1826
|
-
};
|
|
1827
|
-
return {
|
|
1828
|
-
type: "binary",
|
|
1829
|
-
content: result.content,
|
|
1830
|
-
meta
|
|
1831
|
-
};
|
|
1832
|
-
}
|
|
1833
|
-
async deposit(transport, location, data, context) {
|
|
1834
|
-
const buffer = toBuffer(data);
|
|
1835
|
-
try {
|
|
1836
|
-
await transport.set(location, buffer, context.params);
|
|
1837
|
-
} catch (error) {
|
|
1838
|
-
throw new SemanticError(`Failed to deposit binary to "${location}": ${error.message}`, this.name, { cause: error });
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
1841
|
-
async exists(transport, location, _context) {
|
|
1842
|
-
return transport.exists(location);
|
|
1843
|
-
}
|
|
1844
|
-
async delete(transport, location, _context) {
|
|
1845
|
-
try {
|
|
1846
|
-
await transport.delete(location);
|
|
1847
|
-
} catch (error) {
|
|
1848
|
-
throw new SemanticError(`Failed to delete "${location}": ${error.message}`, this.name, { cause: error });
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
var binarySemantic = new BinarySemanticHandler;
|
|
1853
|
-
|
|
1854
|
-
class ARP {
|
|
1855
|
-
transports;
|
|
1856
|
-
semantics;
|
|
1857
|
-
constructor(config = {}) {
|
|
1858
|
-
this.transports = new Map;
|
|
1859
|
-
this.semantics = new Map;
|
|
1860
|
-
const defaultTransports = [fileTransport, httpTransport, httpsTransport];
|
|
1861
|
-
const defaultSemantics = [textSemantic, binarySemantic];
|
|
1862
|
-
for (const handler of defaultTransports) {
|
|
1863
|
-
this.transports.set(handler.name, handler);
|
|
1864
|
-
}
|
|
1865
|
-
for (const handler of defaultSemantics) {
|
|
1866
|
-
this.semantics.set(handler.name, handler);
|
|
1867
|
-
}
|
|
1868
|
-
if (config.transports) {
|
|
1869
|
-
for (const handler of config.transports) {
|
|
1870
|
-
this.transports.set(handler.name, handler);
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
if (config.semantics) {
|
|
1874
|
-
for (const handler of config.semantics) {
|
|
1875
|
-
this.semantics.set(handler.name, handler);
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
registerTransport(handler) {
|
|
1880
|
-
this.transports.set(handler.name, handler);
|
|
1881
|
-
}
|
|
1882
|
-
registerSemantic(handler) {
|
|
1883
|
-
this.semantics.set(handler.name, handler);
|
|
1884
|
-
}
|
|
1885
|
-
getTransportHandler(name) {
|
|
1886
|
-
const handler = this.transports.get(name);
|
|
1887
|
-
if (!handler) {
|
|
1888
|
-
throw new TransportError(`Unsupported transport type: ${name}`, name);
|
|
1889
|
-
}
|
|
1890
|
-
return handler;
|
|
1891
|
-
}
|
|
1892
|
-
getSemanticHandler(name) {
|
|
1893
|
-
const handler = this.semantics.get(name);
|
|
1894
|
-
if (!handler) {
|
|
1895
|
-
throw new SemanticError(`Unsupported semantic type: ${name}`, name);
|
|
1896
|
-
}
|
|
1897
|
-
return handler;
|
|
1898
|
-
}
|
|
1899
|
-
parse(url) {
|
|
1900
|
-
if (!url.startsWith("arp:")) {
|
|
1901
|
-
throw new ParseError(`Invalid ARP URL: must start with "arp:"`, url);
|
|
1902
|
-
}
|
|
1903
|
-
const content = url.substring(4);
|
|
1904
|
-
const separatorIndex = content.indexOf("://");
|
|
1905
|
-
if (separatorIndex === -1) {
|
|
1906
|
-
throw new ParseError(`Invalid ARP URL: missing "://"`, url);
|
|
1907
|
-
}
|
|
1908
|
-
const typePart = content.substring(0, separatorIndex);
|
|
1909
|
-
const location = content.substring(separatorIndex + 3);
|
|
1910
|
-
const colonIndex = typePart.indexOf(":");
|
|
1911
|
-
if (colonIndex === -1) {
|
|
1912
|
-
throw new ParseError(`Invalid ARP URL: must have exactly 2 types (semantic:transport)`, url);
|
|
1913
|
-
}
|
|
1914
|
-
const semantic = typePart.substring(0, colonIndex);
|
|
1915
|
-
const transport = typePart.substring(colonIndex + 1);
|
|
1916
|
-
if (!semantic) {
|
|
1917
|
-
throw new ParseError(`Invalid ARP URL: semantic type cannot be empty`, url);
|
|
1918
|
-
}
|
|
1919
|
-
if (!transport) {
|
|
1920
|
-
throw new ParseError(`Invalid ARP URL: transport type cannot be empty`, url);
|
|
1921
|
-
}
|
|
1922
|
-
if (!location) {
|
|
1923
|
-
throw new ParseError(`Invalid ARP URL: location cannot be empty`, url);
|
|
1924
|
-
}
|
|
1925
|
-
this.getTransportHandler(transport);
|
|
1926
|
-
this.getSemanticHandler(semantic);
|
|
1927
|
-
return new ARL(semantic, transport, location, this);
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
function createARP(config) {
|
|
1931
|
-
return new ARP(config);
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
// src/ARPRegistry.ts
|
|
1468
|
+
// src/LocalRegistry.ts
|
|
1935
1469
|
var DEFAULT_PATH = `${homedir()}/.resourcex`;
|
|
1936
1470
|
|
|
1937
|
-
class
|
|
1938
|
-
arp;
|
|
1471
|
+
class LocalRegistry {
|
|
1939
1472
|
basePath;
|
|
1940
1473
|
typeHandler;
|
|
1941
1474
|
constructor(config) {
|
|
1942
|
-
this.arp = createARP();
|
|
1943
1475
|
this.basePath = config?.path ?? DEFAULT_PATH;
|
|
1944
1476
|
this.typeHandler = TypeHandlerChain.create();
|
|
1945
1477
|
if (config?.types) {
|
|
@@ -1951,79 +1483,79 @@ class ARPRegistry {
|
|
|
1951
1483
|
supportType(type) {
|
|
1952
1484
|
this.typeHandler.register(type);
|
|
1953
1485
|
}
|
|
1954
|
-
|
|
1486
|
+
buildPath(locator) {
|
|
1955
1487
|
const rxl = typeof locator === "string" ? parseRXL(locator) : locator;
|
|
1956
1488
|
const domain = rxl.domain ?? "localhost";
|
|
1957
1489
|
const version = rxl.version ?? "latest";
|
|
1958
|
-
let path =
|
|
1490
|
+
let path = join(this.basePath, domain);
|
|
1959
1491
|
if (rxl.path) {
|
|
1960
|
-
path
|
|
1492
|
+
path = join(path, rxl.path);
|
|
1961
1493
|
}
|
|
1962
1494
|
const resourceName = rxl.type ? `${rxl.name}.${rxl.type}` : rxl.name;
|
|
1963
|
-
return
|
|
1495
|
+
return join(path, resourceName, version);
|
|
1964
1496
|
}
|
|
1965
1497
|
async publish(_resource) {
|
|
1966
1498
|
throw new RegistryError("Remote publish not implemented yet");
|
|
1967
1499
|
}
|
|
1968
1500
|
async link(resource) {
|
|
1969
1501
|
const locator = resource.manifest.toLocator();
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
const
|
|
1502
|
+
const resourcePath = this.buildPath(locator);
|
|
1503
|
+
await mkdir(resourcePath, { recursive: true });
|
|
1504
|
+
const manifestPath = join(resourcePath, "manifest.json");
|
|
1505
|
+
await writeFile(manifestPath, JSON.stringify(resource.manifest.toJSON(), null, 2), "utf-8");
|
|
1506
|
+
const contentPath = join(resourcePath, "content.tar.gz");
|
|
1975
1507
|
const serialized = await this.typeHandler.serialize(resource);
|
|
1976
|
-
await
|
|
1508
|
+
await writeFile(contentPath, serialized);
|
|
1977
1509
|
}
|
|
1978
|
-
async
|
|
1510
|
+
async get(locator) {
|
|
1979
1511
|
if (!await this.exists(locator)) {
|
|
1980
1512
|
throw new RegistryError(`Resource not found: ${locator}`);
|
|
1981
1513
|
}
|
|
1982
|
-
const
|
|
1983
|
-
const
|
|
1984
|
-
const
|
|
1985
|
-
const manifestData = JSON.parse(
|
|
1514
|
+
const resourcePath = this.buildPath(locator);
|
|
1515
|
+
const manifestPath = join(resourcePath, "manifest.json");
|
|
1516
|
+
const manifestContent = await readFile(manifestPath, "utf-8");
|
|
1517
|
+
const manifestData = JSON.parse(manifestContent);
|
|
1986
1518
|
const manifest = createRXM(manifestData);
|
|
1987
|
-
const
|
|
1988
|
-
const
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1519
|
+
const contentPath = join(resourcePath, "content.tar.gz");
|
|
1520
|
+
const data = await readFile(contentPath);
|
|
1521
|
+
return this.typeHandler.deserialize(data, manifest);
|
|
1522
|
+
}
|
|
1523
|
+
async resolve(locator) {
|
|
1524
|
+
const rxr = await this.get(locator);
|
|
1992
1525
|
return this.typeHandler.resolve(rxr);
|
|
1993
1526
|
}
|
|
1994
1527
|
async exists(locator) {
|
|
1995
|
-
const
|
|
1996
|
-
const
|
|
1997
|
-
|
|
1528
|
+
const resourcePath = this.buildPath(locator);
|
|
1529
|
+
const manifestPath = join(resourcePath, "manifest.json");
|
|
1530
|
+
try {
|
|
1531
|
+
await stat(manifestPath);
|
|
1532
|
+
return true;
|
|
1533
|
+
} catch {
|
|
1534
|
+
return false;
|
|
1535
|
+
}
|
|
1998
1536
|
}
|
|
1999
1537
|
async delete(locator) {
|
|
2000
1538
|
if (!await this.exists(locator)) {
|
|
2001
1539
|
return;
|
|
2002
1540
|
}
|
|
2003
|
-
const
|
|
2004
|
-
|
|
2005
|
-
await manifestArl.delete();
|
|
2006
|
-
const contentUrl = this.buildUrl(locator, "content.tar.gz").replace("arp:text:", "arp:binary:");
|
|
2007
|
-
const contentArl = this.arp.parse(contentUrl);
|
|
2008
|
-
await contentArl.delete();
|
|
1541
|
+
const resourcePath = this.buildPath(locator);
|
|
1542
|
+
await rm(resourcePath, { recursive: true, force: true });
|
|
2009
1543
|
}
|
|
2010
1544
|
async search(options) {
|
|
2011
1545
|
const { query, limit, offset = 0 } = options ?? {};
|
|
2012
|
-
const baseUrl = `arp:text:file://${this.basePath}`;
|
|
2013
|
-
const baseArl = this.arp.parse(baseUrl);
|
|
2014
1546
|
let entries;
|
|
2015
1547
|
try {
|
|
2016
|
-
|
|
2017
|
-
entries = JSON.parse(result2.content);
|
|
1548
|
+
entries = await this.listRecursive(this.basePath);
|
|
2018
1549
|
} catch {
|
|
2019
1550
|
return [];
|
|
2020
1551
|
}
|
|
2021
1552
|
const locators = [];
|
|
2022
1553
|
for (const entry of entries) {
|
|
2023
|
-
if (!entry.endsWith("
|
|
1554
|
+
if (!entry.endsWith("manifest.json")) {
|
|
2024
1555
|
continue;
|
|
2025
1556
|
}
|
|
2026
|
-
const
|
|
1557
|
+
const relativePath = entry.slice(this.basePath.length + 1);
|
|
1558
|
+
const rxl = this.parseEntryToRXL(relativePath);
|
|
2027
1559
|
if (rxl) {
|
|
2028
1560
|
locators.push(rxl);
|
|
2029
1561
|
}
|
|
@@ -2042,9 +1574,25 @@ class ARPRegistry {
|
|
|
2042
1574
|
}
|
|
2043
1575
|
return result;
|
|
2044
1576
|
}
|
|
1577
|
+
async listRecursive(dir) {
|
|
1578
|
+
const results = [];
|
|
1579
|
+
try {
|
|
1580
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1581
|
+
for (const entry of entries) {
|
|
1582
|
+
const fullPath = join(dir, entry.name);
|
|
1583
|
+
if (entry.isDirectory()) {
|
|
1584
|
+
const subEntries = await this.listRecursive(fullPath);
|
|
1585
|
+
results.push(...subEntries);
|
|
1586
|
+
} else {
|
|
1587
|
+
results.push(fullPath);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
} catch {}
|
|
1591
|
+
return results;
|
|
1592
|
+
}
|
|
2045
1593
|
parseEntryToRXL(entry) {
|
|
2046
|
-
const dirPath = entry.replace(
|
|
2047
|
-
const parts = dirPath.split(
|
|
1594
|
+
const dirPath = entry.replace(/[/\\]manifest\.json$/, "");
|
|
1595
|
+
const parts = dirPath.split(/[/\\]/);
|
|
2048
1596
|
if (parts.length < 3) {
|
|
2049
1597
|
return null;
|
|
2050
1598
|
}
|
|
@@ -2078,14 +1626,105 @@ class ARPRegistry {
|
|
|
2078
1626
|
}
|
|
2079
1627
|
}
|
|
2080
1628
|
}
|
|
1629
|
+
// src/RemoteRegistry.ts
|
|
1630
|
+
class RemoteRegistry {
|
|
1631
|
+
endpoint;
|
|
1632
|
+
typeHandler;
|
|
1633
|
+
constructor(config) {
|
|
1634
|
+
this.endpoint = config.endpoint.replace(/\/$/, "");
|
|
1635
|
+
this.typeHandler = TypeHandlerChain.create();
|
|
1636
|
+
}
|
|
1637
|
+
supportType(type) {
|
|
1638
|
+
this.typeHandler.register(type);
|
|
1639
|
+
}
|
|
1640
|
+
async publish(_resource) {
|
|
1641
|
+
throw new RegistryError("Remote registry publish not implemented yet");
|
|
1642
|
+
}
|
|
1643
|
+
async link(_resource) {
|
|
1644
|
+
throw new RegistryError("Cannot link to remote registry - use local registry for linking");
|
|
1645
|
+
}
|
|
1646
|
+
async get(locator) {
|
|
1647
|
+
const manifestUrl = `${this.endpoint}/resource?locator=${encodeURIComponent(locator)}`;
|
|
1648
|
+
const manifestResponse = await fetch(manifestUrl);
|
|
1649
|
+
if (!manifestResponse.ok) {
|
|
1650
|
+
if (manifestResponse.status === 404) {
|
|
1651
|
+
throw new RegistryError(`Resource not found: ${locator}`);
|
|
1652
|
+
}
|
|
1653
|
+
throw new RegistryError(`Failed to fetch resource: ${manifestResponse.statusText}`);
|
|
1654
|
+
}
|
|
1655
|
+
const manifestData = await manifestResponse.json();
|
|
1656
|
+
const manifest = createRXM(manifestData);
|
|
1657
|
+
const contentUrl = `${this.endpoint}/content?locator=${encodeURIComponent(locator)}`;
|
|
1658
|
+
const contentResponse = await fetch(contentUrl);
|
|
1659
|
+
if (!contentResponse.ok) {
|
|
1660
|
+
throw new RegistryError(`Failed to fetch content: ${contentResponse.statusText}`);
|
|
1661
|
+
}
|
|
1662
|
+
const contentBuffer = Buffer.from(await contentResponse.arrayBuffer());
|
|
1663
|
+
return this.typeHandler.deserialize(contentBuffer, manifest);
|
|
1664
|
+
}
|
|
1665
|
+
async resolve(locator) {
|
|
1666
|
+
const rxr = await this.get(locator);
|
|
1667
|
+
return this.typeHandler.resolve(rxr);
|
|
1668
|
+
}
|
|
1669
|
+
async exists(locator) {
|
|
1670
|
+
const url = `${this.endpoint}/exists?locator=${encodeURIComponent(locator)}`;
|
|
1671
|
+
const response = await fetch(url);
|
|
1672
|
+
if (!response.ok) {
|
|
1673
|
+
return false;
|
|
1674
|
+
}
|
|
1675
|
+
const data = await response.json();
|
|
1676
|
+
return data.exists === true;
|
|
1677
|
+
}
|
|
1678
|
+
async delete(_locator) {
|
|
1679
|
+
throw new RegistryError("Cannot delete from remote registry - use local registry for deletion");
|
|
1680
|
+
}
|
|
1681
|
+
async search(options) {
|
|
1682
|
+
const params = new URLSearchParams;
|
|
1683
|
+
if (options?.query)
|
|
1684
|
+
params.set("query", options.query);
|
|
1685
|
+
if (options?.limit !== undefined)
|
|
1686
|
+
params.set("limit", String(options.limit));
|
|
1687
|
+
if (options?.offset !== undefined)
|
|
1688
|
+
params.set("offset", String(options.offset));
|
|
1689
|
+
const url = `${this.endpoint}/search?${params.toString()}`;
|
|
1690
|
+
const response = await fetch(url);
|
|
1691
|
+
if (!response.ok) {
|
|
1692
|
+
throw new RegistryError(`Search failed: ${response.statusText}`);
|
|
1693
|
+
}
|
|
1694
|
+
const data = await response.json();
|
|
1695
|
+
return (data.results || []).map((locator) => parseRXL(locator));
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
async function discoverRegistry(domain) {
|
|
1699
|
+
const wellKnownUrl = `https://${domain}/.well-known/resourcex`;
|
|
1700
|
+
try {
|
|
1701
|
+
const response = await fetch(wellKnownUrl);
|
|
1702
|
+
if (!response.ok) {
|
|
1703
|
+
throw new RegistryError(`Well-known discovery failed for ${domain}: ${response.statusText}`);
|
|
1704
|
+
}
|
|
1705
|
+
const data = await response.json();
|
|
1706
|
+
return data.registry;
|
|
1707
|
+
} catch (error) {
|
|
1708
|
+
if (error instanceof RegistryError) {
|
|
1709
|
+
throw error;
|
|
1710
|
+
}
|
|
1711
|
+
throw new RegistryError(`Failed to discover registry for ${domain}: ${error.message}`);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
2081
1714
|
// src/createRegistry.ts
|
|
2082
1715
|
function createRegistry(config) {
|
|
2083
|
-
|
|
1716
|
+
if (isRemoteConfig(config)) {
|
|
1717
|
+
return new RemoteRegistry(config);
|
|
1718
|
+
}
|
|
1719
|
+
return new LocalRegistry(config);
|
|
2084
1720
|
}
|
|
2085
1721
|
export {
|
|
1722
|
+
isRemoteConfig,
|
|
1723
|
+
discoverRegistry,
|
|
2086
1724
|
createRegistry,
|
|
1725
|
+
RemoteRegistry,
|
|
2087
1726
|
RegistryError,
|
|
2088
|
-
|
|
1727
|
+
LocalRegistry
|
|
2089
1728
|
};
|
|
2090
1729
|
|
|
2091
|
-
//# debugId=
|
|
1730
|
+
//# debugId=E606A28A0E6E3E5464756E2164756E21
|