@suiteportal/connector 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/index.cjs +433 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +212 -0
- package/dist/index.d.ts +212 -0
- package/dist/index.js +387 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @suiteportal/connector
|
|
2
|
+
|
|
3
|
+
NetSuite OAuth 1.0a connector with SuiteQL query execution. Zero runtime dependencies — uses Node.js built-in `crypto` and `fetch`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @suiteportal/connector
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createConnector } from '@suiteportal/connector';
|
|
15
|
+
|
|
16
|
+
const connector = createConnector({
|
|
17
|
+
accountId: '1234567_SB1',
|
|
18
|
+
consumerKey: '...',
|
|
19
|
+
consumerSecret: '...',
|
|
20
|
+
tokenId: '...',
|
|
21
|
+
tokenSecret: '...',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Execute a SuiteQL query
|
|
25
|
+
const results = await connector.query('SELECT id, companyname FROM customer WHERE rownum <= 10');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- OAuth 1.0a signing (HMAC-SHA256, RFC 5849)
|
|
31
|
+
- HTTP client with retry, rate-limiting, and timeout
|
|
32
|
+
- SuiteQL query executor with pagination
|
|
33
|
+
- Custom error hierarchy: `AuthError`, `RateLimitError`, `TimeoutError`
|
|
34
|
+
|
|
35
|
+
## Documentation
|
|
36
|
+
|
|
37
|
+
Full docs at [suiteportal.dev](https://suiteportal.dev)
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthError: () => AuthError,
|
|
24
|
+
NetSuiteClient: () => NetSuiteClient,
|
|
25
|
+
NetSuiteError: () => NetSuiteError,
|
|
26
|
+
RateLimitError: () => RateLimitError,
|
|
27
|
+
RateLimiter: () => RateLimiter,
|
|
28
|
+
TimeoutError: () => TimeoutError,
|
|
29
|
+
buildAuthorizationHeader: () => buildAuthorizationHeader,
|
|
30
|
+
buildSignatureBaseString: () => buildSignatureBaseString,
|
|
31
|
+
deriveBaseUrl: () => deriveBaseUrl,
|
|
32
|
+
executeSuiteQL: () => executeSuiteQL,
|
|
33
|
+
executeSuiteQLPaginated: () => executeSuiteQLPaginated,
|
|
34
|
+
generateNonce: () => generateNonce,
|
|
35
|
+
generateOAuthSignature: () => generateOAuthSignature,
|
|
36
|
+
generateTimestamp: () => generateTimestamp,
|
|
37
|
+
getRealm: () => getRealm,
|
|
38
|
+
isRetryableStatus: () => isRetryableStatus,
|
|
39
|
+
percentEncode: () => percentEncode,
|
|
40
|
+
resolveConfig: () => resolveConfig,
|
|
41
|
+
signHmacSha256: () => signHmacSha256,
|
|
42
|
+
withRetry: () => withRetry
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(index_exports);
|
|
45
|
+
|
|
46
|
+
// src/auth/oauth.ts
|
|
47
|
+
var import_node_crypto = require("crypto");
|
|
48
|
+
function percentEncode(str) {
|
|
49
|
+
return encodeURIComponent(str).replace(/[!'()*]/g, (c) => {
|
|
50
|
+
return `%${c.charCodeAt(0).toString(16).toUpperCase()}`;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function generateNonce() {
|
|
54
|
+
return (0, import_node_crypto.randomBytes)(16).toString("hex");
|
|
55
|
+
}
|
|
56
|
+
function generateTimestamp() {
|
|
57
|
+
return Math.floor(Date.now() / 1e3).toString();
|
|
58
|
+
}
|
|
59
|
+
function buildSignatureBaseString(method, baseUrl, params) {
|
|
60
|
+
const sorted = [...params].sort((a, b) => {
|
|
61
|
+
const keyCompare = a[0].localeCompare(b[0]);
|
|
62
|
+
return keyCompare !== 0 ? keyCompare : a[1].localeCompare(b[1]);
|
|
63
|
+
});
|
|
64
|
+
const paramString = sorted.map(([k, v]) => `${percentEncode(k)}=${percentEncode(v)}`).join("&");
|
|
65
|
+
return [
|
|
66
|
+
method.toUpperCase(),
|
|
67
|
+
percentEncode(baseUrl),
|
|
68
|
+
percentEncode(paramString)
|
|
69
|
+
].join("&");
|
|
70
|
+
}
|
|
71
|
+
function signHmacSha256(baseString, consumerSecret, tokenSecret) {
|
|
72
|
+
const signingKey = `${percentEncode(consumerSecret)}&${percentEncode(tokenSecret)}`;
|
|
73
|
+
return (0, import_node_crypto.createHmac)("sha256", signingKey).update(baseString).digest("base64");
|
|
74
|
+
}
|
|
75
|
+
function generateOAuthSignature(params) {
|
|
76
|
+
const timestamp = params.timestamp ?? generateTimestamp();
|
|
77
|
+
const nonce = params.nonce ?? generateNonce();
|
|
78
|
+
const oauthParams = {
|
|
79
|
+
oauth_consumer_key: params.consumerKey,
|
|
80
|
+
oauth_token: params.tokenId,
|
|
81
|
+
oauth_nonce: nonce,
|
|
82
|
+
oauth_timestamp: timestamp,
|
|
83
|
+
oauth_signature_method: "HMAC-SHA256",
|
|
84
|
+
oauth_version: "1.0"
|
|
85
|
+
};
|
|
86
|
+
const urlObj = new URL(params.url);
|
|
87
|
+
const baseUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}`;
|
|
88
|
+
const allParams = Object.entries(oauthParams);
|
|
89
|
+
urlObj.searchParams.forEach((value, key) => {
|
|
90
|
+
allParams.push([key, value]);
|
|
91
|
+
});
|
|
92
|
+
const baseString = buildSignatureBaseString(params.method, baseUrl, allParams);
|
|
93
|
+
const signature = signHmacSha256(baseString, params.consumerSecret, params.tokenSecret);
|
|
94
|
+
return { signature, oauthParams };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/auth/headers.ts
|
|
98
|
+
function buildAuthorizationHeader(params) {
|
|
99
|
+
const { signature, oauthParams } = generateOAuthSignature(params);
|
|
100
|
+
const headerParams = {
|
|
101
|
+
realm: params.realm,
|
|
102
|
+
...oauthParams,
|
|
103
|
+
oauth_signature: signature
|
|
104
|
+
};
|
|
105
|
+
const parts = Object.entries(headerParams).map(([key, value]) => `${percentEncode(key)}="${percentEncode(value)}"`).join(", ");
|
|
106
|
+
return `OAuth ${parts}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/config.ts
|
|
110
|
+
var REQUIRED_FIELDS = [
|
|
111
|
+
"accountId",
|
|
112
|
+
"consumerKey",
|
|
113
|
+
"consumerSecret",
|
|
114
|
+
"tokenId",
|
|
115
|
+
"tokenSecret"
|
|
116
|
+
];
|
|
117
|
+
var DEFAULTS = {
|
|
118
|
+
timeout: 3e4,
|
|
119
|
+
concurrency: 5,
|
|
120
|
+
maxRetries: 3
|
|
121
|
+
};
|
|
122
|
+
function resolveConfig(config) {
|
|
123
|
+
for (const field of REQUIRED_FIELDS) {
|
|
124
|
+
if (!config[field]) {
|
|
125
|
+
throw new Error(`NetSuiteConfig: "${field}" is required`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
accountId: config.accountId,
|
|
130
|
+
consumerKey: config.consumerKey,
|
|
131
|
+
consumerSecret: config.consumerSecret,
|
|
132
|
+
tokenId: config.tokenId,
|
|
133
|
+
tokenSecret: config.tokenSecret,
|
|
134
|
+
timeout: config.timeout ?? DEFAULTS.timeout,
|
|
135
|
+
concurrency: config.concurrency ?? DEFAULTS.concurrency,
|
|
136
|
+
maxRetries: config.maxRetries ?? DEFAULTS.maxRetries,
|
|
137
|
+
baseUrl: config.baseUrl ?? deriveBaseUrl(config.accountId)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function deriveBaseUrl(accountId) {
|
|
141
|
+
const normalized = accountId.toLowerCase().replace(/_/g, "-");
|
|
142
|
+
return `https://${normalized}.suitetalk.api.netsuite.com`;
|
|
143
|
+
}
|
|
144
|
+
function getRealm(accountId) {
|
|
145
|
+
return accountId.toUpperCase().replace(/-/g, "_");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/http/errors.ts
|
|
149
|
+
var NetSuiteError = class extends Error {
|
|
150
|
+
constructor(message, status, code, details) {
|
|
151
|
+
super(message);
|
|
152
|
+
this.status = status;
|
|
153
|
+
this.code = code;
|
|
154
|
+
this.details = details;
|
|
155
|
+
this.name = "NetSuiteError";
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var AuthError = class extends NetSuiteError {
|
|
159
|
+
constructor(message, status, details) {
|
|
160
|
+
super(message, status, "AUTH_ERROR", details);
|
|
161
|
+
this.name = "AuthError";
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var RateLimitError = class extends NetSuiteError {
|
|
165
|
+
constructor(retryAfterMs, details) {
|
|
166
|
+
super(`Rate limited. Retry after ${retryAfterMs}ms`, 429, "RATE_LIMIT", details);
|
|
167
|
+
this.retryAfterMs = retryAfterMs;
|
|
168
|
+
this.name = "RateLimitError";
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var TimeoutError = class extends NetSuiteError {
|
|
172
|
+
constructor(timeoutMs) {
|
|
173
|
+
super(`Request timed out after ${timeoutMs}ms`, void 0, "TIMEOUT");
|
|
174
|
+
this.name = "TimeoutError";
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
|
|
178
|
+
function isRetryableStatus(status) {
|
|
179
|
+
return RETRYABLE_STATUSES.has(status);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/http/rate-limiter.ts
|
|
183
|
+
var RateLimiter = class {
|
|
184
|
+
constructor(maxConcurrent) {
|
|
185
|
+
this.maxConcurrent = maxConcurrent;
|
|
186
|
+
}
|
|
187
|
+
active = 0;
|
|
188
|
+
queue = [];
|
|
189
|
+
/** Acquire a slot. Resolves when a slot is available. */
|
|
190
|
+
async acquire() {
|
|
191
|
+
if (this.active < this.maxConcurrent) {
|
|
192
|
+
this.active++;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
return new Promise((resolve) => {
|
|
196
|
+
this.queue.push(() => {
|
|
197
|
+
this.active++;
|
|
198
|
+
resolve();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
/** Release a slot, unblocking the next waiter if any. */
|
|
203
|
+
release() {
|
|
204
|
+
this.active--;
|
|
205
|
+
const next = this.queue.shift();
|
|
206
|
+
if (next) {
|
|
207
|
+
next();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Run an async function within the concurrency limit. */
|
|
211
|
+
async run(fn) {
|
|
212
|
+
await this.acquire();
|
|
213
|
+
try {
|
|
214
|
+
return await fn();
|
|
215
|
+
} finally {
|
|
216
|
+
this.release();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** Number of currently active tasks. */
|
|
220
|
+
get activeCount() {
|
|
221
|
+
return this.active;
|
|
222
|
+
}
|
|
223
|
+
/** Number of tasks waiting for a slot. */
|
|
224
|
+
get waitingCount() {
|
|
225
|
+
return this.queue.length;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// src/http/retry.ts
|
|
230
|
+
async function withRetry(fn, options) {
|
|
231
|
+
const { maxRetries, baseDelay = 500, maxDelay = 3e4 } = options;
|
|
232
|
+
let lastError;
|
|
233
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
234
|
+
try {
|
|
235
|
+
return await fn();
|
|
236
|
+
} catch (error) {
|
|
237
|
+
lastError = error;
|
|
238
|
+
if (attempt >= maxRetries) break;
|
|
239
|
+
if (error instanceof NetSuiteError && error.code === "AUTH_ERROR") {
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
if (error instanceof NetSuiteError && error.status != null && !isRetryableStatus(error.status)) {
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
let delay;
|
|
246
|
+
if (error instanceof RateLimitError) {
|
|
247
|
+
delay = error.retryAfterMs;
|
|
248
|
+
} else {
|
|
249
|
+
const exponential = baseDelay * Math.pow(2, attempt);
|
|
250
|
+
delay = Math.min(exponential, maxDelay) * Math.random();
|
|
251
|
+
}
|
|
252
|
+
await sleep(delay);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
throw lastError;
|
|
256
|
+
}
|
|
257
|
+
function sleep(ms) {
|
|
258
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/http/client.ts
|
|
262
|
+
var NetSuiteClient = class {
|
|
263
|
+
config;
|
|
264
|
+
rateLimiter;
|
|
265
|
+
constructor(config) {
|
|
266
|
+
this.config = resolveConfig(config);
|
|
267
|
+
this.rateLimiter = new RateLimiter(this.config.concurrency);
|
|
268
|
+
}
|
|
269
|
+
/** Make an authenticated request to the NetSuite REST API. */
|
|
270
|
+
async request(options) {
|
|
271
|
+
return this.rateLimiter.run(
|
|
272
|
+
() => withRetry(
|
|
273
|
+
() => this.executeRequest(options),
|
|
274
|
+
{ maxRetries: this.config.maxRetries }
|
|
275
|
+
)
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
/** Get the resolved base URL. */
|
|
279
|
+
get baseUrl() {
|
|
280
|
+
return this.config.baseUrl;
|
|
281
|
+
}
|
|
282
|
+
/** Get the resolved config (read-only). */
|
|
283
|
+
get resolvedConfig() {
|
|
284
|
+
return this.config;
|
|
285
|
+
}
|
|
286
|
+
async executeRequest(options) {
|
|
287
|
+
const url = this.buildUrl(options.path, options.query);
|
|
288
|
+
const realm = getRealm(this.config.accountId);
|
|
289
|
+
const authHeader = buildAuthorizationHeader({
|
|
290
|
+
consumerKey: this.config.consumerKey,
|
|
291
|
+
consumerSecret: this.config.consumerSecret,
|
|
292
|
+
tokenId: this.config.tokenId,
|
|
293
|
+
tokenSecret: this.config.tokenSecret,
|
|
294
|
+
realm,
|
|
295
|
+
method: options.method,
|
|
296
|
+
url
|
|
297
|
+
});
|
|
298
|
+
const headers = {
|
|
299
|
+
Authorization: authHeader,
|
|
300
|
+
"Content-Type": "application/json",
|
|
301
|
+
Accept: "application/json",
|
|
302
|
+
...options.headers
|
|
303
|
+
};
|
|
304
|
+
const controller = new AbortController();
|
|
305
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
306
|
+
try {
|
|
307
|
+
const response = await fetch(url, {
|
|
308
|
+
method: options.method,
|
|
309
|
+
headers,
|
|
310
|
+
body: options.body != null ? JSON.stringify(options.body) : void 0,
|
|
311
|
+
signal: controller.signal
|
|
312
|
+
});
|
|
313
|
+
clearTimeout(timeoutId);
|
|
314
|
+
if (!response.ok) {
|
|
315
|
+
await this.handleErrorResponse(response);
|
|
316
|
+
}
|
|
317
|
+
const data = await response.json();
|
|
318
|
+
return { status: response.status, headers: response.headers, data };
|
|
319
|
+
} catch (error) {
|
|
320
|
+
clearTimeout(timeoutId);
|
|
321
|
+
if (error instanceof NetSuiteError) throw error;
|
|
322
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
323
|
+
throw new TimeoutError(this.config.timeout);
|
|
324
|
+
}
|
|
325
|
+
throw new NetSuiteError(
|
|
326
|
+
`Network error: ${error instanceof Error ? error.message : String(error)}`,
|
|
327
|
+
void 0,
|
|
328
|
+
"NETWORK_ERROR"
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
buildUrl(path, query) {
|
|
333
|
+
const url = new URL(path, this.config.baseUrl);
|
|
334
|
+
if (query) {
|
|
335
|
+
for (const [key, value] of Object.entries(query)) {
|
|
336
|
+
url.searchParams.set(key, value);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return url.toString();
|
|
340
|
+
}
|
|
341
|
+
async handleErrorResponse(response) {
|
|
342
|
+
let body;
|
|
343
|
+
try {
|
|
344
|
+
body = await response.json();
|
|
345
|
+
} catch {
|
|
346
|
+
body = await response.text().catch(() => void 0);
|
|
347
|
+
}
|
|
348
|
+
if (response.status === 401 || response.status === 403) {
|
|
349
|
+
throw new AuthError(
|
|
350
|
+
`Authentication failed: ${response.status} ${response.statusText}`,
|
|
351
|
+
response.status,
|
|
352
|
+
body
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
if (response.status === 429) {
|
|
356
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
357
|
+
const retryMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : 5e3;
|
|
358
|
+
throw new RateLimitError(retryMs, body);
|
|
359
|
+
}
|
|
360
|
+
throw new NetSuiteError(
|
|
361
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
362
|
+
response.status,
|
|
363
|
+
void 0,
|
|
364
|
+
body
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// src/suiteql/executor.ts
|
|
370
|
+
var SUITEQL_PATH = "/services/rest/query/v1/suiteql";
|
|
371
|
+
async function executeSuiteQL(client, query, options) {
|
|
372
|
+
const limit = options?.limit ?? 1e3;
|
|
373
|
+
const offset = options?.offset ?? 0;
|
|
374
|
+
const response = await client.request({
|
|
375
|
+
method: "POST",
|
|
376
|
+
path: SUITEQL_PATH,
|
|
377
|
+
body: { q: query },
|
|
378
|
+
headers: {
|
|
379
|
+
Prefer: "transient"
|
|
380
|
+
},
|
|
381
|
+
query: {
|
|
382
|
+
limit: limit.toString(),
|
|
383
|
+
offset: offset.toString()
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
return {
|
|
387
|
+
items: response.data.items,
|
|
388
|
+
totalResults: response.data.totalResults,
|
|
389
|
+
hasMore: response.data.hasMore
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/suiteql/paginator.ts
|
|
394
|
+
async function executeSuiteQLPaginated(client, query, options) {
|
|
395
|
+
const pageSize = options?.pageSize ?? 1e3;
|
|
396
|
+
const maxRows = options?.maxRows ?? Infinity;
|
|
397
|
+
const allItems = [];
|
|
398
|
+
let offset = 0;
|
|
399
|
+
while (allItems.length < maxRows) {
|
|
400
|
+
const limit = Math.min(pageSize, maxRows - allItems.length);
|
|
401
|
+
const result = await executeSuiteQL(client, query, { limit, offset });
|
|
402
|
+
allItems.push(...result.items);
|
|
403
|
+
if (!result.hasMore || result.items.length === 0) {
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
offset += result.items.length;
|
|
407
|
+
}
|
|
408
|
+
return allItems;
|
|
409
|
+
}
|
|
410
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
411
|
+
0 && (module.exports = {
|
|
412
|
+
AuthError,
|
|
413
|
+
NetSuiteClient,
|
|
414
|
+
NetSuiteError,
|
|
415
|
+
RateLimitError,
|
|
416
|
+
RateLimiter,
|
|
417
|
+
TimeoutError,
|
|
418
|
+
buildAuthorizationHeader,
|
|
419
|
+
buildSignatureBaseString,
|
|
420
|
+
deriveBaseUrl,
|
|
421
|
+
executeSuiteQL,
|
|
422
|
+
executeSuiteQLPaginated,
|
|
423
|
+
generateNonce,
|
|
424
|
+
generateOAuthSignature,
|
|
425
|
+
generateTimestamp,
|
|
426
|
+
getRealm,
|
|
427
|
+
isRetryableStatus,
|
|
428
|
+
percentEncode,
|
|
429
|
+
resolveConfig,
|
|
430
|
+
signHmacSha256,
|
|
431
|
+
withRetry
|
|
432
|
+
});
|
|
433
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/auth/oauth.ts","../src/auth/headers.ts","../src/config.ts","../src/http/errors.ts","../src/http/rate-limiter.ts","../src/http/retry.ts","../src/http/client.ts","../src/suiteql/executor.ts","../src/suiteql/paginator.ts"],"sourcesContent":["// Core client\nexport { NetSuiteClient } from './http/client.js';\n\n// SuiteQL\nexport { executeSuiteQL } from './suiteql/executor.js';\nexport { executeSuiteQLPaginated } from './suiteql/paginator.js';\nexport type { PaginationOptions } from './suiteql/paginator.js';\n\n// Auth (exposed for advanced use / testing)\nexport { buildAuthorizationHeader } from './auth/headers.js';\nexport {\n generateOAuthSignature,\n percentEncode,\n buildSignatureBaseString,\n signHmacSha256,\n generateNonce,\n generateTimestamp,\n} from './auth/oauth.js';\n\n// Config\nexport { resolveConfig, deriveBaseUrl, getRealm } from './config.js';\nexport type { ResolvedConfig } from './config.js';\n\n// Errors\nexport {\n NetSuiteError,\n AuthError,\n RateLimitError,\n TimeoutError,\n isRetryableStatus,\n} from './http/errors.js';\n\n// Rate limiter\nexport { RateLimiter } from './http/rate-limiter.js';\n\n// Retry\nexport { withRetry } from './http/retry.js';\nexport type { RetryOptions } from './http/retry.js';\n\n// Types\nexport type {\n NetSuiteConfig,\n SuiteQLRow,\n SuiteQLResult,\n SuiteQLResponse,\n OAuthParams,\n RequestOptions,\n NetSuiteResponse,\n} from './types.js';\n","import { createHmac, randomBytes } from 'node:crypto';\nimport type { OAuthParams } from '../types.js';\n\n/**\n * RFC 5849 §3.6 percent-encoding.\n * Encodes all characters except unreserved (ALPHA, DIGIT, '-', '.', '_', '~').\n */\nexport function percentEncode(str: string): string {\n return encodeURIComponent(str).replace(/[!'()*]/g, (c) => {\n return `%${c.charCodeAt(0).toString(16).toUpperCase()}`;\n });\n}\n\n/** Generate a random nonce for OAuth requests. */\nexport function generateNonce(): string {\n return randomBytes(16).toString('hex');\n}\n\n/** Generate a Unix timestamp string. */\nexport function generateTimestamp(): string {\n return Math.floor(Date.now() / 1000).toString();\n}\n\n/**\n * Build the OAuth signature base string per RFC 5849 §3.4.1.\n */\nexport function buildSignatureBaseString(\n method: string,\n baseUrl: string,\n params: Array<[string, string]>,\n): string {\n // Sort params lexicographically by key, then by value\n const sorted = [...params].sort((a, b) => {\n const keyCompare = a[0]!.localeCompare(b[0]!);\n return keyCompare !== 0 ? keyCompare : a[1]!.localeCompare(b[1]!);\n });\n\n const paramString = sorted\n .map(([k, v]) => `${percentEncode(k!)}=${percentEncode(v!)}`)\n .join('&');\n\n return [\n method.toUpperCase(),\n percentEncode(baseUrl),\n percentEncode(paramString),\n ].join('&');\n}\n\n/**\n * Sign the base string with HMAC-SHA256.\n * Signing key = percentEncode(consumerSecret) & percentEncode(tokenSecret)\n */\nexport function signHmacSha256(\n baseString: string,\n consumerSecret: string,\n tokenSecret: string,\n): string {\n const signingKey = `${percentEncode(consumerSecret)}&${percentEncode(tokenSecret)}`;\n return createHmac('sha256', signingKey).update(baseString).digest('base64');\n}\n\n/**\n * Generate the full OAuth 1.0a signature for a request.\n * Returns the signature string and the oauth params used (for header construction).\n */\nexport function generateOAuthSignature(params: OAuthParams): {\n signature: string;\n oauthParams: Record<string, string>;\n} {\n const timestamp = params.timestamp ?? generateTimestamp();\n const nonce = params.nonce ?? generateNonce();\n\n const oauthParams: Record<string, string> = {\n oauth_consumer_key: params.consumerKey,\n oauth_token: params.tokenId,\n oauth_nonce: nonce,\n oauth_timestamp: timestamp,\n oauth_signature_method: 'HMAC-SHA256',\n oauth_version: '1.0',\n };\n\n // Strip query string from URL for base string\n const urlObj = new URL(params.url);\n const baseUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}`;\n\n // Collect all params: oauth params + query string params\n const allParams: Array<[string, string]> = Object.entries(oauthParams);\n urlObj.searchParams.forEach((value, key) => {\n allParams.push([key, value]);\n });\n\n const baseString = buildSignatureBaseString(params.method, baseUrl, allParams);\n const signature = signHmacSha256(baseString, params.consumerSecret, params.tokenSecret);\n\n return { signature, oauthParams };\n}\n","import { generateOAuthSignature, percentEncode } from './oauth.js';\nimport type { OAuthParams } from '../types.js';\n\n/**\n * Build the OAuth Authorization header value.\n * Format: OAuth realm=\"...\", oauth_consumer_key=\"...\", ..., oauth_signature=\"...\"\n */\nexport function buildAuthorizationHeader(params: OAuthParams): string {\n const { signature, oauthParams } = generateOAuthSignature(params);\n\n const headerParams: Record<string, string> = {\n realm: params.realm,\n ...oauthParams,\n oauth_signature: signature,\n };\n\n const parts = Object.entries(headerParams)\n .map(([key, value]) => `${percentEncode(key)}=\"${percentEncode(value)}\"`)\n .join(', ');\n\n return `OAuth ${parts}`;\n}\n","import type { NetSuiteConfig } from './types.js';\n\nconst REQUIRED_FIELDS = [\n 'accountId',\n 'consumerKey',\n 'consumerSecret',\n 'tokenId',\n 'tokenSecret',\n] as const;\n\nconst DEFAULTS = {\n timeout: 30_000,\n concurrency: 5,\n maxRetries: 3,\n} as const;\n\nexport interface ResolvedConfig extends Required<Omit<NetSuiteConfig, 'baseUrl'>> {\n baseUrl: string;\n}\n\n/** Validate config and fill defaults. */\nexport function resolveConfig(config: NetSuiteConfig): ResolvedConfig {\n for (const field of REQUIRED_FIELDS) {\n if (!config[field]) {\n throw new Error(`NetSuiteConfig: \"${field}\" is required`);\n }\n }\n\n return {\n accountId: config.accountId,\n consumerKey: config.consumerKey,\n consumerSecret: config.consumerSecret,\n tokenId: config.tokenId,\n tokenSecret: config.tokenSecret,\n timeout: config.timeout ?? DEFAULTS.timeout,\n concurrency: config.concurrency ?? DEFAULTS.concurrency,\n maxRetries: config.maxRetries ?? DEFAULTS.maxRetries,\n baseUrl: config.baseUrl ?? deriveBaseUrl(config.accountId),\n };\n}\n\n/**\n * Derive the NetSuite REST API base URL from the account ID.\n * Account IDs with underscores (sandbox) have underscores replaced with hyphens.\n * Example: \"1234567_SB1\" → \"https://1234567-sb1.suitetalk.api.netsuite.com\"\n */\nexport function deriveBaseUrl(accountId: string): string {\n const normalized = accountId.toLowerCase().replace(/_/g, '-');\n return `https://${normalized}.suitetalk.api.netsuite.com`;\n}\n\n/** Get the realm (account ID in uppercase, underscores preserved). */\nexport function getRealm(accountId: string): string {\n return accountId.toUpperCase().replace(/-/g, '_');\n}\n","/** Base error class for all NetSuite connector errors. */\nexport class NetSuiteError extends Error {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly code?: string,\n public readonly details?: unknown,\n ) {\n super(message);\n this.name = 'NetSuiteError';\n }\n}\n\n/** Authentication or authorization failure. */\nexport class AuthError extends NetSuiteError {\n constructor(message: string, status?: number, details?: unknown) {\n super(message, status, 'AUTH_ERROR', details);\n this.name = 'AuthError';\n }\n}\n\n/** Rate limit (429) exceeded. */\nexport class RateLimitError extends NetSuiteError {\n constructor(\n public readonly retryAfterMs: number,\n details?: unknown,\n ) {\n super(`Rate limited. Retry after ${retryAfterMs}ms`, 429, 'RATE_LIMIT', details);\n this.name = 'RateLimitError';\n }\n}\n\n/** Request timeout via AbortController. */\nexport class TimeoutError extends NetSuiteError {\n constructor(timeoutMs: number) {\n super(`Request timed out after ${timeoutMs}ms`, undefined, 'TIMEOUT');\n this.name = 'TimeoutError';\n }\n}\n\n/** HTTP status codes that are safe to retry. */\nconst RETRYABLE_STATUSES = new Set([408, 429, 502, 503, 504]);\n\n/** Check if an HTTP status code is retryable. */\nexport function isRetryableStatus(status: number): boolean {\n return RETRYABLE_STATUSES.has(status);\n}\n","/**\n * Async semaphore for concurrency governance.\n * Limits the number of in-flight requests to prevent overwhelming NetSuite.\n */\nexport class RateLimiter {\n private active = 0;\n private queue: Array<() => void> = [];\n\n constructor(private readonly maxConcurrent: number) {}\n\n /** Acquire a slot. Resolves when a slot is available. */\n async acquire(): Promise<void> {\n if (this.active < this.maxConcurrent) {\n this.active++;\n return;\n }\n\n return new Promise<void>((resolve) => {\n this.queue.push(() => {\n this.active++;\n resolve();\n });\n });\n }\n\n /** Release a slot, unblocking the next waiter if any. */\n release(): void {\n this.active--;\n const next = this.queue.shift();\n if (next) {\n next();\n }\n }\n\n /** Run an async function within the concurrency limit. */\n async run<T>(fn: () => Promise<T>): Promise<T> {\n await this.acquire();\n try {\n return await fn();\n } finally {\n this.release();\n }\n }\n\n /** Number of currently active tasks. */\n get activeCount(): number {\n return this.active;\n }\n\n /** Number of tasks waiting for a slot. */\n get waitingCount(): number {\n return this.queue.length;\n }\n}\n","import { RateLimitError, isRetryableStatus, NetSuiteError } from './errors.js';\n\nexport interface RetryOptions {\n maxRetries: number;\n /** Base delay in ms before first retry. Default: 500. */\n baseDelay?: number;\n /** Maximum delay in ms. Default: 30000. */\n maxDelay?: number;\n}\n\n/**\n * Execute a function with exponential backoff + jitter.\n * Respects `Retry-After` from RateLimitError.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n options: RetryOptions,\n): Promise<T> {\n const { maxRetries, baseDelay = 500, maxDelay = 30_000 } = options;\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (attempt >= maxRetries) break;\n\n // Don't retry auth errors — they won't resolve by retrying\n if (error instanceof NetSuiteError && error.code === 'AUTH_ERROR') {\n throw error;\n }\n\n // Only retry on retryable status codes or network errors\n if (error instanceof NetSuiteError && error.status != null && !isRetryableStatus(error.status)) {\n throw error;\n }\n\n let delay: number;\n if (error instanceof RateLimitError) {\n delay = error.retryAfterMs;\n } else {\n // Exponential backoff with full jitter\n const exponential = baseDelay * Math.pow(2, attempt);\n delay = Math.min(exponential, maxDelay) * Math.random();\n }\n\n await sleep(delay);\n }\n }\n\n throw lastError;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { buildAuthorizationHeader } from '../auth/headers.js';\nimport { resolveConfig, getRealm, type ResolvedConfig } from '../config.js';\nimport type { NetSuiteConfig, RequestOptions, NetSuiteResponse } from '../types.js';\nimport { NetSuiteError, AuthError, RateLimitError, TimeoutError } from './errors.js';\nimport { RateLimiter } from './rate-limiter.js';\nimport { withRetry } from './retry.js';\n\n/**\n * Core HTTP client for the NetSuite REST API.\n * Handles OAuth signing, retries, rate limiting, and timeouts.\n */\nexport class NetSuiteClient {\n private readonly config: ResolvedConfig;\n private readonly rateLimiter: RateLimiter;\n\n constructor(config: NetSuiteConfig) {\n this.config = resolveConfig(config);\n this.rateLimiter = new RateLimiter(this.config.concurrency);\n }\n\n /** Make an authenticated request to the NetSuite REST API. */\n async request<T = unknown>(options: RequestOptions): Promise<NetSuiteResponse<T>> {\n return this.rateLimiter.run(() =>\n withRetry(\n () => this.executeRequest<T>(options),\n { maxRetries: this.config.maxRetries },\n ),\n );\n }\n\n /** Get the resolved base URL. */\n get baseUrl(): string {\n return this.config.baseUrl;\n }\n\n /** Get the resolved config (read-only). */\n get resolvedConfig(): Readonly<ResolvedConfig> {\n return this.config;\n }\n\n private async executeRequest<T>(options: RequestOptions): Promise<NetSuiteResponse<T>> {\n const url = this.buildUrl(options.path, options.query);\n const realm = getRealm(this.config.accountId);\n\n const authHeader = buildAuthorizationHeader({\n consumerKey: this.config.consumerKey,\n consumerSecret: this.config.consumerSecret,\n tokenId: this.config.tokenId,\n tokenSecret: this.config.tokenSecret,\n realm,\n method: options.method,\n url,\n });\n\n const headers: Record<string, string> = {\n Authorization: authHeader,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...options.headers,\n };\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const response = await fetch(url, {\n method: options.method,\n headers,\n body: options.body != null ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n const data = (await response.json()) as T;\n return { status: response.status, headers: response.headers, data };\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof NetSuiteError) throw error;\n\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new TimeoutError(this.config.timeout);\n }\n\n throw new NetSuiteError(\n `Network error: ${error instanceof Error ? error.message : String(error)}`,\n undefined,\n 'NETWORK_ERROR',\n );\n }\n }\n\n private buildUrl(path: string, query?: Record<string, string>): string {\n const url = new URL(path, this.config.baseUrl);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n url.searchParams.set(key, value);\n }\n }\n return url.toString();\n }\n\n private async handleErrorResponse(response: Response): Promise<never> {\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n body = await response.text().catch(() => undefined);\n }\n\n if (response.status === 401 || response.status === 403) {\n throw new AuthError(\n `Authentication failed: ${response.status} ${response.statusText}`,\n response.status,\n body,\n );\n }\n\n if (response.status === 429) {\n const retryAfter = response.headers.get('Retry-After');\n const retryMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : 5000;\n throw new RateLimitError(retryMs, body);\n }\n\n throw new NetSuiteError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n undefined,\n body,\n );\n }\n}\n","import type { NetSuiteClient } from '../http/client.js';\nimport type { SuiteQLResult, SuiteQLResponse, SuiteQLRow } from '../types.js';\n\nconst SUITEQL_PATH = '/services/rest/query/v1/suiteql';\n\n/**\n * Execute a SuiteQL query and return the first page of results.\n */\nexport async function executeSuiteQL<T = SuiteQLRow>(\n client: NetSuiteClient,\n query: string,\n options?: { limit?: number; offset?: number },\n): Promise<SuiteQLResult<T>> {\n const limit = options?.limit ?? 1000;\n const offset = options?.offset ?? 0;\n\n const response = await client.request<SuiteQLResponse>({\n method: 'POST',\n path: SUITEQL_PATH,\n body: { q: query },\n headers: {\n Prefer: 'transient',\n },\n query: {\n limit: limit.toString(),\n offset: offset.toString(),\n },\n });\n\n return {\n items: response.data.items as T[],\n totalResults: response.data.totalResults,\n hasMore: response.data.hasMore,\n };\n}\n","import type { NetSuiteClient } from '../http/client.js';\nimport type { SuiteQLRow } from '../types.js';\nimport { executeSuiteQL } from './executor.js';\n\nexport interface PaginationOptions {\n /** Page size per request. Default: 1000. */\n pageSize?: number;\n /** Maximum total rows to fetch. Default: unlimited. */\n maxRows?: number;\n}\n\n/**\n * Execute a SuiteQL query and automatically paginate through all results.\n * Collects all pages into a single array.\n */\nexport async function executeSuiteQLPaginated<T = SuiteQLRow>(\n client: NetSuiteClient,\n query: string,\n options?: PaginationOptions,\n): Promise<T[]> {\n const pageSize = options?.pageSize ?? 1000;\n const maxRows = options?.maxRows ?? Infinity;\n const allItems: T[] = [];\n let offset = 0;\n\n while (allItems.length < maxRows) {\n const limit = Math.min(pageSize, maxRows - allItems.length);\n const result = await executeSuiteQL<T>(client, query, { limit, offset });\n\n allItems.push(...result.items);\n\n if (!result.hasMore || result.items.length === 0) {\n break;\n }\n\n offset += result.items.length;\n }\n\n return allItems;\n}\n"],"mappings":"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","names":[]}
|