-
-
Couldn't load subscription status.
- Fork 1.3k
Improve Deno compatibility: config-first and safe env access #3547
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
base: master
Are you sure you want to change the base?
Conversation
…rors Currently, the pg package defaults the database user to process.env.USER (or USERNAME on Windows). This causes errors when running in Deno without --allow-env, as environment access is restricted. This PR changes the default user to 'postgres', so: - Node.js behavior is unchanged when a user is explicitly provided in config. - Deno users no longer hit NotCapable errors for environment access. - Avoids relying on process.env for default values. Example: Before: user: process.platform === 'win32' ? process.env.USERNAME : process.env.USER, After: user: 'postgres', // default user, avoids using process.env
…meters Previously, ConnectionParameters would first check process.env for connection settings before using the provided config object. This could cause errors in environments like Deno where environment access is restricted. This change updates the val function to: 1. Use the value from config if it is defined. 2. Fall back to environment variables only if config does not provide a value. 3. Use default values if neither config nor environment variables are set. This ensures that explicitly provided configuration values are always respected, avoiding unnecessary errors in restricted environments.
Replace the `typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined'` check with a try/catch to safely handle environments like Deno without `--allow-env`. - Keeps Node.js behavior unchanged. - Prevents errors when accessing process.env in Deno. - Preserves lazy loading of the native module.
Wrap the default database user assignment in a try/catch to prevent errors when environment access is restricted (e.g., in Deno). - Uses process.env.USER / USERNAME on Node if available - Falls back to 'postgres' when env access fails or is unavailable - Maintains code structure and comments - Ensures Node tests continue to pass while preventing Deno runtime errors
|
??? I will close this is you don't respond again |
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.
I think the biggest downside of the old pattern, and this suggested implementation, is that they bind too early. We don't need to, and therefore shouldn't decide which backend to use (either the js backend or the native backend) until the user actually tries to create a Client. If we defer this decision until then, then we open up the possibility for the user to request a different backend using a different API beside env vars (because if you decide at import time, this package has no APIs available yet for the user to inform us what they want).
So what about something like the following, which then provides an actual, non-env way to configure which backend the user wants? I don't love the use of a global variable to define the the behavior, but it is the least api-disruptive
const PG = function (clientConstructor) {
this.defaults = defaults
this.Client = clientConstructor
this.Query = this.Client.Query
this.Pool = poolFactory(this.Client)
this._pools = []
this.Connection = Connection
this.types = require('pg-types')
this.DatabaseError = DatabaseError
this.TypeOverrides = TypeOverrides
this.escapeIdentifier = escapeIdentifier
this.escapeLiteral = escapeLiteral
this.Result = Result
this.utils = utils
// A new property/config item we add? Not needed if you don't want to
// increase your API surface area, or maybe requires some more thought.
this.forceNative = false
}
function makeClient(config) {
// start with the current config setting
let useNative = module.exports.forceNative
try {
useNative = typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined'
} catch {
// ignore, eg in Deno sandbox without --allow-env
}
const actualClient = useNative ? require('./native') : Client
return actualClient(config)
}
module.exports = new PG(makeClient)
// lazy require native module...the native module may not have installed
Object.defineProperty(module.exports, 'native', {
...eg user does
import {Client, forceNative} from 'pg'
forceNative = true
const nativeClient = Client(...)
forceNative = false
const jsClient = Client(...)| // ignore, e.g., Deno without --allow-env | ||
| } | ||
|
|
||
| if (forceNative) { |
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.
If I am reading this right, if you are in a deno environment with no --allow-env, then the above will always error, and forceNative will always remain in its default of false, and so it will be impossible to use the native driver. I'm not sure how much of a dealbreaker this actually is (eg does the native driver actually run in deno???).
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.
I think the environment variable was always a mistake (same with the property), and breaking it in a way that’s backwards-compatible is fine. If a future API of the pg package even includes pg-native at all, I suggest it should be imported from pg/native and that there should be only the one way to do it. (I also recommend never using pg-native, since it has extra bugs and less maintenance. The inherent performance difference is minimal, though both pg and pg-native are both missing key optimizations right now.)
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.
OK, I also agree that the best API would be to import from pg/native. Then we can skip any new APIs here to select the backend. How about this then?
const PG = ... // same as before
function makeClient(config) {
let useNative = false;
try {
useNative = typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined'
} catch {
// ignore, eg in Deno sandbox without --allow-env
}
const actualClient = useNative ? require('./native') : Client
return actualClient(config)
}
module.exports = new PG(makeClient)
// lazy require native module...the native module may not have installed
Object.defineProperty(module.exports, 'native', {
...Is there anything else you need to merge this PR? With that change I'm happy. Honestly, the current suggested implementation is similar enough, I would be fine merging that as well.
| @@ -1,11 +1,18 @@ | |||
| 'use strict' | |||
|
|
|||
| let user | |||
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.
maybe say explicitly let user = undefined? I know they behave the same, but semantically this bare let user (incorrectly) implies to me that we expect to always set it to a value.
This PR introduces three changes to make the pg package more compatible with Deno while
keeping full Node.js functionality:
Default user value
user: process.platform === 'win32' ? process.env.USERNAME : process.env.USERwith
user: 'postgres'in defaults.Config-first parameter resolution
val()in connection-parameters.js to returnconfig[key]first, beforechecking environment variables.
--allow-envis not granted.Safe NODE_PG_FORCE_NATIVE check
NODE_PG_FORCE_NATIVEcheck in atry/catch.process.envaccess in Deno doesn’t throw, while preserving Node.js behavior.These changes maintain Node.js compatibility, preserve the lazy-loading of the native module,
and allow using the package in Deno without requiring
--allow-env.