@amerilux/netsuite-api 0.6.0 → 0.6.1
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/README.md +1 -1
- package/dist-tooling/generate.js +5 -5
- package/dist-tooling/jobReader.d.ts +6 -5
- package/dist-tooling/jobReader.js +46 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -242,7 +242,7 @@ export interface CloseStaleRequest { olderThanDays: number }
|
|
|
242
242
|
export const getInputDataFunction = (input: CloseStaleRequest): StaleOrder[] => listStaleOrders(input.olderThanDays);
|
|
243
243
|
```
|
|
244
244
|
|
|
245
|
-
A stage file exports the stage's name plus `Function`, because the definition file exports the plain names NetSuite looks for and the two would collide. A stage may also be written inline in the definition file, which suits a job whose stages are a line each — the cleanup job `npm run add:jobs` writes is one.
|
|
245
|
+
A stage file exports the stage's name plus `Function`, because the definition file exports the plain names NetSuite looks for and the two would collide. A stage file may name a shape another file of the job's folder declares — the value a map stage writes is declared in map.ts and named again in summarize.ts — and the generator reads it there. A stage may also be written inline in the definition file, which suits a job whose stages are a line each — the cleanup job `npm run add:jobs` writes is one.
|
|
246
246
|
|
|
247
247
|
The first parameter of `getInputData` is the run's input and the return type of `summarize` is its result: the generator reads the shapes from those two annotations wherever the stage is written, the way it reads an endpoint's request and response, and copies the shapes the result names into the browser's module whether the stage file declares them or takes them from a service. The values carried between stages are JSON, so each stage annotates what it expects (`values: number[]` on a reduce stage, `summary: JobSummary<Total>` on summarize) and the wrapper hands them back that way. Export the stages the job has, and `summarize` always: the run is closed there. A stage exported without being declared throws when NetSuite calls it, rather than quietly passing values through.
|
|
248
248
|
|
package/dist-tooling/generate.js
CHANGED
|
@@ -223,11 +223,11 @@ export function planClientGeneration({ config, fileSystem }) {
|
|
|
223
223
|
problems.push({ filePath: jobLabel, message: `a controller is named '${contract.name}' too; their generated modules would be the same file.` });
|
|
224
224
|
}
|
|
225
225
|
const inlinedTypes = [];
|
|
226
|
-
//
|
|
227
|
-
for (const
|
|
228
|
-
addDeclarations(inlinedTypes,
|
|
229
|
-
for (const typeImport of
|
|
230
|
-
addInlinedTypes(inlinedTypes, typeImport,
|
|
226
|
+
// What the job's own files declare, then whatever they take from a service: both are the run's shapes.
|
|
227
|
+
for (const folderFile of contract.folderFiles) {
|
|
228
|
+
addDeclarations(inlinedTypes, folderFile.filePath, folderFile.declarations);
|
|
229
|
+
for (const typeImport of folderFile.inlinedTypeImports)
|
|
230
|
+
addInlinedTypes(inlinedTypes, typeImport, folderFile.filePath);
|
|
231
231
|
}
|
|
232
232
|
for (const typeImport of contract.inlinedTypeImports)
|
|
233
233
|
addInlinedTypes(inlinedTypes, typeImport, jobLabel);
|
|
@@ -32,10 +32,11 @@ export interface DeclaredJob {
|
|
|
32
32
|
parameters: JobParameterContract[];
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* A file
|
|
36
|
-
* copied into the browser's module like a service's, because a run's shapes are written where
|
|
35
|
+
* A file of the job's own folder the reader had to read: a stage, or a file a stage takes a shape from. What
|
|
36
|
+
* it declares is copied into the browser's module like a service's, because a run's shapes are written where
|
|
37
|
+
* its stages are — the value a map stage writes is declared in map.ts and named again in summarize.ts.
|
|
37
38
|
*/
|
|
38
|
-
export interface
|
|
39
|
+
export interface JobFolderFile {
|
|
39
40
|
filePath: string;
|
|
40
41
|
declarations: TypeDeclaration[];
|
|
41
42
|
inlinedTypeImports: InlinedTypeImport[];
|
|
@@ -68,8 +69,8 @@ export interface JobContract {
|
|
|
68
69
|
stages: JobStageName[];
|
|
69
70
|
/** The stages the file exports as NetSuite entry points. */
|
|
70
71
|
exportedStages: string[];
|
|
71
|
-
/** The files the
|
|
72
|
-
|
|
72
|
+
/** The files of the job's folder that were read, in that order: its stages, and whatever they name. */
|
|
73
|
+
folderFiles: JobFolderFile[];
|
|
73
74
|
}
|
|
74
75
|
export interface JobReadResult {
|
|
75
76
|
contract?: JobContract;
|
|
@@ -133,7 +133,7 @@ function readJobDeclaration(declaration, jobName, filePath) {
|
|
|
133
133
|
* The exported type declarations of a file and the type imports it carries, for a job's definition file
|
|
134
134
|
* or for one of its stage files: both end up in the browser's module, so both are read the same way.
|
|
135
135
|
*/
|
|
136
|
-
function readFileTypes(sourceFile, filePath, options) {
|
|
136
|
+
function readFileTypes(sourceFile, filePath, options, folderFiles) {
|
|
137
137
|
const declarations = [];
|
|
138
138
|
const carriedTypeImports = [];
|
|
139
139
|
const inlinedTypeImports = [];
|
|
@@ -141,7 +141,6 @@ function readFileTypes(sourceFile, filePath, options) {
|
|
|
141
141
|
for (const statement of sourceFile.statements) {
|
|
142
142
|
if (ts.isImportDeclaration(statement)) {
|
|
143
143
|
const result = readTypeImport(statement, filePath, options);
|
|
144
|
-
problems.push(...result.problems);
|
|
145
144
|
if (result.read?.kind === 'carried')
|
|
146
145
|
carriedTypeImports.push(result.read.typeImport);
|
|
147
146
|
else if (result.read?.kind === 'inlined')
|
|
@@ -149,6 +148,14 @@ function readFileTypes(sourceFile, filePath, options) {
|
|
|
149
148
|
else if (result.read?.kind === 'controller') {
|
|
150
149
|
problems.push({ filePath, message: "a job does not take types from a controller; its input and result are its own, or a service's." });
|
|
151
150
|
}
|
|
151
|
+
else if (result.problems.length > 0 && ts.isStringLiteral(statement.moduleSpecifier) && isRelativeSpecifier(statement.moduleSpecifier.text)) {
|
|
152
|
+
// A shape another file of this job's folder declares: read there instead of reported as unreachable.
|
|
153
|
+
const sibling = folderFiles?.read(statement.moduleSpecifier.text);
|
|
154
|
+
if (!sibling)
|
|
155
|
+
problems.push(...result.problems);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
problems.push(...result.problems);
|
|
152
159
|
continue;
|
|
153
160
|
}
|
|
154
161
|
if (!ts.isInterfaceDeclaration(statement) && !ts.isTypeAliasDeclaration(statement) && !ts.isEnumDeclaration(statement))
|
|
@@ -166,6 +173,35 @@ function readFileTypes(sourceFile, filePath, options) {
|
|
|
166
173
|
}
|
|
167
174
|
return { declarations, carriedTypeImports, inlinedTypeImports, problems };
|
|
168
175
|
}
|
|
176
|
+
function isRelativeSpecifier(specifier) {
|
|
177
|
+
return specifier.startsWith('./') || specifier.startsWith('../');
|
|
178
|
+
}
|
|
179
|
+
function createJobFolderFiles(options) {
|
|
180
|
+
const collected = [];
|
|
181
|
+
const problems = [];
|
|
182
|
+
const bySpecifier = new Map();
|
|
183
|
+
const folderFiles = {
|
|
184
|
+
collected,
|
|
185
|
+
problems,
|
|
186
|
+
read(specifier) {
|
|
187
|
+
if (bySpecifier.has(specifier))
|
|
188
|
+
return bySpecifier.get(specifier);
|
|
189
|
+
const read = options.readStageFile?.(specifier);
|
|
190
|
+
if (!read) {
|
|
191
|
+
bySpecifier.set(specifier, undefined);
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
const sourceFile = ts.createSourceFile(read.filePath, read.source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
195
|
+
const entry = { sourceFile, filePath: read.filePath };
|
|
196
|
+
bySpecifier.set(specifier, entry);
|
|
197
|
+
const types = readFileTypes(sourceFile, read.filePath, options, folderFiles);
|
|
198
|
+
problems.push(...types.problems);
|
|
199
|
+
collected.push({ filePath: read.filePath, declarations: types.declarations, inlinedTypeImports: types.inlinedTypeImports, carriedTypeImports: types.carriedTypeImports });
|
|
200
|
+
return entry;
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
return folderFiles;
|
|
204
|
+
}
|
|
169
205
|
/** `import { mapFunction } from './map'`: every value imported by name, by the name this file calls it. */
|
|
170
206
|
function readValueImports(sourceFile) {
|
|
171
207
|
const valueImports = new Map();
|
|
@@ -200,30 +236,12 @@ function findExportedFunction(sourceFile, name) {
|
|
|
200
236
|
}
|
|
201
237
|
return undefined;
|
|
202
238
|
}
|
|
203
|
-
function readStages(literal, filePath, sourceFile,
|
|
239
|
+
function readStages(literal, filePath, sourceFile, folderFiles) {
|
|
204
240
|
const problems = [];
|
|
205
241
|
const stages = [];
|
|
206
|
-
const stageFiles = [];
|
|
207
242
|
const valueImports = readValueImports(sourceFile);
|
|
208
|
-
const filesBySpecifier = new Map();
|
|
209
243
|
let inputType;
|
|
210
244
|
let resultType;
|
|
211
|
-
/** A stage file, read once however many stages come from it, and its shapes collected as it is read. */
|
|
212
|
-
function readStageFile(specifier) {
|
|
213
|
-
const alreadyRead = filesBySpecifier.get(specifier);
|
|
214
|
-
if (alreadyRead)
|
|
215
|
-
return alreadyRead;
|
|
216
|
-
const read = options.readStageFile?.(specifier);
|
|
217
|
-
if (!read)
|
|
218
|
-
return undefined;
|
|
219
|
-
const stageSourceFile = ts.createSourceFile(read.filePath, read.source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
220
|
-
const types = readFileTypes(stageSourceFile, read.filePath, options);
|
|
221
|
-
problems.push(...types.problems);
|
|
222
|
-
stageFiles.push({ filePath: read.filePath, declarations: types.declarations, inlinedTypeImports: types.inlinedTypeImports, carriedTypeImports: types.carriedTypeImports });
|
|
223
|
-
const entry = { sourceFile: stageSourceFile, filePath: read.filePath };
|
|
224
|
-
filesBySpecifier.set(specifier, entry);
|
|
225
|
-
return entry;
|
|
226
|
-
}
|
|
227
245
|
/** The function a stage names, in the file it is imported from: `getInputData: getInputDataFunction`. */
|
|
228
246
|
function findReferencedStage(stageName, referenced) {
|
|
229
247
|
const imported = valueImports.get(referenced);
|
|
@@ -235,7 +253,7 @@ function readStages(literal, filePath, sourceFile, options) {
|
|
|
235
253
|
problems.push({ filePath, message: `stage '${stageName}' comes from '${imported.specifier}'; a stage is imported from a file beside the job, such as './${stageName}'.` });
|
|
236
254
|
return undefined;
|
|
237
255
|
}
|
|
238
|
-
const stageFile =
|
|
256
|
+
const stageFile = folderFiles.read(imported.specifier);
|
|
239
257
|
if (!stageFile) {
|
|
240
258
|
problems.push({ filePath, message: `stage '${stageName}' is imported from '${imported.specifier}', which is not a file beside the job the generator can read.` });
|
|
241
259
|
return undefined;
|
|
@@ -295,7 +313,7 @@ function readStages(literal, filePath, sourceFile, options) {
|
|
|
295
313
|
if (!stages.includes('map') && !stages.includes('reduce')) {
|
|
296
314
|
problems.push({ filePath, message: 'a job declares a map stage, a reduce stage, or both; NetSuite has nothing to run otherwise.' });
|
|
297
315
|
}
|
|
298
|
-
return { stages, inputType, resultType,
|
|
316
|
+
return { stages, inputType, resultType, problems };
|
|
299
317
|
}
|
|
300
318
|
export function readJobContract(filePath, source, options) {
|
|
301
319
|
const problems = [];
|
|
@@ -314,7 +332,8 @@ export function readJobContract(filePath, source, options) {
|
|
|
314
332
|
if (header !== JOB_SCRIPT_TYPE_HEADER) {
|
|
315
333
|
problems.push({ filePath, message: `the leading JSDoc says '@NScriptType ${header ?? '(none)'}'; a job is a ${JOB_SCRIPT_TYPE_HEADER}.` });
|
|
316
334
|
}
|
|
317
|
-
const
|
|
335
|
+
const folderFiles = createJobFolderFiles(options);
|
|
336
|
+
const fileTypes = readFileTypes(sourceFile, filePath, options, folderFiles);
|
|
318
337
|
problems.push(...fileTypes.problems);
|
|
319
338
|
const { carriedTypeImports, inlinedTypeImports, declarations: typeDeclarations } = fileTypes;
|
|
320
339
|
let contract;
|
|
@@ -342,7 +361,7 @@ export function readJobContract(filePath, source, options) {
|
|
|
342
361
|
}
|
|
343
362
|
const declared = readJobDeclaration(declarationArgument, name, filePath);
|
|
344
363
|
problems.push(...declared.problems);
|
|
345
|
-
const stages = readStages(stagesArgument, filePath, sourceFile,
|
|
364
|
+
const stages = readStages(stagesArgument, filePath, sourceFile, folderFiles);
|
|
346
365
|
problems.push(...stages.problems);
|
|
347
366
|
for (const exported of found.exportedStages) {
|
|
348
367
|
if (!JOB_STAGE_NAMES.includes(exported)) {
|
|
@@ -371,9 +390,10 @@ export function readJobContract(filePath, source, options) {
|
|
|
371
390
|
resultType: stages.resultType,
|
|
372
391
|
stages: stages.stages,
|
|
373
392
|
exportedStages: found.exportedStages,
|
|
374
|
-
|
|
393
|
+
folderFiles: folderFiles.collected,
|
|
375
394
|
};
|
|
376
395
|
}
|
|
396
|
+
problems.push(...folderFiles.problems);
|
|
377
397
|
if (!contract) {
|
|
378
398
|
if (problems.length === 0) {
|
|
379
399
|
problems.push({ filePath, message: 'must declare its script: `export const { getInputData, map, summarize } = defineJob({ name, scriptId, deployments, runParameter, runs }, { ... })`.' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amerilux/netsuite-api",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The API layer for a NetSuite single-page app: endpoints served by a Restlet or a Suitelet, a typed browser client, SuiteScript module stubs for tests, and a generator that writes the client module from the controllers.",
|
|
6
6
|
"license": "MIT",
|