-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathsearch.js
224 lines (224 loc) · 10.4 KB
/
search.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/**
* This module provides a lazy, functional processing approach to working with NetSuite saved searches.
* It automatically handles paging behind the scenes allowing the developer to focus on 'per result' business logic.
*
* Use `LazySearch.from()` and `LazySearch.load()` to get started.
* Turn search results into plain objects using `nsSearchResult2obj()` and leverage
* the methods of [ImmutableJS](https://facebook.github.io/immutable-js/) to process search results.
* @module
*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "N/search", "./EC_Logger"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LazySearch = void 0;
exports.nsSearchResult2obj = nsSearchResult2obj;
const search = require("N/search");
const LogManager = require("./EC_Logger");
/**
* Rudimentary conversion of a NS search result to a simple flat plain javascript object. Suitable as an argument to `map()`
* @param useLabels set to false to ignore search column labels, using the column name (internalid) instead.
* Defaults to true which means the property names on the returned object will match the column label names if set.
* If useLabels = true and no label exists, falls back to using column name. Note that label strings should be valid
* characters for property names (e.g. contain no ':', '-', '>' etc.)
* @param addGetTextProps if true, for each column which has a _truthy_ getText() value, include that as a 'propnameText' field similar to how nsdal behaves
* @returns a mapping function taking a NetSuite search result and returns a POJO representation of that search result.
* The return type will always have an 'id' property merged with type T if provided.
*
* @typeparam T declares the shape of the plain objects returned. e.g. `nsSearchResult2obj<{ companyname, memo }>` for a search result
* that has columns _companyname_ and _memo_. Including an optional type here ensures strong typing on followup chained
* method calls.
*
* @example (using Immutable JS Sequence)
*
* ```typescript
* // default (uses column labels if present)
* Seq(LazySearch.load(1234)).map(nsSearchResult2obj()).forEach(...)
*
* // force ignoring search column labels
* Seq(LazySearch.load(1234)).map(nsSearchResult2obj(false)).forEach(...)
*
* ```
*/
function nsSearchResult2obj(useLabels = true, addGetTextProps = true) {
return function (result) {
let output = { id: result.id, recordType: result.recordType };
// assigns each column VALUE from the search result to the output object
if (result.columns && result.columns.length > 0)
result.columns.forEach((col) => {
const propName = (useLabels && col.label) ? col.label : col.name;
output[propName] = result.getValue(col);
// if the column has a truthy text value, include that as a 'propnameText' field similar to how nsdal behaves
if (addGetTextProps) {
const text = result.getText(col);
if (text)
output[`${propName}Text`] = text;
}
});
return output;
};
}
/**
* Makes a NetSuite search an ES2015 style Iterator. That is, it follows the Iterator Protocol for iterating
* over search results in a forward-only fashion. The result can be passed to any library
* that accepts Iterators (such as ImmutableJS)
* to provide easy chainable logic on arbitrary length search result sets.
*
* Started with this as a class due to other library requirements and left it as a class just as an easy
* way to contain state about currentpage and index into that page.
*
* This is exposed as an iterator so that it could be used with other libraries. For example
* I've heard Ramda may support iterators so if we choose to go a more pure FP route down the
* road this class would be useful - i.e. it remains untied to any particular library.
*
* @example take the first result of a search as a plain object (ImmutableJS)
* ```typescript
* import {Seq} from './NFT-X.Y.Z/immutable'
* const oneResult = Seq(LazySearch.load('1234')).map(nsSearchResult2obj()).take(1)
* ```
*/
class LazySearch {
/**
* LazySearch is both an iterable and an iterator for search results.
*/
[Symbol.iterator]() {
return this;
}
// /**
// * A LazySearch is iterable per the iterable protocol, which also plays nicely with immutablejs
// * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
// */
/**
* Loads an existing NS search by id and prepares it for lazy evaluation
* @param id internal id of the search to load
* @param pageSize how many records to retrieve per page (paging is automatic) Maximum value: 1000
* @returns {LazySearch}
*
* @example do something for each search result, automatically exiting if out of governance.
*
* ```typescript
*
* import {Seq} from './NFT-X.Y.Z/immutable'
* import {governanceRemains, LazySearch, nsSearchResult2obj} from './NFT-X.Y.Z/search'
*
* Seq(LazySearch.load('1234'))
* .takeWhile(governanceRemains()) // process until we drop below default governance threshold
* .map(nsSearchResult2obj()) // convert search results to plain objects with properties
* .forEach( r => log.debug(r))
* ```
*/
static load(id, pageSize) {
return new LazySearch(search.load({ id: id }), pageSize);
}
/**
* Creates a lazy search from an existing NS search.
* @param search
* @param pageSize
* @returns {LazySearch}
*
* @example create a search and begin lazy processing of results
*
* ```
* import {Seq} from './NFT-X.Y.Z/immutable'
* import * as search from 'N/search
* import {governanceRemains, LazySearch, nsSearchResult2obj} from './NFT-X.Y.Z/search'
*
* Seq(LazySearch.from(search.create({
* filters: [['internalid', 'anyof', [1,2]),
* columns:['item', 'description'],
* type: search.Type.ITEM,
* })))
* .takeWhile(governanceRemains()) // process until we drop below default governance threshold
* .map(nsSearchResult2obj()) // convert search results to plain objects with properties
* .forEach( r => log.debug(r))
* ```
*/
static from(search, pageSize) {
return new LazySearch(search, pageSize);
}
/**
* Not meant to be used directly, use factory methods such as `load` or `from`
* @param search the netsuite search object to wrap
* @param pageSize optional pagesize, can be up to 1000
*/
constructor(search, pageSize = 1000) {
this.search = search;
this.pageSize = pageSize;
// Starting point of the next page
this.nextPageStart = 0;
// Total length of the search result set
this.totalSearchResultLength = 0;
if (pageSize > 1000)
throw new Error('page size must be <= 1000');
this.log = LogManager.getLogger(LazySearch.LOGNAME);
this.log.debug('pageSize', pageSize);
this.currentData = [];
this.executedSearch = search.run();
this.currentRange = this.executedSearch.getRange({
start: 0,
end: pageSize
});
this.log.debug('Length', this.currentRange.length);
if (this.currentRange.length) {
this.nextPageStart = this.currentRange.length;
this.log.debug('results returned');
}
else {
this.currentData = [];
this.log.debug('run() search return zero results');
}
this.index = 0;
this.log.info(`lazy search id ${search.searchId || 'ad-hoc'}`, `using "page" size ${this.pageSize}, record count ${this.totalSearchResultLength}`);
}
/**
* per the iterator protocol, retrieves the next element. Also returns `null` if done as the specification for
* the protocol says the value property is optional when 'done'
*
* You don't typically call this function yourself - libraries like ImmutableJS do.
*/
next() {
this.log.debug('In Next function');
this.log.debug('index', this.index);
this.log.debug('currentRange.length', this.currentRange.length);
const atEndOfRange = this.index === this.currentRange.length;
if (atEndOfRange) {
this.index = 0;
this.currentRange = this.executedSearch.getRange({
start: this.nextPageStart,
end: this.nextPageStart + this.pageSize
});
this.log.debug('this.currentRange.length === 0', this.currentRange.length === 0);
if (this.currentRange.length === 0)
return {
done: true,
value: null
};
this.nextPageStart = this.nextPageStart + this.currentRange.length;
}
this.log.info(`returning from next`, {
done: false,
value: this.currentRange[this.index]
});
this.log.debug('this.index', this.index);
this.log.debug('this.currentRange[this.index]', this.currentRange[this.index]);
const obj = {
done: false,
value: this.currentRange[this.index]
};
this.index++;
return obj;
}
}
exports.LazySearch = LazySearch;
/**
* the name of the custom logger for this component for independent logging control
*/
LazySearch.LOGNAME = 'lazy';
});