Skip to content

Commit

Permalink
chore(*): polishing upstream work on the data proxy [DPGA, 5] (#13629)
Browse files Browse the repository at this point in the history
  • Loading branch information
millsp committed Jun 7, 2022
1 parent 629c63e commit 0ab97f8
Show file tree
Hide file tree
Showing 26 changed files with 186 additions and 174 deletions.
3 changes: 3 additions & 0 deletions helpers/compile/plugins/fill-plugin/fillers/function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const fn = () => {}

fn.prototype = fn
5 changes: 4 additions & 1 deletion packages/cli/src/Init.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -134,6 +135,8 @@ export class Init implements Command {
return this.help()
}

await checkUnsupportedDataProxy('init', args, false)

/**
* Validation
*/
Expand Down
1 change: 1 addition & 0 deletions packages/client/edge.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '.prisma/client'
3 changes: 3 additions & 0 deletions packages/client/edge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
...require('.prisma/client/edge'),
}
1 change: 0 additions & 1 deletion packages/client/edge/index.d.ts

This file was deleted.

3 changes: 0 additions & 3 deletions packages/client/edge/index.js

This file was deleted.

14 changes: 14 additions & 0 deletions packages/client/helpers/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
},
}

Expand All @@ -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`
Expand Down
2 changes: 2 additions & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
"runtime",
"scripts",
"generator-build",
"edge.js",
"edge.d.ts",
"index.js",
"index.d.ts",
"index-browser.js"
Expand Down
11 changes: 5 additions & 6 deletions packages/client/scripts/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,21 +197,20 @@ 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 {
const dotPrismaClientDir = path.join(__dirname, '../../../.prisma/client')
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)
}
Expand Down
6 changes: 3 additions & 3 deletions packages/client/src/generation/TSClient/Generatable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface Generatable {
toJS?(edge?: boolean): string | Promise<string>
toTS(): string | Promise<string>
toTS(edge?: boolean): string | Promise<string>
toBrowserJS?(): string | Promise<string>
toTSWithoutNamespace?(): string | Promise<string>
}
Expand All @@ -13,6 +13,6 @@ export function BrowserJS(gen: Generatable): string | Promise<string> {
return gen.toBrowserJS?.() ?? ''
}

export function TS(gen: Generatable): string | Promise<string> {
return gen.toTS()
export function TS(gen: Generatable, edge = false): string | Promise<string> {
return gen.toTS(edge)
}
5 changes: 4 additions & 1 deletion packages/client/src/generation/TSClient/TSClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,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,
Expand Down
16 changes: 3 additions & 13 deletions packages/client/src/generation/generateClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,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 {
Expand Down Expand Up @@ -222,7 +213,6 @@ export async function generateClient(options: GenerateClientOptions): Promise<vo
}

await makeDir(finalOutputDir)
await makeDir(path.join(finalOutputDir, 'edge'))
await makeDir(path.join(outputDir, 'runtime'))
// TODO: why do we sometimes use outputDir and sometimes finalOutputDir?
// outputDir: /home/millsp/Work/prisma/packages/client
Expand Down Expand Up @@ -435,7 +425,7 @@ async function getGenerationDirs({ testMode, runtimeDirs, generator, outputDir }
const _runtimeDirs = {
// if we have an override, we use it, but if not then use the defaults
node: runtimeDirs?.node || (useDefaultOutdir ? '@prisma/client/runtime' : './runtime'),
edge: runtimeDirs?.edge || (useDefaultOutdir ? '@prisma/client/runtime' : '../runtime'),
edge: runtimeDirs?.edge || (useDefaultOutdir ? '@prisma/client/runtime' : './runtime'),
}

const finalOutputDir = useDefaultOutdir ? await getDefaultOutdir(outputDir) : outputDir
Expand Down
95 changes: 34 additions & 61 deletions packages/client/src/generation/utils/buildInjectableEdgeEnv.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { InternalDatasource } from '../../runtime/utils/printDatasources'

type LoadedEnv = {
type InjectableEnv = {
parsed: {
[x: string]: string | undefined
}
Expand All @@ -9,24 +9,51 @@ type LoadedEnv = {
/**
* Builds an injectable environment for the data proxy edge client. It's useful
* because it is designed to run in browser-like environments where things like
* `fs`, `process.env`, and .env file loading are not available. The injectable
* env is the default fallback when `tryLoadEnvs` wasn't called by the client.
* `fs`, `process.env`, and .env file loading are not available. That means env
* vars are represented as a global variable or injected at build time. This is
* the glue code to make this work with our existing env var loading logic. It
* is the place where we make collect the env vars for the edge client. To
* understand this better, take a look at the generated code in the edge client.
* @see {@link declareInjectableEdgeEnv}
* @param edge
* @param datasources
* @returns
*/
export function buildInjectableEdgeEnv(edge: boolean, datasources: InternalDatasource[]) {
if (edge === true) {
const envVarNames = getSelectedEnvVarNames(datasources)
const loadedEnv = getEmptyEnvObjectForVars(envVarNames)

return declareInjectableEdgeEnv(loadedEnv)
return declareInjectableEdgeEnv(datasources)
}

return ``
}

/**
* Creates the necessary declarations to embed env vars directly inside of the
* generated client. We abuse a custom `JSON.stringify` to generate the code.
* @param datasources to find env vars in
*/
function declareInjectableEdgeEnv(datasources: InternalDatasource[]) {
// we create a base env with empty values for env names
const injectableEdgeEnv: InjectableEnv = { parsed: {} }
const envVarNames = getSelectedEnvVarNames(datasources)

for (const envVarName of envVarNames) {
// for cloudflare workers, an env var is a global js variable
const cfwEnv = `typeof global !== 'undefined' && global['${envVarName}']`
// for vercel edge functions, it's injected statically at build
const vercelEnv = `process.env.${envVarName}`

injectableEdgeEnv.parsed[envVarName] = `${cfwEnv} || ${vercelEnv} || undefined`
}

// we make it json then remove the quotes to turn it into "code"
const injectableEdgeEnvJson = JSON.stringify(injectableEdgeEnv, null, 2)
const injectableEdgeEnvCode = injectableEdgeEnvJson.replace(/"/g, '')

return `
config.injectableEdgeEnv = ${injectableEdgeEnvCode}`
}

/**
* Determines which env vars we are interested in in the case of the data proxy
* client. Right now, we are only interested in env vars from the `datasource`.
Expand All @@ -42,57 +69,3 @@ function getSelectedEnvVarNames(datasources: InternalDatasource[]) {
return acc
}, [] as string[])
}

/**
* Like `tryLoadEnvs` but we only retain a subset of the env vars that will be
* used in the case of the data proxy client. And we discard the `message` prop.
* @param envVarNames to be selected from the load
* @returns
*/
function getEmptyEnvObjectForVars(envVarNames: string[]) {
const selectedEnv: LoadedEnv = { parsed: {} }

for (const envVarName of envVarNames) {
/** note that we willingly keep `undefined` values because
* we need that later in {@link declareInjectableEdgeEnv} **/
selectedEnv.parsed[envVarName] = undefined
}

return selectedEnv
}

/**
* Creates the necessary declarations to embed env vars directly inside of the
* generated client. We abuse a custom `JSON.stringify` replacer to transform
* {@link loadedEnv} into a piece of code. The goal of this is to take that and
* generate a new object which re-prioritizes the loading of the environment.
*
* By generating an output with `${key} || process.env.${key} || '${value}'` it
* would yield something like `DB_URL || process.env.DB_URL || undefined`:
* - For Cloudflare Workers `DB_URL` will be the first to be found (runtime)
* - `process.env.DB_URL` will be undefined
* - For Vercel `process.env.DB_URL` is replaced by webpack by a value
* - `DB_URL` will be undefined at anytime
* - If none of them were provided, we fallback to the default `undefined`
* @param loadedEnv
*/
function declareInjectableEdgeEnv(loadedEnv: LoadedEnv) {
// abuse a custom replacer to create the injectable env
const injectableEdgeEnvDeclaration = JSON.stringify(
loadedEnv,
(key, value) => {
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}`
}
34 changes: 3 additions & 31 deletions packages/client/src/generation/utils/buildNFTAnnotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
}
Expand Down Expand Up @@ -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 ''
}
2 changes: 1 addition & 1 deletion packages/client/src/runtime/getPrismaClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any> {
return Array.isArray(arg)
Expand Down
2 changes: 1 addition & 1 deletion packages/engine-core/src/common/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export interface EngineConfig {
showColors?: boolean
logQueries?: boolean
logLevel?: 'info' | 'warn'
env?: Record<string, string>
env: Record<string, string>
flags?: string[]
clientVersion?: string
previewFeatures?: string[]
Expand Down

0 comments on commit 0ab97f8

Please sign in to comment.