1 | "use strict";
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
5 | Object.defineProperty(exports, "__esModule", { value: true });
|
6 | const Engine_1 = require("./Engine");
|
7 | const debug_1 = __importDefault(require("debug"));
|
8 | const get_platform_1 = require("@prisma/get-platform");
|
9 | const path_1 = __importDefault(require("path"));
|
10 | const net_1 = __importDefault(require("net"));
|
11 | const fs_1 = __importDefault(require("fs"));
|
12 | const chalk_1 = __importDefault(require("chalk"));
|
13 | const printGeneratorConfig_1 = require("./printGeneratorConfig");
|
14 | const util_1 = require("./util");
|
15 | const util_2 = require("util");
|
16 | const events_1 = __importDefault(require("events"));
|
17 | const log_1 = require("./log");
|
18 | const child_process_1 = require("child_process");
|
19 | const byline_1 = __importDefault(require("./byline"));
|
20 |
|
21 | const bent_1 = __importDefault(require("bent"));
|
22 | const debug = debug_1.default('engine');
|
23 | const exists = util_2.promisify(fs_1.default.exists);
|
24 |
|
25 |
|
26 |
|
27 | const 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 | ];
|
36 | class NodeEngine {
|
37 | constructor({ cwd, datamodelPath, prismaPath, generator, datasources, showColors, logLevel, logQueries, env, flags, ...args }) {
|
38 | |
39 |
|
40 |
|
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.
|
75 | You 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 |
|
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}
|
149 | Prisma Client looked in ${chalk_1.default.underline(prismaPath)} but couldn't find it.
|
150 | Make sure to adjust the generator configuration in the ${chalk_1.default.bold('schema.prisma')} file${info}
|
151 | Please 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}\`.
|
153 | Read 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())}.
|
157 | This 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 |
|
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 |
|
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.
|
278 | You 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 |
|
305 | This is a non-recoverable error which probably happens when the Prisma Query Engine has a stack overflow.
|
306 | Please 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 |
|
327 |
|
328 | resolve();
|
329 | }
|
330 | catch (e) {
|
331 | reject(e);
|
332 | }
|
333 | });
|
334 | }
|
335 | |
336 |
|
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 |
|
352 | await this.child.kill();
|
353 | delete this.child;
|
354 | }
|
355 | }
|
356 | |
357 |
|
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 |
|
378 |
|
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 |
|
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 | }
|
440 | exports.NodeEngine = NodeEngine;
|
441 |
|
\ | No newline at end of file |