@hirely/sdk 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/memory-cache.js +26 -0
- package/dist/client.js +183 -0
- package/dist/constants.js +12 -0
- package/dist/errors.d.ts +2 -6
- package/dist/errors.js +137 -0
- package/dist/http/request.js +138 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +18 -1
- package/dist/resources/certificates.js +6 -0
- package/dist/resources/contacts.js +4 -0
- package/dist/resources/cv.js +4 -0
- package/dist/resources/education.js +6 -0
- package/dist/resources/faqs.js +6 -0
- package/dist/resources/projects.js +7 -0
- package/dist/resources/services.js +6 -0
- package/dist/resources/skills.js +4 -0
- package/dist/resources/testimonials.js +6 -0
- package/dist/resources/work.js +6 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -0
- package/package.json +3 -4
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
export class MemoryCache {
|
|
3
|
+
store = new Map();
|
|
4
|
+
get(key) {
|
|
5
|
+
const entry = this.store.get(key);
|
|
6
|
+
if (!entry)
|
|
7
|
+
return undefined;
|
|
8
|
+
if (Date.now() > entry.expiresAt) {
|
|
9
|
+
this.store.delete(key);
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
return entry.value;
|
|
13
|
+
}
|
|
14
|
+
set(key, value, ttlSeconds) {
|
|
15
|
+
this.store.set(key, {
|
|
16
|
+
value,
|
|
17
|
+
expiresAt: Date.now() + ttlSeconds * 1000,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
delete(key) {
|
|
21
|
+
this.store.delete(key);
|
|
22
|
+
}
|
|
23
|
+
clear() {
|
|
24
|
+
this.store.clear();
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { RequestClient } from "./http/request.js";
|
|
2
|
+
import { API_KEY_PREFIX, DEFAULT_TIMEOUT_MS, DEFAULT_RETRIES } from "./constants.js";
|
|
3
|
+
import { HirelyValidationError } from "./errors.js";
|
|
4
|
+
import { createProjectsResource } from "./resources/projects.js";
|
|
5
|
+
import { createWorkResource } from "./resources/work.js";
|
|
6
|
+
import { createEducationResource } from "./resources/education.js";
|
|
7
|
+
import { createSkillsResource } from "./resources/skills.js";
|
|
8
|
+
import { createCertificatesResource } from "./resources/certificates.js";
|
|
9
|
+
import { createServicesResource } from "./resources/services.js";
|
|
10
|
+
import { createTestimonialsResource } from "./resources/testimonials.js";
|
|
11
|
+
import { createFaqsResource } from "./resources/faqs.js";
|
|
12
|
+
import { createContactsResource } from "./resources/contacts.js";
|
|
13
|
+
import { createCvResource } from "./resources/cv.js";
|
|
14
|
+
/**
|
|
15
|
+
* The Hirely SDK client.
|
|
16
|
+
*
|
|
17
|
+
* Instantiate once with your public API key and use any method or
|
|
18
|
+
* resource to fetch your portfolio data from the Hirely API.
|
|
19
|
+
*
|
|
20
|
+
* @example Basic usage
|
|
21
|
+
* ```ts
|
|
22
|
+
* import Hirely from "@hirely/sdk";
|
|
23
|
+
*
|
|
24
|
+
* const hirely = new Hirely({
|
|
25
|
+
* apiKey: process.env.HIRELY_API_KEY!,
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* const portfolio = await hirely.get();
|
|
29
|
+
* const me = await hirely.me();
|
|
30
|
+
* const project = await hirely.projects.getBySlug("my-app");
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example With caching and retries
|
|
34
|
+
* ```ts
|
|
35
|
+
* const hirely = new Hirely({
|
|
36
|
+
* apiKey: process.env.HIRELY_API_KEY!,
|
|
37
|
+
* timeout: 10_000,
|
|
38
|
+
* retries: 2,
|
|
39
|
+
* cache: { enabled: true, ttl: 300 },
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export default class Hirely {
|
|
44
|
+
/** @internal */
|
|
45
|
+
http;
|
|
46
|
+
/**
|
|
47
|
+
* Projects resource.
|
|
48
|
+
* - `hirely.projects()` — all public projects
|
|
49
|
+
* - `hirely.projects.getById(id)` — fetch by MongoDB ID
|
|
50
|
+
* - `hirely.projects.getBySlug(slug)` — fetch by URL slug
|
|
51
|
+
*/
|
|
52
|
+
projects;
|
|
53
|
+
/**
|
|
54
|
+
* Work experience resource.
|
|
55
|
+
* - `hirely.work()` — all work experience entries
|
|
56
|
+
* - `hirely.work.getById(id)` — fetch a single entry
|
|
57
|
+
*/
|
|
58
|
+
work;
|
|
59
|
+
/**
|
|
60
|
+
* Education resource.
|
|
61
|
+
* - `hirely.education()` — all education entries
|
|
62
|
+
* - `hirely.education.getById(id)` — fetch a single entry
|
|
63
|
+
*/
|
|
64
|
+
education;
|
|
65
|
+
/**
|
|
66
|
+
* Skills resource.
|
|
67
|
+
* - `hirely.skills()` — all skills
|
|
68
|
+
*/
|
|
69
|
+
skills;
|
|
70
|
+
/**
|
|
71
|
+
* Certificates resource.
|
|
72
|
+
* - `hirely.certificates()` — all certificates
|
|
73
|
+
* - `hirely.certificates.getById(id)` — fetch a single certificate
|
|
74
|
+
*/
|
|
75
|
+
certificates;
|
|
76
|
+
/**
|
|
77
|
+
* Services resource.
|
|
78
|
+
* - `hirely.services()` — all public services
|
|
79
|
+
* - `hirely.services.getById(id)` — fetch a single service
|
|
80
|
+
*/
|
|
81
|
+
services;
|
|
82
|
+
/**
|
|
83
|
+
* Testimonials resource (approved and public only).
|
|
84
|
+
* - `hirely.testimonials()` — all testimonials
|
|
85
|
+
* - `hirely.testimonials.getById(id)` — fetch a single testimonial
|
|
86
|
+
*/
|
|
87
|
+
testimonials;
|
|
88
|
+
/**
|
|
89
|
+
* FAQ resource.
|
|
90
|
+
* - `hirely.faqs()` — all FAQ entries
|
|
91
|
+
* - `hirely.faqs.getById(id)` — fetch a single FAQ
|
|
92
|
+
*/
|
|
93
|
+
faqs;
|
|
94
|
+
/**
|
|
95
|
+
* Contacts resource.
|
|
96
|
+
* - `hirely.contacts()` — public social links
|
|
97
|
+
*/
|
|
98
|
+
contacts;
|
|
99
|
+
/**
|
|
100
|
+
* CV resource.
|
|
101
|
+
* - `hirely.cv()` — most recent public CV
|
|
102
|
+
*/
|
|
103
|
+
cv;
|
|
104
|
+
constructor(config) {
|
|
105
|
+
if (!config.apiKey) {
|
|
106
|
+
throw new HirelyValidationError("Hirely API key is required. Pass it as `apiKey` in the constructor.", 400);
|
|
107
|
+
}
|
|
108
|
+
if (!config.apiKey.startsWith(API_KEY_PREFIX)) {
|
|
109
|
+
throw new HirelyValidationError(`Invalid Hirely API key. Keys must start with "${API_KEY_PREFIX}".`, 400);
|
|
110
|
+
}
|
|
111
|
+
this.http = new RequestClient({
|
|
112
|
+
apiKey: config.apiKey,
|
|
113
|
+
timeout: config.timeout ?? DEFAULT_TIMEOUT_MS,
|
|
114
|
+
retries: config.retries ?? DEFAULT_RETRIES,
|
|
115
|
+
fetchFn: config.fetch ?? globalThis.fetch.bind(globalThis),
|
|
116
|
+
cacheConfig: config.cache, // ← can be `undefined`
|
|
117
|
+
});
|
|
118
|
+
this.projects = createProjectsResource(this.http);
|
|
119
|
+
this.work = createWorkResource(this.http);
|
|
120
|
+
this.education = createEducationResource(this.http);
|
|
121
|
+
this.skills = createSkillsResource(this.http);
|
|
122
|
+
this.certificates = createCertificatesResource(this.http);
|
|
123
|
+
this.services = createServicesResource(this.http);
|
|
124
|
+
this.testimonials = createTestimonialsResource(this.http);
|
|
125
|
+
this.faqs = createFaqsResource(this.http);
|
|
126
|
+
this.contacts = createContactsResource(this.http);
|
|
127
|
+
this.cv = createCvResource(this.http);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Fetches the complete public portfolio in a single request.
|
|
131
|
+
*
|
|
132
|
+
* Returns profile, projects, work, education, skills, certificates,
|
|
133
|
+
* services, testimonials, FAQs, contacts, and CV.
|
|
134
|
+
*
|
|
135
|
+
* @param options - Optional per-request options.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* const portfolio = await hirely.get();
|
|
140
|
+
* console.log(portfolio.profile?.firstName);
|
|
141
|
+
* console.log(portfolio.projects.length);
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* @example Next.js Server Component
|
|
145
|
+
* ```tsx
|
|
146
|
+
* export default async function Page() {
|
|
147
|
+
* const portfolio = await hirely.get();
|
|
148
|
+
* return (
|
|
149
|
+
* <main>
|
|
150
|
+
* <h1>{portfolio.profile?.firstName} {portfolio.profile?.lastName}</h1>
|
|
151
|
+
* {portfolio.projects.map((p) => (
|
|
152
|
+
* <article key={p._id}><h2>{p.title}</h2></article>
|
|
153
|
+
* ))}
|
|
154
|
+
* </main>
|
|
155
|
+
* );
|
|
156
|
+
* }
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
get(options) {
|
|
160
|
+
return this.http.request("/all", options);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Fetches account information and the full public profile.
|
|
164
|
+
*
|
|
165
|
+
* Returns account-level data (username, email, role, plan) plus the
|
|
166
|
+
* complete profile (name, bio, avatar, location, birthday, etc.).
|
|
167
|
+
*
|
|
168
|
+
* @param options - Optional per-request options.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* const me = await hirely.me();
|
|
173
|
+
* console.log(me.userName); // "mahmoud"
|
|
174
|
+
* console.log(me.plan); // "pro"
|
|
175
|
+
* console.log(me.profile?.firstName); // "Mahmoud"
|
|
176
|
+
* console.log(me.profile?.positionName); // "Full-Stack Developer"
|
|
177
|
+
* console.log(me.profile?.avatar?.url); // "https://..."
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
me(options) {
|
|
181
|
+
return this.http.request("/me", options);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @internal Base URL for the Hirely public API. Not configurable by consumers. */
|
|
2
|
+
export const BASE_URL = "https://api.hirely.cc/api/v1/sdk";
|
|
3
|
+
/** @internal Required prefix for all Hirely public API keys. */
|
|
4
|
+
export const API_KEY_PREFIX = "hk_pub_";
|
|
5
|
+
/** Default request timeout in milliseconds. */
|
|
6
|
+
export const DEFAULT_TIMEOUT_MS = 30_000;
|
|
7
|
+
/** Default number of retry attempts for transient errors. */
|
|
8
|
+
export const DEFAULT_RETRIES = 2;
|
|
9
|
+
/** HTTP status codes that trigger a retry. */
|
|
10
|
+
export const RETRYABLE_STATUS_CODES = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
11
|
+
/** Maximum backoff delay between retries in milliseconds. */
|
|
12
|
+
export const MAX_BACKOFF_MS = 30_000;
|
package/dist/errors.d.ts
CHANGED
|
@@ -15,12 +15,9 @@
|
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
export declare class HirelyError extends Error {
|
|
18
|
-
/** HTTP status code (0 for network/timeout errors). */
|
|
19
18
|
readonly status: number;
|
|
20
|
-
/** Machine-readable error code. */
|
|
21
19
|
readonly code: string;
|
|
22
|
-
|
|
23
|
-
readonly requestId?: string;
|
|
20
|
+
readonly requestId: string | undefined;
|
|
24
21
|
constructor(message: string, status: number, code?: string, requestId?: string);
|
|
25
22
|
}
|
|
26
23
|
/**
|
|
@@ -84,8 +81,7 @@ export declare class HirelyValidationError extends HirelyError {
|
|
|
84
81
|
* ```
|
|
85
82
|
*/
|
|
86
83
|
export declare class HirelyRateLimitError extends HirelyError {
|
|
87
|
-
|
|
88
|
-
readonly retryAfter?: number;
|
|
84
|
+
readonly retryAfter: number | undefined;
|
|
89
85
|
constructor(message?: string, retryAfter?: number, requestId?: string);
|
|
90
86
|
}
|
|
91
87
|
/**
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for all Hirely SDK errors.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { HirelyError } from "@hirely/sdk";
|
|
7
|
+
*
|
|
8
|
+
* try {
|
|
9
|
+
* await hirely.get();
|
|
10
|
+
* } catch (error) {
|
|
11
|
+
* if (error instanceof HirelyError) {
|
|
12
|
+
* console.error(error.status, error.message);
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export class HirelyError extends Error {
|
|
18
|
+
status;
|
|
19
|
+
code;
|
|
20
|
+
requestId;
|
|
21
|
+
constructor(message, status, code = "HIRELY_ERROR", requestId) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "HirelyError";
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.requestId = requestId;
|
|
27
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Thrown when the API key is missing, invalid, or revoked (HTTP 401/403).
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { HirelyAuthenticationError } from "@hirely/sdk";
|
|
36
|
+
*
|
|
37
|
+
* try {
|
|
38
|
+
* await hirely.get();
|
|
39
|
+
* } catch (error) {
|
|
40
|
+
* if (error instanceof HirelyAuthenticationError) {
|
|
41
|
+
* console.error("Check your HIRELY_API_KEY.");
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export class HirelyAuthenticationError extends HirelyError {
|
|
47
|
+
constructor(message = "Invalid or missing API key", status = 401, requestId) {
|
|
48
|
+
super(message, status, "AUTHENTICATION_ERROR", requestId);
|
|
49
|
+
this.name = "HirelyAuthenticationError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Thrown when a requested resource does not exist (HTTP 404).
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { HirelyNotFoundError } from "@hirely/sdk";
|
|
58
|
+
*
|
|
59
|
+
* try {
|
|
60
|
+
* const project = await hirely.projects.getBySlug("nonexistent");
|
|
61
|
+
* } catch (error) {
|
|
62
|
+
* if (error instanceof HirelyNotFoundError) {
|
|
63
|
+
* console.log("Project not found");
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export class HirelyNotFoundError extends HirelyError {
|
|
69
|
+
constructor(message = "Resource not found", requestId) {
|
|
70
|
+
super(message, 404, "NOT_FOUND", requestId);
|
|
71
|
+
this.name = "HirelyNotFoundError";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Thrown when a request fails validation (HTTP 400/422).
|
|
76
|
+
*/
|
|
77
|
+
export class HirelyValidationError extends HirelyError {
|
|
78
|
+
constructor(message = "Validation error", status = 422, requestId) {
|
|
79
|
+
super(message, status, "VALIDATION_ERROR", requestId);
|
|
80
|
+
this.name = "HirelyValidationError";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Thrown when the API rate limit has been exceeded (HTTP 429).
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { HirelyRateLimitError } from "@hirely/sdk";
|
|
89
|
+
*
|
|
90
|
+
* try {
|
|
91
|
+
* await hirely.get();
|
|
92
|
+
* } catch (error) {
|
|
93
|
+
* if (error instanceof HirelyRateLimitError) {
|
|
94
|
+
* console.log(`Retry after ${error.retryAfter}s`);
|
|
95
|
+
* }
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export class HirelyRateLimitError extends HirelyError {
|
|
100
|
+
retryAfter; // ← was `?: number`
|
|
101
|
+
constructor(message = "Rate limit exceeded", retryAfter, requestId) {
|
|
102
|
+
super(message, 429, "RATE_LIMIT_EXCEEDED", requestId);
|
|
103
|
+
this.name = "HirelyRateLimitError";
|
|
104
|
+
this.retryAfter = retryAfter;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Thrown when a request exceeds the configured timeout.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* const hirely = new Hirely({ apiKey, timeout: 5000 });
|
|
113
|
+
*
|
|
114
|
+
* try {
|
|
115
|
+
* await hirely.get();
|
|
116
|
+
* } catch (error) {
|
|
117
|
+
* if (error instanceof HirelyTimeoutError) {
|
|
118
|
+
* console.error("Request timed out after 5s");
|
|
119
|
+
* }
|
|
120
|
+
* }
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export class HirelyTimeoutError extends HirelyError {
|
|
124
|
+
constructor(message = "Request timed out") {
|
|
125
|
+
super(message, 0, "TIMEOUT");
|
|
126
|
+
this.name = "HirelyTimeoutError";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Thrown when the Hirely API returns a server-side error (HTTP 5xx).
|
|
131
|
+
*/
|
|
132
|
+
export class HirelyServerError extends HirelyError {
|
|
133
|
+
constructor(message = "Internal server error", status = 500, requestId) {
|
|
134
|
+
super(message, status, "SERVER_ERROR", requestId);
|
|
135
|
+
this.name = "HirelyServerError";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { HirelyError, HirelyAuthenticationError, HirelyNotFoundError, HirelyValidationError, HirelyRateLimitError, HirelyTimeoutError, HirelyServerError, } from "../errors.js";
|
|
2
|
+
import { MemoryCache } from "../cache/memory-cache.js";
|
|
3
|
+
import { BASE_URL, DEFAULT_TIMEOUT_MS, DEFAULT_RETRIES, RETRYABLE_STATUS_CODES, MAX_BACKOFF_MS, } from "../constants.js";
|
|
4
|
+
/** @internal */
|
|
5
|
+
export class RequestClient {
|
|
6
|
+
apiKey;
|
|
7
|
+
timeout;
|
|
8
|
+
retries;
|
|
9
|
+
fetchFn;
|
|
10
|
+
cache = null;
|
|
11
|
+
cacheTtl;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.apiKey = config.apiKey;
|
|
14
|
+
this.timeout = config.timeout;
|
|
15
|
+
this.retries = config.retries;
|
|
16
|
+
this.fetchFn = config.fetchFn;
|
|
17
|
+
this.cacheTtl = config.cacheConfig?.ttl ?? 300;
|
|
18
|
+
if (config.cacheConfig?.enabled) {
|
|
19
|
+
this.cache = config.cacheConfig.store ?? new MemoryCache();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
buildCacheKey(endpoint) {
|
|
23
|
+
const keyScope = this.apiKey.slice(-8);
|
|
24
|
+
return `${keyScope}:${BASE_URL}${endpoint}`;
|
|
25
|
+
}
|
|
26
|
+
async fetchWithTimeout(url, init) {
|
|
27
|
+
if (this.timeout === 0) {
|
|
28
|
+
return this.fetchFn(url, init);
|
|
29
|
+
}
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
32
|
+
try {
|
|
33
|
+
return await this.fetchFn(url, { ...init, signal: controller.signal });
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
37
|
+
throw new HirelyTimeoutError(`Request timed out after ${this.timeout}ms`);
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
throwForStatus(status, message, requestId) {
|
|
46
|
+
switch (true) {
|
|
47
|
+
case status === 401 || status === 403:
|
|
48
|
+
throw new HirelyAuthenticationError(message, status, requestId);
|
|
49
|
+
case status === 404:
|
|
50
|
+
throw new HirelyNotFoundError(message, requestId);
|
|
51
|
+
case status === 400 || status === 422:
|
|
52
|
+
throw new HirelyValidationError(message, status, requestId);
|
|
53
|
+
case status === 429:
|
|
54
|
+
throw new HirelyRateLimitError(message, undefined, requestId);
|
|
55
|
+
case status >= 500:
|
|
56
|
+
throw new HirelyServerError(message, status, requestId);
|
|
57
|
+
default:
|
|
58
|
+
throw new HirelyError(message, status, "REQUEST_FAILED", requestId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
getRetryAfter(headers) {
|
|
62
|
+
const value = headers.get("retry-after");
|
|
63
|
+
if (!value)
|
|
64
|
+
return undefined;
|
|
65
|
+
const seconds = Number(value);
|
|
66
|
+
return Number.isFinite(seconds) ? seconds : undefined;
|
|
67
|
+
}
|
|
68
|
+
computeBackoff(attempt, retryAfterSeconds) {
|
|
69
|
+
if (retryAfterSeconds != null) {
|
|
70
|
+
return Math.min(retryAfterSeconds * 1000, MAX_BACKOFF_MS);
|
|
71
|
+
}
|
|
72
|
+
return Math.min(1000 * Math.pow(2, attempt), MAX_BACKOFF_MS);
|
|
73
|
+
}
|
|
74
|
+
sleep(ms) {
|
|
75
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
76
|
+
}
|
|
77
|
+
async request(endpoint, options) {
|
|
78
|
+
const bypassCache = options?.cache === false;
|
|
79
|
+
const cacheKey = this.buildCacheKey(endpoint);
|
|
80
|
+
if (this.cache && !bypassCache) {
|
|
81
|
+
const cached = await this.cache.get(cacheKey);
|
|
82
|
+
if (cached !== undefined)
|
|
83
|
+
return cached;
|
|
84
|
+
}
|
|
85
|
+
const url = `${BASE_URL}${endpoint}`;
|
|
86
|
+
const headers = {
|
|
87
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
88
|
+
Accept: "application/json",
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
};
|
|
91
|
+
let lastError;
|
|
92
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
93
|
+
let response;
|
|
94
|
+
try {
|
|
95
|
+
response = await this.fetchWithTimeout(url, { method: "GET", headers });
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (error instanceof HirelyTimeoutError)
|
|
99
|
+
throw error;
|
|
100
|
+
lastError = error;
|
|
101
|
+
if (attempt < this.retries) {
|
|
102
|
+
await this.sleep(this.computeBackoff(attempt));
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
throw new HirelyError(error instanceof Error ? error.message : "Network request failed", 0, "NETWORK_ERROR");
|
|
106
|
+
}
|
|
107
|
+
const requestId = response.headers.get("x-request-id") ?? undefined;
|
|
108
|
+
if (response.status === 429 && attempt < this.retries) {
|
|
109
|
+
const retryAfter = this.getRetryAfter(response.headers);
|
|
110
|
+
await this.sleep(this.computeBackoff(attempt, retryAfter));
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (RETRYABLE_STATUS_CODES.has(response.status) && response.status !== 429 && attempt < this.retries) {
|
|
114
|
+
await this.sleep(this.computeBackoff(attempt));
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
let body;
|
|
118
|
+
try {
|
|
119
|
+
body = (await response.json());
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
this.throwForStatus(response.status, "Request failed", requestId);
|
|
124
|
+
}
|
|
125
|
+
throw new HirelyError("Invalid response from Hirely API", response.status, "PARSE_ERROR", requestId);
|
|
126
|
+
}
|
|
127
|
+
if (!response.ok || !body.success) {
|
|
128
|
+
this.throwForStatus(response.status, body?.message ?? "Request failed", requestId);
|
|
129
|
+
}
|
|
130
|
+
const data = body.data;
|
|
131
|
+
if (this.cache && !bypassCache) {
|
|
132
|
+
await this.cache.set(cacheKey, data, this.cacheTtl);
|
|
133
|
+
}
|
|
134
|
+
return data;
|
|
135
|
+
}
|
|
136
|
+
throw lastError ?? new HirelyError("Request failed after retries", 0, "REQUEST_FAILED");
|
|
137
|
+
}
|
|
138
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,10 +14,9 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @module
|
|
16
16
|
*/
|
|
17
|
-
export { default } from "./client.js";
|
|
18
|
-
export { default as Hirely } from "./client.js";
|
|
17
|
+
export { default, default as Hirely } from "./client.js";
|
|
19
18
|
export { HirelyError, HirelyAuthenticationError, HirelyNotFoundError, HirelyValidationError, HirelyRateLimitError, HirelyTimeoutError, HirelyServerError, } from "./errors.js";
|
|
20
|
-
export type { HirelyConfig, HirelyCacheConfig, HirelyCache, HirelyRequestOptions, HirelyPortfolio, HirelyMe, HirelyProfile, HirelyProject, HirelyProjectMedia, HirelyWork, HirelyEducation, HirelySkill, HirelyCertificate, HirelyService, HirelyTestimonial, HirelyTestimonialClient, HirelyFaq, HirelyContact,
|
|
19
|
+
export type { HirelyConfig, HirelyCacheConfig, HirelyCache, HirelyRequestOptions, HirelyPortfolio, HirelyMe, HirelyProfile, HirelyProject, HirelyProjectMedia, HirelyWork, HirelyEducation, HirelySkill, HirelyCertificate, HirelyService, HirelyTestimonial, HirelyTestimonialClient, HirelyFaq, HirelyContact, HirelySocialLink, HirelyCV, } from "./types.js";
|
|
21
20
|
export type { ProjectsResource } from "./resources/projects.js";
|
|
22
21
|
export type { WorkResource } from "./resources/work.js";
|
|
23
22
|
export type { EducationResource } from "./resources/education.js";
|
package/dist/index.js
CHANGED
|
@@ -1 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @hirely/sdk — Official JavaScript & TypeScript SDK for Hirely.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import Hirely from "@hirely/sdk";
|
|
7
|
+
*
|
|
8
|
+
* const hirely = new Hirely({ apiKey: process.env.HIRELY_API_KEY! });
|
|
9
|
+
*
|
|
10
|
+
* const portfolio = await hirely.get();
|
|
11
|
+
* const me = await hirely.me();
|
|
12
|
+
* const project = await hirely.projects.getBySlug("my-app");
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
export { default, default as Hirely } from "./client.js";
|
|
18
|
+
export { HirelyError, HirelyAuthenticationError, HirelyNotFoundError, HirelyValidationError, HirelyRateLimitError, HirelyTimeoutError, HirelyServerError, } from "./errors.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
export function createProjectsResource(http) {
|
|
3
|
+
const fn = (options) => http.request("/projects", options);
|
|
4
|
+
fn.getById = (id, options) => http.request(`/projects/${id}`, options);
|
|
5
|
+
fn.getBySlug = (slug, options) => http.request(`/projects/slug/${slug}`, options);
|
|
6
|
+
return fn;
|
|
7
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -434,10 +434,10 @@ export interface HirelyContact {
|
|
|
434
434
|
/** MongoDB document ID. */
|
|
435
435
|
_id: string;
|
|
436
436
|
/** List of social/contact links. */
|
|
437
|
-
socialLinks?:
|
|
437
|
+
socialLinks?: HirelySocialLink[];
|
|
438
438
|
}
|
|
439
439
|
/** A single social or contact link. */
|
|
440
|
-
export interface
|
|
440
|
+
export interface HirelySocialLink {
|
|
441
441
|
/** Social platform name (e.g. "github", "linkedIn"). */
|
|
442
442
|
platform: string;
|
|
443
443
|
/** The profile URL. */
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hirely/sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Official JavaScript and TypeScript SDK for connecting your Hirely portfolio to any website or application.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -28,13 +28,12 @@
|
|
|
28
28
|
"CHANGELOG.md"
|
|
29
29
|
],
|
|
30
30
|
"scripts": {
|
|
31
|
-
"build:js": "bun build src/index.ts --outdir dist --target node --format esm --minify",
|
|
32
|
-
"build:types": "tsc -p tsconfig.build.json",
|
|
33
31
|
"build": "bun scripts/build.ts",
|
|
34
32
|
"typecheck": "bunx tsgo --noEmit",
|
|
35
33
|
"test": "bun test",
|
|
34
|
+
"smoke": "bun scripts/smoke.ts",
|
|
36
35
|
"clean": "rm -rf dist",
|
|
37
|
-
"prepublishOnly": "bun run clean && bun run typecheck && bun run build && bun test",
|
|
36
|
+
"prepublishOnly": "bun run clean && bun run typecheck && bun run build && bun run smoke && bun test",
|
|
38
37
|
"release:patch": "npm version patch && git push --follow-tags && npm publish",
|
|
39
38
|
"release:minor": "npm version minor && git push --follow-tags && npm publish",
|
|
40
39
|
"release:major": "npm version major && git push --follow-tags && npm publish"
|