@slyxup/core 0.1.0 → 0.2.1

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.
@@ -0,0 +1,4 @@
1
+
2
+ > @slyxup/core@0.2.1 build /home/runner/work/stack/stack/packages/core
3
+ > tsc -p tsconfig.json
4
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # @slyxup/core
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
+
9
+ ## 0.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`7cf24ff`](https://github.com/slyxup/stack/commit/7cf24ffe37fe6f2bca41fdc0281618c2db02b3ae) - Full V1 SDKs: SlyxupClient with cookie jar, React provider + hooks, Next.js server helpers/middleware, themed UI components, slyxup CLI
14
+
15
+ ### Patch Changes
16
+
17
+ - [`44da26b`](https://github.com/slyxup/stack/commit/44da26bd8f952b6fce4b67f75ddb0ef041a984ac) - ci: verify all packages CI/CD — little change in every package + auth/stack/billing
18
+
19
+ - [`fe4f8bb`](https://github.com/slyxup/stack/commit/fe4f8bb4ab412a58303f59921548d3625b0675c9) - ci: second verify all — 14:55
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 ADDED
@@ -0,0 +1,81 @@
1
+ # @slyxup/core
2
+
3
+ Core HTTP client for the SlyxUp Auth platform. Zero framework dependencies — works in browsers, Node.js, and edge runtimes.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @slyxup/core
9
+ # pnpm add @slyxup/core
10
+ ```
11
+
12
+ ## Quick start
13
+
14
+ ```ts
15
+ import { SlyxupClient } from '@slyxup/core';
16
+
17
+ const client = new SlyxupClient({
18
+ publishableKey: 'pk_test_xxx', // from `slyxup keys create`
19
+ apiUrl: 'https://auth.slyxup.online', // default; override for self-host/local
20
+ });
21
+
22
+ // ── Auth ──
23
+ await client.auth.signUp({ email: 'ada@example.com', password: 'password123', firstName: 'Ada' });
24
+ await client.auth.signIn({ email: 'ada@example.com', password: 'password123' });
25
+ await client.auth.signOut();
26
+
27
+ // ── Sessions ──
28
+ const { session, user } = await client.sessions.get();
29
+ // session.expiresAt — ISO date; throws UnauthorizedError (401) when signed out
30
+
31
+ // ── Users ──
32
+ const me = await client.users.me(); // full profile
33
+ await client.users.update({ firstName: 'Ada', lastName: 'Lovelace' });
34
+ await client.users.delete(); // GDPR delete
35
+ ```
36
+
37
+ The client keeps an internal cookie jar, so sign-in state persists across calls in **Node/SSR too** (browsers manage cookies natively).
38
+
39
+ ## Error handling
40
+
41
+ ```ts
42
+ import { SlyxupError, UnauthorizedError, RateLimitError, NetworkError, ValidationError } from '@slyxup/core';
43
+
44
+ try {
45
+ await client.auth.signIn({ email, password });
46
+ } catch (e) {
47
+ if (e instanceof UnauthorizedError) showInvalidCredentials();
48
+ else if (e instanceof RateLimitError) backOff();
49
+ else if (e instanceof SlyxupError) console.error(e.status, e.code, e.message);
50
+ }
51
+ ```
52
+
53
+ | Error | Status | Meaning |
54
+ |---|---|---|
55
+ | `ValidationError` | 400 | Bad input |
56
+ | `UnauthorizedError` | 401 | No/expired session |
57
+ | `RateLimitError` | 429 | Too many requests |
58
+ | `NetworkError` | 0 | Fetch failed |
59
+ | `SlyxupError` | * | Base class (`e.status`, `e.code`) |
60
+
61
+ ## API surface
62
+
63
+ | Namespace | Method | Endpoint |
64
+ |---|---|---|
65
+ | `auth.signUp(input)` | POST | `/v1/auth/sign-up` |
66
+ | `auth.signIn(input)` | POST | `/v1/auth/sign-in` |
67
+ | `auth.signOut()` | POST | `/v1/auth/sign-out` |
68
+ | `sessions.get()` | GET | `/v1/session` |
69
+ | `users.me()` | GET | `/v1/user` |
70
+ | `users.update(patch)` | PATCH | `/v1/user` |
71
+ | `users.delete()` | DELETE | `/v1/user` |
72
+
73
+ ## Framework wrappers
74
+
75
+ - React hooks/components: [`@slyxup/react`](../react)
76
+ - Next.js server helpers + middleware: [`@slyxup/nextjs`](../nextjs)
77
+ - Prebuilt UI cards: [`@slyxup/ui`](../ui)
78
+
79
+ ## License
80
+
81
+ MIT © [SlyxUp](https://github.com/slyxup/stack)
package/dist/client.d.ts CHANGED
@@ -1,2 +1,35 @@
1
- export * from './index';
1
+ import type { AuthResponse, SessionResponse, SignInInput, SignUpInput, SlyxupClientOptions, UpdateUserInput, UserResponse } from './types.js';
2
+ /**
3
+ * SlyxUp Core Client.
4
+ *
5
+ * ```ts
6
+ * const client = new SlyxupClient({ publishableKey: 'pk_test_xxx' });
7
+ * await client.auth.signIn({ email: 'a@b.com', password: '12345678' });
8
+ * const session = await client.sessions.get();
9
+ * ```
10
+ */
11
+ export declare class SlyxupClient {
12
+ readonly publishableKey?: string;
13
+ readonly apiUrl: string;
14
+ readonly auth: {
15
+ signUp: (input: SignUpInput) => Promise<AuthResponse>;
16
+ signIn: (input: SignInInput) => Promise<AuthResponse>;
17
+ signOut: () => Promise<{
18
+ ok: true;
19
+ }>;
20
+ };
21
+ readonly sessions: {
22
+ get: () => Promise<SessionResponse>;
23
+ };
24
+ readonly users: {
25
+ me: () => Promise<UserResponse>;
26
+ update: (input: UpdateUserInput) => Promise<UserResponse>;
27
+ delete: () => Promise<{
28
+ ok: true;
29
+ }>;
30
+ };
31
+ constructor(options?: SlyxupClientOptions);
32
+ }
33
+ export * from './types.js';
34
+ export * from './errors.js';
2
35
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,YAAY,EAGZ,eAAe,EACf,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,YAAY,EACb,MAAM,YAAY,CAAC;AAkBpB;;;;;;;;GAQG;AACH,qBAAa,YAAY;IACvB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,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;KACtC,CAAC;IAEF,QAAQ,CAAC,QAAQ,EAAE;QACjB,GAAG,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;KACrC,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;gBAEU,OAAO,GAAE,mBAAwB;CAuG9C;AAGD,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
package/dist/client.js CHANGED
@@ -1,2 +1,125 @@
1
- export * from './index';
1
+ import { NetworkError, RateLimitError, SlyxupError, UnauthorizedError, } from './errors.js';
2
+ const DEFAULT_API_URL = 'https://auth.slyxup.online';
3
+ /** Minimal cookie jar so the SDK works in Node/SSR (browsers manage cookies natively). */
4
+ function createCookieJar() {
5
+ let jar;
6
+ return {
7
+ capture(res) {
8
+ const setCookie = res.headers.get('set-cookie');
9
+ if (setCookie)
10
+ jar = setCookie.split(';')[0]?.trim();
11
+ },
12
+ header() {
13
+ return jar ? { Cookie: jar } : {};
14
+ },
15
+ };
16
+ }
17
+ /**
18
+ * SlyxUp Core Client.
19
+ *
20
+ * ```ts
21
+ * const client = new SlyxupClient({ publishableKey: 'pk_test_xxx' });
22
+ * await client.auth.signIn({ email: 'a@b.com', password: '12345678' });
23
+ * const session = await client.sessions.get();
24
+ * ```
25
+ */
26
+ export class SlyxupClient {
27
+ publishableKey;
28
+ apiUrl;
29
+ auth;
30
+ sessions;
31
+ users;
32
+ constructor(options = {}) {
33
+ const jar = createCookieJar();
34
+ this.publishableKey = options.publishableKey;
35
+ this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, '');
36
+ const request = async (path, init = {}) => {
37
+ let res;
38
+ try {
39
+ res = await fetch(`${this.apiUrl}${path}`, {
40
+ ...init,
41
+ headers: {
42
+ 'Content-Type': 'application/json',
43
+ ...jar.header(),
44
+ ...init.headers,
45
+ },
46
+ credentials: 'include',
47
+ });
48
+ }
49
+ catch {
50
+ throw new NetworkError();
51
+ }
52
+ jar.capture(res);
53
+ const data = (await res
54
+ .json()
55
+ .catch(() => ({ ok: false, error: 'Invalid response' })));
56
+ if (!res.ok) {
57
+ if (res.status === 401)
58
+ throw new UnauthorizedError('error' in data ? String(data.error) : undefined);
59
+ if (res.status === 429)
60
+ throw new RateLimitError();
61
+ throw new SlyxupError('error' in data
62
+ ? String(data.error)
63
+ : `Request failed (${res.status})`, res.status, 'api_error');
64
+ }
65
+ return data;
66
+ };
67
+ const post = (path, body) => request(path, {
68
+ method: 'POST',
69
+ body: body === undefined ? undefined : JSON.stringify(body),
70
+ });
71
+ this.auth = {
72
+ signUp: async (input) => {
73
+ const res = await post('/v1/auth/sign-up', input);
74
+ if (!('user' in res))
75
+ throw new SlyxupError(res.error, 400, 'api_error');
76
+ return res;
77
+ },
78
+ signIn: async (input) => {
79
+ const res = await post('/v1/auth/sign-in', input);
80
+ if (!('user' in res))
81
+ throw new SlyxupError(res.error, 401, 'api_error');
82
+ return res;
83
+ },
84
+ signOut: async () => {
85
+ const res = await post('/v1/auth/sign-out');
86
+ return res;
87
+ },
88
+ };
89
+ this.sessions = {
90
+ get: async () => {
91
+ const res = await request('/v1/session');
92
+ if (!('session' in res))
93
+ throw new UnauthorizedError(res.error);
94
+ return res;
95
+ },
96
+ };
97
+ this.users = {
98
+ me: async () => {
99
+ const res = await request('/v1/user');
100
+ if (!('user' in res))
101
+ throw new UnauthorizedError(res.error);
102
+ return res;
103
+ },
104
+ update: async (input) => {
105
+ const res = await request('/v1/user', {
106
+ method: 'PATCH',
107
+ body: JSON.stringify(input),
108
+ });
109
+ if (!('user' in res))
110
+ throw new SlyxupError(res.error, 400, 'api_error');
111
+ return res;
112
+ },
113
+ delete: async () => {
114
+ const res = await request('/v1/user', {
115
+ method: 'DELETE',
116
+ });
117
+ return res;
118
+ },
119
+ };
120
+ }
121
+ }
122
+ // Re-exports so consumers can import from '@slyxup/core'
123
+ export * from './types.js';
124
+ export * from './errors.js';
2
125
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export declare class SlyxupError extends Error {
2
+ readonly status: number;
3
+ readonly code: string;
4
+ constructor(message: string, status: number, code: string);
5
+ }
6
+ export declare class UnauthorizedError extends SlyxupError {
7
+ constructor(message?: string);
8
+ }
9
+ export declare class ValidationError extends SlyxupError {
10
+ constructor(message?: string);
11
+ }
12
+ export declare class NetworkError extends SlyxupError {
13
+ constructor(message?: string);
14
+ }
15
+ export declare class RateLimitError extends SlyxupError {
16
+ constructor(message?: string);
17
+ }
18
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAM1D;AAED,qBAAa,iBAAkB,SAAQ,WAAW;gBACpC,OAAO,SAAiB;CAIrC;AAED,qBAAa,eAAgB,SAAQ,WAAW;gBAClC,OAAO,SAAsB;CAI1C;AAED,qBAAa,YAAa,SAAQ,WAAW;gBAC/B,OAAO,SAA2B;CAI/C;AAED,qBAAa,cAAe,SAAQ,WAAW;gBACjC,OAAO,SAAsB;CAI1C"}
package/dist/errors.js ADDED
@@ -0,0 +1,36 @@
1
+ // SlyxUp Core SDK — typed errors
2
+ export class SlyxupError extends Error {
3
+ status;
4
+ code;
5
+ constructor(message, status, code) {
6
+ super(message);
7
+ this.name = 'SlyxupError';
8
+ this.status = status;
9
+ this.code = code;
10
+ }
11
+ }
12
+ export class UnauthorizedError extends SlyxupError {
13
+ constructor(message = 'Unauthorized') {
14
+ super(message, 401, 'unauthorized');
15
+ this.name = 'UnauthorizedError';
16
+ }
17
+ }
18
+ export class ValidationError extends SlyxupError {
19
+ constructor(message = 'Validation failed') {
20
+ super(message, 400, 'validation_error');
21
+ this.name = 'ValidationError';
22
+ }
23
+ }
24
+ export class NetworkError extends SlyxupError {
25
+ constructor(message = 'Network request failed') {
26
+ super(message, 0, 'network_error');
27
+ this.name = 'NetworkError';
28
+ }
29
+ }
30
+ export class RateLimitError extends SlyxupError {
31
+ constructor(message = 'Too many requests') {
32
+ super(message, 429, 'rate_limited');
33
+ this.name = 'RateLimitError';
34
+ }
35
+ }
36
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,MAAM,CAAS;IACf,IAAI,CAAS;IAEtB,YAAY,OAAe,EAAE,MAAc,EAAE,IAAY;QACvD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,WAAW;IAChD,YAAY,OAAO,GAAG,cAAc;QAClC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,WAAW;IAC9C,YAAY,OAAO,GAAG,mBAAmB;QACvC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,WAAW;IAC3C,YAAY,OAAO,GAAG,wBAAwB;QAC5C,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,WAAW;IAC7C,YAAY,OAAO,GAAG,mBAAmB;QACvC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
- export declare const placeholder = "core";
1
+ export { SlyxupClient } from './client.js';
2
+ export * from './types.js';
3
+ export * from './errors.js';
2
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,4 @@
1
- export const placeholder = 'core';
1
+ export { SlyxupClient } from './client.js';
2
+ export * from './types.js';
3
+ export * from './errors.js';
2
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
@@ -0,0 +1,57 @@
1
+ export interface SlyxupUser {
2
+ id: string;
3
+ projectId: string | null;
4
+ email: string;
5
+ emailVerified: boolean;
6
+ firstName: string | null;
7
+ lastName: string | null;
8
+ avatarUrl: string | null;
9
+ preferences: Record<string, unknown> | null;
10
+ createdAt: string;
11
+ updatedAt: string;
12
+ }
13
+ export interface SlyxupSession {
14
+ id: string;
15
+ userId: string;
16
+ expiresAt: string;
17
+ }
18
+ export interface SignInInput {
19
+ email: string;
20
+ password: string;
21
+ projectId?: string;
22
+ }
23
+ export interface SignUpInput extends SignInInput {
24
+ firstName?: string;
25
+ lastName?: string;
26
+ }
27
+ export interface UpdateUserInput {
28
+ firstName?: string;
29
+ lastName?: string;
30
+ avatarUrl?: string;
31
+ preferences?: Record<string, unknown>;
32
+ }
33
+ export interface SessionResponse {
34
+ ok: true;
35
+ user: Pick<SlyxupUser, 'id' | 'email'>;
36
+ session: Pick<SlyxupSession, 'id' | 'expiresAt'>;
37
+ }
38
+ export interface UserResponse {
39
+ ok: true;
40
+ user: SlyxupUser;
41
+ }
42
+ export interface AuthResponse {
43
+ ok: true;
44
+ user: Pick<SlyxupUser, 'id' | 'email'>;
45
+ }
46
+ export interface ErrorResponse {
47
+ ok: false;
48
+ error: string;
49
+ }
50
+ export type Result<T> = T | ErrorResponse;
51
+ export interface SlyxupClientOptions {
52
+ /** Publishable key — pk_test_xxx / pk_live_xxx */
53
+ publishableKey?: string;
54
+ /** API base URL, default https://auth.slyxup.online */
55
+ apiUrl?: string;
56
+ }
57
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +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;CACxC;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/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // SlyxUp Core SDK — shared types
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,iCAAiC"}
package/package.json CHANGED
@@ -1,9 +1,30 @@
1
1
  {
2
2
  "name": "@slyxup/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
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": { "build": "tsc -p tsconfig.json", "typecheck": "tsc --noEmit" }
9
- }
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/slyxup/stack.git",
11
+ "directory": "packages/core"
12
+ },
13
+ "homepage": "https://github.com/slyxup/stack/tree/main/packages/core#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/slyxup/stack/issues"
16
+ },
17
+ "author": "SlyxUp <hello@slyxup.online>",
18
+ "license": "MIT",
19
+ "publishConfig": {
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"
29
+ }
30
+ }
package/src/client.ts CHANGED
@@ -1 +1,171 @@
1
- export * from './index';
1
+ import {
2
+ NetworkError,
3
+ RateLimitError,
4
+ SlyxupError,
5
+ UnauthorizedError,
6
+ } from './errors.js';
7
+ import type {
8
+ AuthResponse,
9
+ ErrorResponse,
10
+ Result,
11
+ SessionResponse,
12
+ SignInInput,
13
+ SignUpInput,
14
+ SlyxupClientOptions,
15
+ UpdateUserInput,
16
+ UserResponse,
17
+ } from './types.js';
18
+
19
+ const DEFAULT_API_URL = 'https://auth.slyxup.online';
20
+
21
+ /** Minimal cookie jar so the SDK works in Node/SSR (browsers manage cookies natively). */
22
+ function createCookieJar() {
23
+ let jar: string | undefined;
24
+ return {
25
+ capture(res: Response) {
26
+ const setCookie = res.headers.get('set-cookie');
27
+ if (setCookie) jar = setCookie.split(';')[0]?.trim();
28
+ },
29
+ header(): Record<string, string> {
30
+ return jar ? { Cookie: jar } : {};
31
+ },
32
+ };
33
+ }
34
+
35
+ /**
36
+ * SlyxUp Core Client.
37
+ *
38
+ * ```ts
39
+ * const client = new SlyxupClient({ publishableKey: 'pk_test_xxx' });
40
+ * await client.auth.signIn({ email: 'a@b.com', password: '12345678' });
41
+ * const session = await client.sessions.get();
42
+ * ```
43
+ */
44
+ export class SlyxupClient {
45
+ readonly publishableKey?: string;
46
+ readonly apiUrl: string;
47
+
48
+ readonly auth: {
49
+ signUp: (input: SignUpInput) => Promise<AuthResponse>;
50
+ signIn: (input: SignInInput) => Promise<AuthResponse>;
51
+ signOut: () => Promise<{ ok: true }>;
52
+ };
53
+
54
+ readonly sessions: {
55
+ get: () => Promise<SessionResponse>;
56
+ };
57
+
58
+ readonly users: {
59
+ me: () => Promise<UserResponse>;
60
+ update: (input: UpdateUserInput) => Promise<UserResponse>;
61
+ delete: () => Promise<{ ok: true }>;
62
+ };
63
+
64
+ constructor(options: SlyxupClientOptions = {}) {
65
+ const jar = createCookieJar();
66
+ this.publishableKey = options.publishableKey;
67
+ this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, '');
68
+
69
+ const request = async <T>(
70
+ path: string,
71
+ init: RequestInit & { body?: string } = {}
72
+ ): Promise<Result<T>> => {
73
+ let res: Response;
74
+ try {
75
+ res = await fetch(`${this.apiUrl}${path}`, {
76
+ ...init,
77
+ headers: {
78
+ 'Content-Type': 'application/json',
79
+ ...jar.header(),
80
+ ...init.headers,
81
+ },
82
+ credentials: 'include',
83
+ });
84
+ } catch {
85
+ throw new NetworkError();
86
+ }
87
+ jar.capture(res);
88
+
89
+ const data = (await res
90
+ .json()
91
+ .catch(() => ({ ok: false, error: 'Invalid response' }))) as object;
92
+
93
+ if (!res.ok) {
94
+ if (res.status === 401)
95
+ throw new UnauthorizedError(
96
+ 'error' in data ? String(data.error) : undefined
97
+ );
98
+ if (res.status === 429) throw new RateLimitError();
99
+ throw new SlyxupError(
100
+ 'error' in data
101
+ ? String(data.error)
102
+ : `Request failed (${res.status})`,
103
+ res.status,
104
+ 'api_error'
105
+ );
106
+ }
107
+
108
+ return data as Result<T>;
109
+ };
110
+
111
+ const post = <T>(path: string, body?: unknown) =>
112
+ request<T>(path, {
113
+ method: 'POST',
114
+ body: body === undefined ? undefined : JSON.stringify(body),
115
+ });
116
+
117
+ this.auth = {
118
+ signUp: async (input) => {
119
+ const res = await post<AuthResponse>('/v1/auth/sign-up', input);
120
+ if (!('user' in res))
121
+ throw new SlyxupError(res.error, 400, 'api_error');
122
+ return res;
123
+ },
124
+ signIn: async (input) => {
125
+ const res = await post<AuthResponse>('/v1/auth/sign-in', input);
126
+ if (!('user' in res))
127
+ throw new SlyxupError(res.error, 401, 'api_error');
128
+ return res;
129
+ },
130
+ signOut: async () => {
131
+ const res = await post<{ ok: true }>('/v1/auth/sign-out');
132
+ return res as { ok: true };
133
+ },
134
+ };
135
+
136
+ this.sessions = {
137
+ get: async () => {
138
+ const res = await request<SessionResponse>('/v1/session');
139
+ if (!('session' in res)) throw new UnauthorizedError(res.error);
140
+ return res;
141
+ },
142
+ };
143
+
144
+ this.users = {
145
+ me: async () => {
146
+ const res = await request<UserResponse>('/v1/user');
147
+ if (!('user' in res)) throw new UnauthorizedError(res.error);
148
+ return res;
149
+ },
150
+ update: async (input) => {
151
+ const res = await request<UserResponse>('/v1/user', {
152
+ method: 'PATCH',
153
+ body: JSON.stringify(input),
154
+ });
155
+ if (!('user' in res))
156
+ throw new SlyxupError(res.error, 400, 'api_error');
157
+ return res;
158
+ },
159
+ delete: async () => {
160
+ const res = await request<{ ok: true }>('/v1/user', {
161
+ method: 'DELETE',
162
+ });
163
+ return res as { ok: true };
164
+ },
165
+ };
166
+ }
167
+ }
168
+
169
+ // Re-exports so consumers can import from '@slyxup/core'
170
+ export * from './types.js';
171
+ export * from './errors.js';
package/src/errors.ts ADDED
@@ -0,0 +1,41 @@
1
+ // SlyxUp Core SDK — typed errors
2
+
3
+ export class SlyxupError extends Error {
4
+ readonly status: number;
5
+ readonly code: string;
6
+
7
+ constructor(message: string, status: number, code: string) {
8
+ super(message);
9
+ this.name = 'SlyxupError';
10
+ this.status = status;
11
+ this.code = code;
12
+ }
13
+ }
14
+
15
+ export class UnauthorizedError extends SlyxupError {
16
+ constructor(message = 'Unauthorized') {
17
+ super(message, 401, 'unauthorized');
18
+ this.name = 'UnauthorizedError';
19
+ }
20
+ }
21
+
22
+ export class ValidationError extends SlyxupError {
23
+ constructor(message = 'Validation failed') {
24
+ super(message, 400, 'validation_error');
25
+ this.name = 'ValidationError';
26
+ }
27
+ }
28
+
29
+ export class NetworkError extends SlyxupError {
30
+ constructor(message = 'Network request failed') {
31
+ super(message, 0, 'network_error');
32
+ this.name = 'NetworkError';
33
+ }
34
+ }
35
+
36
+ export class RateLimitError extends SlyxupError {
37
+ constructor(message = 'Too many requests') {
38
+ super(message, 429, 'rate_limited');
39
+ this.name = 'RateLimitError';
40
+ }
41
+ }
package/src/index.ts CHANGED
@@ -1 +1,3 @@
1
- export const placeholder = 'core';
1
+ export { SlyxupClient } from './client.js';
2
+ export * from './types.js';
3
+ export * from './errors.js';
package/src/types.ts ADDED
@@ -0,0 +1,68 @@
1
+ // SlyxUp Core SDK — shared types
2
+
3
+ export interface SlyxupUser {
4
+ id: string;
5
+ projectId: string | null;
6
+ email: string;
7
+ emailVerified: boolean;
8
+ firstName: string | null;
9
+ lastName: string | null;
10
+ avatarUrl: string | null;
11
+ preferences: Record<string, unknown> | null;
12
+ createdAt: string;
13
+ updatedAt: string;
14
+ }
15
+
16
+ export interface SlyxupSession {
17
+ id: string;
18
+ userId: string;
19
+ expiresAt: string;
20
+ }
21
+
22
+ export interface SignInInput {
23
+ email: string;
24
+ password: string;
25
+ projectId?: string;
26
+ }
27
+
28
+ export interface SignUpInput extends SignInInput {
29
+ firstName?: string;
30
+ lastName?: string;
31
+ }
32
+
33
+ export interface UpdateUserInput {
34
+ firstName?: string;
35
+ lastName?: string;
36
+ avatarUrl?: string;
37
+ preferences?: Record<string, unknown>;
38
+ }
39
+
40
+ export interface SessionResponse {
41
+ ok: true;
42
+ user: Pick<SlyxupUser, 'id' | 'email'>;
43
+ session: Pick<SlyxupSession, 'id' | 'expiresAt'>;
44
+ }
45
+
46
+ export interface UserResponse {
47
+ ok: true;
48
+ user: SlyxupUser;
49
+ }
50
+
51
+ export interface AuthResponse {
52
+ ok: true;
53
+ user: Pick<SlyxupUser, 'id' | 'email'>;
54
+ }
55
+
56
+ export interface ErrorResponse {
57
+ ok: false;
58
+ error: string;
59
+ }
60
+
61
+ export type Result<T> = T | ErrorResponse;
62
+
63
+ export interface SlyxupClientOptions {
64
+ /** Publishable key — pk_test_xxx / pk_live_xxx */
65
+ publishableKey?: string;
66
+ /** API base URL, default https://auth.slyxup.online */
67
+ apiUrl?: string;
68
+ }
@@ -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/tsconfig.json CHANGED
@@ -1 +1 @@
1
- { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "dist", "rootDir": "src" }, "include": ["src"] }
1
+ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "dist", "rootDir": "src", "lib": ["ES2022", "DOM", "DOM.Iterable"] }, "include": ["src"] }
@@ -0,0 +1,2 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ export default defineConfig({ test: { globals: true, environment: 'node' } });