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 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