-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore(*): polishing upstream work on the data proxy [DPGA, 5] #13629
Changes from 34 commits
fd0821b
5aadcaf
54e4c51
089304c
d14aa39
cab67ed
25d5a43
dceabd0
41e6298
4d0b741
752d757
5cf1374
21294c6
b951a42
e94e596
1f33f60
227da0a
dfc6fb7
cfff964
3476eb9
779a3db
1de120d
f4eac36
b87e240
961b7f6
4a4d8ef
3c335ef
3170776
2aaf6e0
347765e
3d2b349
091f807
736ce04
985c6ae
727b290
0755229
fa0b011
ce8ce89
2df7160
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const fn = () => {} | ||
|
||
fn.prototype = fn | ||
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, | ||
|
@@ -134,6 +135,8 @@ export class Init implements Command { | |
return this.help() | ||
} | ||
|
||
await checkUnsupportedDataProxy('init', args, false) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
/** | ||
* Validation | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from '.prisma/client' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
...require('.prisma/client/edge'), | ||
} |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restored this as it's still needed, otherwise it causes issues with webpack. |
||
}, | ||
} | ||
|
||
|
@@ -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: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re-define |
||
define: 'fn', | ||
inject: functionPolyfillPath, | ||
}, | ||
|
||
// TODO no tree shaking on wrapper pkgs | ||
'@prisma/get-platform': { contents: '' }, | ||
// removes un-needed code out of `chalk` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not needed anymore, instead of having a full folder, we just have one file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Isomorphism is not needed because bundling for the edge means that we always bundle for the browser) |
||
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) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not needed anymore, instead of having a full folder, we just have one file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Isomorphism is not needed because bundling for the edge means that we always bundle for the browser) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, so that means in isomporphic setups we will be bundling the whole client for the browser, right? In previous setup it was only enums, version info and a bunch of stubs, so significantly smaller amount of code. Is that what we want? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you deploy on Cloudflare Workers, it is by default bundled via the |
||
{ | ||
name: '.prisma/client/edge', | ||
main: 'index.js', | ||
types: '../index.d.ts', | ||
browser: '../index-browser.js', | ||
}, | ||
null, | ||
2, | ||
) | ||
fileMap[path.join('edge.js')] = await JS(edgeTsClient, true) | ||
millsp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fileMap[path.join('edge.d.ts')] = await TS(edgeTsClient, true) | ||
} | ||
|
||
return { | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import type { InternalDatasource } from '../../runtime/utils/printDatasources' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here I simplified the code and updated the comments, hopefully more comprehensive. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (this code is only for the edge client) |
||
|
||
type LoadedEnv = { | ||
type InjectableEnv = { | ||
parsed: { | ||
[x: string]: string | undefined | ||
} | ||
|
@@ -9,90 +9,70 @@ 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. 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 `` | ||
} | ||
|
||
/** | ||
* 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`. | ||
* 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 | ||
* @returns | ||
*/ | ||
function getSelectedEnvVarNames(datasources: InternalDatasource[]) { | ||
return datasources.reduce((acc, datasource) => { | ||
if (datasource.url.fromEnvVar) { | ||
return [...acc, datasource.url.fromEnvVar] | ||
} | ||
function declareInjectableEdgeEnv(datasources: InternalDatasource[]) { | ||
const envVarNames = getSelectedEnvVarNames(datasources) | ||
|
||
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: {} } | ||
const injectableEdgeEnv: InjectableEnv = { parsed: {} } | ||
|
||
// we create a base env with empty values for env names | ||
for (const envVarName of envVarNames) { | ||
/** note that we willingly keep `undefined` values because | ||
* we need that later in {@link declareInjectableEdgeEnv} **/ | ||
selectedEnv.parsed[envVarName] = undefined | ||
injectableEdgeEnv.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, | ||
injectableEdgeEnv, | ||
(key, value) => { | ||
if (key === '') return value | ||
if (key === 'parsed') return value | ||
|
||
// for cloudflare workers, an env var is a global js variable | ||
const cfwEnv = `typeof global !== 'undefined' && global['${key}']` | ||
const vercelEnv = `process.env['${key}']` | ||
const dotEnv = value ? `'${value}'` : 'undefined' | ||
// for vercel edge functions, it's injected statically at build | ||
const vercelEnv = `process.env.${key}` | ||
|
||
return `${cfwEnv} || ${vercelEnv} || ${dotEnv}` | ||
return `${cfwEnv} || ${vercelEnv} || undefined` | ||
}, | ||
2, | ||
).replace(/"/g, '') // remove quotes to make code | ||
|
||
return ` | ||
config.injectableEdgeEnv = ${injectableEdgeEnvDeclaration}` | ||
} | ||
|
||
/** | ||
* 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`. | ||
* @param datasources to find env vars in | ||
* @returns | ||
*/ | ||
function getSelectedEnvVarNames(datasources: InternalDatasource[]) { | ||
return datasources.reduce((acc, datasource) => { | ||
if (datasource.url.fromEnvVar) { | ||
return [...acc, datasource.url.fromEnvVar] | ||
} | ||
|
||
return acc | ||
}, [] as string[]) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just shrinking the code here. |
||
|
||
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 '' | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,7 +47,7 @@ declare global { | |
} | ||
|
||
// used by esbuild for tree-shaking | ||
globalThis.NODE_CLIENT = true | ||
typeof globalThis === 'object' ? (globalThis.NODE_CLIENT = true) : 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's better to check if this exists always. |
||
|
||
function isReadonlyArray(arg: any): arg is ReadonlyArray<any> { | ||
return Array.isArray(arg) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Polyfill for Vercel which disallows
Function
.