@slyxup/core 0.2.0 → 0.2.2
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/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +13 -0
- package/dist/client.d.ts +20 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +85 -7
- package/dist/client.js.map +1 -1
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -6
- package/src/client.ts +93 -7
- package/src/types.ts +27 -0
- package/test/client.test.ts +249 -0
- package/test/errors.test.ts +109 -0
- package/vitest.config.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @slyxup/core
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`2011599`](https://github.com/slyxup/stack/commit/2011599a9c989560e34bcea3147c18923e15962d) - CLI: new `slyxup auth` command group using @slyxup/core SDK — `auth signup`, `auth signin`, `auth verify` (email verification), `auth oauth` (opens Google/GitHub in browser). Browser open is non-blocking. CLI now depends on @slyxup/core.
|
|
8
|
+
|
|
3
9
|
## 0.2.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 SlyxUp
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -23,10 +23,18 @@ const client = new SlyxupClient({
|
|
|
23
23
|
await client.auth.signUp({ email: 'ada@example.com', password: 'password123', firstName: 'Ada' });
|
|
24
24
|
await client.auth.signIn({ email: 'ada@example.com', password: 'password123' });
|
|
25
25
|
await client.auth.signOut();
|
|
26
|
+
await client.auth.resendVerification('ada@example.com'); // resend email verification
|
|
26
27
|
|
|
27
28
|
// ── Sessions ──
|
|
28
29
|
const { session, user } = await client.sessions.get();
|
|
29
30
|
// session.expiresAt — ISO date; throws UnauthorizedError (401) when signed out
|
|
31
|
+
const { sessions } = await client.sessions.list();
|
|
32
|
+
// SlyxupSessionInfo[]: { id, ipAddress, userAgent, expiresAt, createdAt, isCurrent }
|
|
33
|
+
await client.sessions.revoke('session-id'); // revoke one device
|
|
34
|
+
const { revoked } = await client.sessions.revokeOthers(); // sign out everywhere else
|
|
35
|
+
|
|
36
|
+
// ── Password ──
|
|
37
|
+
await client.password.change({ currentPassword, newPassword });
|
|
30
38
|
|
|
31
39
|
// ── Users ──
|
|
32
40
|
const me = await client.users.me(); // full profile
|
|
@@ -65,7 +73,12 @@ try {
|
|
|
65
73
|
| `auth.signUp(input)` | POST | `/v1/auth/sign-up` |
|
|
66
74
|
| `auth.signIn(input)` | POST | `/v1/auth/sign-in` |
|
|
67
75
|
| `auth.signOut()` | POST | `/v1/auth/sign-out` |
|
|
76
|
+
| `auth.resendVerification(email)` | POST | `/v1/verification/resend` |
|
|
68
77
|
| `sessions.get()` | GET | `/v1/session` |
|
|
78
|
+
| `sessions.list()` | GET | `/v1/sessions` |
|
|
79
|
+
| `sessions.revoke(id)` | DELETE | `/v1/sessions/:id` |
|
|
80
|
+
| `sessions.revokeOthers()` | DELETE | `/v1/sessions` |
|
|
81
|
+
| `password.change(input)` | POST | `/v1/user/password` |
|
|
69
82
|
| `users.me()` | GET | `/v1/user` |
|
|
70
83
|
| `users.update(patch)` | PATCH | `/v1/user` |
|
|
71
84
|
| `users.delete()` | DELETE | `/v1/user` |
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuthResponse, SessionResponse, SignInInput, SignUpInput, SlyxupClientOptions, UpdateUserInput, UserResponse } from './types.js';
|
|
1
|
+
import type { AuthResponse, ChangePasswordInput, RevokeSessionsResponse, SessionResponse, SessionsResponse, SignInInput, SignUpInput, SlyxupClientOptions, UpdateUserInput, UserResponse } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* SlyxUp Core Client.
|
|
4
4
|
*
|
|
@@ -11,15 +11,30 @@ import type { AuthResponse, SessionResponse, SignInInput, SignUpInput, SlyxupCli
|
|
|
11
11
|
export declare class SlyxupClient {
|
|
12
12
|
readonly publishableKey?: string;
|
|
13
13
|
readonly apiUrl: string;
|
|
14
|
+
private _getToken?;
|
|
15
|
+
private _request?;
|
|
14
16
|
readonly auth: {
|
|
15
17
|
signUp: (input: SignUpInput) => Promise<AuthResponse>;
|
|
16
18
|
signIn: (input: SignInInput) => Promise<AuthResponse>;
|
|
17
19
|
signOut: () => Promise<{
|
|
18
20
|
ok: true;
|
|
19
21
|
}>;
|
|
22
|
+
resendVerification: (email: string) => Promise<{
|
|
23
|
+
ok: true;
|
|
24
|
+
}>;
|
|
20
25
|
};
|
|
21
26
|
readonly sessions: {
|
|
22
27
|
get: () => Promise<SessionResponse>;
|
|
28
|
+
list: () => Promise<SessionsResponse>;
|
|
29
|
+
revoke: (sessionId: string) => Promise<{
|
|
30
|
+
ok: true;
|
|
31
|
+
}>;
|
|
32
|
+
revokeOthers: () => Promise<RevokeSessionsResponse>;
|
|
33
|
+
};
|
|
34
|
+
readonly password: {
|
|
35
|
+
change: (input: ChangePasswordInput) => Promise<{
|
|
36
|
+
ok: true;
|
|
37
|
+
}>;
|
|
23
38
|
};
|
|
24
39
|
readonly users: {
|
|
25
40
|
me: () => Promise<UserResponse>;
|
|
@@ -28,6 +43,10 @@ export declare class SlyxupClient {
|
|
|
28
43
|
ok: true;
|
|
29
44
|
}>;
|
|
30
45
|
};
|
|
46
|
+
/** Get current session token (for custom project APIs that need Bearer) */
|
|
47
|
+
getToken(): string | undefined;
|
|
48
|
+
/** Raw request for custom endpoints (uses same auth headers + cookies as SDK) */
|
|
49
|
+
request<T>(path: string, init?: RequestInit): Promise<T>;
|
|
31
50
|
constructor(options?: SlyxupClientOptions);
|
|
32
51
|
}
|
|
33
52
|
export * from './types.js';
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,YAAY,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EAGnB,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,YAAY,EACb,MAAM,YAAY,CAAC;AAqBpB;;;;;;;;GAQG;AACH,qBAAa,YAAY;IACvB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,SAAS,CAAC,CAA2B;IAC7C,OAAO,CAAC,QAAQ,CAAC,CAAsD;IAEvE,QAAQ,CAAC,IAAI,EAAE;QACb,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QACtD,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QACtD,OAAO,EAAE,MAAM,OAAO,CAAC;YAAE,EAAE,EAAE,IAAI,CAAA;SAAE,CAAC,CAAC;QACrC,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;YAAE,EAAE,EAAE,IAAI,CAAA;SAAE,CAAC,CAAC;KAC9D,CAAC;IAEF,QAAQ,CAAC,QAAQ,EAAE;QACjB,GAAG,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;QACpC,IAAI,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACtC,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;YAAE,EAAE,EAAE,IAAI,CAAA;SAAE,CAAC,CAAC;QACrD,YAAY,EAAE,MAAM,OAAO,CAAC,sBAAsB,CAAC,CAAC;KACrD,CAAC;IAEF,QAAQ,CAAC,QAAQ,EAAE;QACjB,MAAM,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC;YAAE,EAAE,EAAE,IAAI,CAAA;SAAE,CAAC,CAAC;KAC/D,CAAC;IAEF,QAAQ,CAAC,KAAK,EAAE;QACd,EAAE,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;QAChC,MAAM,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QAC1D,MAAM,EAAE,MAAM,OAAO,CAAC;YAAE,EAAE,EAAE,IAAI,CAAA;SAAE,CAAC,CAAC;KACrC,CAAC;IAEF,2EAA2E;IAC3E,QAAQ,IAAI,MAAM,GAAG,SAAS;IAG9B,iFAAiF;IAC3E,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;gBAKlD,OAAO,GAAE,mBAAwB;CAiK9C;AAGD,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
package/dist/client.js
CHANGED
|
@@ -12,6 +12,9 @@ function createCookieJar() {
|
|
|
12
12
|
header() {
|
|
13
13
|
return jar ? { Cookie: jar } : {};
|
|
14
14
|
},
|
|
15
|
+
clear() {
|
|
16
|
+
jar = undefined;
|
|
17
|
+
},
|
|
15
18
|
};
|
|
16
19
|
}
|
|
17
20
|
/**
|
|
@@ -26,21 +29,51 @@ function createCookieJar() {
|
|
|
26
29
|
export class SlyxupClient {
|
|
27
30
|
publishableKey;
|
|
28
31
|
apiUrl;
|
|
32
|
+
_getToken;
|
|
33
|
+
_request;
|
|
29
34
|
auth;
|
|
30
35
|
sessions;
|
|
36
|
+
password;
|
|
31
37
|
users;
|
|
38
|
+
/** Get current session token (for custom project APIs that need Bearer) */
|
|
39
|
+
getToken() {
|
|
40
|
+
return this._getToken?.();
|
|
41
|
+
}
|
|
42
|
+
/** Raw request for custom endpoints (uses same auth headers + cookies as SDK) */
|
|
43
|
+
async request(path, init) {
|
|
44
|
+
if (!this._request)
|
|
45
|
+
throw new Error('Client not initialized');
|
|
46
|
+
return this._request(path, init);
|
|
47
|
+
}
|
|
32
48
|
constructor(options = {}) {
|
|
33
49
|
const jar = createCookieJar();
|
|
50
|
+
let storedToken;
|
|
34
51
|
this.publishableKey = options.publishableKey;
|
|
35
52
|
this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, '');
|
|
36
|
-
|
|
53
|
+
this._getToken = () => storedToken;
|
|
54
|
+
// _request will be assigned after `request` is defined below
|
|
55
|
+
const requestInner = async (path, init = {}) => {
|
|
37
56
|
let res;
|
|
38
57
|
try {
|
|
58
|
+
// Build headers: prefer cookie jar (SSR), fall back to stored Bearer token (browser cross-origin)
|
|
59
|
+
const authHeaders = {};
|
|
60
|
+
const jarHeader = jar.header();
|
|
61
|
+
if (Object.keys(jarHeader).length > 0) {
|
|
62
|
+
Object.assign(authHeaders, jarHeader);
|
|
63
|
+
}
|
|
64
|
+
else if (storedToken) {
|
|
65
|
+
authHeaders.Authorization = `Bearer ${storedToken}`;
|
|
66
|
+
}
|
|
67
|
+
// Always send publishable key so server can scope auth to the correct project
|
|
68
|
+
// and reject requests with invalid / missing keys (fixes demo-with-wrong-pk bug).
|
|
69
|
+
if (this.publishableKey && this.publishableKey !== 'pk_test_missing') {
|
|
70
|
+
authHeaders['X-Publishable-Key'] = this.publishableKey;
|
|
71
|
+
}
|
|
39
72
|
res = await fetch(`${this.apiUrl}${path}`, {
|
|
40
73
|
...init,
|
|
41
74
|
headers: {
|
|
42
75
|
'Content-Type': 'application/json',
|
|
43
|
-
...
|
|
76
|
+
...authHeaders,
|
|
44
77
|
...init.headers,
|
|
45
78
|
},
|
|
46
79
|
credentials: 'include',
|
|
@@ -64,7 +97,7 @@ export class SlyxupClient {
|
|
|
64
97
|
}
|
|
65
98
|
return data;
|
|
66
99
|
};
|
|
67
|
-
const post = (path, body) =>
|
|
100
|
+
const post = (path, body) => requestInner(path, {
|
|
68
101
|
method: 'POST',
|
|
69
102
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
70
103
|
});
|
|
@@ -73,36 +106,78 @@ export class SlyxupClient {
|
|
|
73
106
|
const res = await post('/v1/auth/sign-up', input);
|
|
74
107
|
if (!('user' in res))
|
|
75
108
|
throw new SlyxupError(res.error, 400, 'api_error');
|
|
109
|
+
if (res.sessionToken)
|
|
110
|
+
storedToken = res.sessionToken;
|
|
76
111
|
return res;
|
|
77
112
|
},
|
|
78
113
|
signIn: async (input) => {
|
|
79
114
|
const res = await post('/v1/auth/sign-in', input);
|
|
80
115
|
if (!('user' in res))
|
|
81
116
|
throw new SlyxupError(res.error, 401, 'api_error');
|
|
117
|
+
if (res.sessionToken)
|
|
118
|
+
storedToken = res.sessionToken;
|
|
82
119
|
return res;
|
|
83
120
|
},
|
|
84
121
|
signOut: async () => {
|
|
85
122
|
const res = await post('/v1/auth/sign-out');
|
|
123
|
+
storedToken = undefined;
|
|
124
|
+
jar.clear();
|
|
125
|
+
return res;
|
|
126
|
+
},
|
|
127
|
+
resendVerification: async (email) => {
|
|
128
|
+
const res = await post('/v1/verification/resend', {
|
|
129
|
+
email,
|
|
130
|
+
});
|
|
131
|
+
if (!res.ok)
|
|
132
|
+
throw new SlyxupError(res.error, 400, 'api_error');
|
|
86
133
|
return res;
|
|
87
134
|
},
|
|
88
135
|
};
|
|
89
136
|
this.sessions = {
|
|
90
137
|
get: async () => {
|
|
91
|
-
const res = await
|
|
138
|
+
const res = await requestInner('/v1/session');
|
|
92
139
|
if (!('session' in res))
|
|
93
140
|
throw new UnauthorizedError(res.error);
|
|
94
141
|
return res;
|
|
95
142
|
},
|
|
143
|
+
list: async () => {
|
|
144
|
+
const res = await requestInner('/v1/sessions');
|
|
145
|
+
if (!('sessions' in res))
|
|
146
|
+
throw new SlyxupError(res.error, 400, 'api_error');
|
|
147
|
+
return res;
|
|
148
|
+
},
|
|
149
|
+
revoke: async (sessionId) => {
|
|
150
|
+
const res = await requestInner(`/v1/sessions/${sessionId}`, {
|
|
151
|
+
method: 'DELETE',
|
|
152
|
+
});
|
|
153
|
+
return res;
|
|
154
|
+
},
|
|
155
|
+
revokeOthers: async () => {
|
|
156
|
+
const res = await requestInner('/v1/sessions', {
|
|
157
|
+
method: 'DELETE',
|
|
158
|
+
});
|
|
159
|
+
if (!('revoked' in res))
|
|
160
|
+
throw new SlyxupError(res.error, 400, 'api_error');
|
|
161
|
+
return res;
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
this.password = {
|
|
165
|
+
change: async (input) => {
|
|
166
|
+
const res = await post('/v1/user/password', input);
|
|
167
|
+
if (!res.ok)
|
|
168
|
+
throw new SlyxupError(res.error, 400, 'api_error');
|
|
169
|
+
return res;
|
|
170
|
+
},
|
|
96
171
|
};
|
|
97
172
|
this.users = {
|
|
98
173
|
me: async () => {
|
|
99
|
-
const res = await
|
|
174
|
+
const res = await requestInner('/v1/user');
|
|
100
175
|
if (!('user' in res))
|
|
101
176
|
throw new UnauthorizedError(res.error);
|
|
102
177
|
return res;
|
|
103
178
|
},
|
|
104
179
|
update: async (input) => {
|
|
105
|
-
const res = await
|
|
180
|
+
const res = await requestInner('/v1/user', {
|
|
106
181
|
method: 'PATCH',
|
|
107
182
|
body: JSON.stringify(input),
|
|
108
183
|
});
|
|
@@ -111,15 +186,18 @@ export class SlyxupClient {
|
|
|
111
186
|
return res;
|
|
112
187
|
},
|
|
113
188
|
delete: async () => {
|
|
114
|
-
const res = await
|
|
189
|
+
const res = await requestInner('/v1/user', {
|
|
115
190
|
method: 'DELETE',
|
|
116
191
|
});
|
|
117
192
|
return res;
|
|
118
193
|
},
|
|
119
194
|
};
|
|
195
|
+
this._request = requestInner;
|
|
120
196
|
}
|
|
121
197
|
}
|
|
122
198
|
// Re-exports so consumers can import from '@slyxup/core'
|
|
123
199
|
export * from './types.js';
|
|
124
200
|
export * from './errors.js';
|
|
201
|
+
// auto-version test 2026-08-27T10:01:57Z
|
|
202
|
+
// ensure auto-version works for packages
|
|
125
203
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,cAAc,EACd,WAAW,EACX,iBAAiB,GAClB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -35,6 +35,27 @@ export interface SessionResponse {
|
|
|
35
35
|
user: Pick<SlyxupUser, 'id' | 'email'>;
|
|
36
36
|
session: Pick<SlyxupSession, 'id' | 'expiresAt'>;
|
|
37
37
|
}
|
|
38
|
+
export interface ChangePasswordInput {
|
|
39
|
+
currentPassword: string;
|
|
40
|
+
newPassword: string;
|
|
41
|
+
}
|
|
42
|
+
export interface SlyxupSessionInfo {
|
|
43
|
+
id: string;
|
|
44
|
+
ipAddress: string | null;
|
|
45
|
+
userAgent: string | null;
|
|
46
|
+
expiresAt: string;
|
|
47
|
+
createdAt: string;
|
|
48
|
+
/** true when this session belongs to the caller's current cookie */
|
|
49
|
+
isCurrent: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface SessionsResponse {
|
|
52
|
+
ok: true;
|
|
53
|
+
sessions: SlyxupSessionInfo[];
|
|
54
|
+
}
|
|
55
|
+
export interface RevokeSessionsResponse {
|
|
56
|
+
ok: true;
|
|
57
|
+
revoked: number;
|
|
58
|
+
}
|
|
38
59
|
export interface UserResponse {
|
|
39
60
|
ok: true;
|
|
40
61
|
user: SlyxupUser;
|
|
@@ -42,6 +63,8 @@ export interface UserResponse {
|
|
|
42
63
|
export interface AuthResponse {
|
|
43
64
|
ok: true;
|
|
44
65
|
user: Pick<SlyxupUser, 'id' | 'email'>;
|
|
66
|
+
sessionToken?: string;
|
|
67
|
+
expiresAt?: string;
|
|
45
68
|
}
|
|
46
69
|
export interface ErrorResponse {
|
|
47
70
|
ok: false;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;IACvC,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,WAAW,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;IACvC,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,WAAW,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC;AAE1C,MAAM,WAAW,mBAAmB;IAClC,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slyxup/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "@slyxup/core — SlyxUp SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"build": "tsc -p tsconfig.json",
|
|
10
|
-
"typecheck": "tsc --noEmit"
|
|
11
|
-
},
|
|
12
8
|
"repository": {
|
|
13
9
|
"type": "git",
|
|
14
10
|
"url": "git+https://github.com/slyxup/stack.git",
|
|
@@ -22,5 +18,13 @@
|
|
|
22
18
|
"license": "MIT",
|
|
23
19
|
"publishConfig": {
|
|
24
20
|
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"vitest": "^4.1.11"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"test": "vitest run --passWithNoTests"
|
|
25
29
|
}
|
|
26
|
-
}
|
|
30
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -6,9 +6,12 @@ import {
|
|
|
6
6
|
} from './errors.js';
|
|
7
7
|
import type {
|
|
8
8
|
AuthResponse,
|
|
9
|
+
ChangePasswordInput,
|
|
9
10
|
ErrorResponse,
|
|
10
11
|
Result,
|
|
12
|
+
RevokeSessionsResponse,
|
|
11
13
|
SessionResponse,
|
|
14
|
+
SessionsResponse,
|
|
12
15
|
SignInInput,
|
|
13
16
|
SignUpInput,
|
|
14
17
|
SlyxupClientOptions,
|
|
@@ -29,6 +32,9 @@ function createCookieJar() {
|
|
|
29
32
|
header(): Record<string, string> {
|
|
30
33
|
return jar ? { Cookie: jar } : {};
|
|
31
34
|
},
|
|
35
|
+
clear() {
|
|
36
|
+
jar = undefined;
|
|
37
|
+
},
|
|
32
38
|
};
|
|
33
39
|
}
|
|
34
40
|
|
|
@@ -44,15 +50,25 @@ function createCookieJar() {
|
|
|
44
50
|
export class SlyxupClient {
|
|
45
51
|
readonly publishableKey?: string;
|
|
46
52
|
readonly apiUrl: string;
|
|
53
|
+
private _getToken?: () => string | undefined;
|
|
54
|
+
private _request?: <T>(path: string, init?: RequestInit) => Promise<T>;
|
|
47
55
|
|
|
48
56
|
readonly auth: {
|
|
49
57
|
signUp: (input: SignUpInput) => Promise<AuthResponse>;
|
|
50
58
|
signIn: (input: SignInInput) => Promise<AuthResponse>;
|
|
51
59
|
signOut: () => Promise<{ ok: true }>;
|
|
60
|
+
resendVerification: (email: string) => Promise<{ ok: true }>;
|
|
52
61
|
};
|
|
53
62
|
|
|
54
63
|
readonly sessions: {
|
|
55
64
|
get: () => Promise<SessionResponse>;
|
|
65
|
+
list: () => Promise<SessionsResponse>;
|
|
66
|
+
revoke: (sessionId: string) => Promise<{ ok: true }>;
|
|
67
|
+
revokeOthers: () => Promise<RevokeSessionsResponse>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
readonly password: {
|
|
71
|
+
change: (input: ChangePasswordInput) => Promise<{ ok: true }>;
|
|
56
72
|
};
|
|
57
73
|
|
|
58
74
|
readonly users: {
|
|
@@ -61,22 +77,49 @@ export class SlyxupClient {
|
|
|
61
77
|
delete: () => Promise<{ ok: true }>;
|
|
62
78
|
};
|
|
63
79
|
|
|
80
|
+
/** Get current session token (for custom project APIs that need Bearer) */
|
|
81
|
+
getToken(): string | undefined {
|
|
82
|
+
return this._getToken?.();
|
|
83
|
+
}
|
|
84
|
+
/** Raw request for custom endpoints (uses same auth headers + cookies as SDK) */
|
|
85
|
+
async request<T>(path: string, init?: RequestInit): Promise<T> {
|
|
86
|
+
if (!this._request) throw new Error('Client not initialized');
|
|
87
|
+
return this._request<T>(path, init);
|
|
88
|
+
}
|
|
89
|
+
|
|
64
90
|
constructor(options: SlyxupClientOptions = {}) {
|
|
65
91
|
const jar = createCookieJar();
|
|
92
|
+
let storedToken: string | undefined;
|
|
66
93
|
this.publishableKey = options.publishableKey;
|
|
67
94
|
this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, '');
|
|
95
|
+
this._getToken = () => storedToken;
|
|
96
|
+
// _request will be assigned after `request` is defined below
|
|
68
97
|
|
|
69
|
-
const
|
|
98
|
+
const requestInner = async <T>(
|
|
70
99
|
path: string,
|
|
71
100
|
init: RequestInit & { body?: string } = {}
|
|
72
101
|
): Promise<Result<T>> => {
|
|
73
102
|
let res: Response;
|
|
74
103
|
try {
|
|
104
|
+
// Build headers: prefer cookie jar (SSR), fall back to stored Bearer token (browser cross-origin)
|
|
105
|
+
const authHeaders: Record<string, string> = {};
|
|
106
|
+
const jarHeader = jar.header();
|
|
107
|
+
if (Object.keys(jarHeader).length > 0) {
|
|
108
|
+
Object.assign(authHeaders, jarHeader);
|
|
109
|
+
} else if (storedToken) {
|
|
110
|
+
authHeaders.Authorization = `Bearer ${storedToken}`;
|
|
111
|
+
}
|
|
112
|
+
// Always send publishable key so server can scope auth to the correct project
|
|
113
|
+
// and reject requests with invalid / missing keys (fixes demo-with-wrong-pk bug).
|
|
114
|
+
if (this.publishableKey && this.publishableKey !== 'pk_test_missing') {
|
|
115
|
+
authHeaders['X-Publishable-Key'] = this.publishableKey;
|
|
116
|
+
}
|
|
117
|
+
|
|
75
118
|
res = await fetch(`${this.apiUrl}${path}`, {
|
|
76
119
|
...init,
|
|
77
120
|
headers: {
|
|
78
121
|
'Content-Type': 'application/json',
|
|
79
|
-
...
|
|
122
|
+
...authHeaders,
|
|
80
123
|
...init.headers,
|
|
81
124
|
},
|
|
82
125
|
credentials: 'include',
|
|
@@ -109,7 +152,7 @@ export class SlyxupClient {
|
|
|
109
152
|
};
|
|
110
153
|
|
|
111
154
|
const post = <T>(path: string, body?: unknown) =>
|
|
112
|
-
|
|
155
|
+
requestInner<T>(path, {
|
|
113
156
|
method: 'POST',
|
|
114
157
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
115
158
|
});
|
|
@@ -119,36 +162,75 @@ export class SlyxupClient {
|
|
|
119
162
|
const res = await post<AuthResponse>('/v1/auth/sign-up', input);
|
|
120
163
|
if (!('user' in res))
|
|
121
164
|
throw new SlyxupError(res.error, 400, 'api_error');
|
|
165
|
+
if (res.sessionToken) storedToken = res.sessionToken;
|
|
122
166
|
return res;
|
|
123
167
|
},
|
|
124
168
|
signIn: async (input) => {
|
|
125
169
|
const res = await post<AuthResponse>('/v1/auth/sign-in', input);
|
|
126
170
|
if (!('user' in res))
|
|
127
171
|
throw new SlyxupError(res.error, 401, 'api_error');
|
|
172
|
+
if (res.sessionToken) storedToken = res.sessionToken;
|
|
128
173
|
return res;
|
|
129
174
|
},
|
|
130
175
|
signOut: async () => {
|
|
131
176
|
const res = await post<{ ok: true }>('/v1/auth/sign-out');
|
|
177
|
+
storedToken = undefined;
|
|
178
|
+
jar.clear();
|
|
179
|
+
return res as { ok: true };
|
|
180
|
+
},
|
|
181
|
+
resendVerification: async (email: string) => {
|
|
182
|
+
const res = await post<{ ok: true }>('/v1/verification/resend', {
|
|
183
|
+
email,
|
|
184
|
+
});
|
|
185
|
+
if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
|
|
132
186
|
return res as { ok: true };
|
|
133
187
|
},
|
|
134
188
|
};
|
|
135
189
|
|
|
136
190
|
this.sessions = {
|
|
137
191
|
get: async () => {
|
|
138
|
-
const res = await
|
|
192
|
+
const res = await requestInner<SessionResponse>('/v1/session');
|
|
139
193
|
if (!('session' in res)) throw new UnauthorizedError(res.error);
|
|
140
194
|
return res;
|
|
141
195
|
},
|
|
196
|
+
list: async () => {
|
|
197
|
+
const res = await requestInner<SessionsResponse>('/v1/sessions');
|
|
198
|
+
if (!('sessions' in res))
|
|
199
|
+
throw new SlyxupError(res.error, 400, 'api_error');
|
|
200
|
+
return res;
|
|
201
|
+
},
|
|
202
|
+
revoke: async (sessionId: string) => {
|
|
203
|
+
const res = await requestInner<{ ok: true }>(`/v1/sessions/${sessionId}`, {
|
|
204
|
+
method: 'DELETE',
|
|
205
|
+
});
|
|
206
|
+
return res as { ok: true };
|
|
207
|
+
},
|
|
208
|
+
revokeOthers: async () => {
|
|
209
|
+
const res = await requestInner<RevokeSessionsResponse>('/v1/sessions', {
|
|
210
|
+
method: 'DELETE',
|
|
211
|
+
});
|
|
212
|
+
if (!('revoked' in res))
|
|
213
|
+
throw new SlyxupError(res.error, 400, 'api_error');
|
|
214
|
+
return res;
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
this.password = {
|
|
219
|
+
change: async (input) => {
|
|
220
|
+
const res = await post<{ ok: true }>('/v1/user/password', input);
|
|
221
|
+
if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
|
|
222
|
+
return res as { ok: true };
|
|
223
|
+
},
|
|
142
224
|
};
|
|
143
225
|
|
|
144
226
|
this.users = {
|
|
145
227
|
me: async () => {
|
|
146
|
-
const res = await
|
|
228
|
+
const res = await requestInner<UserResponse>('/v1/user');
|
|
147
229
|
if (!('user' in res)) throw new UnauthorizedError(res.error);
|
|
148
230
|
return res;
|
|
149
231
|
},
|
|
150
232
|
update: async (input) => {
|
|
151
|
-
const res = await
|
|
233
|
+
const res = await requestInner<UserResponse>('/v1/user', {
|
|
152
234
|
method: 'PATCH',
|
|
153
235
|
body: JSON.stringify(input),
|
|
154
236
|
});
|
|
@@ -157,15 +239,19 @@ export class SlyxupClient {
|
|
|
157
239
|
return res;
|
|
158
240
|
},
|
|
159
241
|
delete: async () => {
|
|
160
|
-
const res = await
|
|
242
|
+
const res = await requestInner<{ ok: true }>('/v1/user', {
|
|
161
243
|
method: 'DELETE',
|
|
162
244
|
});
|
|
163
245
|
return res as { ok: true };
|
|
164
246
|
},
|
|
165
247
|
};
|
|
248
|
+
|
|
249
|
+
this._request = requestInner as unknown as <T>(path: string, init?: RequestInit) => Promise<T>;
|
|
166
250
|
}
|
|
167
251
|
}
|
|
168
252
|
|
|
169
253
|
// Re-exports so consumers can import from '@slyxup/core'
|
|
170
254
|
export * from './types.js';
|
|
171
255
|
export * from './errors.js';
|
|
256
|
+
// auto-version test 2026-08-27T10:01:57Z
|
|
257
|
+
// ensure auto-version works for packages
|
package/src/types.ts
CHANGED
|
@@ -43,6 +43,31 @@ export interface SessionResponse {
|
|
|
43
43
|
session: Pick<SlyxupSession, 'id' | 'expiresAt'>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export interface ChangePasswordInput {
|
|
47
|
+
currentPassword: string;
|
|
48
|
+
newPassword: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SlyxupSessionInfo {
|
|
52
|
+
id: string;
|
|
53
|
+
ipAddress: string | null;
|
|
54
|
+
userAgent: string | null;
|
|
55
|
+
expiresAt: string;
|
|
56
|
+
createdAt: string;
|
|
57
|
+
/** true when this session belongs to the caller's current cookie */
|
|
58
|
+
isCurrent: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface SessionsResponse {
|
|
62
|
+
ok: true;
|
|
63
|
+
sessions: SlyxupSessionInfo[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RevokeSessionsResponse {
|
|
67
|
+
ok: true;
|
|
68
|
+
revoked: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
46
71
|
export interface UserResponse {
|
|
47
72
|
ok: true;
|
|
48
73
|
user: SlyxupUser;
|
|
@@ -51,6 +76,8 @@ export interface UserResponse {
|
|
|
51
76
|
export interface AuthResponse {
|
|
52
77
|
ok: true;
|
|
53
78
|
user: Pick<SlyxupUser, 'id' | 'email'>;
|
|
79
|
+
sessionToken?: string;
|
|
80
|
+
expiresAt?: string;
|
|
54
81
|
}
|
|
55
82
|
|
|
56
83
|
export interface ErrorResponse {
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { SlyxupClient } from '../src/client.js';
|
|
3
|
+
import { SlyxupError, UnauthorizedError, NetworkError, RateLimitError } from '../src/errors.js';
|
|
4
|
+
|
|
5
|
+
function mockFetch(
|
|
6
|
+
data: unknown,
|
|
7
|
+
init?: { ok?: boolean; status?: number; headers?: Record<string, string> }
|
|
8
|
+
) {
|
|
9
|
+
const fn = vi.fn().mockResolvedValue({
|
|
10
|
+
ok: init?.ok ?? true,
|
|
11
|
+
status: init?.status ?? 200,
|
|
12
|
+
headers: {
|
|
13
|
+
get: (key: string) => {
|
|
14
|
+
const lower = key.toLowerCase();
|
|
15
|
+
for (const [k, v] of Object.entries(init?.headers ?? {})) {
|
|
16
|
+
if (k.toLowerCase() === lower) return v;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
json: () => Promise.resolve(data),
|
|
22
|
+
});
|
|
23
|
+
global.fetch = fn;
|
|
24
|
+
return fn;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('SlyxupClient', () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
vi.clearAllMocks();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('constructor', () => {
|
|
33
|
+
it('should use default apiUrl', () => {
|
|
34
|
+
mockFetch({ ok: true });
|
|
35
|
+
const client = new SlyxupClient();
|
|
36
|
+
expect(client.apiUrl).toBe('https://auth.slyxup.online');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should strip trailing slash from apiUrl', () => {
|
|
40
|
+
mockFetch({ ok: true });
|
|
41
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost:3000/' });
|
|
42
|
+
expect(client.apiUrl).toBe('http://localhost:3000');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should store publishableKey', () => {
|
|
46
|
+
mockFetch({ ok: true });
|
|
47
|
+
const client = new SlyxupClient({ publishableKey: 'pk_test_123' });
|
|
48
|
+
expect(client.publishableKey).toBe('pk_test_123');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('auth.signUp', () => {
|
|
53
|
+
it('should POST to /v1/auth/sign-up with JSON body', async () => {
|
|
54
|
+
const fn = mockFetch({ ok: true, user: { id: 'u1', email: 'a@b.com' } });
|
|
55
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
56
|
+
const res = await client.auth.signUp({
|
|
57
|
+
email: 'a@b.com',
|
|
58
|
+
password: '12345678',
|
|
59
|
+
firstName: 'Test',
|
|
60
|
+
});
|
|
61
|
+
expect(fn).toHaveBeenCalledWith(
|
|
62
|
+
'http://localhost/v1/auth/sign-up',
|
|
63
|
+
expect.objectContaining({
|
|
64
|
+
method: 'POST',
|
|
65
|
+
body: JSON.stringify({ email: 'a@b.com', password: '12345678', firstName: 'Test' }),
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
expect(res).toEqual({ ok: true, user: { id: 'u1', email: 'a@b.com' } });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should throw SlyxupError when response has no user', async () => {
|
|
72
|
+
mockFetch({ ok: true, error: 'Invalid email' });
|
|
73
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
74
|
+
await expect(
|
|
75
|
+
client.auth.signUp({ email: 'bad', password: '12345678' })
|
|
76
|
+
).rejects.toThrow(SlyxupError);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('auth.signIn', () => {
|
|
81
|
+
it('should POST to /v1/auth/sign-in', async () => {
|
|
82
|
+
const fn = mockFetch({ ok: true, user: { id: 'u1', email: 'a@b.com' } });
|
|
83
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
84
|
+
const res = await client.auth.signIn({ email: 'a@b.com', password: 'pass' });
|
|
85
|
+
expect(fn).toHaveBeenCalledWith(
|
|
86
|
+
'http://localhost/v1/auth/sign-in',
|
|
87
|
+
expect.objectContaining({ method: 'POST' })
|
|
88
|
+
);
|
|
89
|
+
expect(res).toEqual({ ok: true, user: { id: 'u1', email: 'a@b.com' } });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should throw UnauthorizedError on 401', async () => {
|
|
93
|
+
mockFetch({ ok: false, error: 'Bad credentials' }, { ok: false, status: 401 });
|
|
94
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
95
|
+
await expect(
|
|
96
|
+
client.auth.signIn({ email: 'a@b.com', password: 'wrong' })
|
|
97
|
+
).rejects.toThrow(UnauthorizedError);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should throw SlyxupError when response has no user', async () => {
|
|
101
|
+
mockFetch({ ok: true, error: 'No user' });
|
|
102
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
103
|
+
await expect(
|
|
104
|
+
client.auth.signIn({ email: 'a@b.com', password: 'pass' })
|
|
105
|
+
).rejects.toThrow(SlyxupError);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('auth.signOut', () => {
|
|
110
|
+
it('should POST to /v1/auth/sign-out', async () => {
|
|
111
|
+
const fn = mockFetch({ ok: true });
|
|
112
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
113
|
+
const res = await client.auth.signOut();
|
|
114
|
+
expect(fn).toHaveBeenCalledWith(
|
|
115
|
+
'http://localhost/v1/auth/sign-out',
|
|
116
|
+
expect.objectContaining({ method: 'POST' })
|
|
117
|
+
);
|
|
118
|
+
expect(res).toEqual({ ok: true });
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('sessions.get', () => {
|
|
123
|
+
it('should GET /v1/session', async () => {
|
|
124
|
+
const data = { ok: true, user: { id: 'u1', email: 'a@b.com' }, session: { id: 's1', expiresAt: '2030' } };
|
|
125
|
+
const fn = mockFetch(data);
|
|
126
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
127
|
+
const res = await client.sessions.get();
|
|
128
|
+
expect(fn).toHaveBeenCalledWith('http://localhost/v1/session', expect.anything());
|
|
129
|
+
expect(res).toEqual(data);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should throw UnauthorizedError when no session in response', async () => {
|
|
133
|
+
mockFetch({ ok: true, error: 'No session' });
|
|
134
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
135
|
+
await expect(client.sessions.get()).rejects.toThrow(UnauthorizedError);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should throw NetworkError when fetch rejects', async () => {
|
|
139
|
+
global.fetch = vi.fn().mockRejectedValue(new Error('fetch failed'));
|
|
140
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
141
|
+
await expect(client.sessions.get()).rejects.toThrow(NetworkError);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('users.me', () => {
|
|
146
|
+
it('should GET /v1/user', async () => {
|
|
147
|
+
const user = { id: 'u1', email: 'a@b.com', firstName: 'Test', lastName: 'User', createdAt: '', updatedAt: '', projectId: null, emailVerified: true, avatarUrl: null, preferences: null };
|
|
148
|
+
const fn = mockFetch({ ok: true, user });
|
|
149
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
150
|
+
const res = await client.users.me();
|
|
151
|
+
expect(fn).toHaveBeenCalledWith('http://localhost/v1/user', expect.anything());
|
|
152
|
+
expect(res.user).toEqual(user);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should throw UnauthorizedError when no user', async () => {
|
|
156
|
+
mockFetch({ ok: true, error: 'Not found' });
|
|
157
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
158
|
+
await expect(client.users.me()).rejects.toThrow(UnauthorizedError);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('users.update', () => {
|
|
163
|
+
it('should PATCH /v1/user with JSON body', async () => {
|
|
164
|
+
const fn = mockFetch({ ok: true, user: { id: 'u1', firstName: 'Jane' } });
|
|
165
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
166
|
+
await client.users.update({ firstName: 'Jane' });
|
|
167
|
+
expect(fn).toHaveBeenCalledWith(
|
|
168
|
+
'http://localhost/v1/user',
|
|
169
|
+
expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ firstName: 'Jane' }) })
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('users.delete', () => {
|
|
175
|
+
it('should DELETE /v1/user', async () => {
|
|
176
|
+
const fn = mockFetch({ ok: true });
|
|
177
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
178
|
+
const res = await client.users.delete();
|
|
179
|
+
expect(fn).toHaveBeenCalledWith('http://localhost/v1/user', expect.objectContaining({ method: 'DELETE' }));
|
|
180
|
+
expect(res).toEqual({ ok: true });
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('cookie jar', () => {
|
|
185
|
+
it('should capture Set-Cookie and forward as Cookie header', async () => {
|
|
186
|
+
const fn = vi.fn()
|
|
187
|
+
.mockResolvedValueOnce({
|
|
188
|
+
ok: true,
|
|
189
|
+
status: 200,
|
|
190
|
+
headers: {
|
|
191
|
+
get: (key: string) => key.toLowerCase() === 'set-cookie' ? 'slyxup_session=abc123; Path=/; HttpOnly' : null,
|
|
192
|
+
},
|
|
193
|
+
json: () => Promise.resolve({ ok: true, user: { id: 'u1', email: 'a@b.com' } }),
|
|
194
|
+
})
|
|
195
|
+
.mockResolvedValueOnce({
|
|
196
|
+
ok: true,
|
|
197
|
+
status: 200,
|
|
198
|
+
headers: { get: () => null },
|
|
199
|
+
json: () => Promise.resolve({ ok: true, user: { id: 'u1', email: 'a@b.com' }, session: { id: 's1', expiresAt: '2030' } }),
|
|
200
|
+
});
|
|
201
|
+
global.fetch = fn;
|
|
202
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
203
|
+
await client.auth.signIn({ email: 'a@b.com', password: 'pass' });
|
|
204
|
+
await client.sessions.get();
|
|
205
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
206
|
+
const secondCall = fn.mock.calls[1] as any[];
|
|
207
|
+
expect(secondCall[1].headers).toMatchObject({ Cookie: 'slyxup_session=abc123' });
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('error handling', () => {
|
|
212
|
+
it('should throw RateLimitError on 429', async () => {
|
|
213
|
+
mockFetch({ error: 'rate limited' }, { ok: false, status: 429 });
|
|
214
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
215
|
+
await expect(client.sessions.get()).rejects.toThrow(RateLimitError);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should throw NetworkError when fetch rejects', async () => {
|
|
219
|
+
global.fetch = vi.fn().mockRejectedValue(new TypeError('Failed to fetch'));
|
|
220
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
221
|
+
await expect(client.auth.signIn({ email: 'a', password: 'b' })).rejects.toThrow(NetworkError);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should throw SlyxupError on 500 with error message', async () => {
|
|
225
|
+
mockFetch({ error: 'Server error' }, { ok: false, status: 500 });
|
|
226
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
227
|
+
try {
|
|
228
|
+
await client.sessions.get();
|
|
229
|
+
expect.fail('Should have thrown');
|
|
230
|
+
} catch (e) {
|
|
231
|
+
expect(e).toBeInstanceOf(SlyxupError);
|
|
232
|
+
expect((e as SlyxupError).status).toBe(500);
|
|
233
|
+
expect((e as SlyxupError).code).toBe('api_error');
|
|
234
|
+
expect((e as SlyxupError).message).toBe('Server error');
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should handle invalid JSON gracefully', async () => {
|
|
239
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
240
|
+
ok: true,
|
|
241
|
+
status: 200,
|
|
242
|
+
headers: { get: () => null },
|
|
243
|
+
json: () => Promise.reject(new Error('not json')),
|
|
244
|
+
});
|
|
245
|
+
const client = new SlyxupClient({ apiUrl: 'http://localhost' });
|
|
246
|
+
await expect(client.sessions.get()).rejects.toThrow(UnauthorizedError);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
SlyxupError,
|
|
4
|
+
UnauthorizedError,
|
|
5
|
+
ValidationError,
|
|
6
|
+
NetworkError,
|
|
7
|
+
RateLimitError,
|
|
8
|
+
} from '../src/errors.js';
|
|
9
|
+
|
|
10
|
+
describe('SlyxupError', () => {
|
|
11
|
+
it('should set message, status, code, and name', () => {
|
|
12
|
+
const err = new SlyxupError('test', 500, 'test_error');
|
|
13
|
+
expect(err.message).toBe('test');
|
|
14
|
+
expect(err.status).toBe(500);
|
|
15
|
+
expect(err.code).toBe('test_error');
|
|
16
|
+
expect(err.name).toBe('SlyxupError');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should be instanceof Error', () => {
|
|
20
|
+
const err = new SlyxupError('test', 400, 'err');
|
|
21
|
+
expect(err).toBeInstanceOf(Error);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should be instanceof SlyxupError', () => {
|
|
25
|
+
const err = new SlyxupError('test', 400, 'err');
|
|
26
|
+
expect(err).toBeInstanceOf(SlyxupError);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('UnauthorizedError', () => {
|
|
31
|
+
it('should have default values', () => {
|
|
32
|
+
const err = new UnauthorizedError();
|
|
33
|
+
expect(err.message).toBe('Unauthorized');
|
|
34
|
+
expect(err.status).toBe(401);
|
|
35
|
+
expect(err.code).toBe('unauthorized');
|
|
36
|
+
expect(err.name).toBe('UnauthorizedError');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should accept custom message', () => {
|
|
40
|
+
const err = new UnauthorizedError('Custom');
|
|
41
|
+
expect(err.message).toBe('Custom');
|
|
42
|
+
expect(err.status).toBe(401);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should be instanceof SlyxupError', () => {
|
|
46
|
+
expect(new UnauthorizedError()).toBeInstanceOf(SlyxupError);
|
|
47
|
+
expect(new UnauthorizedError()).toBeInstanceOf(Error);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('ValidationError', () => {
|
|
52
|
+
it('should have default values', () => {
|
|
53
|
+
const err = new ValidationError();
|
|
54
|
+
expect(err.message).toBe('Validation failed');
|
|
55
|
+
expect(err.status).toBe(400);
|
|
56
|
+
expect(err.code).toBe('validation_error');
|
|
57
|
+
expect(err.name).toBe('ValidationError');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should accept custom message', () => {
|
|
61
|
+
const err = new ValidationError('Bad input');
|
|
62
|
+
expect(err.message).toBe('Bad input');
|
|
63
|
+
expect(err.status).toBe(400);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should be instanceof SlyxupError', () => {
|
|
67
|
+
expect(new ValidationError()).toBeInstanceOf(SlyxupError);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('NetworkError', () => {
|
|
72
|
+
it('should have default values', () => {
|
|
73
|
+
const err = new NetworkError();
|
|
74
|
+
expect(err.message).toBe('Network request failed');
|
|
75
|
+
expect(err.status).toBe(0);
|
|
76
|
+
expect(err.code).toBe('network_error');
|
|
77
|
+
expect(err.name).toBe('NetworkError');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should accept custom message', () => {
|
|
81
|
+
const err = new NetworkError('Timeout');
|
|
82
|
+
expect(err.message).toBe('Timeout');
|
|
83
|
+
expect(err.status).toBe(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should be instanceof SlyxupError', () => {
|
|
87
|
+
expect(new NetworkError()).toBeInstanceOf(SlyxupError);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('RateLimitError', () => {
|
|
92
|
+
it('should have default values', () => {
|
|
93
|
+
const err = new RateLimitError();
|
|
94
|
+
expect(err.message).toBe('Too many requests');
|
|
95
|
+
expect(err.status).toBe(429);
|
|
96
|
+
expect(err.code).toBe('rate_limited');
|
|
97
|
+
expect(err.name).toBe('RateLimitError');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should accept custom message', () => {
|
|
101
|
+
const err = new RateLimitError('Slow down');
|
|
102
|
+
expect(err.message).toBe('Slow down');
|
|
103
|
+
expect(err.status).toBe(429);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should be instanceof SlyxupError', () => {
|
|
107
|
+
expect(new RateLimitError()).toBeInstanceOf(SlyxupError);
|
|
108
|
+
});
|
|
109
|
+
});
|
package/vitest.config.ts
ADDED