netsuite-sdk 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/LICENSE +21 -0
- package/README.md +258 -0
- package/dist/index.d.mts +457 -0
- package/dist/index.d.ts +457 -0
- package/dist/index.js +839 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +819 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 David Turton
|
|
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,258 @@
|
|
|
1
|
+
# netsuite-sdk
|
|
2
|
+
|
|
3
|
+
TypeScript-first NetSuite REST API client with first-class SuiteQL support, OAuth 1.0a authentication, automatic retries, and a fluent query builder.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **SuiteQL** — `query()`, `queryOne()`, and `queryPages()` with auto-pagination
|
|
8
|
+
- **Fluent query builder** — type-safe SQL construction with value escaping
|
|
9
|
+
- **REST Record API** — full CRUD: `get`, `list`, `create`, `update`, `replace`, `delete`, `upsert`
|
|
10
|
+
- **RESTlet support** — auto-built URLs from script/deploy IDs
|
|
11
|
+
- **OAuth 1.0a (TBA)** — HMAC-SHA256 signing, fresh nonce on each retry
|
|
12
|
+
- **Automatic retries** — exponential backoff with jitter for 5xx/timeout/network errors
|
|
13
|
+
- **Middleware** — extensible request/response pipeline
|
|
14
|
+
- **TypeScript-first** — strict mode, full type inference, generics everywhere
|
|
15
|
+
- **Dual format** — ships both CJS and ESM with `.d.ts` declarations
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install netsuite-sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { NetSuiteClient } from 'netsuite-sdk';
|
|
27
|
+
|
|
28
|
+
const client = new NetSuiteClient({
|
|
29
|
+
auth: {
|
|
30
|
+
consumerKey: 'your-consumer-key',
|
|
31
|
+
consumerSecret: 'your-consumer-secret',
|
|
32
|
+
tokenKey: 'your-token-key',
|
|
33
|
+
tokenSecret: 'your-token-secret',
|
|
34
|
+
realm: 'your-account-id',
|
|
35
|
+
},
|
|
36
|
+
accountId: 'your-account-id', // e.g., "1234567" or "1234567_SB1" for sandbox
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## SuiteQL
|
|
41
|
+
|
|
42
|
+
### Raw SQL queries
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
interface Customer {
|
|
46
|
+
id: string;
|
|
47
|
+
companyname: string;
|
|
48
|
+
email: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Auto-paginates and returns all rows
|
|
52
|
+
const result = await client.suiteql.query<Customer>(
|
|
53
|
+
"SELECT id, companyname, email FROM customer WHERE isinactive = 'F'"
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
console.log(result.items); // Customer[]
|
|
57
|
+
console.log(result.totalResults); // total count from NetSuite
|
|
58
|
+
console.log(result.pagesFetched); // number of API calls made
|
|
59
|
+
console.log(result.duration); // total ms
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Get a single row
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const customer = await client.suiteql.queryOne<Customer>(
|
|
66
|
+
'SELECT id, companyname FROM customer WHERE id = 123'
|
|
67
|
+
);
|
|
68
|
+
// Returns Customer | null
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Stream large result sets
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
for await (const page of client.suiteql.queryPages<Customer>(
|
|
75
|
+
'SELECT id, companyname FROM customer',
|
|
76
|
+
{ pageSize: 500 }
|
|
77
|
+
)) {
|
|
78
|
+
await processBatch(page); // page is Customer[]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Query builder
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { suiteql } from 'netsuite-sdk';
|
|
86
|
+
|
|
87
|
+
const sql = suiteql()
|
|
88
|
+
.select('c.id', 'c.companyname', 'COUNT(t.id) AS order_count')
|
|
89
|
+
.from('customer', 'c')
|
|
90
|
+
.leftJoin('transaction t', 'c.id = t.entity')
|
|
91
|
+
.whereEquals('c.isinactive', false) // false → 'F' automatically
|
|
92
|
+
.whereNotNull('c.email')
|
|
93
|
+
.whereIn('c.subsidiary', [1, 2, 3])
|
|
94
|
+
.groupBy('c.id', 'c.companyname')
|
|
95
|
+
.having('COUNT(t.id) > 0')
|
|
96
|
+
.orderBy('order_count', 'DESC')
|
|
97
|
+
.build();
|
|
98
|
+
|
|
99
|
+
const result = await client.suiteql.query(sql);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Builder methods: `select()`, `from()`, `join()`, `leftJoin()`, `rightJoin()`, `where()`, `whereEquals()`, `whereNotEquals()`, `whereIn()`, `whereNull()`, `whereNotNull()`, `whereBetween()`, `whereLike()`, `groupBy()`, `having()`, `orderBy()`.
|
|
103
|
+
|
|
104
|
+
String values are automatically escaped (single quotes doubled).
|
|
105
|
+
|
|
106
|
+
### SuiteQL Options
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
await client.suiteql.query(sql, {
|
|
110
|
+
pageSize: 500, // rows per page (default: 1000, max: 1000)
|
|
111
|
+
offset: 0, // starting offset (default: 0)
|
|
112
|
+
maxRows: 10000, // cap total rows fetched (default: Infinity)
|
|
113
|
+
timeout: 60000, // override timeout for this query
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## REST Record API
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// Get a record
|
|
121
|
+
const customer = await client.records.get('customer', 123, {
|
|
122
|
+
fields: ['companyname', 'email'],
|
|
123
|
+
expandSubResources: true,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// List records
|
|
127
|
+
const list = await client.records.list('invoice', {
|
|
128
|
+
limit: 25,
|
|
129
|
+
offset: 0,
|
|
130
|
+
fields: ['tranid', 'total'],
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Create
|
|
134
|
+
await client.records.create('customer', {
|
|
135
|
+
companyname: 'Acme Corp',
|
|
136
|
+
email: 'info@acme.com',
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Update (PATCH — partial)
|
|
140
|
+
await client.records.update('customer', 123, { email: 'new@acme.com' });
|
|
141
|
+
|
|
142
|
+
// Replace (PUT — full)
|
|
143
|
+
await client.records.replace('customer', 123, { companyname: 'New Name' });
|
|
144
|
+
|
|
145
|
+
// Delete
|
|
146
|
+
await client.records.delete('customer', 123);
|
|
147
|
+
|
|
148
|
+
// Upsert via external ID
|
|
149
|
+
await client.records.upsert('customer', 'externalId', 'CRM-001', {
|
|
150
|
+
companyname: 'Upserted Corp',
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## RESTlets
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const result = await client.restlets.call(
|
|
158
|
+
{ script: '100', deploy: '1', params: { action: 'search' } },
|
|
159
|
+
{ method: 'POST', body: { type: 'customer' } }
|
|
160
|
+
);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Raw HTTP
|
|
164
|
+
|
|
165
|
+
Escape hatch for custom endpoints:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const res = await client.get<MyType>('https://1234567.suitetalk.api.netsuite.com/custom/endpoint');
|
|
169
|
+
const res = await client.post<MyType>(url, body);
|
|
170
|
+
const res = await client.put<MyType>(url, body);
|
|
171
|
+
const res = await client.patch<MyType>(url, body);
|
|
172
|
+
const res = await client.delete(url);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Middleware
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// Logging middleware
|
|
179
|
+
client.use(async (ctx, next) => {
|
|
180
|
+
console.log(`→ ${ctx.method} ${ctx.url}`);
|
|
181
|
+
const res = await next();
|
|
182
|
+
console.log(`← ${res.status} (${res.duration}ms)`);
|
|
183
|
+
return res;
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Custom header injection
|
|
187
|
+
client.use(async (ctx, next) => {
|
|
188
|
+
ctx.headers['X-Custom-Header'] = 'value';
|
|
189
|
+
return next();
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Error Handling
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { NetSuiteError } from 'netsuite-sdk';
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
await client.records.get('customer', 999);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (error instanceof NetSuiteError) {
|
|
202
|
+
error.status; // HTTP status code
|
|
203
|
+
error.code; // NetSuite error code (e.g., "RCRD_DSNT_EXIST")
|
|
204
|
+
error.message; // Human-readable message
|
|
205
|
+
error.details; // Full NetSuite error response body
|
|
206
|
+
error.requestUrl; // URL that failed
|
|
207
|
+
error.requestMethod; // HTTP method that failed
|
|
208
|
+
error.isRetryable; // true for 5xx, timeout, network errors
|
|
209
|
+
error.isAuthError; // true for 401, 403
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Configuration
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const client = new NetSuiteClient({
|
|
218
|
+
auth: {
|
|
219
|
+
consumerKey: '...',
|
|
220
|
+
consumerSecret: '...',
|
|
221
|
+
tokenKey: '...',
|
|
222
|
+
tokenSecret: '...',
|
|
223
|
+
realm: '...',
|
|
224
|
+
},
|
|
225
|
+
accountId: '1234567', // or "1234567_SB1" for sandbox
|
|
226
|
+
timeout: 30000, // request timeout in ms (default: 30000)
|
|
227
|
+
maxRetries: 3, // retry attempts for transient errors (default: 3)
|
|
228
|
+
retryDelay: 1000, // initial retry delay in ms (default: 1000)
|
|
229
|
+
defaultHeaders: { // headers added to every request
|
|
230
|
+
'X-App-Name': 'my-app',
|
|
231
|
+
},
|
|
232
|
+
logger: console, // any object with debug/info/warn/error methods
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Utilities
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
import {
|
|
240
|
+
ResponseCache,
|
|
241
|
+
createCacheKey,
|
|
242
|
+
RateLimiter,
|
|
243
|
+
validateConfig,
|
|
244
|
+
formatNetSuiteDate,
|
|
245
|
+
parseNetSuiteDate,
|
|
246
|
+
parseNetSuiteError,
|
|
247
|
+
normalizeAccountId,
|
|
248
|
+
} from 'netsuite-sdk';
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Requirements
|
|
252
|
+
|
|
253
|
+
- Node.js >= 20
|
|
254
|
+
- NetSuite account with Token-Based Authentication (TBA) enabled
|
|
255
|
+
|
|
256
|
+
## License
|
|
257
|
+
|
|
258
|
+
MIT
|