diff --git a/helpers/compile/plugins/fill-plugin/fillers/function.ts b/helpers/compile/plugins/fill-plugin/fillers/function.ts new file mode 100644 index 000000000000..8581d768bbee --- /dev/null +++ b/helpers/compile/plugins/fill-plugin/fillers/function.ts @@ -0,0 +1,3 @@ +export const fn = () => {} + +fn.prototype = fn diff --git a/packages/cli/src/Doctor.ts b/packages/cli/src/Doctor.ts index 7705fcb9df1a..d720889e5f39 100644 --- a/packages/cli/src/Doctor.ts +++ b/packages/cli/src/Doctor.ts @@ -1,9 +1,10 @@ import type { DMMF } from '@prisma/generator-helper' import { getSchemaPathAndPrint } from '@prisma/migrate' -import type { Command } from '@prisma/sdk' import { arg, canConnectToDatabase, + checkUnsupportedDataProxy, + Command, format, getConfig, getDMMF, @@ -64,6 +65,8 @@ ${chalk.bold('Examples')} return this.help(args.message) } + await checkUnsupportedDataProxy('doctor', args, true) + if (args['--help']) { return this.help() } diff --git a/packages/cli/src/Generate.ts b/packages/cli/src/Generate.ts index 61fc58c98ffb..c7c4f58a5460 100644 --- a/packages/cli/src/Generate.ts +++ b/packages/cli/src/Generate.ts @@ -138,7 +138,7 @@ ${chalk.bold('Examples')} printDownloadProgress: !watchMode, version: enginesVersion, cliVersion: pkg.version, - dataProxy: args['--data-proxy'], + dataProxy: !!args['--data-proxy'], }) if (!generators || generators.length === 0) { @@ -261,7 +261,7 @@ Please run \`${getCommandWithExecutor('prisma generate')}\` to see the errors.`) printDownloadProgress: !watchMode, version: enginesVersion, cliVersion: pkg.version, - dataProxy: args['--data-proxy'], + dataProxy: !!args['--data-proxy'], }) if (!generatorsWatch || generatorsWatch.length === 0) { diff --git a/packages/cli/src/Init.ts b/packages/cli/src/Init.ts index 2cad34973935..111876c53137 100644 --- a/packages/cli/src/Init.ts +++ b/packages/cli/src/Init.ts @@ -1,8 +1,9 @@ import type { ConnectorType } from '@prisma/generator-helper' -import type { Command } from '@prisma/sdk' import { arg, canConnectToDatabase, + checkUnsupportedDataProxy, + Command, format, getCommandWithExecutor, HelpError, @@ -134,6 +135,8 @@ export class Init implements Command { return this.help() } + await checkUnsupportedDataProxy('init', args, false) + /** * Validation */ diff --git a/packages/cli/src/Studio.ts b/packages/cli/src/Studio.ts index 3d919b34b638..dd32b0ad8340 100644 --- a/packages/cli/src/Studio.ts +++ b/packages/cli/src/Studio.ts @@ -1,7 +1,6 @@ import { enginesVersion } from '@prisma/engines' import { getSchemaPathAndPrint } from '@prisma/migrate' -import type { Command } from '@prisma/sdk' -import { arg, format, HelpError, isError, loadEnvFile } from '@prisma/sdk' +import { arg, checkUnsupportedDataProxy, Command, format, HelpError, isError, loadEnvFile } from '@prisma/sdk' import { StudioServer } from '@prisma/studio-server' import chalk from 'chalk' import getPort from 'get-port' @@ -75,6 +74,8 @@ ${chalk.bold('Examples')} return this.help(args.message) } + await checkUnsupportedDataProxy('studio', args, true) + if (args['--help']) { return this.help() } diff --git a/packages/client/edge.d.ts b/packages/client/edge.d.ts new file mode 100644 index 000000000000..483980844cea --- /dev/null +++ b/packages/client/edge.d.ts @@ -0,0 +1 @@ +export * from '.prisma/client' diff --git a/packages/client/edge.js b/packages/client/edge.js new file mode 100644 index 000000000000..8d790165589b --- /dev/null +++ b/packages/client/edge.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('.prisma/client/edge'), +} diff --git a/packages/client/edge/index.d.ts b/packages/client/edge/index.d.ts deleted file mode 100644 index e1051b2162f3..000000000000 --- a/packages/client/edge/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../.prisma/client' diff --git a/packages/client/edge/index.js b/packages/client/edge/index.js deleted file mode 100644 index ffb4b3b9cd94..000000000000 --- a/packages/client/edge/index.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - ...require('../.prisma/client/edge'), -} diff --git a/packages/client/helpers/build.ts b/packages/client/helpers/build.ts index 9ebfbb16c201..67c303edf7c5 100644 --- a/packages/client/helpers/build.ts +++ b/packages/client/helpers/build.ts @@ -5,6 +5,9 @@ import type { BuildOptions } from '../../../helpers/compile/build' import { build } from '../../../helpers/compile/build' import { fillPlugin } from '../../../helpers/compile/plugins/fill-plugin/fillPlugin' +const fillPluginPath = path.join('..', '..', 'helpers', 'compile', 'plugins', 'fill-plugin') +const functionPolyfillPath = path.join(fillPluginPath, 'fillers', 'function.ts') + // we define the config for runtime const nodeRuntimeBuildConfig: BuildOptions = { name: 'runtime', @@ -13,6 +16,8 @@ const nodeRuntimeBuildConfig: BuildOptions = { bundle: true, define: { NODE_CLIENT: 'true', + // that fixes an issue with lz-string umd builds + 'define.amd': 'false', }, } @@ -36,9 +41,18 @@ const edgeRuntimeBuildConfig: BuildOptions = { define: { // that helps us to tree-shake unused things out NODE_CLIENT: 'false', + // that fixes an issue with lz-string umd builds + 'define.amd': 'false', }, plugins: [ fillPlugin({ + // we remove eval and Function for vercel + eval: { define: 'undefined' }, + Function: { + define: 'fn', + inject: functionPolyfillPath, + }, + // TODO no tree shaking on wrapper pkgs '@prisma/get-platform': { contents: '' }, // removes un-needed code out of `chalk` diff --git a/packages/client/jest.config.js b/packages/client/jest.config.js index a5f5a608c916..bacf7f2e9508 100644 --- a/packages/client/jest.config.js +++ b/packages/client/jest.config.js @@ -1,5 +1,11 @@ module.exports = { preset: 'ts-jest', + globals: { + 'ts-jest': { + tsconfig: 'tsconfig.json', + isolatedModules: true, + }, + }, testEnvironment: 'node', collectCoverage: process.env.CI ? true : false, coverageReporters: ['clover'], diff --git a/packages/client/package.json b/packages/client/package.json index 05629fb918f1..8b3414096b09 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -56,6 +56,8 @@ "runtime", "scripts", "generator-build", + "edge.js", + "edge.d.ts", "index.js", "index.d.ts", "index-browser.js" diff --git a/packages/client/scripts/postinstall.js b/packages/client/scripts/postinstall.js index 9155eeee0181..1f903cb97eb2 100644 --- a/packages/client/scripts/postinstall.js +++ b/packages/client/scripts/postinstall.js @@ -197,7 +197,9 @@ function run(cmd, params, cwd = process.cwd()) { /** * Copies our default "throw" files into the default generation folder. These * files are dummy and informative because they just throw an error to let the - * user know that they have forgotten to run `prisma generate`. + * user know that they have forgotten to run `prisma generate` or that they + * don't have a a schema file yet. We only add these files at the default + * location `node_modules/.prisma/client`. */ async function createDefaultGeneratedThrowFiles() { try { @@ -205,13 +207,10 @@ async function createDefaultGeneratedThrowFiles() { const defaultNodeIndexPath = path.join(dotPrismaClientDir, 'index.js') const defaultNodeIndexDtsPath = path.join(dotPrismaClientDir, 'index.d.ts') const defaultBrowserIndexPath = path.join(dotPrismaClientDir, 'index-browser.js') + const defaultEdgeIndexPath = path.join(dotPrismaClientDir, 'edge.js') + const defaultEdgeIndexDtsPath = path.join(dotPrismaClientDir, 'edge.d.ts') await makeDir(dotPrismaClientDir) - const dotPrismaClientEdgeDir = path.join(dotPrismaClientDir, 'edge') - const defaultEdgeIndexPath = path.join(dotPrismaClientEdgeDir, 'index.js') - const defaultEdgeIndexDtsPath = path.join(dotPrismaClientEdgeDir, 'index.d.ts') - await makeDir(dotPrismaClientEdgeDir) - if (!fs.existsSync(defaultNodeIndexPath)) { await copyFile(path.join(__dirname, 'default-index.js'), defaultNodeIndexPath) } diff --git a/packages/client/src/generation/TSClient/Generatable.ts b/packages/client/src/generation/TSClient/Generatable.ts index 20de764a94e2..6ab59925d4a5 100644 --- a/packages/client/src/generation/TSClient/Generatable.ts +++ b/packages/client/src/generation/TSClient/Generatable.ts @@ -1,6 +1,6 @@ export interface Generatable { toJS?(edge?: boolean): string | Promise - toTS(): string | Promise + toTS(edge?: boolean): string | Promise toBrowserJS?(): string | Promise toTSWithoutNamespace?(): string | Promise } @@ -13,6 +13,6 @@ export function BrowserJS(gen: Generatable): string | Promise { return gen.toBrowserJS?.() ?? '' } -export function TS(gen: Generatable): string | Promise { - return gen.toTS() +export function TS(gen: Generatable, edge = false): string | Promise { + return gen.toTS(edge) } diff --git a/packages/client/src/generation/TSClient/TSClient.ts b/packages/client/src/generation/TSClient/TSClient.ts index 54e5ad601b6a..3678149511fe 100644 --- a/packages/client/src/generation/TSClient/TSClient.ts +++ b/packages/client/src/generation/TSClient/TSClient.ts @@ -39,10 +39,10 @@ export interface TSClientOptions { generator?: GeneratorConfig platforms?: Platform[] // TODO: consider making it non-nullable sqliteDatasourceOverrides?: DatasourceOverwrite[] - schemaDir: string + schemaPath: string outputDir: string activeProvider: string - dataProxy?: boolean + dataProxy: boolean } export class TSClient implements Generatable { @@ -53,19 +53,18 @@ export class TSClient implements Generatable { this.dmmf = new DMMFHelper(klona(options.document)) } - public async toJS(edge?: boolean): Promise { + public async toJS(edge = false): Promise { const { platforms, generator, sqliteDatasourceOverrides, outputDir, - schemaDir, + schemaPath, runtimeDir, runtimeName, datasources, dataProxy, } = this.options - const schemaPath = path.join(schemaDir, 'schema.prisma') const envPaths = getEnvPaths(schemaPath, { cwd: outputDir }) const relativeEnvPaths = { @@ -83,7 +82,7 @@ export class TSClient implements Generatable { generator, relativeEnvPaths, sqliteDatasourceOverrides, - relativePath: path.relative(outputDir, schemaDir), + relativePath: path.relative(outputDir, path.dirname(schemaPath)), clientVersion: this.options.clientVersion, engineVersion: this.options.engineVersion, datasourceNames: datasources.map((d) => d.name), @@ -96,8 +95,8 @@ export class TSClient implements Generatable { const relativeOutdir = path.relative(process.cwd(), outputDir) const code = `${commonCodeJS({ ...this.options, browser: false })} -${buildRequirePath(dataProxy)} -${buildDirname(dataProxy, relativeOutdir, runtimeDir)} +${buildRequirePath(edge)} +${buildDirname(edge, relativeOutdir, runtimeDir)} /** * Enums */ @@ -134,7 +133,10 @@ ${buildNFTAnnotations(dataProxy, engineType, platforms, relativeOutdir)} ` return code } - public toTS(): string { + public toTS(edge = false): string { + // edge exports the same ts definitions as the index + if (edge === true) return `export * from './index'` + const prismaClientClass = new PrismaClientClass( this.dmmf, this.options.datasources, @@ -142,7 +144,7 @@ ${buildNFTAnnotations(dataProxy, engineType, platforms, relativeOutdir)} this.options.browser, this.options.generator, this.options.sqliteDatasourceOverrides, - this.options.schemaDir, + path.dirname(this.options.schemaPath), ) const collector = new ExportCollector() diff --git a/packages/client/src/generation/generateClient.ts b/packages/client/src/generation/generateClient.ts index 0d4b48f4a48b..6bebaf8825b4 100644 --- a/packages/client/src/generation/generateClient.ts +++ b/packages/client/src/generation/generateClient.ts @@ -33,8 +33,7 @@ export class DenylistError extends Error { export interface GenerateClientOptions { projectRoot?: string datamodel: string - datamodelPath: string - schemaDir?: string + schemaPath: string transpile?: boolean runtimeDirs?: { node: string; edge: string } outputDir: string @@ -47,7 +46,7 @@ export interface GenerateClientOptions { engineVersion: string clientVersion: string activeProvider: string - dataProxy?: boolean + dataProxy: boolean } export interface BuildClientResult { @@ -57,7 +56,7 @@ export interface BuildClientResult { // eslint-disable-next-line @typescript-eslint/require-await export async function buildClient({ - schemaDir, + schemaPath, runtimeDirs, binaryPaths, outputDir, @@ -69,7 +68,7 @@ export async function buildClient({ projectRoot, activeProvider, dataProxy, -}: O.Required): Promise { +}: O.Required): Promise { // we define the basic options for the client generation const document = getPrismaClientDMMF(dmmf) const clientEngineType = getClientEngineType(generator!) @@ -81,7 +80,7 @@ export async function buildClient({ clientEngineType === ClientEngineType.Library ? (Object.keys(binaryPaths.libqueryEngine ?? {}) as Platform[]) : (Object.keys(binaryPaths.queryEngine ?? {}) as Platform[]), - schemaDir, + schemaPath, outputDir, clientVersion, engineVersion, @@ -124,17 +123,8 @@ export async function buildClient({ // we only generate the edge client if `--data-proxy` is passed if (dataProxy === true) { - fileMap[path.join('edge', 'index.js')] = await JS(edgeTsClient, true) - fileMap[path.join('edge', 'package.json')] = JSON.stringify( - { - name: '.prisma/client/edge', - main: 'index.js', - types: '../index.d.ts', - browser: '../index-browser.js', - }, - null, - 2, - ) + fileMap['edge.js'] = await JS(edgeTsClient, true) + fileMap['edge.d.ts'] = await TS(edgeTsClient, true) } return { @@ -171,8 +161,7 @@ async function getDefaultOutdir(outputDir: string): Promise { export async function generateClient(options: GenerateClientOptions): Promise { const { datamodel, - datamodelPath, - schemaDir = datamodelPath ? path.dirname(datamodelPath) : process.cwd(), + schemaPath, outputDir, transpile, generator, @@ -192,8 +181,7 @@ export async function generateClient(options: GenerateClientOptions): Promise { - if (key === '') return value - if (key === 'parsed') return value - - const cfwEnv = `typeof global !== 'undefined' && global['${key}']` - const vercelEnv = `process.env['${key}']` - const dotEnv = value ? `'${value}'` : 'undefined' - - return `${cfwEnv} || ${vercelEnv} || ${dotEnv}` - }, - 2, - ).replace(/"/g, '') // remove quotes to make code - - return ` -config.injectableEdgeEnv = ${injectableEdgeEnvDeclaration}` -} diff --git a/packages/client/src/generation/utils/buildInlineDatasources.ts b/packages/client/src/generation/utils/buildInlineDatasources.ts index 85c8c6858781..f16e4284c5dd 100644 --- a/packages/client/src/generation/utils/buildInlineDatasources.ts +++ b/packages/client/src/generation/utils/buildInlineDatasources.ts @@ -14,7 +14,7 @@ export type InlineDatasources = { * @param internalDatasources * @returns */ -export function buildInlineDatasource(dataProxy: boolean | undefined, internalDatasources: InternalDatasource[]) { +export function buildInlineDatasource(dataProxy: boolean, internalDatasources: InternalDatasource[]) { if (dataProxy === true) { const datasources = internalToInlineDatasources(internalDatasources) diff --git a/packages/client/src/generation/utils/buildInlineSchema.ts b/packages/client/src/generation/utils/buildInlineSchema.ts index 24f7dd2ef307..451ae6ed0357 100644 --- a/packages/client/src/generation/utils/buildInlineSchema.ts +++ b/packages/client/src/generation/utils/buildInlineSchema.ts @@ -10,7 +10,7 @@ const readFile = fs.promises.readFile * @param schemaPath * @returns */ -export async function buildInlineSchema(dataProxy: boolean | undefined, schemaPath: string) { +export async function buildInlineSchema(dataProxy: boolean, schemaPath: string) { if (dataProxy === true) { const b64Schema = (await readFile(schemaPath)).toString('base64') const schemaHash = crypto.createHash('sha256').update(b64Schema).digest('hex') diff --git a/packages/client/src/generation/utils/buildNFTAnnotations.ts b/packages/client/src/generation/utils/buildNFTAnnotations.ts index 127a223caa2f..26e1cde24672 100644 --- a/packages/client/src/generation/utils/buildNFTAnnotations.ts +++ b/packages/client/src/generation/utils/buildNFTAnnotations.ts @@ -16,7 +16,7 @@ import { map } from '../../../../../helpers/blaze/map' * @returns */ export function buildNFTAnnotations( - dataProxy: boolean | undefined, + dataProxy: boolean, engineType: ClientEngineType, platforms: Platform[] | undefined, relativeOutdir: string, @@ -35,10 +35,11 @@ export function buildNFTAnnotations( } const engineAnnotations = map(platforms, (platform) => { - return buildNFTEngineAnnotation(engineType, platform, relativeOutdir) + const engineFilename = getQueryEngineFilename(engineType, platform) + return engineFilename ? buildNFTAnnotation(engineFilename, relativeOutdir) : '' }).join('\n') - const schemaAnnotations = buildNFTSchemaAnnotation(engineType, relativeOutdir) + const schemaAnnotations = buildNFTAnnotation('schema.prisma', relativeOutdir) return `${engineAnnotations}${schemaAnnotations}` } @@ -76,32 +77,3 @@ function buildNFTAnnotation(fileName: string, relativeOutdir: string) { path.join(__dirname, ${JSON.stringify(fileName)}); path.join(process.cwd(), ${JSON.stringify(relativeFilePath)})` } - -/** - * Build an annotation for the prisma client engine files - * @param engineType - * @param platform - * @param relativeOutdir - * @returns - */ -function buildNFTEngineAnnotation(engineType: ClientEngineType, platform: Platform, relativeOutdir: string) { - const engineFilename = getQueryEngineFilename(engineType, platform) - - if (engineFilename === undefined) return '' - - return buildNFTAnnotation(engineFilename, relativeOutdir) -} - -/** - * Build an annotation for the prisma schema files - * @param engineType - * @param relativeOutdir - * @returns - */ -function buildNFTSchemaAnnotation(engineType: ClientEngineType, relativeOutdir: string) { - if (engineType === ClientEngineType.Library || engineType === ClientEngineType.Binary) { - return buildNFTAnnotation('schema.prisma', relativeOutdir) - } - - return '' -} diff --git a/packages/client/src/generation/utils/buildRequirePath.ts b/packages/client/src/generation/utils/buildRequirePath.ts index d7778ae7e40b..648397482993 100644 --- a/packages/client/src/generation/utils/buildRequirePath.ts +++ b/packages/client/src/generation/utils/buildRequirePath.ts @@ -1,10 +1,10 @@ /** * Builds a require statement for `path`. - * @param dataProxy + * @param edge * @returns */ -export function buildRequirePath(dataProxy: boolean | undefined) { - if (dataProxy === true) return '' +export function buildRequirePath(edge: boolean) { + if (edge === true) return '' return ` const path = require('path')` diff --git a/packages/client/src/generation/utils/buildWarnEnvConflicts.ts b/packages/client/src/generation/utils/buildWarnEnvConflicts.ts index ffc089e92fae..a1df4ded66d7 100644 --- a/packages/client/src/generation/utils/buildWarnEnvConflicts.ts +++ b/packages/client/src/generation/utils/buildWarnEnvConflicts.ts @@ -6,7 +6,7 @@ * @param runtimeName * @returns */ -export function buildWarnEnvConflicts(edge: boolean | undefined, runtimeDir: string, runtimeName: string) { +export function buildWarnEnvConflicts(edge: boolean, runtimeDir: string, runtimeName: string) { if (edge === true) return '' return ` diff --git a/packages/client/src/runtime/getPrismaClient.ts b/packages/client/src/runtime/getPrismaClient.ts index 948db9dc6b72..82ee214a7cd8 100644 --- a/packages/client/src/runtime/getPrismaClient.ts +++ b/packages/client/src/runtime/getPrismaClient.ts @@ -47,7 +47,7 @@ declare global { } // used by esbuild for tree-shaking -globalThis.NODE_CLIENT = true +typeof globalThis === 'object' ? (globalThis.NODE_CLIENT = true) : 0 function isReadonlyArray(arg: any): arg is ReadonlyArray { return Array.isArray(arg) @@ -229,7 +229,7 @@ export interface GetPrismaClientConfig { * If enabled, we disregard the generator config engineType. * It means that `--data-proxy` binds you to the Data Proxy. */ - dataProxy?: boolean + dataProxy: boolean /** * The contents of the schema encoded into a string @@ -341,7 +341,7 @@ export function getPrismaClient(config: GetPrismaClientConfig) { this._rejectOnNotFound = optionsArg?.rejectOnNotFound this._clientVersion = config.clientVersion ?? clientVersion this._activeProvider = config.activeProvider - this._dataProxy = config.dataProxy ?? false + this._dataProxy = config.dataProxy this._clientEngineType = getClientEngineType(config.generator!) const envPaths = { rootEnvPath: diff --git a/packages/client/src/utils/generateInFolder.ts b/packages/client/src/utils/generateInFolder.ts index 7f3573aed8bb..7db12c6974a2 100644 --- a/packages/client/src/utils/generateInFolder.ts +++ b/packages/client/src/utils/generateInFolder.ts @@ -133,16 +133,16 @@ export async function generateInFolder({ dmmf, ...config, outputDir, - schemaDir: path.dirname(schemaPath), runtimeDirs, transpile, testMode: true, - datamodelPath: schemaPath, + schemaPath, copyRuntime: false, generator: config.generators[0], clientVersion: 'local', engineVersion: 'local', activeProvider: config.datasources[0].activeProvider, + dataProxy: !!process.env.DATA_PROXY, }) const time = performance.now() - before debug(`Done generating client in ${time}`) diff --git a/packages/client/tests/functional/_utils/setupTestSuiteClient.ts b/packages/client/tests/functional/_utils/setupTestSuiteClient.ts index 2182bbcbd8dd..703055653ede 100644 --- a/packages/client/tests/functional/_utils/setupTestSuiteClient.ts +++ b/packages/client/tests/functional/_utils/setupTestSuiteClient.ts @@ -35,7 +35,7 @@ export async function setupTestSuiteClient(suiteMeta: TestSuiteMeta, suiteConfig await generateClient({ datamodel: schema, - datamodelPath: getTestSuiteSchemaPath(suiteMeta, suiteConfig), + schemaPath: getTestSuiteSchemaPath(suiteMeta, suiteConfig), binaryPaths: { libqueryEngine: {}, queryEngine: {} }, datasources: config.datasources, outputDir: path.join(suiteFolderPath, 'node_modules/@prisma/client'), @@ -53,6 +53,7 @@ export async function setupTestSuiteClient(suiteMeta: TestSuiteMeta, suiteConfig edge: [__dirname.replace(/\\/g, '/'), '..', '..', '..', 'runtime', 'edge'].join('/'), }, projectRoot: suiteFolderPath, + dataProxy: !!process.env.DATA_PROXY, }) return require(path.join(suiteFolderPath, 'node_modules/@prisma/client')) diff --git a/packages/engine-core/src/common/Engine.ts b/packages/engine-core/src/common/Engine.ts index a802767668b5..50a74780cbbb 100644 --- a/packages/engine-core/src/common/Engine.ts +++ b/packages/engine-core/src/common/Engine.ts @@ -56,7 +56,7 @@ export interface EngineConfig { showColors?: boolean logQueries?: boolean logLevel?: 'info' | 'warn' - env?: Record + env: Record flags?: string[] clientVersion?: string previewFeatures?: string[] diff --git a/packages/engine-core/src/data-proxy/DataProxyEngine.ts b/packages/engine-core/src/data-proxy/DataProxyEngine.ts index 965c602ec4f6..f1bad4f5ccd4 100644 --- a/packages/engine-core/src/data-proxy/DataProxyEngine.ts +++ b/packages/engine-core/src/data-proxy/DataProxyEngine.ts @@ -18,17 +18,19 @@ import { request } from './utils/request' const MAX_RETRIES = 10 +// to defer the execution of promises in the constructor +const P = Promise.resolve() + export class DataProxyEngine extends Engine { - private pushPromise: Promise private inlineSchema: string private inlineSchemaHash: string private inlineDatasources: any private config: EngineConfig private logEmitter: EventEmitter - private env: { [k: string]: string } + private env: { [k in string]?: string } private clientVersion: string - private remoteClientVersion: string + private remoteClientVersion: Promise private headers: { Authorization: string } private host: string @@ -36,7 +38,7 @@ export class DataProxyEngine extends Engine { super() this.config = config - this.env = this.config.env ?? {} + this.env = { ...this.config.env, ...process.env } this.inlineSchema = config.inlineSchema ?? '' this.inlineDatasources = config.inlineDatasources ?? {} this.inlineSchemaHash = config.inlineSchemaHash ?? '' @@ -46,27 +48,9 @@ export class DataProxyEngine extends Engine { this.logEmitter.on('error', () => {}) const [host, apiKey] = this.extractHostAndApiKey() - this.remoteClientVersion = getClientVersion(this.config) + this.remoteClientVersion = P.then(() => getClientVersion(this.config)) this.headers = { Authorization: `Bearer ${apiKey}` } this.host = host - - // hack for Cloudflare - // That's because we instantiate the client outside of the request handler. This essentially prevents immediate execution of the promise. - // Removing this will produce the following error - // [Error] Some functionality, such as asynchronous I/O, timeouts, and generating random values, can only be performed while handling a request. - const promise = Promise.resolve() - this.pushPromise = promise.then(() => this.pushSchema()) - } - - private async pushSchema() { - const response = await request(this.url('schema'), { - method: 'GET', - headers: this.headers, - }) - - if (response.status === 404) { - await this.uploadSchema() - } } version() { @@ -88,8 +72,8 @@ export class DataProxyEngine extends Engine { } } - private url(s: string) { - return `https://${this.host}/${this.remoteClientVersion}/${this.inlineSchemaHash}/${s}` + private async url(s: string) { + return `https://${this.host}/${await this.remoteClientVersion}/${this.inlineSchemaHash}/${s}` } // TODO: looks like activeProvider is the only thing @@ -105,10 +89,11 @@ export class DataProxyEngine extends Engine { } private async uploadSchema() { - const response = await request(this.url('schema'), { + const response = await request(await this.url('schema'), { method: 'PUT', headers: this.headers, body: this.inlineSchema, + clientVersion: this.clientVersion, }) const err = await responseToError(response, this.clientVersion) @@ -145,32 +130,29 @@ export class DataProxyEngine extends Engine { } private async requestInternal(body: Record, headers: Record, attempt: number) { - await this.pushPromise - try { this.logEmitter.emit('info', { - message: `Calling ${this.url('graphql')} (n=${attempt})`, + message: `Calling ${await this.url('graphql')} (n=${attempt})`, }) - const response = await request(this.url('graphql'), { + const response = await request(await this.url('graphql'), { method: 'POST', headers: { ...headers, ...this.headers }, body: JSON.stringify(body), + clientVersion: this.clientVersion, }) - const err = await responseToError(response, this.clientVersion) + const e = await responseToError(response, this.clientVersion) - if (err instanceof SchemaMissingError) { + if (e instanceof SchemaMissingError) { await this.uploadSchema() throw new ForcedRetryError({ clientVersion: this.clientVersion, - cause: err, + cause: e, }) } - if (err) { - throw err - } + if (e) throw e const data = await response.json() @@ -181,22 +163,18 @@ export class DataProxyEngine extends Engine { } return data - } catch (err) { + } catch (e) { this.logEmitter.emit('error', { - message: `Error while querying: ${err.message ?? '(unknown)'}`, + message: `Error while querying: ${e.message ?? '(unknown)'}`, }) - if (!(err instanceof DataProxyError)) { - throw err - } - if (!err.isRetryable) { - throw err - } + if (!(e instanceof DataProxyError)) throw e + if (!e.isRetryable) throw e if (attempt >= MAX_RETRIES) { - if (err instanceof ForcedRetryError) { - throw err.cause + if (e instanceof ForcedRetryError) { + throw e.cause } else { - throw err + throw e } } diff --git a/packages/engine-core/src/data-proxy/errors/NetworkError.ts b/packages/engine-core/src/data-proxy/errors/NetworkError.ts new file mode 100644 index 000000000000..540b90102cfc --- /dev/null +++ b/packages/engine-core/src/data-proxy/errors/NetworkError.ts @@ -0,0 +1,14 @@ +import type { DataProxyErrorInfo } from './DataProxyError' +import { DataProxyError } from './DataProxyError' +import { setRetryable } from './utils/setRetryable' + +export interface RequestErrorInfo extends DataProxyErrorInfo {} + +export class RequestError extends DataProxyError { + public name = 'RequestError' + public code = 'P5010' + + constructor(message: string, info: RequestErrorInfo) { + super(`Cannot fetch data from service:\n${message}`, setRetryable(info, true)) + } +} diff --git a/packages/engine-core/src/data-proxy/utils/getClientVersion.ts b/packages/engine-core/src/data-proxy/utils/getClientVersion.ts index 66e022a51c78..3ac61a5538cf 100644 --- a/packages/engine-core/src/data-proxy/utils/getClientVersion.ts +++ b/packages/engine-core/src/data-proxy/utils/getClientVersion.ts @@ -1,19 +1,62 @@ +import Debug from '@prisma/debug' +import { version as engineVersion } from '@prisma/engines/package.json' + import type { EngineConfig } from '../../common/Engine' +import { NotImplementedYetError } from '../errors/NotImplementedYetError' +import { request } from './request' + +const semverRegex = /^[1-9][0-9]*\.[0-9]+\.[0-9]+$/ +const prismaNpm = 'https://registry.npmjs.org/prisma' +const debug = Debug('prisma:client:dataproxyEngine') + +async function _getClientVersion(config: EngineConfig) { + const clientVersion = config.clientVersion ?? 'unknown' + + // internal override for testing and manual version overrides + if (process.env.PRISMA_CLIENT_DATA_PROXY_CLIENT_VERSION) { + return process.env.PRISMA_CLIENT_DATA_PROXY_CLIENT_VERSION + } + + const [version, suffix] = clientVersion?.split('-') ?? [] + + // we expect the version to match the pattern major.minor.patch + if (suffix === undefined && semverRegex.test(version)) { + return version + } + + // if it's an integration version, we resolve its data proxy + if (suffix === 'integration' || clientVersion === '0.0.0') { + // we infer the data proxy version from the engine version + const [version] = engineVersion.split('-') ?? [] + const [major, minor, patch] = version.split('.') + + // if a patch has happened, then we return that version + if (patch !== '0') return `${major}.${minor}.${patch}` + + // if not, we know that the minor must be minus with 1 + const published = `${major}.${parseInt(minor) - 1}.x` + + // we don't know what `x` is, so we query the registry + const res = await request(`${prismaNpm}/${published}`, { clientVersion }) + + return ((await res.json())['version'] as string) ?? 'undefined' + } + + // nothing matched, meaning that the provided version is invalid + throw new NotImplementedYetError('Only `major.minor.patch` versions are supported by Prisma Data Proxy.', { + clientVersion, + }) +} /** * Determine the client version to be sent to the DataProxy * @param config * @returns */ -export function getClientVersion(config: EngineConfig) { - const [version, suffix] = config.clientVersion?.split('-') ?? [] +export async function getClientVersion(config: EngineConfig) { + const version = await _getClientVersion(config) - // we expect the version to match the pattern major.minor.patch - if (!suffix && /^[1-9][0-9]*\.[0-9]+\.[0-9]+$/.test(version)) { - return version - } + debug('version', version) - // TODO: we should have a Data Proxy deployment which accepts any - // arbitrary version for testing purposes. - return '3.4.1' // and we default it to the latest stable + return version } diff --git a/packages/engine-core/src/data-proxy/utils/request.ts b/packages/engine-core/src/data-proxy/utils/request.ts index 534ca0c0834f..53d432d90b85 100644 --- a/packages/engine-core/src/data-proxy/utils/request.ts +++ b/packages/engine-core/src/data-proxy/utils/request.ts @@ -1,8 +1,9 @@ -import { IncomingMessage } from 'http' +import type { IncomingMessage } from 'http' import type Https from 'https' import type { RequestInit, Response } from 'node-fetch' -import { O } from 'ts-toolbelt' +import type { O } from 'ts-toolbelt' +import { RequestError } from '../errors/NetworkError' import { getJSRuntimeName } from './getJSRuntimeName' // our implementation handles less @@ -18,13 +19,22 @@ declare let fetch: typeof nodeFetch * @param options * @returns */ -export async function request(url: string, options: RequestOptions = {}): Promise { +export async function request( + url: string, + options: RequestOptions & { clientVersion: string }, +): Promise { + const clientVersion = options.clientVersion const jsRuntimeName = getJSRuntimeName() - if (jsRuntimeName === 'browser') { - return fetch(url, options) - } else { - return nodeFetch(url, options) + try { + if (jsRuntimeName === 'browser') { + return await fetch(url, options) + } else { + return await nodeFetch(url, options) + } + } catch (e) { + const message = e.message ?? 'Unknown error' + throw new RequestError(message, { clientVersion }) } } @@ -76,7 +86,7 @@ function buildResponse(incomingData: Buffer[], response: IncomingMessage): Reque * @returns */ async function nodeFetch(url: string, options: RequestOptions = {}): Promise { - const https: typeof Https = await globalThis[['e', 'v', 'a', 'l'].join('')](`import('https')`) + const https: typeof Https = include('https') const httpsOptions = buildOptions(options) const incomingData = [] as Buffer[] @@ -93,3 +103,6 @@ async function nodeFetch(url: string, options: RequestOptions = {}): Promise {} diff --git a/packages/generator-helper/src/types.ts b/packages/generator-helper/src/types.ts index 23701dd12ca7..7fcb833301f4 100644 --- a/packages/generator-helper/src/types.ts +++ b/packages/generator-helper/src/types.ts @@ -89,7 +89,7 @@ export type GeneratorOptions = { // TODO is it really always version hash? Feature is unclear. version: string // version hash binaryPaths?: BinaryPaths - dataProxy?: boolean + dataProxy: boolean } export type EngineType = 'queryEngine' | 'libqueryEngine' | 'migrationEngine' | 'introspectionEngine' | 'prismaFmt' diff --git a/packages/migrate/src/Migrate.ts b/packages/migrate/src/Migrate.ts index 18a4eb180d17..8a39bd6b7f5f 100644 --- a/packages/migrate/src/Migrate.ts +++ b/packages/migrate/src/Migrate.ts @@ -155,6 +155,7 @@ export class Migrate { printDownloadProgress: true, version: enginesVersion, cliVersion: packageJson.version, + dataProxy: false, }) for (const generator of generators) { diff --git a/packages/migrate/src/commands/DbDrop.ts b/packages/migrate/src/commands/DbDrop.ts index 72b2b9efac6b..558e0948f4ad 100644 --- a/packages/migrate/src/commands/DbDrop.ts +++ b/packages/migrate/src/commands/DbDrop.ts @@ -1,5 +1,16 @@ -import type { Command } from '@prisma/sdk' -import { arg, dropDatabase, format, getSchemaDir, HelpError, isCi, isError, link, loadEnvFile } from '@prisma/sdk' +import { + arg, + checkUnsupportedDataProxy, + Command, + dropDatabase, + format, + getSchemaDir, + HelpError, + isCi, + isError, + link, + loadEnvFile, +} from '@prisma/sdk' import chalk from 'chalk' import prompt from 'prompts' @@ -60,6 +71,8 @@ ${chalk.bold('Examples')} return this.help(args.message) } + await checkUnsupportedDataProxy('db drop', args, true) + if (args['--help']) { return this.help() } diff --git a/packages/migrate/src/commands/DbExecute.ts b/packages/migrate/src/commands/DbExecute.ts index 675d1079d2c5..b84dbbfe617d 100644 --- a/packages/migrate/src/commands/DbExecute.ts +++ b/packages/migrate/src/commands/DbExecute.ts @@ -1,12 +1,12 @@ -import type { Command } from '@prisma/sdk' import { arg, + checkUnsupportedDataProxy, + Command, format, getCommandWithExecutor, getSchemaPath, HelpError, isError, - link, loadEnvFile, logger, } from '@prisma/sdk' @@ -100,6 +100,8 @@ ${chalk.bold('Examples')} return this.help(args.message) } + await checkUnsupportedDataProxy('db execute', args, !args['--url']) + if (args['--help']) { return this.help() } diff --git a/packages/migrate/src/commands/DbPull.ts b/packages/migrate/src/commands/DbPull.ts index a83a762c6a4a..831dc46feb02 100644 --- a/packages/migrate/src/commands/DbPull.ts +++ b/packages/migrate/src/commands/DbPull.ts @@ -1,6 +1,7 @@ -import type { Command, IntrospectionSchemaVersion, IntrospectionWarnings } from '@prisma/sdk' import { arg, + checkUnsupportedDataProxy, + Command, createSpinner, drawBox, format, @@ -11,6 +12,8 @@ import { getSchemaPath, HelpError, IntrospectionEngine, + IntrospectionSchemaVersion, + IntrospectionWarnings, link, loadEnvFile, protocolToConnectorType, @@ -101,6 +104,8 @@ Set composite types introspection depth to 2 levels return this.help(args.message) } + await checkUnsupportedDataProxy('db pull', args, !args['--url']) + if (args['--help']) { return this.help() } diff --git a/packages/migrate/src/commands/DbPush.ts b/packages/migrate/src/commands/DbPush.ts index ac864b00820e..028d0a395e18 100644 --- a/packages/migrate/src/commands/DbPush.ts +++ b/packages/migrate/src/commands/DbPush.ts @@ -1,6 +1,7 @@ -import type { Command } from '@prisma/sdk' import { arg, + checkUnsupportedDataProxy, + Command, format, formatms, getCommandWithExecutor, @@ -76,6 +77,8 @@ ${chalk.bold('Examples')} return this.help(args.message) } + await checkUnsupportedDataProxy('db push', args, true) + if (args['--help']) { return this.help() } diff --git a/packages/migrate/src/commands/DbSeed.ts b/packages/migrate/src/commands/DbSeed.ts index 7119cd84c040..b327da42cc57 100644 --- a/packages/migrate/src/commands/DbSeed.ts +++ b/packages/migrate/src/commands/DbSeed.ts @@ -1,5 +1,4 @@ -import type { Command } from '@prisma/sdk' -import { arg, format, getSchemaPath, HelpError, isError, loadEnvFile, logger } from '@prisma/sdk' +import { arg, Command, format, getSchemaPath, HelpError, isError, loadEnvFile, logger } from '@prisma/sdk' import chalk from 'chalk' import { diff --git a/packages/migrate/src/commands/MigrateDeploy.ts b/packages/migrate/src/commands/MigrateDeploy.ts index 97adc33938c0..a2bcaa1fc2c1 100644 --- a/packages/migrate/src/commands/MigrateDeploy.ts +++ b/packages/migrate/src/commands/MigrateDeploy.ts @@ -1,6 +1,5 @@ import Debug from '@prisma/debug' -import type { Command } from '@prisma/sdk' -import { arg, format, HelpError, isError, loadEnvFile } from '@prisma/sdk' +import { arg, checkUnsupportedDataProxy, Command, format, HelpError, isError, loadEnvFile } from '@prisma/sdk' import chalk from 'chalk' import { Migrate } from '../Migrate' @@ -58,6 +57,8 @@ ${chalk.bold('Examples')} return this.help(args.message) } + await checkUnsupportedDataProxy('migrate deploy', args, true) + if (args['--help']) { return this.help() } diff --git a/packages/migrate/src/commands/MigrateDev.ts b/packages/migrate/src/commands/MigrateDev.ts index 3a57aad6b266..6daca39d2fdb 100644 --- a/packages/migrate/src/commands/MigrateDev.ts +++ b/packages/migrate/src/commands/MigrateDev.ts @@ -1,7 +1,8 @@ import Debug from '@prisma/debug' -import type { Command } from '@prisma/sdk' import { arg, + checkUnsupportedDataProxy, + Command, format, getCommandWithExecutor, getConfig, @@ -90,6 +91,8 @@ ${chalk.bold('Examples')} return this.help(args.message) } + await checkUnsupportedDataProxy('migrate dev', args, true) + if (args['--help']) { return this.help() } diff --git a/packages/migrate/src/commands/MigrateDiff.ts b/packages/migrate/src/commands/MigrateDiff.ts index c0f4f7ab7f5c..b413c01e81db 100644 --- a/packages/migrate/src/commands/MigrateDiff.ts +++ b/packages/migrate/src/commands/MigrateDiff.ts @@ -1,6 +1,15 @@ import Debug from '@prisma/debug' -import type { Command } from '@prisma/sdk' -import { arg, format, HelpError, isError, link, loadEnvFile, logger } from '@prisma/sdk' +import { + arg, + checkUnsupportedDataProxy, + Command, + format, + HelpError, + isError, + link, + loadEnvFile, + logger, +} from '@prisma/sdk' import chalk from 'chalk' import path from 'path' @@ -144,6 +153,8 @@ ${chalk.bold('Examples')} return this.help(args.message) } + await checkUnsupportedDataProxy('migrate diff', args, false) + if (args['--help']) { return this.help() } diff --git a/packages/migrate/src/commands/MigrateReset.ts b/packages/migrate/src/commands/MigrateReset.ts index 8106e31324c9..acd58b1eda12 100644 --- a/packages/migrate/src/commands/MigrateReset.ts +++ b/packages/migrate/src/commands/MigrateReset.ts @@ -1,5 +1,14 @@ -import type { Command } from '@prisma/sdk' -import { arg, format, getSchemaPath, HelpError, isCi, isError, loadEnvFile } from '@prisma/sdk' +import { + arg, + checkUnsupportedDataProxy, + Command, + format, + getSchemaPath, + HelpError, + isCi, + isError, + loadEnvFile, +} from '@prisma/sdk' import chalk from 'chalk' import prompt from 'prompts' @@ -63,6 +72,8 @@ ${chalk.bold('Examples')} return this.help(args.message) } + await checkUnsupportedDataProxy('migrate reset', args, true) + if (args['--help']) { return this.help() } diff --git a/packages/migrate/src/commands/MigrateResolve.ts b/packages/migrate/src/commands/MigrateResolve.ts index 41921b049d9c..ef69c596041b 100644 --- a/packages/migrate/src/commands/MigrateResolve.ts +++ b/packages/migrate/src/commands/MigrateResolve.ts @@ -1,5 +1,14 @@ -import type { Command } from '@prisma/sdk' -import { arg, format, getCommandWithExecutor, HelpError, isError, link, loadEnvFile } from '@prisma/sdk' +import { + arg, + checkUnsupportedDataProxy, + Command, + format, + getCommandWithExecutor, + HelpError, + isError, + link, + loadEnvFile, +} from '@prisma/sdk' import chalk from 'chalk' import { Migrate } from '../Migrate' @@ -67,6 +76,8 @@ ${chalk.bold('Examples')} return this.help(args.message) } + await checkUnsupportedDataProxy('migrate resolve', args, true) + if (args['--help']) { return this.help() } diff --git a/packages/migrate/src/commands/MigrateStatus.ts b/packages/migrate/src/commands/MigrateStatus.ts index fea86ee5572f..5b552a175d2c 100644 --- a/packages/migrate/src/commands/MigrateStatus.ts +++ b/packages/migrate/src/commands/MigrateStatus.ts @@ -1,6 +1,14 @@ import Debug from '@prisma/debug' -import type { Command } from '@prisma/sdk' -import { arg, format, getCommandWithExecutor, HelpError, isError, loadEnvFile } from '@prisma/sdk' +import { + arg, + checkUnsupportedDataProxy, + Command, + format, + getCommandWithExecutor, + HelpError, + isError, + loadEnvFile, +} from '@prisma/sdk' import chalk from 'chalk' import { Migrate } from '../Migrate' @@ -58,6 +66,8 @@ Check the status of your database migrations return this.help(args.message) } + await checkUnsupportedDataProxy('migrate status', args, true) + if (args['--help']) { return this.help() } diff --git a/packages/sdk/src/__tests__/getGenerators/getGenerators.test.ts b/packages/sdk/src/__tests__/getGenerators/getGenerators.test.ts index 25ca01f42628..09239dda67d2 100644 --- a/packages/sdk/src/__tests__/getGenerators/getGenerators.test.ts +++ b/packages/sdk/src/__tests__/getGenerators/getGenerators.test.ts @@ -37,6 +37,7 @@ describe('getGenerators', () => { const generators = await getGenerators({ schemaPath: path.join(__dirname, 'valid-minimal-schema.prisma'), providerAliases: aliases, + dataProxy: false, }) expect(generators.map((g) => g.manifest)).toMatchInlineSnapshot(` @@ -119,6 +120,7 @@ describe('getGenerators', () => { const generators = await getGenerators({ schemaPath: path.join(__dirname, 'valid-minimal-schema-binaryTargets.prisma'), providerAliases: aliases, + dataProxy: false, }) expect(generators.map((g) => g.manifest)).toMatchInlineSnapshot(` @@ -209,6 +211,7 @@ describe('getGenerators', () => { const generators = await getGenerators({ schemaPath: path.join(__dirname, 'valid-minimal-schema-binaryTargets-env-var.prisma'), providerAliases: aliases, + dataProxy: false, }) expect(generators.map((g) => g.manifest)).toMatchInlineSnapshot(` @@ -299,6 +302,7 @@ describe('getGenerators', () => { const generators = await getGenerators({ schemaPath: path.join(__dirname, 'valid-minimal-schema-binaryTargets-env-var.prisma'), providerAliases: aliases, + dataProxy: false, }) expect(generators.map((g) => g.manifest)).toMatchInlineSnapshot(` @@ -389,6 +393,7 @@ describe('getGenerators', () => { const generators = await getGenerators({ schemaPath: path.join(__dirname, 'valid-minimal-schema-binaryTargets-env-var.prisma'), providerAliases: aliases, + dataProxy: false, }) expect(generators.map((g) => g.manifest)).toMatchInlineSnapshot(` @@ -486,6 +491,7 @@ describe('getGenerators', () => { const generators = await getGenerators({ schemaPath: path.join(__dirname, 'valid-minimal-schema-binaryTargets-env-var.prisma'), providerAliases: aliases, + dataProxy: false, }) expect(generators.map((g) => g.manifest)).toMatchInlineSnapshot(` @@ -583,6 +589,7 @@ describe('getGenerators', () => { binaryPathsOverride: { queryEngine: queryEnginePath, }, + dataProxy: false, }) const options = generators.map((g) => g.options?.binaryPaths) @@ -609,6 +616,7 @@ describe('getGenerators', () => { getGenerators({ schemaPath: path.join(__dirname, 'invalid-platforms-schema.prisma'), providerAliases: aliases, + dataProxy: false, }), ).rejects.toThrow('deprecated') }) @@ -625,6 +633,7 @@ describe('getGenerators', () => { getGenerators({ schemaPath: path.join(__dirname, 'invalid-binary-target-schema.prisma'), providerAliases: aliases, + dataProxy: false, }), ).rejects.toThrow('Unknown') @@ -647,6 +656,7 @@ describe('getGenerators', () => { await getGenerators({ schemaPath: path.join(__dirname, 'missing-datasource-schema.prisma'), providerAliases: aliases, + dataProxy: false, }) } catch (e) { expect(stripAnsi(e.message)).toMatchInlineSnapshot(` @@ -684,6 +694,7 @@ describe('getGenerators', () => { await getGenerators({ schemaPath: path.join(__dirname, 'missing-models-sqlite-schema.prisma'), providerAliases: aliases, + dataProxy: false, }) } catch (e) { expect(stripAnsi(e.message)).toMatchInlineSnapshot(` @@ -722,6 +733,7 @@ describe('getGenerators', () => { await getGenerators({ schemaPath: path.join(__dirname, 'missing-models-mongodb-schema.prisma'), providerAliases: aliases, + dataProxy: false, }) } catch (e) { expect(stripAnsi(e.message)).toMatchInlineSnapshot(` @@ -769,8 +781,7 @@ describe('getGenerators', () => { interactiveTransactions preview feature is not yet available with --data-proxy. Please remove interactiveTransactions from the previewFeatures in your schema. - More information in our documentation: - https://pris.ly/d/data-proxy + More information about Data Proxy: https://pris.ly/d/data-proxy " `) } diff --git a/packages/sdk/src/cli/checkUnsupportedDataProxy.ts b/packages/sdk/src/cli/checkUnsupportedDataProxy.ts new file mode 100644 index 000000000000..c01616ff75f1 --- /dev/null +++ b/packages/sdk/src/cli/checkUnsupportedDataProxy.ts @@ -0,0 +1,82 @@ +import chalk from 'chalk' +import fs from 'fs' +import { O } from 'ts-toolbelt' + +import { getConfig, getSchemaPath, link } from '..' +import { loadEnvFile } from '../utils/loadEnvFile' + +/** + * These are the cli args that we check the data proxy for. If in use + */ +const checkedArgs = { + // Directly contain connection string + '--url': true, + '--to-url': true, + '--from-url': true, + '--shadow-database-url': true, + // Contain path to schema file with connection string (directly or via env var) + '--schema': true, + '--from-schema-datamodel': true, + '--to-schema-datamodel': true, +} + +type Args = O.Optional> + +/** + * Get the message to display when a command is forbidden with a data proxy flag + * @param command the cli command (eg. db push) + * @returns + */ +export const forbiddenCmdWithDataProxyFlagMessage = (command: string) => ` +Using the Data Proxy (connection URL starting with protocol ${chalk.green( + 'prisma://', +)}) is not supported for this CLI command ${chalk.green( + `prisma ${command}`, +)} yet. Please use a direct connection to your database for now. + +More information about Data Proxy: ${link('https://pris.ly/d/data-proxy-cli')} +` + +/** + * Check that the data proxy cannot be used through the given args + * @param command the cli command (eg. db push) + * @param args the cli command arguments + * @param implicitSchema if this command implicitly loads a schema + */ +async function checkUnsupportedDataProxyMessage(command: string, args: Args, implicitSchema: boolean) { + // when the schema can be implicit, we use its default location + if (implicitSchema === true) { + args['--schema'] = (await getSchemaPath(args['--schema'])) ?? undefined + } + + const argList = Object.entries(args) + for (const [argName, argValue] of argList) { + // for all the args that represent an url ensure data proxy isn't used + if (argName.includes('url') && argValue.includes('prisma://')) { + return forbiddenCmdWithDataProxyFlagMessage(command) + } + + // for all the args that represent a schema path ensure data proxy isn't used + if (argName.includes('schema')) { + loadEnvFile(argValue, false) + + const datamodel = await fs.promises.readFile(argValue, 'utf-8') + const config = await getConfig({ datamodel, ignoreEnvVarErrors: true }) + const urlFromValue = config.datasources[0]?.url.value + const urlEnvVarName = config.datasources[0]?.url.fromEnvVar + const urlEnvVarValue = urlEnvVarName ? process.env[urlEnvVarName] : undefined + + if ((urlFromValue ?? urlEnvVarValue)?.startsWith('prisma://')) { + return forbiddenCmdWithDataProxyFlagMessage(command) + } + } + } + + return undefined +} + +export async function checkUnsupportedDataProxy(command: string, args: Args, implicitSchema: boolean) { + const message = await checkUnsupportedDataProxyMessage(command, args, implicitSchema).catch(() => undefined) + + if (message) throw new Error(message) +} diff --git a/packages/sdk/src/cli/getGeneratorSuccessMessage.ts b/packages/sdk/src/cli/getGeneratorSuccessMessage.ts index b823c27991c5..023cc6dd2f72 100644 --- a/packages/sdk/src/cli/getGeneratorSuccessMessage.ts +++ b/packages/sdk/src/cli/getGeneratorSuccessMessage.ts @@ -23,7 +23,8 @@ function formatVersion(generator: Generator): string | undefined { if (generator.getProvider() === 'prisma-client-js') { const engineType = getClientEngineType(generator.config) - return version ? `${version} | ${engineType}` : engineType + const engineMode = generator.options?.dataProxy ? 'dataproxy' : engineType + return version ? `${version} | ${engineMode}` : engineMode } return version diff --git a/packages/sdk/src/get-generators/getGenerators.ts b/packages/sdk/src/get-generators/getGenerators.ts index 5b9029a74367..acba9d1494a2 100644 --- a/packages/sdk/src/get-generators/getGenerators.ts +++ b/packages/sdk/src/get-generators/getGenerators.ts @@ -50,7 +50,7 @@ export type GetGeneratorOptions = { overrideGenerators?: GeneratorConfig[] skipDownload?: boolean binaryPathsOverride?: BinaryPathsOverride - dataProxy?: boolean + dataProxy: boolean } /** * Makes sure that all generators have the binaries they deserve and returns a diff --git a/packages/sdk/src/get-generators/utils/check-feature-flags/checkFeatureFlags.ts b/packages/sdk/src/get-generators/utils/check-feature-flags/checkFeatureFlags.ts index d33a2bd316ef..dfcf32fb042c 100644 --- a/packages/sdk/src/get-generators/utils/check-feature-flags/checkFeatureFlags.ts +++ b/packages/sdk/src/get-generators/utils/check-feature-flags/checkFeatureFlags.ts @@ -1,6 +1,6 @@ import type { ConfigMetaFormat } from '../../../engine-commands' import { GetGeneratorOptions } from '../../getGenerators' -import { forbiddenItxWithDataProxyFlagMessage } from './forbiddenItxWithProxyFlagMessage' +import { forbiddenPreviewFeatureWithDataProxyFlagMessage } from './forbiddenItxWithProxyFlagMessage' /** * Check feature flags and preview features @@ -12,16 +12,18 @@ export function checkFeatureFlags(config: ConfigMetaFormat, options: GetGenerato } function checkForbiddenItxWithDataProxyFlag(config: ConfigMetaFormat, options: GetGeneratorOptions) { - if ( - options.dataProxy === true && + options.dataProxy === true && config.generators.some((generatorConfig) => { - return generatorConfig.previewFeatures.some( - (feature) => feature.toLocaleLowerCase() === 'interactiveTransactions'.toLocaleLowerCase(), - ) + return generatorConfig.previewFeatures.some((feature) => { + if (feature.toLocaleLowerCase() === 'metrics'.toLocaleLowerCase()) { + throw new Error(forbiddenPreviewFeatureWithDataProxyFlagMessage('metrics')) + } + + if (feature.toLocaleLowerCase() === 'interactiveTransactions'.toLocaleLowerCase()) { + throw new Error(forbiddenPreviewFeatureWithDataProxyFlagMessage('interactiveTransactions')) + } + }) }) - ) { - throw new Error(forbiddenItxWithDataProxyFlagMessage) - } } /* Example diff --git a/packages/sdk/src/get-generators/utils/check-feature-flags/forbiddenItxWithProxyFlagMessage.ts b/packages/sdk/src/get-generators/utils/check-feature-flags/forbiddenItxWithProxyFlagMessage.ts index 8f889081b805..f006c3fc5e06 100644 --- a/packages/sdk/src/get-generators/utils/check-feature-flags/forbiddenItxWithProxyFlagMessage.ts +++ b/packages/sdk/src/get-generators/utils/check-feature-flags/forbiddenItxWithProxyFlagMessage.ts @@ -2,10 +2,9 @@ import chalk from 'chalk' import { link } from '../../../utils/link' -export const forbiddenItxWithDataProxyFlagMessage = ` -${chalk.green('interactiveTransactions')} preview feature is not yet available with ${chalk.green('--data-proxy')}. -Please remove ${chalk.red('interactiveTransactions')} from the ${chalk.green('previewFeatures')} in your schema. +export const forbiddenPreviewFeatureWithDataProxyFlagMessage = (previewFeatureName: string) => ` +${chalk.green(previewFeatureName)} preview feature is not yet available with ${chalk.green('--data-proxy')}. +Please remove ${chalk.red(previewFeatureName)} from the ${chalk.green('previewFeatures')} in your schema. -More information in our documentation: -${link('https://pris.ly/d/data-proxy')} +More information about Data Proxy: ${link('https://pris.ly/d/data-proxy')} ` diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index dbdf8212e95b..6673bbbbad56 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,3 +1,4 @@ +export { checkUnsupportedDataProxy } from './cli/checkUnsupportedDataProxy' export { getGeneratorSuccessMessage } from './cli/getGeneratorSuccessMessage' export { getPrismaConfigFromPackageJson, diff --git a/tsconfig.build.regular.json b/tsconfig.build.regular.json index 51a02abe19a2..6dfc2740656a 100644 --- a/tsconfig.build.regular.json +++ b/tsconfig.build.regular.json @@ -17,7 +17,8 @@ "skipDefaultLibCheck": true, "skipLibCheck": true, - "emitDeclarationOnly": true + "emitDeclarationOnly": true, + "resolveJsonModule": true }, "exclude": ["**/dist", "**/node_modules", "**/src/__tests__"] }