UNPKG

18.5 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6const Engine_1 = require("./Engine");
7const debug_1 = __importDefault(require("debug"));
8const get_platform_1 = require("@prisma/get-platform");
9const path_1 = __importDefault(require("path"));
10const net_1 = __importDefault(require("net"));
11const fs_1 = __importDefault(require("fs"));
12const chalk_1 = __importDefault(require("chalk"));
13const printGeneratorConfig_1 = require("./printGeneratorConfig");
14const util_1 = require("./util");
15const util_2 = require("util");
16const events_1 = __importDefault(require("events"));
17const log_1 = require("./log");
18const child_process_1 = require("child_process");
19const byline_1 = __importDefault(require("./byline"));
20// import { Client } from './client'
21const bent_1 = __importDefault(require("bent"));
22const debug = debug_1.default('engine');
23const exists = util_2.promisify(fs_1.default.exists);
24/**
25 * Node.js based wrapper to run the Prisma binary
26 */
27const knownPlatforms = [
28 'native',
29 'darwin',
30 'debian-openssl-1.0.x',
31 'debian-openssl-1.1.x',
32 'rhel-openssl-1.0.x',
33 'rhel-openssl-1.1.x',
34 'windows',
35];
36class NodeEngine {
37 constructor({ cwd, datamodelPath, prismaPath, generator, datasources, showColors, logLevel, logQueries, env, flags, ...args }) {
38 /**
39 * exiting is used to tell the .on('exit') hook, if the exit came from our script.
40 * As soon as the Prisma binary returns a correct return code (like 1 or 0), we don't need this anymore
41 */
42 this.exiting = false;
43 this.managementApiEnabled = false;
44 this.ready = false;
45 this.stderrLogs = '';
46 this.stdoutLogs = '';
47 this.fail = async (e, why) => {
48 debug(e, why);
49 await this.stop();
50 };
51 this.env = env;
52 this.cwd = this.resolveCwd(cwd);
53 this.debug = args.debug || false;
54 this.datamodelPath = datamodelPath;
55 this.prismaPath = process.env.PRISMA_QUERY_ENGINE_BINARY || prismaPath;
56 this.generator = generator;
57 this.datasources = datasources;
58 this.logEmitter = new events_1.default();
59 this.showColors = showColors || false;
60 this.logLevel = logLevel;
61 this.logQueries = logQueries || false;
62 this.flags = flags || [];
63 this.logEmitter.on('error', (log) => {
64 if (this.debug) {
65 debug_1.default('engine:log')(log);
66 }
67 this.lastErrorLog = log;
68 if (log.fields.message === 'PANIC') {
69 this.handlePanic(log);
70 }
71 });
72 if (this.platform) {
73 if (!knownPlatforms.includes(this.platform) && !fs_1.default.existsSync(this.platform)) {
74 throw new Engine_1.PrismaClientInitializationError(`Unknown ${chalk_1.default.red('PRISMA_QUERY_ENGINE_BINARY')} ${chalk_1.default.redBright.bold(this.platform)}. Possible binaryTargets: ${chalk_1.default.greenBright(knownPlatforms.join(', '))} or a path to the query engine binary.
75You may have to run ${chalk_1.default.greenBright('prisma2 generate')} for your changes to take effect.`);
76 }
77 }
78 else {
79 this.getPlatform();
80 }
81 if (this.debug) {
82 debug_1.default.enable('*');
83 }
84 }
85 resolveCwd(cwd) {
86 if (cwd && fs_1.default.existsSync(cwd) && fs_1.default.lstatSync(cwd).isDirectory()) {
87 return cwd;
88 }
89 return process.cwd();
90 }
91 on(event, listener) {
92 this.logEmitter.on(event, listener);
93 }
94 async getPlatform() {
95 if (this.platformPromise) {
96 return this.platformPromise;
97 }
98 this.platformPromise = get_platform_1.getPlatform();
99 return this.platformPromise;
100 }
101 getQueryEnginePath(platform, prefix = __dirname) {
102 let queryEnginePath = path_1.default.join(prefix, `query-engine-${platform}`);
103 if (platform === 'windows') {
104 queryEnginePath = `${queryEnginePath}.exe`;
105 }
106 return queryEnginePath;
107 }
108 handlePanic(log) {
109 this.child.kill();
110 if (this.currentRequestPromise) {
111 ;
112 this.currentRequestPromise.cancel();
113 }
114 }
115 async resolvePrismaPath() {
116 if (this.prismaPath) {
117 return this.prismaPath;
118 }
119 const platform = await this.getPlatform();
120 if (this.platform && this.platform !== platform) {
121 this.incorrectlyPinnedPlatform = this.platform;
122 }
123 this.platform = this.platform || platform;
124 const fileName = eval(`require('path').basename(__filename)`);
125 if (fileName === 'NodeEngine.js') {
126 return this.getQueryEnginePath(this.platform, path_1.default.resolve(__dirname, `..`));
127 }
128 else {
129 return this.getQueryEnginePath(this.platform);
130 }
131 }
132 // get prisma path
133 async getPrismaPath() {
134 const prismaPath = await this.resolvePrismaPath();
135 const platform = await this.getPlatform();
136 if (!(await exists(prismaPath))) {
137 let info = '.';
138 if (this.generator) {
139 const fixedGenerator = {
140 ...this.generator,
141 binaryTargets: util_1.fixPlatforms(this.generator.binaryTargets, this.platform),
142 };
143 info = `:\n${chalk_1.default.greenBright(printGeneratorConfig_1.printGeneratorConfig(fixedGenerator))}`;
144 }
145 const pinnedStr = this.incorrectlyPinnedPlatform
146 ? `\nYou incorrectly pinned it to ${chalk_1.default.redBright.bold(`${this.incorrectlyPinnedPlatform}`)}\n`
147 : '';
148 throw new Engine_1.PrismaClientInitializationError(`Query engine binary for current platform ${chalk_1.default.bold.greenBright(platform)} could not be found.${pinnedStr}
149Prisma Client looked in ${chalk_1.default.underline(prismaPath)} but couldn't find it.
150Make sure to adjust the generator configuration in the ${chalk_1.default.bold('schema.prisma')} file${info}
151Please run ${chalk_1.default.greenBright('prisma2 generate')} for your changes to take effect.
152${chalk_1.default.gray(`Note, that by providing \`native\`, Prisma Client automatically resolves \`${platform}\`.
153Read more about deploying Prisma Client: ${chalk_1.default.underline('https://github.com/prisma/prisma2/blob/master/docs/core/generators/prisma-client-js.md')}`)}`);
154 }
155 if (this.incorrectlyPinnedPlatform) {
156 console.log(`${chalk_1.default.yellow('Warning:')} You pinned the platform ${chalk_1.default.bold(this.incorrectlyPinnedPlatform)}, but Prisma Client detects ${chalk_1.default.bold(await this.getPlatform())}.
157This means you should very likely pin the platform ${chalk_1.default.greenBright(await this.getPlatform())} instead.
158${chalk_1.default.dim("In case we're mistaken, please report this to us 🙏.")}`);
159 }
160 util_1.plusX(prismaPath);
161 return prismaPath;
162 }
163 printDatasources() {
164 if (this.datasources) {
165 return JSON.stringify(this.datasources);
166 }
167 return '[]';
168 }
169 /**
170 * Starts the engine, returns the url that it runs on
171 */
172 async start() {
173 if (!this.startPromise) {
174 this.startPromise = this.internalStart();
175 }
176 return this.startPromise;
177 }
178 internalStart() {
179 return new Promise(async (resolve, reject) => {
180 try {
181 this.port = await this.getFreePort();
182 const env = {
183 PRISMA_DML_PATH: this.datamodelPath,
184 PORT: String(this.port),
185 RUST_BACKTRACE: '1',
186 RUST_LOG: 'info',
187 };
188 if (this.logQueries || this.logLevel === 'info') {
189 env.RUST_LOG = 'info';
190 if (this.logQueries) {
191 env.LOG_QUERIES = 'true';
192 }
193 }
194 if (this.logLevel === 'warn') {
195 env.RUST_LOG = 'warn';
196 }
197 if (this.datasources) {
198 env.OVERWRITE_DATASOURCES = this.printDatasources();
199 }
200 if (!process.env.NO_COLOR && this.showColors) {
201 env.CLICOLOR_FORCE = '1';
202 }
203 debug(env);
204 debug({ cwd: this.cwd });
205 const prismaPath = await this.getPrismaPath();
206 const flags = ['--enable_raw_queries', ...this.flags];
207 debug({ flags });
208 this.child = child_process_1.spawn(prismaPath, flags, {
209 env: {
210 ...this.env,
211 ...process.env,
212 ...env,
213 },
214 cwd: this.cwd,
215 stdio: ['pipe', 'pipe', 'pipe'],
216 });
217 byline_1.default(this.child.stderr).on('data', msg => {
218 const data = String(msg);
219 debug('stderr', data);
220 try {
221 const json = JSON.parse(data);
222 if (typeof json.is_panic !== 'undefined') {
223 debug(json);
224 this.lastError = json;
225 if (this.engineStartDeferred) {
226 this.engineStartDeferred.reject(new Engine_1.PrismaClientInitializationError(this.lastError.message));
227 }
228 }
229 }
230 catch (e) {
231 if (!data.includes('Printing to stderr') && !data.includes('Listening on ')) {
232 this.stderrLogs += '\n' + data;
233 }
234 }
235 });
236 byline_1.default(this.child.stdout).on('data', msg => {
237 var _a;
238 const data = String(msg);
239 try {
240 const json = JSON.parse(data);
241 debug('stdout', json);
242 if (this.engineStartDeferred &&
243 json.level === 'INFO' &&
244 json.target === 'prisma::server' && ((_a = json.fields) === null || _a === void 0 ? void 0 : _a.message.startsWith('Started http server'))) {
245 this.engineStartDeferred.resolve();
246 this.engineStartDeferred = undefined;
247 }
248 if (typeof json.is_panic === 'undefined') {
249 const log = log_1.convertLog(json);
250 this.logEmitter.emit(log.level, log);
251 }
252 else {
253 this.lastError = json;
254 }
255 }
256 catch (e) {
257 // debug(e, data)
258 }
259 });
260 this.child.on('exit', (code, signal) => {
261 if (!this.child) {
262 return;
263 }
264 if (this.lastError) {
265 return;
266 }
267 if (this.lastErrorLog) {
268 this.lastErrorLog.target = 'exit';
269 return;
270 }
271 if (code === 126) {
272 this.lastErrorLog = {
273 timestamp: new Date(),
274 target: 'exit',
275 level: 'error',
276 fields: {
277 message: `Couldn't start query engine as it's not executable on this operating system.
278You very likely have the wrong "binaryTarget" defined in the schema.prisma file.`,
279 },
280 };
281 }
282 else {
283 this.lastErrorLog = {
284 target: 'exit',
285 timestamp: new Date(),
286 level: 'error',
287 fields: {
288 message: (this.stderrLogs || '') + (this.stdoutLogs || '') + code,
289 },
290 };
291 }
292 });
293 this.child.on('error', err => {
294 this.lastError = {
295 message: err.message,
296 backtrace: 'Could not start query engine',
297 is_panic: false,
298 };
299 reject(err);
300 });
301 this.child.on('close', (code, signal) => {
302 if (code === null && signal === 'SIGABRT' && this.child) {
303 console.error(`${chalk_1.default.bold.red(`Error in Prisma Client:`)}${this.stderrLogs}
304
305This is a non-recoverable error which probably happens when the Prisma Query Engine has a stack overflow.
306Please create an issue in https://github.com/prisma/prisma-client-js describing the last Prisma Client query you called.`);
307 }
308 });
309 if (this.lastError) {
310 return reject(new Engine_1.PrismaClientInitializationError(Engine_1.getMessage(this.lastError)));
311 }
312 if (this.lastErrorLog) {
313 return reject(new Engine_1.PrismaClientInitializationError(Engine_1.getMessage(this.lastErrorLog)));
314 }
315 try {
316 await new Promise((resolve, reject) => {
317 this.engineStartDeferred = { resolve, reject };
318 });
319 }
320 catch (err) {
321 await this.child.kill();
322 throw err;
323 }
324 const url = `http://localhost:${this.port}`;
325 this.url = url;
326 // TODO: Re-enable
327 // this.client = new Client(url)
328 resolve();
329 }
330 catch (e) {
331 reject(e);
332 }
333 });
334 }
335 /**
336 * If Prisma runs, stop it
337 */
338 async stop() {
339 await this.start();
340 if (this.currentRequestPromise) {
341 try {
342 await this.currentRequestPromise;
343 }
344 catch (e) {
345 //
346 }
347 }
348 if (this.child) {
349 debug(`Stopping Prisma engine`);
350 this.exiting = true;
351 // this.client.close()
352 await this.child.kill();
353 delete this.child;
354 }
355 }
356 /**
357 * Use the port 0 trick to get a new port
358 */
359 getFreePort() {
360 return new Promise((resolve, reject) => {
361 const server = net_1.default.createServer(s => s.end(''));
362 server.unref();
363 server.on('error', reject);
364 server.listen(0, () => {
365 const address = server.address();
366 const port = typeof address === 'string' ? parseInt(address.split(':').slice(-1)[0], 10) : address.port;
367 server.close(e => {
368 if (e) {
369 reject(e);
370 }
371 resolve(port);
372 });
373 });
374 });
375 }
376 /**
377 * Make sure that our internal port is not conflicting with the prisma.yml's port
378 * @param str config
379 */
380 trimPort(str) {
381 return str
382 .split('\n')
383 .filter(l => !l.startsWith('port:'))
384 .join('\n');
385 }
386 async request(queries) {
387 await this.start();
388 if (!this.child) {
389 throw new Engine_1.PrismaClientUnknownRequestError(`Can't perform request, as the Engine has already been stopped`);
390 }
391 const variables = {};
392 const body = {
393 batch: queries.map(query => ({ query, variables })),
394 };
395 const post = bent_1.default(this.url, 'POST', 'json', 200);
396 this.currentRequestPromise = post('/', body);
397 return this.currentRequestPromise
398 .then(data => {
399 return data.map(result => {
400 if (result.errors) {
401 return this.graphQLToJSError(result.errors[0]);
402 }
403 return result;
404 });
405 })
406 .catch(error => {
407 debug({ error });
408 if (this.currentRequestPromise.isCanceled && this.lastError) {
409 // TODO: Replace these errors with known or unknown request errors
410 if (this.lastError.is_panic) {
411 throw new Engine_1.PrismaClientRustPanicError(Engine_1.getMessage(this.lastError));
412 }
413 else {
414 throw new Engine_1.PrismaClientUnknownRequestError(Engine_1.getMessage(this.lastError));
415 }
416 }
417 if (this.currentRequestPromise.isCanceled && this.lastErrorLog) {
418 throw new Engine_1.PrismaClientUnknownRequestError(Engine_1.getMessage(this.lastErrorLog));
419 }
420 if ((error.code && error.code === 'ECONNRESET') || error.code === 'ECONNREFUSED') {
421 if (this.lastError) {
422 throw new Engine_1.PrismaClientUnknownRequestError(Engine_1.getMessage(this.lastError));
423 }
424 if (this.lastErrorLog) {
425 throw new Engine_1.PrismaClientUnknownRequestError(Engine_1.getMessage(this.lastErrorLog));
426 }
427 const logs = this.stderrLogs || this.stdoutLogs;
428 throw new Engine_1.PrismaClientUnknownRequestError(logs);
429 }
430 throw error;
431 });
432 }
433 graphQLToJSError(error) {
434 if (error.user_facing_error.error_code) {
435 return new Engine_1.PrismaClientKnownRequestError(error.user_facing_error.message, error.user_facing_error.error_code, error.user_facing_error.meta);
436 }
437 return new Engine_1.PrismaClientUnknownRequestError(error.user_facing_error.message);
438 }
439}
440exports.NodeEngine = NodeEngine;
441//# sourceMappingURL=NodeEngine.js.map
\No newline at end of file