xmatter 0.0.0 → 0.10.3

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/client.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ import type { Frontmatter } from "./schema.js";
2
+ export type { Frontmatter };
3
+ export declare class XmatterClient {
4
+ private readonly namespace;
5
+ private readonly baseUrl;
6
+ private readonly cache;
7
+ constructor(namespace: string, baseUrl?: string | URL, cacheSize?: number);
8
+ /**
9
+ * Fetch `/{namespace}/{chainId}/{prefix}/index.txt` and return the entries.
10
+ * Returns full addresses if <=256 matches, otherwise returns next-byte sub-prefixes.
11
+ * Results are cached with TTL from the response Cache-Control header.
12
+ */
13
+ getIndex(chainId: string, prefix: string): Promise<string[]>;
14
+ /**
15
+ * Check if an address exists by walking the index prefix tree in 2-byte steps.
16
+ * Fetches progressively longer prefixes until entries resolve to full addresses,
17
+ * then checks for an exact match.
18
+ */
19
+ has(chainId: string, address: string): Promise<boolean>;
20
+ /**
21
+ * Fetch `/{namespace}/{chainId}/{address}/frontmatter.json` which returns
22
+ * parsed YAML frontmatter from the address's README.md.
23
+ * Checks existence via the index first to avoid unnecessary requests.
24
+ */
25
+ getFrontmatter(chainId: string, address: string): Promise<Frontmatter | undefined>;
26
+ /**
27
+ * Build the URL for `/{namespace}/{chainId}/{address}/icon` which serves
28
+ * the original image (svg/png/jpg) if <25KB, otherwise converts to 256x256 WebP.
29
+ * Checks existence via the index first, returning undefined for missing addresses.
30
+ */
31
+ getIconUrl(chainId: string, address: string): Promise<URL | undefined>;
32
+ /**
33
+ * Build the URL for `/{namespace}/{chainId}/{address}/icon.webp` which always
34
+ * converts the original icon (svg/png/jpg) to 256x256 WebP.
35
+ * Checks existence via the index first, returning undefined for missing addresses.
36
+ */
37
+ getIconWebpUrl(chainId: string, address: string): Promise<URL | undefined>;
38
+ }
39
+ export declare class XmatterError extends Error {
40
+ readonly status: number;
41
+ constructor(status: number, message: string);
42
+ }
43
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAM;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;gBAEvC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,GAAG,GAA2B,EAAE,SAAS,GAAE,MAAa;IAMtG;;;;OAIG;IACG,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgBlE;;;;OAIG;IACG,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiB7D;;;;OAIG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAWxF;;;;OAIG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC;IAK5E;;;;OAIG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC;CAIjF;AAOD,qBAAa,YAAa,SAAQ,KAAK;aAEnB,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM;CAIlB"}
package/client.js ADDED
@@ -0,0 +1,97 @@
1
+ import { LRUCache } from "lru-cache";
2
+ export class XmatterClient {
3
+ namespace;
4
+ baseUrl;
5
+ cache;
6
+ constructor(namespace, baseUrl = "https://xmatter.org", cacheSize = 4096) {
7
+ this.namespace = namespace;
8
+ this.baseUrl = new URL(baseUrl);
9
+ this.cache = new LRUCache({ max: cacheSize });
10
+ }
11
+ /**
12
+ * Fetch `/{namespace}/{chainId}/{prefix}/index.txt` and return the entries.
13
+ * Returns full addresses if <=256 matches, otherwise returns next-byte sub-prefixes.
14
+ * Results are cached with TTL from the response Cache-Control header.
15
+ */
16
+ async getIndex(chainId, prefix) {
17
+ const key = `${chainId}/${prefix.toLowerCase()}`;
18
+ const cached = this.cache.get(key);
19
+ if (cached !== undefined)
20
+ return cached;
21
+ const res = await fetch(new URL(`/${this.namespace}/${chainId}/${prefix}/index.txt`, this.baseUrl));
22
+ if (!res.ok) {
23
+ throw new XmatterError(res.status, await res.text());
24
+ }
25
+ const text = await res.text();
26
+ const entries = text === "" ? [] : text.split("\n");
27
+ const ttl = parseMaxAge(res.headers.get("cache-control"));
28
+ this.cache.set(key, entries, { ttl });
29
+ return entries;
30
+ }
31
+ /**
32
+ * Check if an address exists by walking the index prefix tree in 2-byte steps.
33
+ * Fetches progressively longer prefixes until entries resolve to full addresses,
34
+ * then checks for an exact match.
35
+ */
36
+ async has(chainId, address) {
37
+ const lower = address.toLowerCase();
38
+ for (let len = 2; len < lower.length; len += 2) {
39
+ const prefix = lower.slice(0, len);
40
+ const entries = await this.getIndex(chainId, prefix);
41
+ if (entries.length === 0)
42
+ return false;
43
+ const first = entries[0].toLowerCase();
44
+ if (first.length === 42) {
45
+ return entries.some((e) => e.toLowerCase() === lower);
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+ /**
51
+ * Fetch `/{namespace}/{chainId}/{address}/frontmatter.json` which returns
52
+ * parsed YAML frontmatter from the address's README.md.
53
+ * Checks existence via the index first to avoid unnecessary requests.
54
+ */
55
+ async getFrontmatter(chainId, address) {
56
+ if (!(await this.has(chainId, address)))
57
+ return undefined;
58
+ const res = await fetch(new URL(`/${this.namespace}/${chainId}/${address}/frontmatter.json`, this.baseUrl));
59
+ if (res.status === 404)
60
+ return undefined;
61
+ if (!res.ok) {
62
+ throw new XmatterError(res.status, await res.text());
63
+ }
64
+ return res.json();
65
+ }
66
+ /**
67
+ * Build the URL for `/{namespace}/{chainId}/{address}/icon` which serves
68
+ * the original image (svg/png/jpg) if <25KB, otherwise converts to 256x256 WebP.
69
+ * Checks existence via the index first, returning undefined for missing addresses.
70
+ */
71
+ async getIconUrl(chainId, address) {
72
+ if (!(await this.has(chainId, address)))
73
+ return undefined;
74
+ return new URL(`/${this.namespace}/${chainId}/${address}/icon`, this.baseUrl);
75
+ }
76
+ /**
77
+ * Build the URL for `/{namespace}/{chainId}/{address}/icon.webp` which always
78
+ * converts the original icon (svg/png/jpg) to 256x256 WebP.
79
+ * Checks existence via the index first, returning undefined for missing addresses.
80
+ */
81
+ async getIconWebpUrl(chainId, address) {
82
+ if (!(await this.has(chainId, address)))
83
+ return undefined;
84
+ return new URL(`/${this.namespace}/${chainId}/${address}/icon.webp`, this.baseUrl);
85
+ }
86
+ }
87
+ function parseMaxAge(header) {
88
+ const match = header?.match(/max-age=(\d+)/);
89
+ return match ? parseInt(match[1], 10) * 1000 : 30 * 60 * 1000;
90
+ }
91
+ export class XmatterError extends Error {
92
+ status;
93
+ constructor(status, message) {
94
+ super(message);
95
+ this.status = status;
96
+ }
97
+ }
@@ -0,0 +1,20 @@
1
+ import { type ImageProps } from "next/image";
2
+ import { type ReactNode } from "react";
3
+ export type XmatterIconProps = {
4
+ namespace: string;
5
+ chainId: string;
6
+ address: string;
7
+ fallback: ReactNode;
8
+ baseUrl?: string;
9
+ } & Omit<ImageProps, "src" | "onError">;
10
+ export type IconWithFallbackProps = {
11
+ fallback: ReactNode;
12
+ } & Omit<ImageProps, "onError">;
13
+ export declare function IconWithFallback({ fallback, ...props }: IconWithFallbackProps): ReactNode;
14
+ export declare function XmatterIcon({ namespace, chainId, address, fallback, baseUrl, ...props }: XmatterIconProps): ReactNode;
15
+ export declare const RemotePattern: {
16
+ readonly protocol: "https";
17
+ readonly hostname: "xmatter.org";
18
+ readonly pathname: "/*/*/*/icon";
19
+ };
20
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["client.tsx"],"names":[],"mappings":"AAEA,OAAc,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAC;AAEjD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,GAAG,SAAS,CAAC,CAAC;AAExC,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AAEhC,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,qBAAqB,GAAG,SAAS,CAQzF;AAED,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,OAAO,EACP,OAAO,EACP,QAAQ,EACR,OAA+B,EAC/B,GAAG,KAAK,EACT,EAAE,gBAAgB,GAAG,SAAS,CAE9B;AAED,eAAO,MAAM,aAAa;;;;CAIhB,CAAC"}
package/next/client.js ADDED
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import Image from "next/image";
4
+ import { useState } from "react";
5
+ export function IconWithFallback({ fallback, ...props }) {
6
+ const [error, setError] = useState(false);
7
+ if (error) {
8
+ return fallback;
9
+ }
10
+ return _jsx(Image, { onError: () => setError(true), ...props });
11
+ }
12
+ export function XmatterIcon({ namespace, chainId, address, fallback, baseUrl = "https://xmatter.org", ...props }) {
13
+ return _jsx(IconWithFallback, { src: `${baseUrl}/${namespace}/${chainId}/${address}/icon`, fallback: fallback, ...props });
14
+ }
15
+ export const RemotePattern = {
16
+ protocol: "https",
17
+ hostname: "xmatter.org",
18
+ pathname: "/*/*/*/icon",
19
+ };
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from "react";
2
+ import type { XmatterClient } from "../client.js";
3
+ import { type IconWithFallbackProps } from "./client.js";
4
+ export type XmatterIconProps = {
5
+ client: XmatterClient;
6
+ chainId: string;
7
+ address: string;
8
+ } & Omit<IconWithFallbackProps, "src">;
9
+ export declare function XmatterIcon({ client, chainId, address, fallback, ...props }: XmatterIconProps): Promise<ReactNode>;
10
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["server.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE3E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;AAEvC,wBAAsB,WAAW,CAAC,EAChC,MAAM,EACN,OAAO,EACP,OAAO,EACP,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CAOvC"}
package/next/server.js ADDED
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { IconWithFallback } from "./client.js";
3
+ export async function XmatterIcon({ client, chainId, address, fallback, ...props }) {
4
+ const url = await client.getIconUrl(chainId, address);
5
+ if (!url) {
6
+ return fallback;
7
+ }
8
+ return _jsx(IconWithFallback, { src: url.toString(), fallback: fallback, ...props });
9
+ }
package/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "xmatter",
3
- "version": "0.0.0",
3
+ "version": "0.10.3",
4
4
  "private": false,
5
- "files": []
6
- }
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/fuxingloh/xmatter",
8
+ "directory": "packages/xmatter"
9
+ },
10
+ "type": "module",
11
+ "files": [
12
+ "**/*.d.ts",
13
+ "**/*.d.ts.map",
14
+ "**/*.js"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "clean": "tsc --build --clean"
19
+ },
20
+ "lint-staged": {
21
+ "*": [
22
+ "prettier --write --ignore-unknown"
23
+ ]
24
+ },
25
+ "dependencies": {
26
+ "lru-cache": "^11.2.5",
27
+ "zod": "^4.3.5"
28
+ },
29
+ "devDependencies": {
30
+ "@types/react": "^19"
31
+ },
32
+ "peerDependencies": {
33
+ "next": ">=14",
34
+ "react": ">=18"
35
+ },
36
+ "peerDependenciesMeta": {
37
+ "next": {
38
+ "optional": true
39
+ },
40
+ "react": {
41
+ "optional": true
42
+ }
43
+ }
44
+ }
package/schema.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ export declare const FrontmatterSchema: z.ZodObject<{
3
+ name: z.ZodString;
4
+ description: z.ZodOptional<z.ZodString>;
5
+ provenance: z.ZodString;
6
+ standards: z.ZodArray<z.ZodString>;
7
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
8
+ links: z.ZodOptional<z.ZodArray<z.ZodObject<{
9
+ name: z.ZodString;
10
+ url: z.ZodURL;
11
+ }, z.core.$strip>>>;
12
+ symbol: z.ZodOptional<z.ZodString>;
13
+ decimals: z.ZodOptional<z.ZodNumber>;
14
+ icon: z.ZodOptional<z.ZodString>;
15
+ color: z.ZodOptional<z.ZodString>;
16
+ }, z.core.$strip>;
17
+ export declare const XmatterSchema: z.ZodObject<{
18
+ data: z.ZodObject<{
19
+ name: z.ZodString;
20
+ description: z.ZodOptional<z.ZodString>;
21
+ provenance: z.ZodString;
22
+ standards: z.ZodArray<z.ZodString>;
23
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
24
+ links: z.ZodOptional<z.ZodArray<z.ZodObject<{
25
+ name: z.ZodString;
26
+ url: z.ZodURL;
27
+ }, z.core.$strip>>>;
28
+ symbol: z.ZodOptional<z.ZodString>;
29
+ decimals: z.ZodOptional<z.ZodNumber>;
30
+ icon: z.ZodOptional<z.ZodString>;
31
+ color: z.ZodOptional<z.ZodString>;
32
+ }, z.core.$strip>;
33
+ content: z.ZodOptional<z.ZodString>;
34
+ }, z.core.$strip>;
35
+ export type Frontmatter = z.infer<typeof FrontmatterSchema>;
36
+ export type XmatterFile = z.infer<typeof XmatterSchema>;
37
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;iBA8B5B,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;iBAGxB,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC5D,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
package/schema.js ADDED
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ export const FrontmatterSchema = z.object({
3
+ name: z
4
+ .string()
5
+ .min(1)
6
+ .regex(/^(?!\s)(?!.*\s$).*$/)
7
+ .describe("Name of the token"),
8
+ description: z.string().optional(),
9
+ provenance: z.string().describe("Where did this entry come from?"),
10
+ standards: z.array(z.string()),
11
+ tags: z.array(z.string()).optional(),
12
+ links: z
13
+ .array(z.object({
14
+ name: z.string(),
15
+ url: z.url(),
16
+ }))
17
+ .optional(),
18
+ symbol: z
19
+ .string()
20
+ .min(1)
21
+ .regex(/^(?!\s)(?!.*\s$).*$/)
22
+ .optional(),
23
+ decimals: z.number().int().min(0).max(256).optional(),
24
+ icon: z.string().optional().describe("Primary icon for this entry."),
25
+ color: z
26
+ .string()
27
+ .regex(/^#[0-9a-f]{6}$/i)
28
+ .optional()
29
+ .describe("Primary color for this entry based on its icon."),
30
+ });
31
+ export const XmatterSchema = z.object({
32
+ data: FrontmatterSchema,
33
+ content: z.string().optional(),
34
+ });