0

Execute SuiteQL

by
Published today

Run a SuiteQL query (NetSuite SQL) and return one page of rows.

Script netsuite Verified

The script

Submitted by hugo989 Typescript (fetch-only)
Verified 3 hours ago
1
//native
2

3
const percentEncode = (s: string) =>
4
  encodeURIComponent(s).replace(
5
    /[!'()*]/g,
6
    (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase()
7
  )
8

9
function restBase(auth: RT.Netsuite) {
10
  return `https://${auth.account_id.trim().toLowerCase()}.suitetalk.api.netsuite.com/services/rest`
11
}
12

13
// Bearer when an OAuth 2.0 token is set, otherwise TBA (OAuth 1.0a HMAC-SHA256).
14
async function authHeader(auth: RT.Netsuite, method: string, url: URL) {
15
  if (auth.token !== undefined && auth.token !== "") {
16
    return `Bearer ${auth.token}`
17
  }
18
  // Re-serialize the query with %20 instead of '+' for spaces so the URL on
19
  // the wire matches the form the signature base string encodes
20
  url.search = [...url.searchParams.entries()]
21
    .map(([k, v]) => `${percentEncode(k)}=${percentEncode(v)}`)
22
    .join("&")
23
  const nonce = Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) =>
24
    b.toString(16).padStart(2, "0")
25
  ).join("")
26
  const timestamp = Math.floor(Date.now() / 1000).toString()
27
  const oauthParams: [string, string][] = [
28
    ["oauth_consumer_key", auth.consumer_key ?? ""],
29
    ["oauth_nonce", nonce],
30
    ["oauth_signature_method", "HMAC-SHA256"],
31
    ["oauth_timestamp", timestamp],
32
    ["oauth_token", auth.token_id ?? ""],
33
    ["oauth_version", "1.0"],
34
  ]
35
  const sortedParams = [...url.searchParams.entries(), ...oauthParams]
36
    .map(([k, v]) => [percentEncode(k), percentEncode(v)])
37
    .sort(([ak, av], [bk, bv]) =>
38
      ak === bk ? (av < bv ? -1 : 1) : ak < bk ? -1 : 1
39
    )
40
    .map(([k, v]) => `${k}=${v}`)
41
    .join("&")
42
  const baseString = [
43
    method.toUpperCase(),
44
    percentEncode(`${url.protocol}//${url.host}${url.pathname}`),
45
    percentEncode(sortedParams),
46
  ].join("&")
47
  const key = await crypto.subtle.importKey(
48
    "raw",
49
    new TextEncoder().encode(
50
      `${percentEncode(auth.consumer_secret ?? "")}&${percentEncode(auth.token_secret ?? "")}`
51
    ),
52
    { name: "HMAC", hash: "SHA-256" },
53
    false,
54
    ["sign"]
55
  )
56
  const signature = btoa(
57
    String.fromCharCode(
58
      ...new Uint8Array(
59
        await crypto.subtle.sign(
60
          "HMAC",
61
          key,
62
          new TextEncoder().encode(baseString)
63
        )
64
      )
65
    )
66
  )
67
  const realm = auth.account_id.trim().toUpperCase().replace(/-/g, "_")
68
  return `OAuth realm="${realm}", oauth_consumer_key="${percentEncode(auth.consumer_key ?? "")}", oauth_token="${percentEncode(auth.token_id ?? "")}", oauth_signature_method="HMAC-SHA256", oauth_timestamp="${timestamp}", oauth_nonce="${nonce}", oauth_version="1.0", oauth_signature="${percentEncode(signature)}"`
69
}
70

71
/**
72
 * Execute SuiteQL
73
 * Run a SuiteQL query (NetSuite's SQL — e.g. SELECT id, entityid, email FROM customer WHERE email LIKE '%@example.com'). Returns one page of up to limit rows (max 1000); page with offset and the hasMore/totalResults response fields.
74
 */
75
export async function main(
76
  auth: RT.Netsuite,
77
  query: string,
78
  limit: number | undefined,
79
  offset: number | undefined
80
) {
81
  const url = new URL(`${restBase(auth)}/query/v1/suiteql`)
82
  if (limit !== undefined) {
83
    url.searchParams.append("limit", String(limit))
84
  }
85
  if (offset !== undefined) {
86
    url.searchParams.append("offset", String(offset))
87
  }
88
  const response = await fetch(url, {
89
    method: "POST",
90
    headers: {
91
      Authorization: await authHeader(auth, "POST", url),
92
      Prefer: "transient",
93
      "Content-Type": "application/json",
94
      Accept: "application/json",
95
    },
96
    body: JSON.stringify({ q: query }),
97
  })
98
  if (!response.ok) {
99
    throw new Error(`${response.status} ${await response.text()}`)
100
  }
101
  return await response.json()
102
}
103