@oxyhq/core 3.12.0 → 3.13.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.
@@ -3,11 +3,16 @@
3
3
  *
4
4
  * `id` is a stable key for list rendering (the source entry's id when present,
5
5
  * otherwise the source index as a string). `url` is always a non-empty string.
6
+ * `title`, `description`, and `image` are carried through from `linksMetadata`
7
+ * only when present; the legacy `links: string[]` path produces just
8
+ * `{ id, url }`.
6
9
  */
7
10
  export interface ProfileLink {
8
11
  id: string;
9
12
  title?: string;
10
13
  url: string;
14
+ description?: string;
15
+ image?: string;
11
16
  }
12
17
  /** Source shape of a single `User.linksMetadata` entry. */
13
18
  export interface ProfileLinkMetadata {
@@ -23,10 +28,13 @@ export interface ProfileLinkMetadata {
23
28
  * Pure, no side effects, no I/O.
24
29
  *
25
30
  * - Prefers `linksMetadata` when it is a non-empty array: maps each entry to
26
- * `{ id, title, url }`, using `entry.id` when present and falling back to the
27
- * entry index. Entries without a non-empty string `url` are dropped.
31
+ * `{ id, title, url, description, image }`, using `entry.id` when present and
32
+ * falling back to the entry index. `title`, `description`, and `image` are
33
+ * carried through only when they are present strings. Entries without a
34
+ * non-empty string `url` are dropped.
28
35
  * - Otherwise falls back to the legacy `links` string array: maps each string to
29
- * `{ id: <index>, url }` (no title). Empty/non-string values are dropped.
36
+ * `{ id: <index>, url }` (no title/description/image). Empty/non-string values
37
+ * are dropped.
30
38
  * - Returns `[]` when both are absent or empty (including when `linksMetadata`
31
39
  * is present but every entry is dropped — it does NOT fall back to `links`).
32
40
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/core",
3
- "version": "3.12.0",
3
+ "version": "3.13.0",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -98,7 +98,7 @@
98
98
  }
99
99
  },
100
100
  "dependencies": {
101
- "@oxyhq/contracts": "0.4.0",
101
+ "@oxyhq/contracts": "workspace:*",
102
102
  "bip39": "^3.1.0",
103
103
  "buffer": "^6.0.3",
104
104
  "elliptic": "^6.6.1",
@@ -30,6 +30,53 @@ describe('normalizeProfileLinks', () => {
30
30
  expect(result[0]).not.toHaveProperty('title');
31
31
  });
32
32
 
33
+ it('carries through description and image when present', () => {
34
+ const result = normalizeProfileLinks([
35
+ {
36
+ url: 'https://oxy.so',
37
+ title: 'Oxy',
38
+ description: 'The Oxy ecosystem',
39
+ image: 'https://cdn.example/oxy.png',
40
+ id: 'abc',
41
+ },
42
+ ]);
43
+ expect(result).toEqual<ProfileLink[]>([
44
+ {
45
+ id: 'abc',
46
+ title: 'Oxy',
47
+ url: 'https://oxy.so',
48
+ description: 'The Oxy ecosystem',
49
+ image: 'https://cdn.example/oxy.png',
50
+ },
51
+ ]);
52
+ });
53
+
54
+ it('omits description and image when absent', () => {
55
+ const result = normalizeProfileLinks([
56
+ { url: 'https://min.example', title: 'Min' },
57
+ ]);
58
+ expect(result).toEqual<ProfileLink[]>([
59
+ { id: '0', title: 'Min', url: 'https://min.example' },
60
+ ]);
61
+ expect(result[0]).not.toHaveProperty('description');
62
+ expect(result[0]).not.toHaveProperty('image');
63
+ });
64
+
65
+ it('carries description and image independently of title', () => {
66
+ const result = normalizeProfileLinks([
67
+ { url: 'https://only-image.example', image: 'https://cdn.example/i.png' },
68
+ { url: 'https://only-desc.example', description: 'Just a description' },
69
+ ]);
70
+ expect(result).toEqual<ProfileLink[]>([
71
+ { id: '0', url: 'https://only-image.example', image: 'https://cdn.example/i.png' },
72
+ { id: '1', url: 'https://only-desc.example', description: 'Just a description' },
73
+ ]);
74
+ expect(result[0]).not.toHaveProperty('title');
75
+ expect(result[0]).not.toHaveProperty('description');
76
+ expect(result[1]).not.toHaveProperty('title');
77
+ expect(result[1]).not.toHaveProperty('image');
78
+ });
79
+
33
80
  it('drops entries with missing or empty url and keeps stable index ids', () => {
34
81
  const result = normalizeProfileLinks([
35
82
  { url: 'https://keep.example', title: 'Keep', id: 'keep' },
@@ -60,7 +107,7 @@ describe('normalizeProfileLinks', () => {
60
107
  });
61
108
 
62
109
  describe('legacy links path (no linksMetadata)', () => {
63
- it('maps strings to { id, url } without titles', () => {
110
+ it('maps strings to { id, url } without title/description/image', () => {
64
111
  const result = normalizeProfileLinks(undefined, [
65
112
  'https://a.example',
66
113
  'https://b.example',
@@ -69,7 +116,14 @@ describe('normalizeProfileLinks', () => {
69
116
  { id: '0', url: 'https://a.example' },
70
117
  { id: '1', url: 'https://b.example' },
71
118
  ]);
72
- expect(result.every((link) => !('title' in link))).toBe(true);
119
+ expect(
120
+ result.every(
121
+ (link) =>
122
+ !('title' in link) &&
123
+ !('description' in link) &&
124
+ !('image' in link),
125
+ ),
126
+ ).toBe(true);
73
127
  });
74
128
 
75
129
  it('drops empty and whitespace-only strings, preserving source index ids', () => {
@@ -3,11 +3,16 @@
3
3
  *
4
4
  * `id` is a stable key for list rendering (the source entry's id when present,
5
5
  * otherwise the source index as a string). `url` is always a non-empty string.
6
+ * `title`, `description`, and `image` are carried through from `linksMetadata`
7
+ * only when present; the legacy `links: string[]` path produces just
8
+ * `{ id, url }`.
6
9
  */
7
10
  export interface ProfileLink {
8
11
  id: string;
9
12
  title?: string;
10
13
  url: string;
14
+ description?: string;
15
+ image?: string;
11
16
  }
12
17
 
13
18
  /** Source shape of a single `User.linksMetadata` entry. */
@@ -31,10 +36,13 @@ function cleanUrl(value: unknown): string | null {
31
36
  * Pure, no side effects, no I/O.
32
37
  *
33
38
  * - Prefers `linksMetadata` when it is a non-empty array: maps each entry to
34
- * `{ id, title, url }`, using `entry.id` when present and falling back to the
35
- * entry index. Entries without a non-empty string `url` are dropped.
39
+ * `{ id, title, url, description, image }`, using `entry.id` when present and
40
+ * falling back to the entry index. `title`, `description`, and `image` are
41
+ * carried through only when they are present strings. Entries without a
42
+ * non-empty string `url` are dropped.
36
43
  * - Otherwise falls back to the legacy `links` string array: maps each string to
37
- * `{ id: <index>, url }` (no title). Empty/non-string values are dropped.
44
+ * `{ id: <index>, url }` (no title/description/image). Empty/non-string values
45
+ * are dropped.
38
46
  * - Returns `[]` when both are absent or empty (including when `linksMetadata`
39
47
  * is present but every entry is dropped — it does NOT fall back to `links`).
40
48
  *
@@ -55,7 +63,16 @@ export function normalizeProfileLinks(
55
63
  ? entry.id
56
64
  : String(index);
57
65
  const title = typeof entry?.title === 'string' ? entry.title : undefined;
58
- result.push({ id, url, ...(title !== undefined ? { title } : {}) });
66
+ const description =
67
+ typeof entry?.description === 'string' ? entry.description : undefined;
68
+ const image = typeof entry?.image === 'string' ? entry.image : undefined;
69
+ result.push({
70
+ id,
71
+ url,
72
+ ...(title !== undefined ? { title } : {}),
73
+ ...(description !== undefined ? { description } : {}),
74
+ ...(image !== undefined ? { image } : {}),
75
+ });
59
76
  });
60
77
  return result;
61
78
  }