@amerilux/netsuite-api 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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -0
  3. package/dist/client/apiClient.d.ts +57 -0
  4. package/dist/client/apiClient.js +96 -0
  5. package/dist/client/index.d.ts +8 -0
  6. package/dist/client/index.js +6 -0
  7. package/dist/index.d.ts +76 -0
  8. package/dist/index.js +9 -0
  9. package/dist/server/apiError.d.ts +9 -0
  10. package/dist/server/apiError.js +18 -0
  11. package/dist/server/defineRestlet.d.ts +15 -0
  12. package/dist/server/defineRestlet.js +4 -0
  13. package/dist/server/defineSuitelet.d.ts +16 -0
  14. package/dist/server/defineSuitelet.js +20 -0
  15. package/dist/server/endpoint.d.ts +59 -0
  16. package/dist/server/endpoint.js +95 -0
  17. package/dist/server/fileCabinet.d.ts +14 -0
  18. package/dist/server/fileCabinet.js +40 -0
  19. package/dist/server/index.d.ts +21 -0
  20. package/dist/server/index.js +14 -0
  21. package/dist/server/rawResponse.d.ts +39 -0
  22. package/dist/server/rawResponse.js +28 -0
  23. package/dist/server/suiteletClient.d.ts +23 -0
  24. package/dist/server/suiteletClient.js +54 -0
  25. package/dist/testing/N/error.d.ts +2 -0
  26. package/dist/testing/N/error.js +6 -0
  27. package/dist/testing/N/file.d.ts +9 -0
  28. package/dist/testing/N/file.js +6 -0
  29. package/dist/testing/N/format.d.ts +8 -0
  30. package/dist/testing/N/format.js +4 -0
  31. package/dist/testing/N/https.d.ts +15 -0
  32. package/dist/testing/N/https.js +10 -0
  33. package/dist/testing/N/log.d.ts +5 -0
  34. package/dist/testing/N/log.js +5 -0
  35. package/dist/testing/N/query.d.ts +7 -0
  36. package/dist/testing/N/query.js +7 -0
  37. package/dist/testing/N/record.d.ts +14 -0
  38. package/dist/testing/N/record.js +11 -0
  39. package/dist/testing/N/runtime.d.ts +11 -0
  40. package/dist/testing/N/runtime.js +8 -0
  41. package/dist/testing/N/search.d.ts +10 -0
  42. package/dist/testing/N/search.js +8 -0
  43. package/dist/testing/N/task.d.ts +7 -0
  44. package/dist/testing/N/task.js +4 -0
  45. package/dist/testing/N/ui/serverWidget.d.ts +7 -0
  46. package/dist/testing/N/ui/serverWidget.js +7 -0
  47. package/dist/testing/N/url.d.ts +9 -0
  48. package/dist/testing/N/url.js +6 -0
  49. package/dist/testing/index.d.ts +28 -0
  50. package/dist/testing/index.js +32 -0
  51. package/dist-tooling/appReader.d.ts +12 -0
  52. package/dist-tooling/appReader.js +31 -0
  53. package/dist-tooling/cli/arguments.d.ts +9 -0
  54. package/dist-tooling/cli/arguments.js +34 -0
  55. package/dist-tooling/cli/bin.d.ts +2 -0
  56. package/dist-tooling/cli/bin.js +7 -0
  57. package/dist-tooling/cli/main.d.ts +13 -0
  58. package/dist-tooling/cli/main.js +88 -0
  59. package/dist-tooling/config.d.ts +43 -0
  60. package/dist-tooling/config.js +77 -0
  61. package/dist-tooling/controllerReader.d.ts +68 -0
  62. package/dist-tooling/controllerReader.js +267 -0
  63. package/dist-tooling/emit.d.ts +29 -0
  64. package/dist-tooling/emit.js +79 -0
  65. package/dist-tooling/file-system.d.ts +15 -0
  66. package/dist-tooling/file-system.js +53 -0
  67. package/dist-tooling/generate.d.ts +36 -0
  68. package/dist-tooling/generate.js +128 -0
  69. package/dist-tooling/index.d.ts +15 -0
  70. package/dist-tooling/index.js +8 -0
  71. package/dist-tooling/scriptsReader.d.ts +17 -0
  72. package/dist-tooling/scriptsReader.js +57 -0
  73. package/package.json +77 -0
@@ -0,0 +1,95 @@
1
+ import * as log from 'N/log';
2
+ import { ENDPOINT_PARAMETER } from '../index.js';
3
+ import { ApiError } from './apiError.js';
4
+ import { isRawResponse } from './rawResponse.js';
5
+ /**
6
+ * Endpoints are the transport-agnostic unit of an API controller: named, each a synchronous function
7
+ * from a request to a response, declared together in the controller file with the request and
8
+ * response shapes they speak. The handler signatures are the contract: a client is built from
9
+ * `typeof <name>Endpoints` and calls each endpoint by name. Every call is a POST whose body names the
10
+ * endpoint. The controller file wraps the map as a Restlet (`defineRestlet`) or a Suitelet
11
+ * (`defineSuitelet`); switching transport is a change to that one statement and its SDF object, never
12
+ * to the endpoints.
13
+ */
14
+ /**
15
+ * Declares a controller's endpoints. Annotate each handler's parameter with its request type and its
16
+ * return value with its response type; both reach the clients through the type, and the client
17
+ * generator reads them from the annotations.
18
+ */
19
+ export function defineEndpoints(endpoints) {
20
+ return endpoints;
21
+ }
22
+ /** NetSuite hands a body as either an object or a JSON string. */
23
+ export function parseEndpointRequest(rawRequest) {
24
+ if (typeof rawRequest !== 'string')
25
+ return rawRequest !== null && rawRequest !== void 0 ? rawRequest : {};
26
+ const trimmed = rawRequest.trim();
27
+ if (trimmed === '')
28
+ return {};
29
+ try {
30
+ return JSON.parse(trimmed);
31
+ }
32
+ catch {
33
+ throw ApiError.badRequest('Request body is not valid JSON.');
34
+ }
35
+ }
36
+ /** Splits the endpoint name off the parsed body. */
37
+ export function readEndpointCall(parsedRequest) {
38
+ if (!parsedRequest || typeof parsedRequest !== 'object' || Array.isArray(parsedRequest)) {
39
+ throw ApiError.badRequest('The request must be an object.');
40
+ }
41
+ const { [ENDPOINT_PARAMETER]: name, ...request } = parsedRequest;
42
+ return { name: typeof name === 'string' && name !== '' ? name : undefined, request };
43
+ }
44
+ function describeError(error) {
45
+ if (error instanceof Error)
46
+ return { message: error.message, stack: error.stack };
47
+ return { message: String(error) };
48
+ }
49
+ function findEndpoint(controllerName, endpoints, call) {
50
+ if (call.name === undefined) {
51
+ throw ApiError.badRequest(`The ${ENDPOINT_PARAMETER} property is required.`, { controller: controllerName });
52
+ }
53
+ if (!Object.prototype.hasOwnProperty.call(endpoints, call.name)) {
54
+ throw ApiError.notFound(`${controllerName} has no endpoint named ${call.name}.`, { controller: controllerName, endpoint: call.name });
55
+ }
56
+ return endpoints[call.name];
57
+ }
58
+ /**
59
+ * Runs the endpoint the body names and produces the outcome: 200 with data (or a raw answer), an
60
+ * ApiError's own status and message (400 without an endpoint name, 404 for an unknown one, whatever
61
+ * the authorize hook threw), or 500 with the details logged. Audits the outcome and timing either
62
+ * way. Log titles are constant phrases; the controller, endpoint and ids live in the details object.
63
+ */
64
+ export function invokeEndpoint(controllerName, endpoints, rawRequest, options = {}) {
65
+ var _a;
66
+ const started = Date.now();
67
+ let status = 200;
68
+ let endpointName;
69
+ try {
70
+ const call = readEndpointCall(parseEndpointRequest(rawRequest));
71
+ endpointName = call.name;
72
+ const endpoint = findEndpoint(controllerName, endpoints, call);
73
+ (_a = options.authorize) === null || _a === void 0 ? void 0 : _a.call(options, { controller: controllerName, endpoint: call.name, request: call.request });
74
+ const data = endpoint(call.request);
75
+ if (isRawResponse(data)) {
76
+ if (!options.allowRawResponse)
77
+ throw new Error(`${controllerName}.${endpointName} answered a raw response; only a Suitelet can write one.`);
78
+ return { envelope: { status, error: null, data: null }, raw: data.options };
79
+ }
80
+ return { envelope: { status, error: null, data: data !== null && data !== void 0 ? data : null } };
81
+ }
82
+ catch (error) {
83
+ if (error instanceof ApiError) {
84
+ status = error.status;
85
+ log.debug('endpoint rejected', { controller: controllerName, endpoint: endpointName, status, message: error.message, details: error.details });
86
+ return { envelope: { status, error: error.message, data: null } };
87
+ }
88
+ status = 500;
89
+ log.error('endpoint failed', { controller: controllerName, endpoint: endpointName, ...describeError(error) });
90
+ return { envelope: { status, error: 'Internal Server Error', data: null } };
91
+ }
92
+ finally {
93
+ log.audit('endpoint completed', { controller: controllerName, endpoint: endpointName, status, durationMs: Date.now() - started });
94
+ }
95
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * File Cabinet lookups by folder and file name. The host Suitelet finds the client bundle this
3
+ * way on purpose: internal ids differ between accounts, names do not.
4
+ */
5
+ export interface FileCabinetLocation {
6
+ /** Folder directly under /SuiteScripts, e.g. the application folder. */
7
+ parentFolderName: string;
8
+ /** Folder inside the parent, e.g. "client". */
9
+ folderName: string;
10
+ fileName: string;
11
+ }
12
+ export declare function findFolderId(folderName: string, parentFolderName: string): number | undefined;
13
+ export declare function findFileId(location: FileCabinetLocation): number | undefined;
14
+ export declare function getFileUrlByName(location: FileCabinetLocation): string | undefined;
@@ -0,0 +1,40 @@
1
+ import * as file from 'N/file';
2
+ import * as search from 'N/search';
3
+ export function findFolderId(folderName, parentFolderName) {
4
+ const folderSearch = search.create({
5
+ type: 'folder',
6
+ filters: [
7
+ ['name', search.Operator.IS, folderName],
8
+ 'AND',
9
+ ['formulatext: {parent}', search.Operator.IS, parentFolderName],
10
+ ],
11
+ columns: ['internalid'],
12
+ });
13
+ const results = folderSearch.run().getRange({ start: 0, end: 1 });
14
+ return results.length > 0 ? Number(results[0].id) : undefined;
15
+ }
16
+ export function findFileId(location) {
17
+ const folderId = findFolderId(location.folderName, location.parentFolderName);
18
+ if (folderId === undefined)
19
+ return undefined;
20
+ const fileSearch = search.create({
21
+ type: 'file',
22
+ filters: [
23
+ ['name', search.Operator.IS, location.fileName],
24
+ 'AND',
25
+ ['folder', search.Operator.ANYOF, String(folderId)],
26
+ ],
27
+ columns: ['internalid'],
28
+ });
29
+ const results = fileSearch.run().getRange({ start: 0, end: 2 });
30
+ if (results.length > 1) {
31
+ throw new Error(`Multiple files named ${location.fileName} in ${location.parentFolderName}/${location.folderName}.`);
32
+ }
33
+ return results.length === 1 ? Number(results[0].id) : undefined;
34
+ }
35
+ export function getFileUrlByName(location) {
36
+ const fileId = findFileId(location);
37
+ if (fileId === undefined)
38
+ return undefined;
39
+ return file.load({ id: fileId }).url;
40
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The server side of the API: declare a controller's endpoints, expose them as a Restlet or a
3
+ * Suitelet, reject a call with an ApiError, authorize calls, answer with a document instead of JSON,
4
+ * call another Suitelet controller from server code, and find a File Cabinet file by name. Runs
5
+ * inside NetSuite only; the modules here import N/*.
6
+ */
7
+ export { ApiError } from './apiError.js';
8
+ export { defineEndpoints, invokeEndpoint, parseEndpointRequest, readEndpointCall } from './endpoint.js';
9
+ export type { AuthorizeEndpoint, ControllerOptions, EndpointCall, EndpointCallContext, EndpointOutcome, InvokeEndpointOptions } from './endpoint.js';
10
+ export { defineRestlet } from './defineRestlet.js';
11
+ export type { RestletEntryPoint } from './defineRestlet.js';
12
+ export { defineSuitelet } from './defineSuitelet.js';
13
+ export type { SuiteletEntryPoint } from './defineSuitelet.js';
14
+ export { isRawResponse, rawResponse, writeRawResponse } from './rawResponse.js';
15
+ export type { RawFileResponse, RawResponseOptions, RawTextResponse, SuiteletResponse } from './rawResponse.js';
16
+ export { callSuiteletEndpoint, createSuiteletClient } from './suiteletClient.js';
17
+ export type { SuiteletClient } from './suiteletClient.js';
18
+ export { findFileId, findFolderId, getFileUrlByName } from './fileCabinet.js';
19
+ export type { FileCabinetLocation } from './fileCabinet.js';
20
+ export { ENDPOINT_PARAMETER } from '../index.js';
21
+ export type { ApiEnvelope, ApiErrorBody, Endpoint, Endpoints, EndpointRequest, EndpointResponse, RawResponse, ScriptDeclaration, ScriptKind, ScriptRef } from '../index.js';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The server side of the API: declare a controller's endpoints, expose them as a Restlet or a
3
+ * Suitelet, reject a call with an ApiError, authorize calls, answer with a document instead of JSON,
4
+ * call another Suitelet controller from server code, and find a File Cabinet file by name. Runs
5
+ * inside NetSuite only; the modules here import N/*.
6
+ */
7
+ export { ApiError } from './apiError.js';
8
+ export { defineEndpoints, invokeEndpoint, parseEndpointRequest, readEndpointCall } from './endpoint.js';
9
+ export { defineRestlet } from './defineRestlet.js';
10
+ export { defineSuitelet } from './defineSuitelet.js';
11
+ export { isRawResponse, rawResponse, writeRawResponse } from './rawResponse.js';
12
+ export { callSuiteletEndpoint, createSuiteletClient } from './suiteletClient.js';
13
+ export { findFileId, findFolderId, getFileUrlByName } from './fileCabinet.js';
14
+ export { ENDPOINT_PARAMETER } from '../index.js';
@@ -0,0 +1,39 @@
1
+ import type { File as NetSuiteFile } from 'N/file';
2
+ import type { EntryPoints } from 'N/types';
3
+ import type { RawResponse } from '../index.js';
4
+ /**
5
+ * The escape hatch from the JSON envelope, for a Suitelet endpoint that answers with a document: a
6
+ * CSV export, a rendered PDF, a File Cabinet file. The handler returns rawResponse(...) and declares
7
+ * `RawResponse` as its return type; defineSuitelet writes the answer as it is, and the generated
8
+ * client resolves the call to a Blob. A Restlet cannot write one: its handler returning a raw response
9
+ * is a 500.
10
+ */
11
+ /** Text (or a string body of any kind) with its content type; `fileName` makes it a download. */
12
+ export interface RawTextResponse {
13
+ contentType: string;
14
+ body: string;
15
+ /** Set to answer as an attachment named this way. */
16
+ fileName?: string;
17
+ headers?: Record<string, string>;
18
+ }
19
+ /** A File Cabinet file or an N/file object built in memory (a rendered PDF, say). */
20
+ export interface RawFileResponse {
21
+ file: NetSuiteFile;
22
+ /** True to display in the browser where it can; false (the default) to download. */
23
+ inline?: boolean;
24
+ headers?: Record<string, string>;
25
+ }
26
+ export type RawResponseOptions = RawTextResponse | RawFileResponse;
27
+ declare class SuiteletRawResponse implements RawResponse {
28
+ readonly options: RawResponseOptions;
29
+ readonly isRawResponse: true;
30
+ constructor(options: RawResponseOptions);
31
+ }
32
+ /** Builds the answer a Suitelet endpoint returns instead of data. */
33
+ export declare function rawResponse(options: RawResponseOptions): RawResponse;
34
+ /** True for a value built by rawResponse(); narrows to its options. */
35
+ export declare function isRawResponse(value: unknown): value is SuiteletRawResponse;
36
+ export type SuiteletResponse = EntryPoints.Suitelet.onRequestContext['response'];
37
+ /** Writes a raw answer to the Suitelet response: headers, then the file or the body. */
38
+ export declare function writeRawResponse(response: SuiteletResponse, options: RawResponseOptions): void;
39
+ export {};
@@ -0,0 +1,28 @@
1
+ class SuiteletRawResponse {
2
+ constructor(options) {
3
+ this.options = options;
4
+ this.isRawResponse = true;
5
+ }
6
+ }
7
+ /** Builds the answer a Suitelet endpoint returns instead of data. */
8
+ export function rawResponse(options) {
9
+ return new SuiteletRawResponse(options);
10
+ }
11
+ /** True for a value built by rawResponse(); narrows to its options. */
12
+ export function isRawResponse(value) {
13
+ return value instanceof SuiteletRawResponse;
14
+ }
15
+ /** Writes a raw answer to the Suitelet response: headers, then the file or the body. */
16
+ export function writeRawResponse(response, options) {
17
+ var _a, _b;
18
+ for (const [name, value] of Object.entries((_a = options.headers) !== null && _a !== void 0 ? _a : {}))
19
+ response.setHeader({ name, value });
20
+ if ('file' in options) {
21
+ response.writeFile({ file: options.file, isInline: (_b = options.inline) !== null && _b !== void 0 ? _b : false });
22
+ return;
23
+ }
24
+ response.setHeader({ name: 'Content-Type', value: options.contentType });
25
+ if (options.fileName !== undefined)
26
+ response.setHeader({ name: 'Content-Disposition', value: `attachment; filename="${options.fileName}"` });
27
+ response.write({ output: options.body });
28
+ }
@@ -0,0 +1,23 @@
1
+ import { type EndpointRequest, type EndpointResponse, type Endpoints, type ScriptRef } from '../index.js';
2
+ /**
3
+ * Calls another controller of the application from server code, the way the browser client does
4
+ * from the browser: by its scripts entry and the type of its endpoints, one endpoint at a time,
5
+ * synchronously. Only a Suitelet is reachable this way (https.requestSuitelet rides the caller's
6
+ * session). The usual reason to call one is that it is deployed to run as a role the caller lacks.
7
+ * A repository builds the client, so the service never knows the answer came from another script.
8
+ */
9
+ /** One function per endpoint, typed by the controller's handlers: `userRolesApi.byEmployee({ employeeId })`. */
10
+ export type SuiteletClient<TEndpoints extends Endpoints> = {
11
+ readonly [TName in keyof TEndpoints]: (request: EndpointRequest<TEndpoints[TName]>) => EndpointResponse<TEndpoints[TName]>;
12
+ };
13
+ /**
14
+ * Calls one endpoint of a Suitelet controller: a POST whose JSON body carries the request and the
15
+ * endpoint name. A transport failure is a 502; an error envelope keeps the status the Suitelet
16
+ * answered with.
17
+ */
18
+ export declare function callSuiteletEndpoint<TData>(scriptRef: ScriptRef, endpointName: string, request?: object): TData;
19
+ /**
20
+ * Builds the typed client for a Suitelet controller from its scripts entry: `createSuiteletClient<UserRolesEndpoints>(scripts.userRoles)`.
21
+ * The endpoint names come from the type alone; the property accessed is the endpoint named on the wire.
22
+ */
23
+ export declare function createSuiteletClient<TEndpoints extends Endpoints>(scriptRef: ScriptRef): SuiteletClient<TEndpoints>;
@@ -0,0 +1,54 @@
1
+ import * as https from 'N/https';
2
+ import { ENDPOINT_PARAMETER } from '../index.js';
3
+ import { ApiError } from './apiError.js';
4
+ function parseEnvelope(scriptRef, endpointName, body) {
5
+ let parsed;
6
+ try {
7
+ parsed = JSON.parse(body);
8
+ }
9
+ catch {
10
+ parsed = undefined;
11
+ }
12
+ if (parsed && typeof parsed === 'object' && 'status' in parsed)
13
+ return parsed;
14
+ throw new ApiError(502, `${scriptRef.scriptId} did not answer with the API envelope.`, { script: scriptRef.scriptId, endpoint: endpointName, body: body.slice(0, 500) });
15
+ }
16
+ /**
17
+ * Calls one endpoint of a Suitelet controller: a POST whose JSON body carries the request and the
18
+ * endpoint name. A transport failure is a 502; an error envelope keeps the status the Suitelet
19
+ * answered with.
20
+ */
21
+ export function callSuiteletEndpoint(scriptRef, endpointName, request = {}) {
22
+ var _a;
23
+ if (scriptRef.kind !== 'suitelet') {
24
+ throw new Error(`${scriptRef.scriptId} is a ${scriptRef.kind}; server code reaches Suitelets only.`);
25
+ }
26
+ const response = https.requestSuitelet({
27
+ scriptId: scriptRef.scriptId,
28
+ deploymentId: scriptRef.deployId,
29
+ method: https.Method.POST,
30
+ body: JSON.stringify({ ...request, [ENDPOINT_PARAMETER]: endpointName }),
31
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
32
+ });
33
+ if (response.code !== 200) {
34
+ throw new ApiError(502, `${scriptRef.scriptId} answered HTTP ${response.code}.`, { script: scriptRef.scriptId, endpoint: endpointName });
35
+ }
36
+ const envelope = parseEnvelope(scriptRef, endpointName, response.body);
37
+ if (envelope.error !== null || envelope.status >= 400) {
38
+ throw new ApiError(envelope.status, (_a = envelope.error) !== null && _a !== void 0 ? _a : `Request failed (${envelope.status})`, { script: scriptRef.scriptId, endpoint: endpointName });
39
+ }
40
+ return envelope.data;
41
+ }
42
+ /**
43
+ * Builds the typed client for a Suitelet controller from its scripts entry: `createSuiteletClient<UserRolesEndpoints>(scripts.userRoles)`.
44
+ * The endpoint names come from the type alone; the property accessed is the endpoint named on the wire.
45
+ */
46
+ export function createSuiteletClient(scriptRef) {
47
+ return new Proxy({}, {
48
+ get(_target, endpointName) {
49
+ if (typeof endpointName !== 'string')
50
+ return undefined;
51
+ return (request) => callSuiteletEndpoint(scriptRef, endpointName, (request !== null && request !== void 0 ? request : {}));
52
+ },
53
+ });
54
+ }
@@ -0,0 +1,2 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const create: Mock;
@@ -0,0 +1,6 @@
1
+ import { vi } from 'vitest';
2
+ export const create = vi.fn((options) => {
3
+ const suiteScriptError = new Error(options.message);
4
+ suiteScriptError.name = options.name;
5
+ return suiteScriptError;
6
+ });
@@ -0,0 +1,9 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const load: Mock;
3
+ export declare const create: Mock;
4
+ declare const deleteFile: Mock;
5
+ export { deleteFile as delete };
6
+ export declare const Type: {
7
+ PLAINTEXT: string;
8
+ JAVASCRIPT: string;
9
+ };
@@ -0,0 +1,6 @@
1
+ import { vi } from 'vitest';
2
+ export const load = vi.fn((options) => ({ id: options.id, url: `/core/media/media.nl?id=${options.id}`, getContents: () => '' }));
3
+ export const create = vi.fn();
4
+ const deleteFile = vi.fn();
5
+ export { deleteFile as delete };
6
+ export const Type = { PLAINTEXT: 'PLAINTEXT', JAVASCRIPT: 'JAVASCRIPT' };
@@ -0,0 +1,8 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const parse: Mock;
3
+ export declare const format: Mock;
4
+ export declare const Type: {
5
+ DATE: string;
6
+ DATETIME: string;
7
+ CURRENCY: string;
8
+ };
@@ -0,0 +1,4 @@
1
+ import { vi } from 'vitest';
2
+ export const parse = vi.fn((options) => options.value);
3
+ export const format = vi.fn((options) => String(options.value));
4
+ export const Type = { DATE: 'date', DATETIME: 'datetime', CURRENCY: 'currency' };
@@ -0,0 +1,15 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const get: Mock;
3
+ export declare const post: Mock;
4
+ export declare const put: Mock;
5
+ export declare const request: Mock;
6
+ export declare const requestRestlet: Mock;
7
+ export declare const requestSuitelet: Mock;
8
+ declare const deleteRequest: Mock;
9
+ export { deleteRequest as delete };
10
+ export declare const Method: {
11
+ GET: string;
12
+ POST: string;
13
+ PUT: string;
14
+ DELETE: string;
15
+ };
@@ -0,0 +1,10 @@
1
+ import { vi } from 'vitest';
2
+ export const get = vi.fn();
3
+ export const post = vi.fn();
4
+ export const put = vi.fn();
5
+ export const request = vi.fn();
6
+ export const requestRestlet = vi.fn();
7
+ export const requestSuitelet = vi.fn();
8
+ const deleteRequest = vi.fn();
9
+ export { deleteRequest as delete };
10
+ export const Method = { GET: 'GET', POST: 'POST', PUT: 'PUT', DELETE: 'DELETE' };
@@ -0,0 +1,5 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const debug: Mock;
3
+ export declare const audit: Mock;
4
+ export declare const error: Mock;
5
+ export declare const emergency: Mock;
@@ -0,0 +1,5 @@
1
+ import { vi } from 'vitest';
2
+ export const debug = vi.fn();
3
+ export const audit = vi.fn();
4
+ export const error = vi.fn();
5
+ export const emergency = vi.fn();
@@ -0,0 +1,7 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const runSuiteQL: Mock;
3
+ export declare const runSuiteQLPaged: Mock;
4
+ export declare const create: Mock;
5
+ export declare const load: Mock;
6
+ export declare const Operator: {};
7
+ export declare const Type: {};
@@ -0,0 +1,7 @@
1
+ import { vi } from 'vitest';
2
+ export const runSuiteQL = vi.fn(() => ({ asMappedResults: () => [], results: [] }));
3
+ export const runSuiteQLPaged = vi.fn();
4
+ export const create = vi.fn();
5
+ export const load = vi.fn();
6
+ export const Operator = {};
7
+ export const Type = {};
@@ -0,0 +1,14 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const load: Mock;
3
+ export declare const create: Mock;
4
+ export declare const copy: Mock;
5
+ export declare const transform: Mock;
6
+ export declare const submitFields: Mock;
7
+ export declare const attach: Mock;
8
+ export declare const detach: Mock;
9
+ declare const deleteRecord: Mock;
10
+ export { deleteRecord as delete };
11
+ export declare const Type: {
12
+ CUSTOMER: string;
13
+ FOLDER: string;
14
+ };
@@ -0,0 +1,11 @@
1
+ import { vi } from 'vitest';
2
+ export const load = vi.fn();
3
+ export const create = vi.fn();
4
+ export const copy = vi.fn();
5
+ export const transform = vi.fn();
6
+ export const submitFields = vi.fn();
7
+ export const attach = vi.fn();
8
+ export const detach = vi.fn();
9
+ const deleteRecord = vi.fn();
10
+ export { deleteRecord as delete };
11
+ export const Type = { CUSTOMER: 'customer', FOLDER: 'folder' };
@@ -0,0 +1,11 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const getCurrentScript: Mock;
3
+ export declare const getCurrentUser: Mock;
4
+ export declare const getCurrentSession: Mock;
5
+ export declare const isFeatureInEffect: Mock;
6
+ export declare const accountId = "TSTDRV0000000";
7
+ export declare const envType = "SANDBOX";
8
+ export declare const EnvType: {
9
+ SANDBOX: string;
10
+ PRODUCTION: string;
11
+ };
@@ -0,0 +1,8 @@
1
+ import { vi } from 'vitest';
2
+ export const getCurrentScript = vi.fn(() => ({ id: 'customscript_test', deploymentId: 'customdeploy_test', getParameter: vi.fn(), getRemainingUsage: () => 1000 }));
3
+ export const getCurrentUser = vi.fn(() => ({ id: 1, name: 'Test User', role: 3, email: 'test@example.com' }));
4
+ export const getCurrentSession = vi.fn(() => ({ get: vi.fn(), set: vi.fn() }));
5
+ export const isFeatureInEffect = vi.fn(() => false);
6
+ export const accountId = 'TSTDRV0000000';
7
+ export const envType = 'SANDBOX';
8
+ export const EnvType = { SANDBOX: 'SANDBOX', PRODUCTION: 'PRODUCTION' };
@@ -0,0 +1,10 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const create: Mock;
3
+ export declare const load: Mock;
4
+ export declare const lookupFields: Mock;
5
+ export declare const Operator: {
6
+ IS: string;
7
+ ANYOF: string;
8
+ CONTAINS: string;
9
+ };
10
+ export declare const Type: {};
@@ -0,0 +1,8 @@
1
+ import { vi } from 'vitest';
2
+ export const create = vi.fn(() => ({
3
+ run: () => ({ getRange: () => [], each: () => undefined }),
4
+ }));
5
+ export const load = vi.fn();
6
+ export const lookupFields = vi.fn();
7
+ export const Operator = { IS: 'is', ANYOF: 'anyof', CONTAINS: 'contains' };
8
+ export const Type = {};
@@ -0,0 +1,7 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const create: Mock;
3
+ export declare const checkStatus: Mock;
4
+ export declare const TaskType: {
5
+ MAP_REDUCE: string;
6
+ SCHEDULED_SCRIPT: string;
7
+ };
@@ -0,0 +1,4 @@
1
+ import { vi } from 'vitest';
2
+ export const create = vi.fn(() => ({ submit: vi.fn(() => 'TASK_ID') }));
3
+ export const checkStatus = vi.fn();
4
+ export const TaskType = { MAP_REDUCE: 'MAP_REDUCE', SCHEDULED_SCRIPT: 'SCHEDULED_SCRIPT' };
@@ -0,0 +1,7 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const FieldType: {
3
+ INLINEHTML: string;
4
+ LABEL: string;
5
+ TEXT: string;
6
+ };
7
+ export declare const createForm: Mock;
@@ -0,0 +1,7 @@
1
+ import { vi } from 'vitest';
2
+ export const FieldType = { INLINEHTML: 'INLINEHTML', LABEL: 'LABEL', TEXT: 'TEXT' };
3
+ export const createForm = vi.fn((options) => ({
4
+ title: options.title,
5
+ clientScriptModulePath: '',
6
+ addField: vi.fn((field) => ({ id: field.id, defaultValue: '' })),
7
+ }));
@@ -0,0 +1,9 @@
1
+ import { type Mock } from 'vitest';
2
+ export declare const resolveScript: Mock;
3
+ export declare const resolveRecord: Mock;
4
+ export declare const resolveDomain: Mock;
5
+ export declare const format: Mock;
6
+ export declare const HostType: {
7
+ APPLICATION: string;
8
+ RESTLET: string;
9
+ };
@@ -0,0 +1,6 @@
1
+ import { vi } from 'vitest';
2
+ export const resolveScript = vi.fn(() => '/app/site/hosting/scriptlet.nl?script=1&deploy=1');
3
+ export const resolveRecord = vi.fn(() => '/app/common/entity/custjob.nl?id=1');
4
+ export const resolveDomain = vi.fn(() => 'localhost');
5
+ export const format = vi.fn((options) => options.domain);
6
+ export const HostType = { APPLICATION: 'APPLICATION', RESTLET: 'RESTLET' };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Test support for a project's api workspace. N/* modules are only real inside NetSuite; the stubs
3
+ * under ./N answer with vi.fn shells so a service or controller can run under vitest. A project's
4
+ * vitest config aliases N/* to them and inlines this package so its own N/* imports go through the
5
+ * alias too:
6
+ *
7
+ * resolve: { alias: [...netsuiteModuleStubAliases()] },
8
+ * test: { server: { deps: { inline: [...inlinedPackagesForNetsuiteStubs] } } },
9
+ *
10
+ * This module runs in the vitest config, so it never imports vitest itself.
11
+ */
12
+ /** Absolute directory of the stub modules, one file per N/* module (N/ui/serverWidget included). */
13
+ export declare const netsuiteModuleStubsDirectory: string;
14
+ export interface ModuleAlias {
15
+ find: RegExp;
16
+ replacement: string;
17
+ }
18
+ /**
19
+ * The vitest alias that routes every N/* import to its stub. A wrapper package that re-exports the
20
+ * modules under its own names passes patterns of its own; each must capture the module name
21
+ * (`log`, `ui/serverWidget`) in its first group.
22
+ */
23
+ export declare function netsuiteModuleStubAliases(additionalPatterns?: RegExp[]): ModuleAlias[];
24
+ /**
25
+ * Packages whose own N/* imports must go through the alias. Vitest externalizes node_modules by
26
+ * default and Node's resolver knows no N/log; inlining routes the import through vite instead.
27
+ */
28
+ export declare const inlinedPackagesForNetsuiteStubs: readonly ["@amerilux/netsuite-api"];
@@ -0,0 +1,32 @@
1
+ /// <reference types="node" />
2
+ import * as nodePath from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ /**
5
+ * Test support for a project's api workspace. N/* modules are only real inside NetSuite; the stubs
6
+ * under ./N answer with vi.fn shells so a service or controller can run under vitest. A project's
7
+ * vitest config aliases N/* to them and inlines this package so its own N/* imports go through the
8
+ * alias too:
9
+ *
10
+ * resolve: { alias: [...netsuiteModuleStubAliases()] },
11
+ * test: { server: { deps: { inline: [...inlinedPackagesForNetsuiteStubs] } } },
12
+ *
13
+ * This module runs in the vitest config, so it never imports vitest itself.
14
+ */
15
+ /** Absolute directory of the stub modules, one file per N/* module (N/ui/serverWidget included). */
16
+ export const netsuiteModuleStubsDirectory = nodePath.join(nodePath.dirname(fileURLToPath(import.meta.url)), 'N');
17
+ /**
18
+ * The vitest alias that routes every N/* import to its stub. A wrapper package that re-exports the
19
+ * modules under its own names passes patterns of its own; each must capture the module name
20
+ * (`log`, `ui/serverWidget`) in its first group.
21
+ */
22
+ export function netsuiteModuleStubAliases(additionalPatterns = []) {
23
+ const posixDirectory = netsuiteModuleStubsDirectory.split(nodePath.sep).join('/');
24
+ const extension = nodePath.extname(fileURLToPath(import.meta.url));
25
+ const replacement = `${posixDirectory}/$1${extension}`;
26
+ return [/^N\/(.*)$/, ...additionalPatterns].map((find) => ({ find, replacement }));
27
+ }
28
+ /**
29
+ * Packages whose own N/* imports must go through the alias. Vitest externalizes node_modules by
30
+ * default and Node's resolver knows no N/log; inlining routes the import through vite instead.
31
+ */
32
+ export const inlinedPackagesForNetsuiteStubs = ['@amerilux/netsuite-api'];