@oxyhq/core 3.17.0 → 3.18.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.
@@ -37,11 +37,15 @@ export declare function OxyServicesLinksMixin<T extends typeof OxyServicesBase>(
37
37
  * Resolve multiple link previews via `POST /links/previews` (body `{ urls }`).
38
38
  *
39
39
  * Inputs are de-duplicated and split into chunks of {@link LINK_PREVIEWS_CHUNK_SIZE}
40
- * (the server-side cap). Chunks run concurrently and their `data` maps are
40
+ * (the server-side cap). Chunks run concurrently and their result maps are
41
41
  * merged into a single result keyed by the REQUESTED url (the exact string
42
42
  * passed in `urls`) — matching the batch contract — so a caller can always
43
43
  * look its own input back up.
44
44
  *
45
+ * `makeRequest` unwraps the API's top-level `{ data }` envelope (same as
46
+ * `getUsersByIds`'s `makeServiceRequest<User[]>`), so each chunk response is
47
+ * already the `Record<url, LinkPreview>` map — merge it directly.
48
+ *
45
49
  * An empty / whitespace-only input resolves immediately with `{}` and
46
50
  * performs no network call. A failure in any chunk surfaces (via
47
51
  * `handleError`) rather than being swallowed.
@@ -98,8 +98,10 @@ export interface User {
98
98
  privacySettings?: PrivacySettings;
99
99
  /**
100
100
  * Structured human name. `name.displayName` is the canonical display string
101
- * resolved by the API; consumers render it directly instead of recomposing
102
- * names from `first` / `last` / `full` / `username`.
101
+ * resolved by the API when present; consumers render it directly instead of
102
+ * recomposing names from `first` / `last` / `full` / `username`. It is now
103
+ * OPTIONAL (see `UserNameResponse`) — when absent, fall back to a handle
104
+ * (e.g. `getNormalizedUserHandle`) rather than recomposing a name locally.
103
105
  */
104
106
  name: UserNameResponse;
105
107
  bio?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/core",
3
- "version": "3.17.0",
3
+ "version": "3.18.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",
@@ -1026,7 +1026,9 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
1026
1026
  continue;
1027
1027
  }
1028
1028
  const userId = e.user.id ?? e.user._id;
1029
- if (!userId || !e.user.username || !e.user.name?.displayName) {
1029
+ // `name.displayName` is optional on the contract — do NOT drop no-name
1030
+ // accounts. Only an absent userId or username makes the entry unusable.
1031
+ if (!userId || !e.user.username) {
1030
1032
  continue;
1031
1033
  }
1032
1034
  if (typeof e.authuser !== 'number') {
@@ -1040,7 +1042,9 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
1040
1042
  user: {
1041
1043
  id: userId,
1042
1044
  username: e.user.username,
1043
- name: e.user.name,
1045
+ // `name.displayName` is optional; carry whatever structured name the
1046
+ // server sent (possibly an empty object) and let consumers fall back.
1047
+ name: e.user.name ?? {},
1044
1048
  avatar: e.user.avatar ?? null,
1045
1049
  email: e.user.email,
1046
1050
  color: e.user.color ?? null,
@@ -15,7 +15,7 @@
15
15
  * later read, so an SDK GET cache would pin the stale `'pending'` snapshot.
16
16
  * App-side caching (React Query / stores) owns this responsibility.
17
17
  */
18
- import type { LinkPreview, LinkPreviewBatchResponse } from '@oxyhq/contracts';
18
+ import type { LinkPreview } from '@oxyhq/contracts';
19
19
  import type { OxyServicesBase } from '../OxyServices.base';
20
20
  import { buildUrl } from '../utils/apiUtils';
21
21
 
@@ -57,11 +57,15 @@ export function OxyServicesLinksMixin<T extends typeof OxyServicesBase>(Base: T)
57
57
  * Resolve multiple link previews via `POST /links/previews` (body `{ urls }`).
58
58
  *
59
59
  * Inputs are de-duplicated and split into chunks of {@link LINK_PREVIEWS_CHUNK_SIZE}
60
- * (the server-side cap). Chunks run concurrently and their `data` maps are
60
+ * (the server-side cap). Chunks run concurrently and their result maps are
61
61
  * merged into a single result keyed by the REQUESTED url (the exact string
62
62
  * passed in `urls`) — matching the batch contract — so a caller can always
63
63
  * look its own input back up.
64
64
  *
65
+ * `makeRequest` unwraps the API's top-level `{ data }` envelope (same as
66
+ * `getUsersByIds`'s `makeServiceRequest<User[]>`), so each chunk response is
67
+ * already the `Record<url, LinkPreview>` map — merge it directly.
68
+ *
65
69
  * An empty / whitespace-only input resolves immediately with `{}` and
66
70
  * performs no network call. A failure in any chunk surfaces (via
67
71
  * `handleError`) rather than being swallowed.
@@ -82,7 +86,7 @@ export function OxyServicesLinksMixin<T extends typeof OxyServicesBase>(Base: T)
82
86
  try {
83
87
  const responses = await Promise.all(
84
88
  chunks.map((chunk) =>
85
- this.makeRequest<LinkPreviewBatchResponse>(
89
+ this.makeRequest<Record<string, LinkPreview>>(
86
90
  'POST',
87
91
  '/links/previews',
88
92
  { urls: chunk },
@@ -92,7 +96,7 @@ export function OxyServicesLinksMixin<T extends typeof OxyServicesBase>(Base: T)
92
96
  );
93
97
 
94
98
  return responses.reduce<Record<string, LinkPreview>>(
95
- (merged, response) => Object.assign(merged, response?.data ?? {}),
99
+ (merged, response) => Object.assign(merged, response ?? {}),
96
100
  {},
97
101
  );
98
102
  } catch (error) {
@@ -171,14 +171,17 @@ export function OxyServicesSsoMixin<T extends typeof OxyServicesBase>(Base: T) {
171
171
  }
172
172
 
173
173
  const userId = payload.user?.id ?? payload.user?._id;
174
- if (!userId || typeof payload.user?.username !== 'string' || typeof payload.user.name?.displayName !== 'string') {
174
+ if (!userId || typeof payload.user?.username !== 'string') {
175
175
  throw this.handleError(new Error('SSO exchange returned an invalid user'));
176
176
  }
177
177
 
178
178
  const user: MinimalUserData = {
179
179
  id: userId,
180
180
  username: payload.user.username,
181
- name: payload.user.name,
181
+ // `name.displayName` is optional on the contract. A no-name user is
182
+ // valid; carry whatever structured name the server sent (possibly an
183
+ // empty object) and let consumers fall back to a handle.
184
+ name: payload.user.name ?? {},
182
185
  avatar: payload.user.avatar,
183
186
  };
184
187
 
@@ -11,7 +11,7 @@
11
11
  * `data` map keyed by the requested url, and surfaces a chunk failure.
12
12
  */
13
13
 
14
- import type { LinkPreview, LinkPreviewBatchResponse } from '@oxyhq/contracts';
14
+ import type { LinkPreview } from '@oxyhq/contracts';
15
15
  import { OxyServices } from '../../OxyServices';
16
16
 
17
17
  const sampleResolved: LinkPreview = {
@@ -82,10 +82,10 @@ describe('OxyServices.links', () => {
82
82
  });
83
83
 
84
84
  it('de-duplicates and sends a single chunk for <= 50 unique URLs', async () => {
85
- const response: LinkPreviewBatchResponse = {
86
- data: { 'https://a.test/': sampleResolved },
87
- };
88
- makeRequestSpy.mockResolvedValueOnce(response);
85
+ // makeRequest unwraps the API's `{ data }` envelope, so the resolved value
86
+ // is the bare record (NOT `{ data: {...} }`) mirror that real shape here.
87
+ const record: Record<string, LinkPreview> = { 'https://a.test/': sampleResolved };
88
+ makeRequestSpy.mockResolvedValueOnce(record);
89
89
 
90
90
  const result = await oxy.getLinkPreviews([
91
91
  'https://a.test/',
@@ -93,7 +93,7 @@ describe('OxyServices.links', () => {
93
93
  ' ', // dropped
94
94
  ]);
95
95
 
96
- expect(result).toEqual(response.data);
96
+ expect(result).toEqual(record);
97
97
  expect(makeRequestSpy).toHaveBeenCalledTimes(1);
98
98
  expect(makeRequestSpy).toHaveBeenCalledWith(
99
99
  'POST',
@@ -111,13 +111,13 @@ describe('OxyServices.links', () => {
111
111
  _method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
112
112
  _url: string,
113
113
  data?: { urls: string[] },
114
- ): Promise<LinkPreviewBatchResponse> => {
114
+ ): Promise<Record<string, LinkPreview>> => {
115
115
  const chunkUrls = data?.urls ?? [];
116
116
  const dataMap: Record<string, LinkPreview> = {};
117
117
  for (const u of chunkUrls) {
118
118
  dataMap[u] = { url: u, status: 'resolved', title: `t-${u}` };
119
119
  }
120
- return { data: dataMap };
120
+ return dataMap;
121
121
  },
122
122
  );
123
123
 
@@ -108,8 +108,10 @@ export interface User {
108
108
  privacySettings?: PrivacySettings;
109
109
  /**
110
110
  * Structured human name. `name.displayName` is the canonical display string
111
- * resolved by the API; consumers render it directly instead of recomposing
112
- * names from `first` / `last` / `full` / `username`.
111
+ * resolved by the API when present; consumers render it directly instead of
112
+ * recomposing names from `first` / `last` / `full` / `username`. It is now
113
+ * OPTIONAL (see `UserNameResponse`) — when absent, fall back to a handle
114
+ * (e.g. `getNormalizedUserHandle`) rather than recomposing a name locally.
113
115
  */
114
116
  name: UserNameResponse;
115
117
  bio?: string;