Skip to content

Commit 3684f5f

Browse files
committed
introduce AsyncLocalStorage for context store
1 parent be2d25f commit 3684f5f

File tree

1 file changed

+53
-14
lines changed

1 file changed

+53
-14
lines changed

packages/cubejs-schema-compiler/src/compiler/DataSchemaCompiler.ts

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AsyncLocalStorage } from 'async_hooks';
12
import crypto from 'crypto';
23
import vm from 'vm';
34
import fs from 'fs';
@@ -23,6 +24,8 @@ import { YamlCompiler } from './YamlCompiler';
2324
import { CubeDictionary } from './CubeDictionary';
2425
import { CompilerCache } from './CompilerCache';
2526

27+
const ctxFileStorage = new AsyncLocalStorage<FileContent>();
28+
2629
const NATIVE_IS_SUPPORTED = isNativeSupported();
2730

2831
const moduleFileCache = {};
@@ -231,7 +234,7 @@ export class DataSchemaCompiler {
231234
);
232235
}
233236

234-
const transpile = async (stage: CompileStage) => {
237+
const transpile = async (stage: CompileStage): Promise<FileContent[]> => {
235238
let cubeNames: string[] = [];
236239
let cubeSymbols: Record<string, Record<string, boolean>> = {};
237240
let transpilerNames: string[] = [];
@@ -306,34 +309,62 @@ export class DataSchemaCompiler {
306309
let contexts: Record<string, any>[] = [];
307310
let compiledFiles: Record<string, boolean> = {};
308311
let asyncModules: CallableFunction[] = [];
312+
let transpiledFiles: FileContent[] = [];
309313

310314
this.compileV8ContextCache = vm.createContext({
311-
view: (name, cube) => (
312-
!cube ?
315+
view: (name, cube) => {
316+
const file = ctxFileStorage.getStore();
317+
if (!file) {
318+
throw new Error('No file stored in context');
319+
}
320+
return !cube ?
313321
this.cubeFactory({ ...name, fileName: file.fileName, isView: true }) :
314-
cubes.push({ ...cube, name, fileName: file.fileName, isView: true })
315-
),
316-
cube: (name, cube) => (
317-
!cube ?
322+
cubes.push({ ...cube, name, fileName: file.fileName, isView: true });
323+
},
324+
cube: (name, cube) => {
325+
const file = ctxFileStorage.getStore();
326+
if (!file) {
327+
throw new Error('No file stored in context');
328+
}
329+
return !cube ?
318330
this.cubeFactory({ ...name, fileName: file.fileName }) :
319-
cubes.push({ ...cube, name, fileName: file.fileName })
320-
),
321-
context: (name, context) => contexts.push({ ...context, name, fileName: file.fileName }),
331+
cubes.push({ ...cube, name, fileName: file.fileName });
332+
},
333+
context: (name: string, context) => {
334+
const file = ctxFileStorage.getStore();
335+
if (!file) {
336+
throw new Error('No file stored in context');
337+
}
338+
return contexts.push({ ...context, name, fileName: file.fileName });
339+
},
322340
addExport: (obj) => {
341+
const file = ctxFileStorage.getStore();
342+
if (!file) {
343+
throw new Error('No file stored in context');
344+
}
323345
exports[file.fileName] = exports[file.fileName] || {};
324346
exports[file.fileName] = Object.assign(exports[file.fileName], obj);
325347
},
326348
setExport: (obj) => {
349+
const file = ctxFileStorage.getStore();
350+
if (!file) {
351+
throw new Error('No file stored in context');
352+
}
327353
exports[file.fileName] = obj;
328354
},
329355
asyncModule: (fn) => {
330356
asyncModules.push(fn);
331357
},
332-
require: (extensionName) => {
358+
require: (extensionName: string) => {
359+
const file = ctxFileStorage.getStore();
360+
if (!file) {
361+
throw new Error('No file stored in context');
362+
}
363+
333364
if (this.extensions[extensionName]) {
334365
return new (this.extensions[extensionName])(this.cubeFactory, this, cubes);
335366
} else {
336-
const foundFile = this.resolveModuleFile(file, extensionName, toCompile, errorsReport);
367+
const foundFile = this.resolveModuleFile(file, extensionName, transpiledFiles, errorsReport);
337368
if (!foundFile && this.allowNodeRequire) {
338369
if (extensionName.indexOf('.') === 0) {
339370
extensionName = path.resolve(this.repository.localPath(), extensionName);
@@ -360,14 +391,16 @@ export class DataSchemaCompiler {
360391
});
361392

362393
const compilePhase = async (compilers: CompileCubeFilesCompilers, stage: 0 | 1 | 2 | 3) => {
363-
const res = this.compileCubeFiles(cubes, contexts, compiledFiles, asyncModules, compilers, await transpile(stage), errorsReport);
394+
transpiledFiles = await transpile(stage);
395+
const res = this.compileCubeFiles(cubes, contexts, compiledFiles, asyncModules, compilers, transpiledFiles, errorsReport);
364396

365397
// clear the objects for the next phase
366398
cubes = [];
367399
exports = {};
368400
contexts = [];
369401
compiledFiles = {};
370402
asyncModules = [];
403+
transpiledFiles = [];
371404

372405
return res;
373406
};
@@ -676,7 +709,13 @@ export class DataSchemaCompiler {
676709

677710
try {
678711
const script = this.getJsScript(file);
679-
script.runInContext(this.compileV8ContextCache!, { timeout: 15000 });
712+
713+
// We use AsyncLocalStorage to store the current file context
714+
// so that it can be accessed in the script execution context even within async functions.
715+
// @see https://nodejs.org/api/async_context.html
716+
ctxFileStorage.run(file, () => {
717+
script.runInContext(this.compileV8ContextCache!, { timeout: 15000 });
718+
});
680719
} catch (e) {
681720
errorsReport.error(e);
682721
}

0 commit comments

Comments
 (0)