@rvoh/dream 2.32.0 → 2.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/cli/index.js +4 -4
- package/dist/cjs/src/db/helpers/syncDbTypesFiles.js +6 -7
- package/dist/cjs/src/dream/QueryDriver/Base.js +7 -0
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +7 -0
- package/dist/cjs/src/dream/QueryDriver/Postgres.js +7 -1
- package/dist/cjs/src/dream/QueryDriver/helpers/pg/loadPgClient.js +29 -1
- package/dist/esm/src/cli/index.js +4 -4
- package/dist/esm/src/db/helpers/syncDbTypesFiles.js +6 -7
- package/dist/esm/src/dream/QueryDriver/Base.js +7 -0
- package/dist/esm/src/dream/QueryDriver/Kysely.js +7 -0
- package/dist/esm/src/dream/QueryDriver/Postgres.js +7 -1
- package/dist/esm/src/dream/QueryDriver/helpers/pg/loadPgClient.js +29 -1
- package/dist/types/src/cli/index.d.ts +7 -6
- package/dist/types/src/dream/QueryDriver/Base.d.ts +3 -0
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +1 -0
- package/dist/types/src/dream/QueryDriver/helpers/pg/loadPgClient.d.ts +5 -0
- package/dist/types/src/dream-app/index.d.ts +11 -1
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/db.DreamMigrationHelpers.html +11 -11
- package/docs/classes/db.KyselyQueryDriver.html +47 -45
- package/docs/classes/db.PostgresQueryDriver.html +48 -46
- package/docs/classes/db.QueryDriverBase.html +46 -44
- package/docs/classes/errors.CannotSaveMissingDream.html +2 -2
- package/docs/classes/errors.CheckConstraintViolation.html +3 -3
- package/docs/classes/errors.ColumnOverflow.html +3 -3
- package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
- package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
- package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
- package/docs/classes/errors.DecryptionError.html +2 -2
- package/docs/classes/errors.DecryptionParseError.html +2 -2
- package/docs/classes/errors.DecryptionRotationError.html +3 -3
- package/docs/classes/errors.GlobalNameNotSet.html +3 -3
- package/docs/classes/errors.InvalidCalendarDate.html +2 -2
- package/docs/classes/errors.InvalidClockTime.html +2 -2
- package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
- package/docs/classes/errors.InvalidDateTime.html +2 -2
- package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
- package/docs/classes/errors.NonLoadedAssociation.html +3 -3
- package/docs/classes/errors.NotNullViolation.html +3 -3
- package/docs/classes/errors.RecordNotFound.html +3 -3
- package/docs/classes/errors.SortableScopeDidNotStabilize.html +3 -3
- package/docs/classes/errors.SortableScopeLockWaitTimedOut.html +3 -3
- package/docs/classes/errors.ValidationError.html +3 -3
- package/docs/classes/index.CalendarDate.html +33 -33
- package/docs/classes/index.ClockTime.html +32 -32
- package/docs/classes/index.ClockTimeTz.html +35 -35
- package/docs/classes/index.DateTime.html +86 -86
- package/docs/classes/index.Decorators.html +19 -19
- package/docs/classes/index.Dream.html +124 -124
- package/docs/classes/index.DreamApp.html +10 -10
- package/docs/classes/index.DreamTransaction.html +2 -2
- package/docs/classes/index.Env.html +2 -2
- package/docs/classes/index.Query.html +65 -65
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +2 -2
- package/docs/classes/system.DreamCLI.html +11 -11
- package/docs/classes/system.DreamImporter.html +2 -2
- package/docs/classes/system.DreamLogos.html +2 -2
- package/docs/classes/system.DreamSerializerBuilder.html +11 -11
- package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
- package/docs/classes/system.PathHelpers.html +3 -3
- package/docs/classes/utils.Encrypt.html +3 -3
- package/docs/classes/utils.Range.html +2 -2
- package/docs/functions/db.closeAllDbConnections.html +1 -1
- package/docs/functions/db.dreamDbConnections.html +1 -1
- package/docs/functions/db.untypedDb.html +1 -1
- package/docs/functions/db.validateColumn.html +1 -1
- package/docs/functions/db.validateTable.html +1 -1
- package/docs/functions/errors.pgErrorType.html +1 -1
- package/docs/functions/index.DreamSerializer.html +1 -1
- package/docs/functions/index.ObjectSerializer.html +1 -1
- package/docs/functions/index.ReplicaSafe.html +1 -1
- package/docs/functions/index.STI.html +1 -1
- package/docs/functions/index.SoftDelete.html +1 -1
- package/docs/functions/utils.camelize.html +1 -1
- package/docs/functions/utils.capitalize.html +1 -1
- package/docs/functions/utils.cloneDeepSafe.html +1 -1
- package/docs/functions/utils.compact.html +1 -1
- package/docs/functions/utils.groupBy.html +1 -1
- package/docs/functions/utils.hyphenize.html +1 -1
- package/docs/functions/utils.intersection.html +1 -1
- package/docs/functions/utils.isEmpty.html +1 -1
- package/docs/functions/utils.normalizeUnicode.html +1 -1
- package/docs/functions/utils.pascalize.html +1 -1
- package/docs/functions/utils.percent.html +1 -1
- package/docs/functions/utils.range.html +1 -1
- package/docs/functions/utils.round.html +1 -1
- package/docs/functions/utils.sanitizeString.html +1 -1
- package/docs/functions/utils.snakeify.html +1 -1
- package/docs/functions/utils.sort.html +1 -1
- package/docs/functions/utils.sortBy.html +1 -1
- package/docs/functions/utils.sortObjectByKey.html +1 -1
- package/docs/functions/utils.sortObjectByValue.html +1 -1
- package/docs/functions/utils.uncapitalize.html +1 -1
- package/docs/functions/utils.uniq.html +1 -1
- package/docs/hierarchy.html +1 -1
- package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
- package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
- package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
- package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
- package/docs/interfaces/types.BelongsToStatement.html +2 -2
- package/docs/interfaces/types.DecoratorContext.html +2 -2
- package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
- package/docs/interfaces/types.DreamAppOpts.html +2 -2
- package/docs/interfaces/types.DreamDbConfig.html +13 -5
- package/docs/interfaces/types.DurationObject.html +2 -2
- package/docs/interfaces/types.EncryptOptions.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
- package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
- package/docs/types/openapi.OpenapiAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiFormats.html +1 -1
- package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
- package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
- package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
- package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaString.html +1 -1
- package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiTypeField.html +1 -1
- package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
- package/docs/types/types.CalendarDateDurationUnit.html +1 -1
- package/docs/types/types.CalendarDateObject.html +1 -1
- package/docs/types/types.Camelized.html +1 -1
- package/docs/types/types.ClockTimeObject.html +1 -1
- package/docs/types/types.DbConnectionType.html +1 -1
- package/docs/types/types.DbTypes.html +1 -1
- package/docs/types/types.DreamAssociationMetadata.html +1 -1
- package/docs/types/types.DreamAttributes.html +1 -1
- package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
- package/docs/types/types.DreamClassColumn.html +1 -1
- package/docs/types/types.DreamColumn.html +1 -1
- package/docs/types/types.DreamColumnNames.html +1 -1
- package/docs/types/types.DreamLogLevel.html +1 -1
- package/docs/types/types.DreamLogger.html +2 -2
- package/docs/types/types.DreamModelSerializerType.html +1 -1
- package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
- package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
- package/docs/types/types.DreamParamSafeAttributes.html +1 -1
- package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
- package/docs/types/types.DreamSerializable.html +1 -1
- package/docs/types/types.DreamSerializableArray.html +1 -1
- package/docs/types/types.DreamSerializerKey.html +1 -1
- package/docs/types/types.DreamSerializers.html +1 -1
- package/docs/types/types.DreamVirtualColumns.html +1 -1
- package/docs/types/types.DurationUnit.html +1 -1
- package/docs/types/types.EncryptAlgorithm.html +1 -1
- package/docs/types/types.HasManyStatement.html +1 -1
- package/docs/types/types.HasOneStatement.html +1 -1
- package/docs/types/types.Hyphenized.html +1 -1
- package/docs/types/types.Pascalized.html +1 -1
- package/docs/types/types.PrimaryKeyType.html +1 -1
- package/docs/types/types.RoundingPrecision.html +1 -1
- package/docs/types/types.SerializerCasing.html +1 -1
- package/docs/types/types.SimpleObjectSerializerType.html +1 -1
- package/docs/types/types.Snakeified.html +1 -1
- package/docs/types/types.StrictInterface.html +1 -1
- package/docs/types/types.UpdateableAssociationProperties.html +1 -1
- package/docs/types/types.UpdateableProperties.html +1 -1
- package/docs/types/types.ValidationType.html +1 -1
- package/docs/types/types.ViewModel.html +2 -2
- package/docs/types/types.ViewModelClass.html +1 -1
- package/docs/types/types.WeekdayName.html +1 -1
- package/docs/types/types.WhereStatementForDream.html +1 -1
- package/docs/types/types.WhereStatementForDreamClass.html +1 -1
- package/docs/variables/index.DreamConst.html +1 -1
- package/docs/variables/index.ops.html +1 -1
- package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
- package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
- package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
- package/docs/variables/system.primaryKeyTypes.html +1 -1
- package/package.json +1 -1
|
@@ -508,10 +508,10 @@ ${INDENT}Examples: User, Place, Room/Bedroom, Settings/CommunicationPreferences`
|
|
|
508
508
|
* (package.json scripts, CLI argv, scaffold templates) — never from
|
|
509
509
|
* runtime request input or any other untrusted external source.
|
|
510
510
|
*
|
|
511
|
-
* Argv-form
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
511
|
+
* Argv-form passes config values and paths literally to the child,
|
|
512
|
+
* without interpreting shell metacharacters. Credentials belong in
|
|
513
|
+
* the child's environment, since argv is visible in process listings
|
|
514
|
+
* and in the refused-spawn error.
|
|
515
515
|
*
|
|
516
516
|
* ## Layered defense
|
|
517
517
|
*
|
|
@@ -13,16 +13,14 @@ export default async function syncDbTypesFiles(connectionName) {
|
|
|
13
13
|
const dbFilename = dbTypesFilenameForConnection(connectionName);
|
|
14
14
|
const dbSyncFilePath = path.join(dreamPath('types'), dbFilename);
|
|
15
15
|
const absoluteDbSyncPath = path.join(dreamApp.projectRoot, dbSyncFilePath);
|
|
16
|
+
const password = await driverClass.codegenPassword(dbConf.password);
|
|
16
17
|
await CliFileWriter.cache(absoluteDbSyncPath);
|
|
17
18
|
const lowLevelDbOpts = dreamApp.dbCredentialsFor(connectionName);
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
// the child rather than parsed by a shell — credentials are preserved and any
|
|
22
|
-
// future mutable source can't open a command-injection surface.
|
|
23
|
-
const userinfo = `${dbConf.user}${dbConf.password ? `:${dbConf.password}` : ''}`;
|
|
19
|
+
// Pass the URL only in the child's environment. A URL in argv exposes the
|
|
20
|
+
// password in the process command line and DreamCLI's refused-spawn error.
|
|
21
|
+
const userinfo = `${encodeURIComponent(dbConf.user)}${password ? `:${encodeURIComponent(password)}` : ''}`;
|
|
24
22
|
const url = `${driverClass.syncDialect}://${userinfo}@${dbConf.host}:${dbConf.port}/${dbConf.name}`;
|
|
25
|
-
const args = [`--dialect=${driverClass.syncDialect}`,
|
|
23
|
+
const args = [`--dialect=${driverClass.syncDialect}`, '--url=env(DATABASE_URL)'];
|
|
26
24
|
if (lowLevelDbOpts?.tableIncludePattern) {
|
|
27
25
|
args.push(`--include-pattern=${lowLevelDbOpts.tableIncludePattern}`);
|
|
28
26
|
}
|
|
@@ -32,6 +30,7 @@ export default async function syncDbTypesFiles(connectionName) {
|
|
|
32
30
|
args.push(`--out-file=${absoluteDbSyncPath}`);
|
|
33
31
|
await DreamCLI.spawn('kysely-codegen', {
|
|
34
32
|
args,
|
|
33
|
+
env: { ...process.env, DATABASE_URL: url },
|
|
35
34
|
onStdout: message => {
|
|
36
35
|
DreamCLI.logger.logContinueProgress(colorize(`[db]`, { color: 'greenBright' }) + ' ' + message, {
|
|
37
36
|
logPrefixColor: 'greenBright',
|
|
@@ -626,6 +626,13 @@ export default class QueryDriverBase {
|
|
|
626
626
|
static get syncDialect() {
|
|
627
627
|
return 'postgres';
|
|
628
628
|
}
|
|
629
|
+
/** @internal Resolve the credential used by the database type generator. */
|
|
630
|
+
static codegenPassword(password) {
|
|
631
|
+
if (typeof password === 'function') {
|
|
632
|
+
throw new Error('This database adapter does not support a password provider for type generation');
|
|
633
|
+
}
|
|
634
|
+
return Promise.resolve(password);
|
|
635
|
+
}
|
|
629
636
|
/**
|
|
630
637
|
* Returns the sql that would be executed by this Query
|
|
631
638
|
*
|
|
@@ -71,6 +71,7 @@ import softDeleteDream from '../internal/softDeleteDream.js';
|
|
|
71
71
|
import sqlResultToDreamInstance from '../internal/sqlResultToDreamInstance.js';
|
|
72
72
|
import Query from '../Query.js';
|
|
73
73
|
import QueryDriverBase from './Base.js';
|
|
74
|
+
import { PasswordProviderPoolClient } from './helpers/pg/loadPgClient.js';
|
|
74
75
|
import checkForNeedToBeRunMigrations from './helpers/kysely/checkForNeedToBeRunMigrations.js';
|
|
75
76
|
import foreignKeyTypeFromPrimaryKey from './helpers/kysely/foreignKeyTypeFromPrimaryKey.js';
|
|
76
77
|
import runMigration from './helpers/kysely/runMigration.js';
|
|
@@ -103,7 +104,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
103
104
|
// Spread pg passthrough first; Dream's resolved fields follow and
|
|
104
105
|
// always win (per-connection database name, TLS directive).
|
|
105
106
|
...(connectionConf.pg ?? {}),
|
|
107
|
+
Client: typeof connectionConf.password === 'function' ? PasswordProviderPoolClient : undefined,
|
|
106
108
|
user: connectionConf.user || '',
|
|
109
|
+
// pg accepts a callback here and resolves it for each new client when
|
|
110
|
+
// the server requests password authentication.
|
|
107
111
|
password: connectionConf.password || '',
|
|
108
112
|
database: DreamApp.getOrFail().dbName(connectionName, dbConnectionType),
|
|
109
113
|
host: connectionConf.host || 'localhost',
|
|
@@ -227,6 +231,9 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
227
231
|
static get syncDialect() {
|
|
228
232
|
return 'postgres';
|
|
229
233
|
}
|
|
234
|
+
static async codegenPassword(password) {
|
|
235
|
+
return typeof password === 'function' ? await password() : password;
|
|
236
|
+
}
|
|
230
237
|
/**
|
|
231
238
|
* @internal
|
|
232
239
|
*
|
|
@@ -332,7 +332,13 @@ export default class PostgresQueryDriver extends KyselyQueryDriver {
|
|
|
332
332
|
keepAlive: true,
|
|
333
333
|
keepAliveInitialDelayMillis: LOCK_CONNECTION_KEEPALIVE_INITIAL_DELAY_MS,
|
|
334
334
|
});
|
|
335
|
-
|
|
335
|
+
try {
|
|
336
|
+
await client.connect();
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
await client.end().catch(() => undefined);
|
|
340
|
+
throw err;
|
|
341
|
+
}
|
|
336
342
|
return {
|
|
337
343
|
async tryAcquire(namespace, index) {
|
|
338
344
|
const result = await client.query('SELECT pg_try_advisory_lock($1::int4, $2::int4) AS locked', [
|
|
@@ -8,6 +8,28 @@
|
|
|
8
8
|
// @ts-ignore
|
|
9
9
|
import pg from 'pg';
|
|
10
10
|
import DreamApp from '../../../../dream-app/index.js';
|
|
11
|
+
export class PasswordProviderPoolClient extends pg.Client {
|
|
12
|
+
connect(callback) {
|
|
13
|
+
if (callback) {
|
|
14
|
+
super.connect(err => {
|
|
15
|
+
if (err)
|
|
16
|
+
this.destroyConnectionStream();
|
|
17
|
+
callback(err);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return super.connect().catch(err => {
|
|
22
|
+
this.destroyConnectionStream();
|
|
23
|
+
throw err;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
destroyConnectionStream() {
|
|
28
|
+
// pg reports a rejected password provider without closing its socket.
|
|
29
|
+
;
|
|
30
|
+
this.connection?.stream.destroy();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
11
33
|
export default async function loadPgClient({ connectionName, useSystemDb, }) {
|
|
12
34
|
const dreamconf = DreamApp.getOrFail();
|
|
13
35
|
const creds = dreamconf.dbCredentialsFor(connectionName)?.primary;
|
|
@@ -20,6 +42,12 @@ export default async function loadPgClient({ connectionName, useSystemDb, }) {
|
|
|
20
42
|
user: creds.user,
|
|
21
43
|
password: creds.password,
|
|
22
44
|
});
|
|
23
|
-
|
|
45
|
+
try {
|
|
46
|
+
await client.connect();
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
await client.end().catch(() => undefined);
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
24
52
|
return client;
|
|
25
53
|
}
|
|
@@ -508,10 +508,10 @@ ${INDENT}Examples: User, Place, Room/Bedroom, Settings/CommunicationPreferences`
|
|
|
508
508
|
* (package.json scripts, CLI argv, scaffold templates) — never from
|
|
509
509
|
* runtime request input or any other untrusted external source.
|
|
510
510
|
*
|
|
511
|
-
* Argv-form
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
511
|
+
* Argv-form passes config values and paths literally to the child,
|
|
512
|
+
* without interpreting shell metacharacters. Credentials belong in
|
|
513
|
+
* the child's environment, since argv is visible in process listings
|
|
514
|
+
* and in the refused-spawn error.
|
|
515
515
|
*
|
|
516
516
|
* ## Layered defense
|
|
517
517
|
*
|
|
@@ -13,16 +13,14 @@ export default async function syncDbTypesFiles(connectionName) {
|
|
|
13
13
|
const dbFilename = dbTypesFilenameForConnection(connectionName);
|
|
14
14
|
const dbSyncFilePath = path.join(dreamPath('types'), dbFilename);
|
|
15
15
|
const absoluteDbSyncPath = path.join(dreamApp.projectRoot, dbSyncFilePath);
|
|
16
|
+
const password = await driverClass.codegenPassword(dbConf.password);
|
|
16
17
|
await CliFileWriter.cache(absoluteDbSyncPath);
|
|
17
18
|
const lowLevelDbOpts = dreamApp.dbCredentialsFor(connectionName);
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
// the child rather than parsed by a shell — credentials are preserved and any
|
|
22
|
-
// future mutable source can't open a command-injection surface.
|
|
23
|
-
const userinfo = `${dbConf.user}${dbConf.password ? `:${dbConf.password}` : ''}`;
|
|
19
|
+
// Pass the URL only in the child's environment. A URL in argv exposes the
|
|
20
|
+
// password in the process command line and DreamCLI's refused-spawn error.
|
|
21
|
+
const userinfo = `${encodeURIComponent(dbConf.user)}${password ? `:${encodeURIComponent(password)}` : ''}`;
|
|
24
22
|
const url = `${driverClass.syncDialect}://${userinfo}@${dbConf.host}:${dbConf.port}/${dbConf.name}`;
|
|
25
|
-
const args = [`--dialect=${driverClass.syncDialect}`,
|
|
23
|
+
const args = [`--dialect=${driverClass.syncDialect}`, '--url=env(DATABASE_URL)'];
|
|
26
24
|
if (lowLevelDbOpts?.tableIncludePattern) {
|
|
27
25
|
args.push(`--include-pattern=${lowLevelDbOpts.tableIncludePattern}`);
|
|
28
26
|
}
|
|
@@ -32,6 +30,7 @@ export default async function syncDbTypesFiles(connectionName) {
|
|
|
32
30
|
args.push(`--out-file=${absoluteDbSyncPath}`);
|
|
33
31
|
await DreamCLI.spawn('kysely-codegen', {
|
|
34
32
|
args,
|
|
33
|
+
env: { ...process.env, DATABASE_URL: url },
|
|
35
34
|
onStdout: message => {
|
|
36
35
|
DreamCLI.logger.logContinueProgress(colorize(`[db]`, { color: 'greenBright' }) + ' ' + message, {
|
|
37
36
|
logPrefixColor: 'greenBright',
|
|
@@ -626,6 +626,13 @@ export default class QueryDriverBase {
|
|
|
626
626
|
static get syncDialect() {
|
|
627
627
|
return 'postgres';
|
|
628
628
|
}
|
|
629
|
+
/** @internal Resolve the credential used by the database type generator. */
|
|
630
|
+
static codegenPassword(password) {
|
|
631
|
+
if (typeof password === 'function') {
|
|
632
|
+
throw new Error('This database adapter does not support a password provider for type generation');
|
|
633
|
+
}
|
|
634
|
+
return Promise.resolve(password);
|
|
635
|
+
}
|
|
629
636
|
/**
|
|
630
637
|
* Returns the sql that would be executed by this Query
|
|
631
638
|
*
|
|
@@ -71,6 +71,7 @@ import softDeleteDream from '../internal/softDeleteDream.js';
|
|
|
71
71
|
import sqlResultToDreamInstance from '../internal/sqlResultToDreamInstance.js';
|
|
72
72
|
import Query from '../Query.js';
|
|
73
73
|
import QueryDriverBase from './Base.js';
|
|
74
|
+
import { PasswordProviderPoolClient } from './helpers/pg/loadPgClient.js';
|
|
74
75
|
import checkForNeedToBeRunMigrations from './helpers/kysely/checkForNeedToBeRunMigrations.js';
|
|
75
76
|
import foreignKeyTypeFromPrimaryKey from './helpers/kysely/foreignKeyTypeFromPrimaryKey.js';
|
|
76
77
|
import runMigration from './helpers/kysely/runMigration.js';
|
|
@@ -103,7 +104,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
103
104
|
// Spread pg passthrough first; Dream's resolved fields follow and
|
|
104
105
|
// always win (per-connection database name, TLS directive).
|
|
105
106
|
...(connectionConf.pg ?? {}),
|
|
107
|
+
Client: typeof connectionConf.password === 'function' ? PasswordProviderPoolClient : undefined,
|
|
106
108
|
user: connectionConf.user || '',
|
|
109
|
+
// pg accepts a callback here and resolves it for each new client when
|
|
110
|
+
// the server requests password authentication.
|
|
107
111
|
password: connectionConf.password || '',
|
|
108
112
|
database: DreamApp.getOrFail().dbName(connectionName, dbConnectionType),
|
|
109
113
|
host: connectionConf.host || 'localhost',
|
|
@@ -227,6 +231,9 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
227
231
|
static get syncDialect() {
|
|
228
232
|
return 'postgres';
|
|
229
233
|
}
|
|
234
|
+
static async codegenPassword(password) {
|
|
235
|
+
return typeof password === 'function' ? await password() : password;
|
|
236
|
+
}
|
|
230
237
|
/**
|
|
231
238
|
* @internal
|
|
232
239
|
*
|
|
@@ -332,7 +332,13 @@ export default class PostgresQueryDriver extends KyselyQueryDriver {
|
|
|
332
332
|
keepAlive: true,
|
|
333
333
|
keepAliveInitialDelayMillis: LOCK_CONNECTION_KEEPALIVE_INITIAL_DELAY_MS,
|
|
334
334
|
});
|
|
335
|
-
|
|
335
|
+
try {
|
|
336
|
+
await client.connect();
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
await client.end().catch(() => undefined);
|
|
340
|
+
throw err;
|
|
341
|
+
}
|
|
336
342
|
return {
|
|
337
343
|
async tryAcquire(namespace, index) {
|
|
338
344
|
const result = await client.query('SELECT pg_try_advisory_lock($1::int4, $2::int4) AS locked', [
|
|
@@ -8,6 +8,28 @@
|
|
|
8
8
|
// @ts-ignore
|
|
9
9
|
import pg from 'pg';
|
|
10
10
|
import DreamApp from '../../../../dream-app/index.js';
|
|
11
|
+
export class PasswordProviderPoolClient extends pg.Client {
|
|
12
|
+
connect(callback) {
|
|
13
|
+
if (callback) {
|
|
14
|
+
super.connect(err => {
|
|
15
|
+
if (err)
|
|
16
|
+
this.destroyConnectionStream();
|
|
17
|
+
callback(err);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return super.connect().catch(err => {
|
|
22
|
+
this.destroyConnectionStream();
|
|
23
|
+
throw err;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
destroyConnectionStream() {
|
|
28
|
+
// pg reports a rejected password provider without closing its socket.
|
|
29
|
+
;
|
|
30
|
+
this.connection?.stream.destroy();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
11
33
|
export default async function loadPgClient({ connectionName, useSystemDb, }) {
|
|
12
34
|
const dreamconf = DreamApp.getOrFail();
|
|
13
35
|
const creds = dreamconf.dbCredentialsFor(connectionName)?.primary;
|
|
@@ -20,6 +42,12 @@ export default async function loadPgClient({ connectionName, useSystemDb, }) {
|
|
|
20
42
|
user: creds.user,
|
|
21
43
|
password: creds.password,
|
|
22
44
|
});
|
|
23
|
-
|
|
45
|
+
try {
|
|
46
|
+
await client.connect();
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
await client.end().catch(() => undefined);
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
24
52
|
return client;
|
|
25
53
|
}
|
|
@@ -8,8 +8,9 @@ export type SpawnOptions = Omit<NodeSpawnOptions, 'shell'> & {
|
|
|
8
8
|
* Argv elements passed to the child as separate arguments. Defaults to `[]`.
|
|
9
9
|
* Each entry is passed literally — shell meta-characters (`$`, backticks,
|
|
10
10
|
* `&&`, spaces, etc.) inside any element are not interpreted by a shell.
|
|
11
|
-
* This is the right shape for
|
|
12
|
-
*
|
|
11
|
+
* This is the right shape for paths and other non-secret values. Pass
|
|
12
|
+
* credentials through the child's environment so they do not appear in
|
|
13
|
+
* the process command line or a refused-spawn error.
|
|
13
14
|
*/
|
|
14
15
|
args?: string[];
|
|
15
16
|
};
|
|
@@ -103,10 +104,10 @@ export default class DreamCLI {
|
|
|
103
104
|
* (package.json scripts, CLI argv, scaffold templates) — never from
|
|
104
105
|
* runtime request input or any other untrusted external source.
|
|
105
106
|
*
|
|
106
|
-
* Argv-form
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
107
|
+
* Argv-form passes config values and paths literally to the child,
|
|
108
|
+
* without interpreting shell metacharacters. Credentials belong in
|
|
109
|
+
* the child's environment, since argv is visible in process listings
|
|
110
|
+
* and in the refused-spawn error.
|
|
110
111
|
*
|
|
111
112
|
* ## Layered defense
|
|
112
113
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CompiledQuery, DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder } from 'kysely';
|
|
2
|
+
import type { DreamDbConfig } from '../../dream-app/index.js';
|
|
2
3
|
import Dream from '../../Dream.js';
|
|
3
4
|
import { SchemaBuilderAssociationData, SchemaBuilderColumnData } from '../../helpers/cli/ASTBuilder.js';
|
|
4
5
|
import { AssociationStatement } from '../../types/associations/shared.js';
|
|
@@ -480,6 +481,8 @@ export default class QueryDriverBase<DreamInstance extends Dream> {
|
|
|
480
481
|
*/
|
|
481
482
|
static destroyDream(dream: Dream, txn: DreamTransaction<Dream>, reallyDestroy: boolean): Promise<number>;
|
|
482
483
|
static get syncDialect(): string;
|
|
484
|
+
/** @internal Resolve the credential used by the database type generator. */
|
|
485
|
+
static codegenPassword(password: DreamDbConfig['password']): Promise<string>;
|
|
483
486
|
/**
|
|
484
487
|
* Returns the sql that would be executed by this Query
|
|
485
488
|
*
|
|
@@ -44,6 +44,7 @@ export default class KyselyQueryDriver<DreamInstance extends Dream> extends Quer
|
|
|
44
44
|
schemaOnly?: boolean;
|
|
45
45
|
}): Promise<void>;
|
|
46
46
|
static get syncDialect(): string;
|
|
47
|
+
static codegenPassword(password: DreamDbConfig['password']): Promise<string>;
|
|
47
48
|
/**
|
|
48
49
|
* @internal
|
|
49
50
|
*
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import pg from 'pg';
|
|
2
|
+
export declare class PasswordProviderPoolClient extends pg.Client {
|
|
3
|
+
connect(): Promise<void>;
|
|
4
|
+
connect(callback: (err: Error) => void): void;
|
|
5
|
+
private destroyConnectionStream;
|
|
6
|
+
}
|
|
2
7
|
export default function loadPgClient({ connectionName, useSystemDb, }: {
|
|
3
8
|
connectionName: string;
|
|
4
9
|
useSystemDb?: boolean;
|
|
@@ -288,7 +288,17 @@ export interface DreamDbCredentialOptions {
|
|
|
288
288
|
type UnicodeNormalizationForm = 'NFC' | 'NFD' | 'none';
|
|
289
289
|
export interface DreamDbConfig {
|
|
290
290
|
user: string;
|
|
291
|
-
|
|
291
|
+
/**
|
|
292
|
+
* A fixed password, or a provider for Dream's built-in PostgreSQL driver.
|
|
293
|
+
* PostgreSQL calls the provider when the server requests password
|
|
294
|
+
* authentication for a new physical connection. The provider may return a
|
|
295
|
+
* string or a promise of one. Each such connection gets a fresh result;
|
|
296
|
+
* existing pooled connections keep their credential. A rejected provider
|
|
297
|
+
* fails that connection attempt and reaches its caller; Dream does not cache
|
|
298
|
+
* credentials or retry. Custom query drivers receive the configured value
|
|
299
|
+
* and must handle or reject a provider themselves.
|
|
300
|
+
*/
|
|
301
|
+
password: string | (() => string | Promise<string>);
|
|
292
302
|
host: string;
|
|
293
303
|
name: string;
|
|
294
304
|
port: number;
|
package/docs/assets/hierarchy.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
window.hierarchyData = "
|
|
1
|
+
window.hierarchyData = "eJyVk8FOAjEQht9lzgUpCwvboxATY0RNjB4Ih7I7uA3dlnS6mA3h3U3lsiK65dR2Mv/8X6fTAzhrPYFY8nQ8YZzzFQOHG425V9YQiANwzsNiZIUg4KVG18yd2qO7lYTAYKtMAYIPpwxqp0FAriUR0k2x7p9l90tfaWCnDBDgqegFee8UYJCXShcODYhlMhitjgySwajl/tAQ6qZVtcP/V/41BDxJpgEhrC2GZ0v+wyHFU1xQdHAE13TY7vusxHw7s4a8k8r4N2W1DE/0nzU6Zx31/5LGMCRZm8HqujJPe3QbbT9jnH8IYvzGk5bfXHp5b3Jb7aRXa43vypchtpaEdwp1EYHQWeOqiUiHnIWmMJ5OUsbT7HtGw+EM+7XZ4en2YfeoqJI+LyN5L4tj2pe1P8vC+kWt9TWjci7p8jwevwDboG8X"
|