| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892 |
- import * as base from '../source/base.js';
- import * as fs from 'fs/promises';
- import * as node from '../source/node.js';
- import * as path from 'path';
- import * as process from 'process';
- import * as python from '../source/python.js';
- import * as tar from '../source/tar.js';
- import * as url from 'url';
- import * as view from '../source/view.js';
- import * as worker_threads from 'worker_threads';
- import * as zip from '../source/zip.js';
- const access = async (path) => {
- try {
- await fs.access(path);
- return true;
- } catch {
- return false;
- }
- };
- const dirname = (...args) => {
- const file = url.fileURLToPath(import.meta.url);
- const dir = path.dirname(file);
- return path.join(dir, ...args);
- };
- const decompress = (buffer) => {
- let archive = zip.Archive.open(buffer, 'gzip');
- if (archive && archive.entries.size === 1) {
- const stream = archive.entries.values().next().value;
- buffer = stream.peek();
- }
- const formats = [zip, tar];
- for (const module of formats) {
- archive = module.Archive.open(buffer);
- if (archive) {
- break;
- }
- }
- return archive;
- };
- const host = {};
- host.TestHost = class {
- constructor(window, environment) {
- this._window = window;
- this._environment = environment;
- this._errors = [];
- host.TestHost.source = host.TestHost.source || dirname('..', 'source');
- }
- async view(/* view */) {
- }
- async start() {
- }
- get window() {
- return this._window;
- }
- get document() {
- return this._window.document;
- }
- get errors() {
- return this._errors;
- }
- get type() {
- return 'Test';
- }
- environment(name) {
- return this._environment[name];
- }
- update() {
- }
- screen(/* name */) {
- }
- async require(id) {
- const file = path.join(host.TestHost.source, `${id}.js`);
- return await import(`file://${file}`);
- }
- worker(id) {
- const file = path.join(host.TestHost.source, `${id}.js`);
- const worker = new worker_threads.Worker(file);
- worker.addEventListener = (type, listener) => {
- worker.on(type, (message) => listener({ data: message }));
- };
- return worker;
- }
- async request(file, encoding, basename) {
- const pathname = path.join(basename || host.TestHost.source, file);
- const exists = await access(pathname);
- if (!exists) {
- throw new Error(`The file '${file}' does not exist.`);
- }
- const stats = await fs.stat(pathname);
- if (stats.isDirectory()) {
- throw new Error(`The path '${file}' is a directory.`);
- }
- if (encoding) {
- return await fs.readFile(pathname, encoding);
- }
- return new node.FileStream(pathname, 0, stats.size, stats.mtimeMs);
- }
- event(/* name, params */) {
- }
- exception(error /*, fatal */) {
- this._errors.push(error);
- }
- message() {
- }
- };
- host.TestHost.Context = class {
- constructor(host, folder, identifier, stream, entries) {
- this._host = host;
- this._folder = folder;
- this._identifier = identifier;
- this._stream = stream;
- this._entries = entries;
- }
- get identifier() {
- return this._identifier;
- }
- get stream() {
- return this._stream;
- }
- get entries() {
- return this._entries;
- }
- async request(file, encoding, base) {
- return this._host.request(file, encoding, base === undefined ? this._folder : base);
- }
- async require(id) {
- return this._host.require(id);
- }
- error(error, fatal) {
- this._host.exception(error, fatal);
- }
- };
- class CSSStyleDeclaration {
- constructor() {
- this._properties = new Map();
- }
- setProperty(name, value) {
- this._properties.set(name, value);
- }
- removeProperty(name) {
- this._properties.delete(name);
- }
- }
- class DOMTokenList {
- constructor(element) {
- this._element = element;
- }
- add(...tokens) {
- const value = this._element.getAttribute('class') || '';
- const set = new Set(value.split(' ').concat(...tokens));
- this._element.setAttribute('class', Array.from(set).filter((s) => s).join(' '));
- }
- contains(token) {
- const value = this._element.getAttribute('class');
- if (value === null || value.indexOf(token) === -1) {
- return false;
- }
- return value.split(' ').some((s) => s === token);
- }
- }
- class HTMLElement {
- constructor() {
- this._childNodes = [];
- this._attributes = new Map();
- this._style = new CSSStyleDeclaration();
- }
- get style() {
- return this._style;
- }
- get childNodes() {
- return this._childNodes;
- }
- get firstChild() {
- return this._childNodes.length > 0 ? this._childNodes[0] : null;
- }
- get lastChild() {
- const index = this._childNodes.length - 1;
- if (index >= 0) {
- return this._childNodes[index];
- }
- return null;
- }
- appendChild(node) {
- this._childNodes.push(node);
- }
- insertBefore(newNode, referenceNode) {
- const index = this._childNodes.indexOf(referenceNode);
- if (index !== -1) {
- this._childNodes.splice(index, 0, newNode);
- }
- }
- removeChild(node) {
- const index = this._childNodes.lastIndexOf(node);
- if (index !== -1) {
- this._childNodes.splice(index, 1);
- }
- }
- setAttribute(name, value) {
- this._attributes.set(name, value);
- }
- hasAttribute(name) {
- return this._attributes.has(name);
- }
- getAttribute(name) {
- return this._attributes.has(name) ? this._attributes.get(name) : null;
- }
- getElementsByClassName(name) {
- const elements = [];
- for (const node of this._childNodes) {
- if (node instanceof HTMLElement) {
- elements.push(...node.getElementsByClassName(name));
- if (node.classList.contains(name)) {
- elements.push(node);
- }
- }
- }
- return elements;
- }
- addEventListener(/* event, callback */) {
- }
- removeEventListener(/* event, callback */) {
- }
- get classList() {
- this._classList = this._classList || new DOMTokenList(this);
- return this._classList;
- }
- getBBox() {
- return { x: 0, y: 0, width: 10, height: 10 };
- }
- getBoundingClientRect() {
- return { left: 0, top: 0, width: 0, height: 0 };
- }
- scrollTo() {
- }
- focus() {
- }
- }
- class Document {
- constructor() {
- this._elements = {};
- this._documentElement = new HTMLElement();
- this._body = new HTMLElement();
- }
- get documentElement() {
- return this._documentElement;
- }
- get body() {
- return this._body;
- }
- createElement(/* name */) {
- return new HTMLElement();
- }
- createElementNS(/* namespace, name */) {
- return new HTMLElement();
- }
- createTextNode(/* text */) {
- return new HTMLElement();
- }
- getElementById(id) {
- let element = this._elements[id];
- if (!element) {
- element = new HTMLElement();
- this._elements[id] = element;
- }
- return element;
- }
- addEventListener(/* event, callback */) {
- }
- removeEventListener(/* event, callback */) {
- }
- }
- class Window {
- constructor() {
- this._document = new Document();
- }
- get document() {
- return this._document;
- }
- addEventListener(/* event, callback */) {
- }
- removeEventListener(/* event, callback */) {
- }
- requestAnimationFrame(callback) {
- callback();
- }
- }
- export class Target {
- constructor(item) {
- Object.assign(this, item);
- this.events = {};
- this.tags = new Set(this.tags);
- this.folder = item.type ? path.normalize(dirname('..', 'third_party' , 'test', item.type)) : process.cwd();
- this.assert = !this.assert || Array.isArray(this.assert) ? this.assert : [this.assert];
- }
- on(event, callback) {
- this.events[event] = this.events[event] || [];
- this.events[event].push(callback);
- }
- emit(event, data) {
- if (this.events && this.events[event]) {
- for (const callback of this.events[event]) {
- callback(this, data);
- }
- }
- }
- status(message) {
- this.emit('status', message);
- }
- async execute() {
- if (this.measures) {
- this.measures.set('name', this.name);
- }
- await zip.Archive.import();
- this.window = this.window || new Window();
- const environment = {
- zoom: 'none',
- measure: this.measures ? true : false
- };
- this.host = await new host.TestHost(this.window, environment);
- this.view = new view.View(this.host);
- this.view.options.attributes = true;
- this.view.options.initializers = true;
- const time = async (method) => {
- const start = process.hrtime.bigint();
- let err = null;
- try {
- await method.call(this);
- } catch (error) {
- err = error;
- }
- const duration = Number(process.hrtime.bigint() - start) / 1e9;
- if (this.measures) {
- this.measures.set(method.name, duration);
- }
- if (err) {
- throw err;
- }
- };
- this.status({ name: 'name', target: this.name });
- const errors = [];
- try {
- await time(this.download);
- await time(this.load);
- await time(this.validate);
- if (!this.tags.has('skip-render')) {
- await time(this.render);
- }
- } catch (error) {
- errors.push(error);
- }
- errors.push(...this.host.errors);
- if (errors.length === 0 && this.error) {
- throw new Error('Expected error.');
- }
- if (errors.length > 0 && (!this.error || errors.map((error) => error.message).join('\n') !== this.error)) {
- throw errors[0];
- }
- }
- async request(url, init) {
- const response = await fetch(url, init);
- if (!response.ok) {
- throw new Error(response.status.toString());
- }
- if (response.body) {
- const reader = response.body.getReader();
- const length = response.headers.has('Content-Length') ? parseInt(response.headers.get('Content-Length'), 10) : -1;
- let position = 0;
- /* eslint-disable consistent-this */
- const target = this;
- /* eslint-enable consistent-this */
- const stream = new ReadableStream({
- async start(controller) {
- const read = async () => {
- try {
- const result = await reader.read();
- if (result.done) {
- target.status({ name: 'download' });
- controller.close();
- } else {
- position += result.value.length;
- if (length >= 0) {
- const percent = position / length;
- target.status({ name: 'download', target: url, percent });
- } else {
- target.status({ name: 'download', target: url, position });
- }
- controller.enqueue(result.value);
- return await read();
- }
- } catch (error) {
- controller.error(error);
- throw error;
- }
- return null;
- };
- return read();
- }
- });
- return new Response(stream, {
- status: response.status,
- statusText: response.statusText,
- headers: response.headers
- });
- }
- return response;
- }
- async download(targets, sources) {
- targets = targets || Array.from(this.targets);
- sources = sources || this.source;
- const files = targets.map((file) => path.resolve(this.folder, file));
- const exists = await Promise.all(files.map((file) => access(file)));
- if (exists.every((value) => value)) {
- return;
- }
- if (!sources) {
- throw new Error('Download source not specified.');
- }
- let source = '';
- let sourceFiles = [];
- const match = sources.match(/^(.*?)\[(.*?)\](.*)$/);
- if (match) {
- [, source, sourceFiles, sources] = match;
- sourceFiles = sourceFiles.split(',').map((file) => file.trim());
- sources = sources && sources.startsWith(',') ? sources.substring(1).trim() : '';
- } else {
- const comma = sources.indexOf(',');
- if (comma === -1) {
- source = sources;
- sources = '';
- } else {
- source = sources.substring(0, comma);
- sources = sources.substring(comma + 1);
- }
- }
- await Promise.all(targets.map((target) => {
- const dir = path.dirname(`${this.folder}/${target}`);
- return fs.mkdir(dir, { recursive: true });
- }));
- const response = await this.request(source);
- const buffer = await response.arrayBuffer();
- const data = new Uint8Array(buffer);
- if (sourceFiles.length > 0) {
- this.status({ name: 'decompress' });
- const archive = decompress(data);
- for (const name of sourceFiles) {
- this.status({ name: 'write', target: name });
- if (name === '.') {
- const target = targets.shift();
- const dir = path.join(this.folder, target);
- /* eslint-disable no-await-in-loop */
- await fs.mkdir(dir, { recursive: true });
- /* eslint-enable no-await-in-loop */
- } else {
- const stream = archive.entries.get(name);
- if (!stream) {
- throw new Error(`Entry not found '${name}. Archive contains entries: ${JSON.stringify(Array.from(archive.entries.keys()))} .`);
- }
- const target = targets.shift();
- const buffer = stream.peek();
- const file = path.join(this.folder, target);
- /* eslint-disable no-await-in-loop */
- await fs.writeFile(file, buffer, null);
- /* eslint-enable no-await-in-loop */
- }
- }
- } else {
- const target = targets.shift();
- this.status({ name: 'write', target });
- await fs.writeFile(`${this.folder}/${target}`, data, null);
- }
- if (targets.length > 0 && sources.length > 0) {
- await this.download(targets, sources);
- }
- }
- async load() {
- const target = path.resolve(this.folder, this.targets[0]);
- const identifier = path.basename(target);
- const stat = await fs.stat(target);
- let context = null;
- if (stat.isFile()) {
- const stream = new node.FileStream(target, 0, stat.size, stat.mtimeMs);
- const dirname = path.dirname(target);
- context = new host.TestHost.Context(this.host, dirname, identifier, stream, new Map());
- } else if (stat.isDirectory()) {
- const entries = new Map();
- const file = async (pathname) => {
- const stat = await fs.stat(pathname);
- const stream = new node.FileStream(pathname, 0, stat.size, stat.mtimeMs);
- const name = pathname.split(path.sep).join(path.posix.sep);
- entries.set(name, stream);
- };
- const walk = async (dir) => {
- const stats = await fs.readdir(dir, { withFileTypes: true });
- const promises = [];
- for (const stat of stats) {
- const pathname = path.join(dir, stat.name);
- if (stat.isDirectory()) {
- promises.push(walk(pathname));
- } else if (stat.isFile()) {
- promises.push(file(pathname));
- }
- }
- await Promise.all(promises);
- };
- await walk(target);
- context = new host.TestHost.Context(this.host, target, identifier, null, entries);
- }
- const modelFactoryService = new view.ModelFactoryService(this.host);
- this.model = await modelFactoryService.open(context);
- this.view.model = this.model;
- }
- async validate() {
- const model = this.model;
- if (!model.format || (this.format && this.format !== model.format)) {
- throw new Error(`Invalid model format '${model.format}'.`);
- }
- if (this.producer && model.producer !== this.producer) {
- throw new Error(`Invalid producer '${model.producer}'.`);
- }
- if (this.runtime && model.runtime !== this.runtime) {
- throw new Error(`Invalid runtime '${model.runtime}'.`);
- }
- if (model.metadata && (!Array.isArray(model.metadata) || !model.metadata.every((argument) => argument.name && (argument.value || argument.value === null || argument.value === '' || argument.value === false || argument.value === 0)))) {
- throw new Error("Invalid model metadata.");
- }
- if (this.assert) {
- for (const assert of this.assert) {
- const parts = assert.split('==').map((item) => item.trim());
- const properties = parts[0].split('.');
- const value = JSON.parse(parts[1].replace(/\s*'|'\s*/g, '"'));
- let context = { model };
- while (properties.length) {
- const property = properties.shift();
- if (context[property] !== undefined) {
- context = context[property];
- continue;
- }
- const match = /(.*)\[(.*)\]/.exec(property);
- if (match && match.length === 3 && context[match[1]] !== undefined) {
- const array = context[match[1]];
- const index = parseInt(match[2], 10);
- if (array[index] !== undefined) {
- context = array[index];
- continue;
- }
- }
- throw new Error(`Invalid property path '${parts[0]}'.`);
- }
- if (context !== value) {
- throw new Error(`Invalid '${context}' != '${assert}'.`);
- }
- }
- }
- if (model.version || model.description || model.author || model.license) {
- // continue
- }
- const validateGraph = async (graph) => {
- /* eslint-disable no-unused-expressions */
- const values = new Map();
- const validateValue = async (value) => {
- if (value === null) {
- return;
- }
- value.name.toString();
- value.name.length;
- value.description;
- if (value.quantization) {
- if (!this.tags.has('quantization')) {
- throw new Error("Invalid 'quantization' tag.");
- }
- const quantization = new view.Quantization(value.quantization);
- quantization.toString();
- }
- if (value.type) {
- value.type.toString();
- }
- if (value.initializer) {
- value.initializer.type.toString();
- if (value.initializer && value.initializer.peek && !value.initializer.peek()) {
- await value.initializer.read();
- }
- const tensor = new base.Tensor(value.initializer);
- if (!this.tags.has('skip-tensor-value')) {
- if (tensor.encoding !== '<' && tensor.encoding !== '>' && tensor.encoding !== '|') {
- throw new Error(`Tensor encoding '${tensor.encoding}' is not implemented.`);
- }
- if (tensor.layout && (tensor.layout !== 'sparse' && tensor.layout !== 'sparse.coo')) {
- throw new Error(`Tensor layout '${tensor.layout}' is not implemented.`);
- }
- if (!tensor.empty) {
- if (tensor.type && tensor.type.dataType === '?') {
- throw new Error('Tensor data type is not defined.');
- } else if (tensor.type && !tensor.type.shape) {
- throw new Error('Tensor shape is not defined.');
- } else {
- tensor.toString();
- if (this.tags.has('validation')) {
- const size = tensor.type.shape.dimensions.reduce((a, b) => a * b, 1);
- if (tensor.type && tensor.type.dataType !== '?' && size < 8192) {
- let data_type = '?';
- switch (tensor.type.dataType) {
- case 'boolean': data_type = 'bool'; break;
- case 'bfloat16': data_type = 'float32'; break;
- case 'float8e5m2': data_type = 'float16'; break;
- case 'float8e5m2fnuz': data_type = 'float16'; break;
- case 'float8e4m3fn': data_type = 'float16'; break;
- case 'float8e4m3fnuz': data_type = 'float16'; break;
- case 'int4': data_type = 'int8'; break;
- default: data_type = tensor.type.dataType; break;
- }
- Target.execution = Target.execution || new python.Execution();
- const execution = Target.execution;
- const bytes = execution.invoke('io.BytesIO', []);
- const dtype = execution.invoke('numpy.dtype', [data_type]);
- const array = execution.invoke('numpy.asarray', [tensor.value, dtype]);
- execution.invoke('numpy.save', [bytes, array]);
- }
- }
- }
- }
- }
- } else if (value.name.length === 0) {
- throw new Error('Empty value name.');
- }
- if (value.name.length > 0 && value.initializer === null) {
- if (!values.has(value.name)) {
- values.set(value.name, value);
- } else if (value !== values.get(value.name)) {
- throw new Error(`Duplicate value '${value.name}'.`);
- }
- }
- };
- const signatures = Array.isArray(graph.signatures) ? graph.signatures : [graph];
- for (const signature of signatures) {
- for (const input of signature.inputs) {
- input.name.toString();
- input.name.length;
- for (const value of input.value) {
- /* eslint-disable no-await-in-loop */
- await validateValue(value);
- /* eslint-enable no-await-in-loop */
- }
- }
- for (const output of signature.outputs) {
- output.name.toString();
- output.name.length;
- if (Array.isArray(output.value)) {
- for (const value of output.value) {
- /* eslint-disable no-await-in-loop */
- await validateValue(value);
- /* eslint-enable no-await-in-loop */
- }
- }
- }
- }
- if (graph.metadata && (!Array.isArray(graph.metadata) || !graph.metadata.every((argument) => argument.name && argument.value !== undefined))) {
- throw new Error("Invalid graph metadata.");
- }
- for (const node of graph.nodes) {
- const type = node.type;
- if (!type || typeof type.name !== 'string') {
- throw new Error(`Invalid node type '${JSON.stringify(node.type)}'.`);
- }
- if (Array.isArray(type.nodes)) {
- /* eslint-disable no-await-in-loop */
- await validateGraph(type);
- /* eslint-enable no-await-in-loop */
- }
- view.Documentation.open(type);
- node.name.toString();
- node.description;
- if (node.metadata && (!Array.isArray(node.metadata) || !node.metadata.every((argument) => argument.name && argument.value !== undefined))) {
- throw new Error("Invalid node metadata.");
- }
- const attributes = node.attributes;
- if (attributes) {
- for (const attribute of attributes) {
- attribute.name.toString();
- attribute.name.length;
- const type = attribute.type;
- const value = attribute.value;
- if ((type === 'graph' || type === 'function') && value && Array.isArray(value.nodes)) {
- /* eslint-disable no-await-in-loop */
- await validateGraph(value);
- /* eslint-enable no-await-in-loop */
- } else {
- let text = new view.Formatter(attribute.value, attribute.type).toString();
- if (text && text.length > 1000) {
- text = `${text.substring(0, 1000)}...`;
- }
- /* value = */ text.split('<');
- }
- }
- }
- const inputs = node.inputs;
- if (Array.isArray(inputs)) {
- for (const input of inputs) {
- input.name.toString();
- input.name.length;
- if (!input.type || input.type.endsWith('*')) {
- for (const value of input.value) {
- /* eslint-disable no-await-in-loop */
- await validateValue(value);
- /* eslint-enable no-await-in-loop */
- }
- if (this.tags.has('validation')) {
- if (input.value.length === 1 && input.value[0].initializer) {
- const sidebar = new view.TensorSidebar(this.view, input);
- sidebar.render();
- }
- }
- }
- }
- }
- const outputs = node.outputs;
- if (Array.isArray(outputs)) {
- for (const output of node.outputs) {
- output.name.toString();
- output.name.length;
- if (!output.type || output.type.endsWith('*')) {
- for (const value of output.value) {
- /* eslint-disable no-await-in-loop */
- await validateValue(value);
- /* eslint-enable no-await-in-loop */
- }
- }
- }
- }
- if (node.chain) {
- for (const chain of node.chain) {
- chain.name.toString();
- chain.name.length;
- }
- }
- const sidebar = new view.NodeSidebar(this.view, node);
- sidebar.render();
- }
- const sidebar = new view.ModelSidebar(this.view, this.model, graph);
- sidebar.render();
- /* eslint-enable no-unused-expressions */
- };
- const validateTarget = async (target) => {
- switch (target.type) {
- default: {
- await validateGraph(target);
- }
- }
- };
- for (const module of model.modules) {
- /* eslint-disable no-await-in-loop */
- await validateTarget(module);
- /* eslint-enable no-await-in-loop */
- }
- const functions = model.functions || [];
- for (const func of functions) {
- /* eslint-disable no-await-in-loop */
- await validateTarget(func);
- /* eslint-enable no-await-in-loop */
- }
- }
- async render() {
- for (const graph of this.model.modules) {
- const signatures = Array.isArray(graph.signatures) && graph.signatures.length > 0 ? graph.signatures : [graph];
- for (const signature of signatures) {
- /* eslint-disable no-await-in-loop */
- await this.view.render(graph, signature);
- /* eslint-enable no-await-in-loop */
- }
- }
- }
- }
- if (!worker_threads.isMainThread) {
- worker_threads.parentPort.addEventListener('message', async (e) => {
- const message = e.data;
- const response = {};
- try {
- const target = new Target(message);
- response.type = 'complete';
- response.target = target.name;
- target.on('status', (sender, message) => {
- message = { type: 'status', ...message };
- worker_threads.parentPort.postMessage(message);
- });
- if (message.measures) {
- target.measures = new Map();
- }
- await target.execute();
- response.measures = target.measures;
- } catch (error) {
- response.type = 'error';
- response.error = {
- name: error.name,
- message: error.message
- };
- const cause = error.cause;
- if (cause) {
- response.error.cause = {
- name: cause.name,
- message: cause.message
- };
- }
- }
- worker_threads.parentPort.postMessage(response);
- });
- }
|