@rvoh/dream 2.0.3 → 2.1.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/bin/index.js +2 -0
- package/dist/cjs/src/cli/index.js +12 -12
- package/dist/cjs/src/db/helpers/dreamSchemaTypesFilenameForConnection.js +3 -0
- package/dist/cjs/src/db/helpers/syncDbTypesFiles.js +2 -89
- package/dist/cjs/src/dream/QueryDriver/Base.js +2 -2
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +9 -19
- package/dist/cjs/src/dream/QueryDriver/Postgres.js +2 -2
- package/dist/cjs/src/dream-app/index.js +15 -0
- package/dist/cjs/src/helpers/cli/ASTBuilder.js +277 -0
- package/dist/cjs/src/helpers/cli/ASTConnectionBuilder.js +284 -0
- package/dist/cjs/src/helpers/cli/ASTGlobalSchemaBuilder.js +57 -0
- package/dist/cjs/src/helpers/cli/ASTKyselyCodegenEnhancer.js +236 -0
- package/dist/cjs/src/helpers/cli/ASTSchemaBuilder.js +304 -0
- package/dist/cjs/src/helpers/cli/DBClassDeprecation.js +60 -0
- package/dist/esm/src/bin/index.js +2 -0
- package/dist/esm/src/cli/index.js +12 -12
- package/dist/esm/src/db/helpers/dreamSchemaTypesFilenameForConnection.js +3 -0
- package/dist/esm/src/db/helpers/syncDbTypesFiles.js +2 -89
- package/dist/esm/src/dream/QueryDriver/Base.js +2 -2
- package/dist/esm/src/dream/QueryDriver/Kysely.js +9 -19
- package/dist/esm/src/dream/QueryDriver/Postgres.js +2 -2
- package/dist/esm/src/dream-app/index.js +15 -0
- package/dist/esm/src/helpers/cli/ASTBuilder.js +277 -0
- package/dist/esm/src/helpers/cli/ASTConnectionBuilder.js +284 -0
- package/dist/esm/src/helpers/cli/ASTGlobalSchemaBuilder.js +57 -0
- package/dist/esm/src/helpers/cli/ASTKyselyCodegenEnhancer.js +236 -0
- package/dist/esm/src/helpers/cli/ASTSchemaBuilder.js +304 -0
- package/dist/esm/src/helpers/cli/DBClassDeprecation.js +60 -0
- package/dist/types/src/db/helpers/dreamSchemaTypesFilenameForConnection.d.ts +1 -0
- package/dist/types/src/dream/QueryDriver/Base.d.ts +3 -3
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +2 -14
- package/dist/types/src/dream/QueryDriver/Postgres.d.ts +2 -2
- package/dist/types/src/dream-app/index.d.ts +12 -2
- package/dist/types/src/helpers/cli/ASTBuilder.d.ts +159 -0
- package/dist/types/src/helpers/cli/ASTConnectionBuilder.d.ts +104 -0
- package/dist/types/src/helpers/cli/ASTGlobalSchemaBuilder.d.ts +28 -0
- package/dist/types/src/helpers/cli/ASTKyselyCodegenEnhancer.d.ts +68 -0
- package/dist/types/src/helpers/cli/ASTSchemaBuilder.d.ts +80 -0
- package/dist/types/src/helpers/cli/DBClassDeprecation.d.ts +14 -0
- package/docs/assets/search.js +1 -1
- package/docs/classes/db.DreamMigrationHelpers.html +9 -9
- package/docs/classes/db.KyselyQueryDriver.html +31 -44
- package/docs/classes/db.PostgresQueryDriver.html +32 -45
- package/docs/classes/db.QueryDriverBase.html +30 -30
- 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.GlobalNameNotSet.html +3 -3
- package/docs/classes/errors.InvalidCalendarDate.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.ValidationError.html +3 -3
- package/docs/classes/index.CalendarDate.html +2 -2
- package/docs/classes/index.Decorators.html +19 -19
- package/docs/classes/index.Dream.html +113 -113
- package/docs/classes/index.DreamApp.html +7 -6
- package/docs/classes/index.DreamTransaction.html +2 -2
- package/docs/classes/index.Env.html +2 -2
- package/docs/classes/index.Query.html +53 -53
- package/docs/classes/system.CliFileWriter.html +2 -2
- package/docs/classes/system.DreamBin.html +2 -2
- package/docs/classes/system.DreamCLI.html +5 -5
- package/docs/classes/system.DreamImporter.html +2 -2
- package/docs/classes/system.DreamLogos.html +2 -2
- package/docs/classes/system.DreamSerializerBuilder.html +8 -8
- package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
- package/docs/classes/utils.Encrypt.html +2 -2
- 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-1.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/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.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/modules/db.html +1 -1
- package/docs/modules/errors.html +1 -1
- package/docs/modules/index.html +1 -1
- package/docs/modules/openapi.html +1 -1
- package/docs/modules/system.html +1 -1
- package/docs/modules/types.html +1 -1
- package/docs/modules/utils.html +1 -1
- package/docs/types/index.DateTime.html +1 -1
- 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 +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionRef.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
- package/docs/types/openapi.OpenapiSchemaNull.html +1 -1
- 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 +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
- 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.Camelized.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 +1 -1
- 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.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.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 +1 -1
- package/docs/types/types.ViewModelClass.html +1 -1
- package/docs/types/types.WhereStatementForDream.html +1 -1
- package/docs/types/types.WhereStatementForDreamClass.html +1 -1
- package/docs/variables/index.DateTime-1.html +1 -1
- package/docs/variables/index.DreamConst.html +1 -1
- package/docs/variables/index.ops.html +1 -1
- package/docs/variables/openapi.openapiPrimitiveTypes-1.html +1 -1
- package/docs/variables/openapi.openapiShorthandPrimitiveTypes-1.html +1 -1
- package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
- package/docs/variables/types.TRIGRAM_OPERATORS.html +1 -1
- package/docs/variables/types.primaryKeyTypes.html +1 -1
- package/package.json +1 -1
- package/dist/cjs/src/helpers/cli/SchemaBuilder.js +0 -408
- package/dist/esm/src/helpers/cli/SchemaBuilder.js +0 -408
- package/dist/types/src/helpers/cli/SchemaBuilder.d.ts +0 -44
|
@@ -34,8 +34,9 @@ import NotNullViolation from '../../errors/db/NotNullViolation.js';
|
|
|
34
34
|
import UnexpectedUndefined from '../../errors/UnexpectedUndefined.js';
|
|
35
35
|
import CalendarDate from '../../helpers/CalendarDate.js';
|
|
36
36
|
import camelize from '../../helpers/camelize.js';
|
|
37
|
+
import ASTGlobalSchemaBuilder from '../../helpers/cli/ASTGlobalSchemaBuilder.js';
|
|
38
|
+
import ASTSchemaBuilder from '../../helpers/cli/ASTSchemaBuilder.js';
|
|
37
39
|
import generateMigration from '../../helpers/cli/generateMigration.js';
|
|
38
|
-
import SchemaBuilder from '../../helpers/cli/SchemaBuilder.js';
|
|
39
40
|
import compact from '../../helpers/compact.js';
|
|
40
41
|
import { DateTime } from '../../helpers/DateTime.js';
|
|
41
42
|
import EnvInternal from '../../helpers/EnvInternal.js';
|
|
@@ -158,31 +159,20 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
158
159
|
/**
|
|
159
160
|
* defines the syncing behavior for dream and psychic,
|
|
160
161
|
* which is run whenever the `sync` command is called.
|
|
161
|
-
*
|
|
162
|
-
* comlpex to override. You will need to do the following
|
|
163
|
-
* when overriding this method:
|
|
164
|
-
*
|
|
165
|
-
* 1. introspect the db and use it to generate a db.ts file in the
|
|
166
|
-
* same shape as the existing one. Currently, the process for generating
|
|
167
|
-
* this file is extremely complex and messy, and will be difficult
|
|
168
|
-
* to achieve.
|
|
169
|
-
* 2. generate a types/dream.ts file in the same shape as the existing
|
|
170
|
-
* one. This is normally done using `await new SchemaBuilder().build()`,
|
|
171
|
-
* but this will likely need to be overridden to tailor to your custom
|
|
172
|
-
* database engine.
|
|
173
|
-
*/
|
|
162
|
+
* */
|
|
174
163
|
static async sync(connectionName, onSync, options = {}) {
|
|
175
164
|
try {
|
|
176
165
|
if (!options?.schemaOnly) {
|
|
177
166
|
await DreamCLI.logger.logProgress(`introspecting db for connection: ${connectionName}...`, async () => {
|
|
167
|
+
// this calls kysely-codegen under the hood
|
|
178
168
|
await syncDbTypesFiles(connectionName);
|
|
179
169
|
});
|
|
180
170
|
}
|
|
181
|
-
const
|
|
171
|
+
const newSchemaBuilder = new ASTSchemaBuilder(connectionName);
|
|
182
172
|
await DreamCLI.logger.logProgress(`building dream schema for connection ${connectionName}...`, async () => {
|
|
183
|
-
await
|
|
173
|
+
await newSchemaBuilder.build();
|
|
184
174
|
});
|
|
185
|
-
if (
|
|
175
|
+
if (newSchemaBuilder.hasForeignKeyError && !options?.schemaOnly) {
|
|
186
176
|
await DreamCLI.logger.logProgress('triggering resync to correct for foreign key errors...', async () => {
|
|
187
177
|
// TODO: make this customizable to enable dream apps to separate
|
|
188
178
|
const cliCmd = EnvInternal.boolean('DREAM_CORE_DEVELOPMENT') ? 'dream' : 'psy';
|
|
@@ -196,7 +186,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
196
186
|
});
|
|
197
187
|
});
|
|
198
188
|
}
|
|
199
|
-
await
|
|
189
|
+
await new ASTGlobalSchemaBuilder().build();
|
|
200
190
|
if (!options?.schemaOnly) {
|
|
201
191
|
// intentionally leaving logs off here, since it allows other
|
|
202
192
|
// onSync handlers to determine their own independent logging approach
|
|
@@ -205,7 +195,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
205
195
|
}
|
|
206
196
|
catch (error) {
|
|
207
197
|
console.error(error);
|
|
208
|
-
await DreamCLI.logger.logProgress('sync failed, reverting file contents...', async () => {
|
|
198
|
+
await DreamCLI.logger.logProgress('[dream] sync failed, reverting file contents...', async () => {
|
|
209
199
|
await CliFileWriter.revert();
|
|
210
200
|
});
|
|
211
201
|
}
|
|
@@ -111,7 +111,7 @@ export default class PostgresQueryDriver extends KyselyQueryDriver {
|
|
|
111
111
|
* this is used by the SchemaBuilder to store column data permanently
|
|
112
112
|
* within the types/dream.ts file.
|
|
113
113
|
*/
|
|
114
|
-
static async getColumnData(connectionName, tableName,
|
|
114
|
+
static async getColumnData(connectionName, tableName, allTableAssociationData) {
|
|
115
115
|
const db = this.dbFor(connectionName, 'primary');
|
|
116
116
|
const sqlQuery = sql `SELECT column_name, udt_name::regtype, is_nullable, data_type FROM information_schema.columns WHERE table_name = ${tableName}`;
|
|
117
117
|
const columnToDBTypeMap = await sqlQuery.execute(db);
|
|
@@ -120,7 +120,7 @@ export default class PostgresQueryDriver extends KyselyQueryDriver {
|
|
|
120
120
|
rows.forEach(row => {
|
|
121
121
|
const isEnum = ['USER-DEFINED', 'ARRAY'].includes(row.dataType) && !isPrimitiveDataType(row.udtName);
|
|
122
122
|
const isArray = ['ARRAY'].includes(row.dataType);
|
|
123
|
-
const associationMetadata =
|
|
123
|
+
const associationMetadata = allTableAssociationData[row.columnName];
|
|
124
124
|
columnData[camelize(row.columnName)] = {
|
|
125
125
|
dbType: row.udtName,
|
|
126
126
|
allowNull: row.isNullable === 'YES',
|
|
@@ -6,6 +6,7 @@ import Encrypt from '../encrypt/index.js';
|
|
|
6
6
|
import DreamAppInitMissingCallToLoadModels from '../errors/dream-app/DreamAppInitMissingCallToLoadModels.js';
|
|
7
7
|
import DreamAppInitMissingMissingProjectRoot from '../errors/dream-app/DreamAppInitMissingMissingProjectRoot.js';
|
|
8
8
|
import CalendarDate from '../helpers/CalendarDate.js';
|
|
9
|
+
import autogeneratedFileDisclaimer from '../helpers/cli/autoGeneratedFileDisclaimer.js';
|
|
9
10
|
import { DateTime, Settings } from '../helpers/DateTime.js';
|
|
10
11
|
import EnvInternal from '../helpers/EnvInternal.js';
|
|
11
12
|
import globalClassNameFromFullyQualifiedModelName from '../helpers/globalClassNameFromFullyQualifiedModelName.js';
|
|
@@ -92,6 +93,7 @@ export default class DreamApp {
|
|
|
92
93
|
inferSerializersFromDreamClassOrViewModelClass,
|
|
93
94
|
isDreamSerializer,
|
|
94
95
|
serializerNameFromFullyQualifiedModelName,
|
|
96
|
+
autogeneratedFileDisclaimer,
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
99
|
/**
|
|
@@ -233,6 +235,16 @@ Try setting it to something valid, like:
|
|
|
233
235
|
get importExtension() {
|
|
234
236
|
return this._importExtension;
|
|
235
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* if set to true, it will bypass deprecation checks that run
|
|
240
|
+
* during the sync hook. Defaults to false, we only recommend
|
|
241
|
+
* overriding this if you are having issues with the deprecation
|
|
242
|
+
* check.
|
|
243
|
+
*/
|
|
244
|
+
_bypassDeprecationChecks = false;
|
|
245
|
+
get bypassDeprecationChecks() {
|
|
246
|
+
return this._bypassDeprecationChecks;
|
|
247
|
+
}
|
|
236
248
|
loadedModels = false;
|
|
237
249
|
constructor(opts) {
|
|
238
250
|
if (opts?.db)
|
|
@@ -317,6 +329,9 @@ Try setting it to something valid, like:
|
|
|
317
329
|
}
|
|
318
330
|
set(applyOption, options, secondaryOptions) {
|
|
319
331
|
switch (applyOption) {
|
|
332
|
+
case 'bypassDeprecationChecks':
|
|
333
|
+
this._bypassDeprecationChecks = options;
|
|
334
|
+
break;
|
|
320
335
|
case 'db':
|
|
321
336
|
if (typeof options === 'string') {
|
|
322
337
|
this._dbCredentials[options] = secondaryOptions;
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import DreamApp from '../../dream-app/index.js';
|
|
4
|
+
import EnvInternal from '../EnvInternal.js';
|
|
5
|
+
const f = ts.factory;
|
|
6
|
+
/**
|
|
7
|
+
* @internal
|
|
8
|
+
*
|
|
9
|
+
* This is a base class, which is inherited by the ASTSchemaBuilder,
|
|
10
|
+
* the ASTKyselyCodegenEnhancer, and the ASTGlobalSchemaBuilder,
|
|
11
|
+
* each of which is responsible for building up the output of the various
|
|
12
|
+
* type files consumed by dream internally.
|
|
13
|
+
*
|
|
14
|
+
* This base class is just a container for common methods used by all
|
|
15
|
+
* classes.
|
|
16
|
+
*/
|
|
17
|
+
export default class ASTBuilder {
|
|
18
|
+
/**
|
|
19
|
+
* @internal
|
|
20
|
+
*
|
|
21
|
+
* builds a new line, useful for injecting new lines into AST statements
|
|
22
|
+
*/
|
|
23
|
+
newLine() {
|
|
24
|
+
return f.createIdentifier('\n');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @internal
|
|
28
|
+
*
|
|
29
|
+
* given an interface declaration, it will extrace the relevant property statement
|
|
30
|
+
* by the given property name.
|
|
31
|
+
*/
|
|
32
|
+
getPropertyFromInterface(interfaceNode, propertyName) {
|
|
33
|
+
for (const member of interfaceNode.members) {
|
|
34
|
+
if (ts.isPropertySignature(member)) {
|
|
35
|
+
if (ts.isIdentifier(member.name) && member.name.text === propertyName) {
|
|
36
|
+
return member;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* @internal
|
|
44
|
+
*
|
|
45
|
+
* returns an array of string type literals which were extracted from
|
|
46
|
+
* either a type or type union, depending on what is provided
|
|
47
|
+
* for the typeAlias. this allows you to safely and easily collect
|
|
48
|
+
* an array of types given an alias
|
|
49
|
+
*/
|
|
50
|
+
extractStringLiteralTypeNodesFromTypeOrUnion(typeAlias) {
|
|
51
|
+
const literals = [];
|
|
52
|
+
if (ts.isUnionTypeNode(typeAlias.type)) {
|
|
53
|
+
typeAlias.type.types.forEach(typeNode => {
|
|
54
|
+
if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal)) {
|
|
55
|
+
literals.push(typeNode);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else if (ts.isLiteralTypeNode(typeAlias.type) && ts.isStringLiteral(typeAlias.type.literal)) {
|
|
60
|
+
literals.push(typeAlias.type);
|
|
61
|
+
}
|
|
62
|
+
return literals;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* @internal
|
|
66
|
+
*
|
|
67
|
+
* returns an array of type literals which were extracted from
|
|
68
|
+
* either a type or type union, depending on what is provided
|
|
69
|
+
* for the typeAlias. this allows you to safely and easily collect
|
|
70
|
+
* an array of types given an alias
|
|
71
|
+
*/
|
|
72
|
+
extractTypeNodesFromTypeOrUnion(typeAlias) {
|
|
73
|
+
const literals = [];
|
|
74
|
+
if (typeAlias.type && ts.isUnionTypeNode(typeAlias.type)) {
|
|
75
|
+
typeAlias.type.types.forEach(typeNode => {
|
|
76
|
+
literals.push(typeNode);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else if (typeAlias.type) {
|
|
80
|
+
literals.push(typeAlias.type);
|
|
81
|
+
}
|
|
82
|
+
return literals;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* @internal
|
|
86
|
+
*
|
|
87
|
+
* returns the provided node iff
|
|
88
|
+
* a.) the node is an exported type alias
|
|
89
|
+
* b.) the exported name matches the provided name (or else there was no name provided)
|
|
90
|
+
*
|
|
91
|
+
* otherwise, returns null
|
|
92
|
+
*/
|
|
93
|
+
exportedTypeAliasOrNull(node, exportName) {
|
|
94
|
+
if (ts.isTypeAliasDeclaration(node) &&
|
|
95
|
+
node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
|
|
96
|
+
(!exportName ? true : node.name.text === exportName))
|
|
97
|
+
return node;
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* @internal
|
|
102
|
+
*
|
|
103
|
+
* returns the provided node iff
|
|
104
|
+
* a.) the node is an exported interface
|
|
105
|
+
* b.) the exported name matches the provided name (or else there was no name provided)
|
|
106
|
+
*
|
|
107
|
+
* otherwise, returns null
|
|
108
|
+
*/
|
|
109
|
+
exportedInterfaceOrNull(node, exportName) {
|
|
110
|
+
if (ts.isInterfaceDeclaration(node) &&
|
|
111
|
+
node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
|
|
112
|
+
(!exportName ? true : node.name.text === exportName))
|
|
113
|
+
return node;
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* @internal
|
|
118
|
+
*
|
|
119
|
+
* extracts the exportName from the provided dbSourceFile
|
|
120
|
+
*/
|
|
121
|
+
findDbExport(dbSourceFile, exportName) {
|
|
122
|
+
let foundNode;
|
|
123
|
+
ts.forEachChild(dbSourceFile, node => {
|
|
124
|
+
const hasModifiers = ts.isFunctionDeclaration(node) ||
|
|
125
|
+
ts.isClassDeclaration(node) ||
|
|
126
|
+
ts.isInterfaceDeclaration(node) ||
|
|
127
|
+
ts.isVariableStatement(node) ||
|
|
128
|
+
ts.isTypeAliasDeclaration(node);
|
|
129
|
+
if (hasModifiers) {
|
|
130
|
+
const isExported = node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword);
|
|
131
|
+
if (isExported && ts.isVariableStatement(node)) {
|
|
132
|
+
const declarations = node.declarationList.declarations;
|
|
133
|
+
const name = declarations?.[0]?.name;
|
|
134
|
+
if (declarations.length > 0 && ts.isIdentifier(name) && name?.text === exportName) {
|
|
135
|
+
foundNode = node;
|
|
136
|
+
return true; // Stop traversal
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const declarationWithName = node;
|
|
140
|
+
if (isExported &&
|
|
141
|
+
declarationWithName.name &&
|
|
142
|
+
ts.isIdentifier(declarationWithName.name) &&
|
|
143
|
+
declarationWithName.name.text === exportName) {
|
|
144
|
+
foundNode = node;
|
|
145
|
+
return true; // Stop traversal
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return foundNode;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* @internal
|
|
153
|
+
*
|
|
154
|
+
* returns the path to the dream.globals.ts file
|
|
155
|
+
*/
|
|
156
|
+
globalSchemaPath() {
|
|
157
|
+
const dreamApp = DreamApp.getOrFail();
|
|
158
|
+
return path.join(dreamApp.projectRoot, dreamApp.paths.types, 'dream.globals.ts');
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* @internal
|
|
162
|
+
*
|
|
163
|
+
* safely runs prettier against the provided output. If prettier
|
|
164
|
+
* is not installed, then the original output is returned
|
|
165
|
+
*/
|
|
166
|
+
async prettier(output) {
|
|
167
|
+
try {
|
|
168
|
+
// dynamically, safely bring in prettier.
|
|
169
|
+
// ini the event that it fails, we will return the
|
|
170
|
+
// original output, unformatted, since prettier
|
|
171
|
+
// is technically not a real dependency of dream,
|
|
172
|
+
// though psychic and dream apps are provisioned
|
|
173
|
+
// with prettier by default, so this should usually work
|
|
174
|
+
const prettier = (await import('prettier')).default;
|
|
175
|
+
const results = await prettier.format(output, {
|
|
176
|
+
parser: 'typescript',
|
|
177
|
+
semi: false,
|
|
178
|
+
singleQuote: true,
|
|
179
|
+
tabWidth: 2,
|
|
180
|
+
lineWidth: 80,
|
|
181
|
+
});
|
|
182
|
+
return typeof results === 'string' ? results : output;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// intentional noop, we don't want to raise if prettier
|
|
186
|
+
// fails, since it is possible for the end user to not
|
|
187
|
+
// want to use prettier, and it is not a required peer
|
|
188
|
+
// dependency of dream
|
|
189
|
+
return output;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* @internal
|
|
194
|
+
*
|
|
195
|
+
* given a type node, it will send back the first found generic
|
|
196
|
+
* provided to that type.
|
|
197
|
+
*/
|
|
198
|
+
getFirstGenericType(node) {
|
|
199
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
200
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
201
|
+
return node.typeArguments[0];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else if (ts.isCallExpression(node)) {
|
|
205
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
206
|
+
return node.typeArguments[0];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* @internal
|
|
213
|
+
*
|
|
214
|
+
* returns the DateTime and CalendarDate imports. This is fairly
|
|
215
|
+
* tricky, since it considers whether or not we are in the dream
|
|
216
|
+
* internals (i.e. when testing dream). If we are, it will return
|
|
217
|
+
* valid internal import paths to those files. Otherwise, it will
|
|
218
|
+
* import them both from @rvoh/dream.
|
|
219
|
+
*/
|
|
220
|
+
dateAndDateTimeImports() {
|
|
221
|
+
if (EnvInternal.boolean('DREAM_CORE_DEVELOPMENT')) {
|
|
222
|
+
const calendarImport = ts.factory.createImportClause(true, f.createIdentifier('CalendarDate'), undefined);
|
|
223
|
+
const calendarImportDeclaration = ts.factory.createImportDeclaration(undefined, calendarImport, ts.factory.createStringLiteral('../../src/helpers/CalendarDate.js'));
|
|
224
|
+
const dateTimeNamedImports = ts.factory.createNamedImports([
|
|
225
|
+
f.createImportSpecifier(true, undefined, ts.factory.createIdentifier('DateTime')),
|
|
226
|
+
]);
|
|
227
|
+
const dateTimeImportClause = ts.factory.createImportClause(false, // isTypeOnly: false for the clause itself if not all imports are type only
|
|
228
|
+
undefined, // name: undefined for default import
|
|
229
|
+
dateTimeNamedImports // namedBindings
|
|
230
|
+
);
|
|
231
|
+
const dateTimeImportDeclaration = ts.factory.createImportDeclaration(undefined, dateTimeImportClause, ts.factory.createStringLiteral('../../src/helpers/DateTime.js'));
|
|
232
|
+
return [calendarImportDeclaration, dateTimeImportDeclaration];
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
const namedImports = ts.factory.createNamedImports(['CalendarDate', 'DateTime'].map(importName => f.createImportSpecifier(true, undefined, ts.factory.createIdentifier(importName))));
|
|
236
|
+
const importClause = ts.factory.createImportClause(false, // isTypeOnly: false for the clause itself if not all imports are type only
|
|
237
|
+
undefined, // name: undefined for default import
|
|
238
|
+
namedImports // namedBindings
|
|
239
|
+
);
|
|
240
|
+
const importDeclaration = ts.factory.createImportDeclaration(undefined, // modifiers: e.g., 'export' or 'declare'
|
|
241
|
+
importClause, ts.factory.createStringLiteral('@rvoh/dream'));
|
|
242
|
+
return [importDeclaration];
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* @internal
|
|
247
|
+
*
|
|
248
|
+
* for a given table name (i.e. balloon_lines), it will return the exported
|
|
249
|
+
* `BalloonLines` interface within the dbSourceFile
|
|
250
|
+
*/
|
|
251
|
+
getTableInterfaceDeclaration(dbSourceFile, tableName) {
|
|
252
|
+
const DB = this.findDbExport(dbSourceFile, 'DB');
|
|
253
|
+
let targetProperty = null;
|
|
254
|
+
for (const member of DB.members) {
|
|
255
|
+
if (ts.isPropertySignature(member) && member.name.getText(dbSourceFile) === tableName) {
|
|
256
|
+
targetProperty = member;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const tableInterfaceName = (targetProperty?.type)
|
|
261
|
+
.typeName?.escapedText;
|
|
262
|
+
if (!tableInterfaceName)
|
|
263
|
+
throw new Error(`failed to find table interface for table: ${tableName}`);
|
|
264
|
+
const tableInterface = this.findDbExport(dbSourceFile, tableInterfaceName);
|
|
265
|
+
return tableInterface;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* @internal
|
|
269
|
+
*
|
|
270
|
+
* returns an array of global names for all serializers in the app
|
|
271
|
+
*/
|
|
272
|
+
globalSerializerNames() {
|
|
273
|
+
const dreamApp = DreamApp.getOrFail();
|
|
274
|
+
const serializers = dreamApp.serializers;
|
|
275
|
+
return Object.keys(serializers);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import dbTypesFilenameForConnection from '../../db/helpers/dbTypesFilenameForConnection.js';
|
|
5
|
+
import dreamSchemaTypesFilenameForConnection from '../../db/helpers/dreamSchemaTypesFilenameForConnection.js';
|
|
6
|
+
import DreamApp from '../../dream-app/index.js';
|
|
7
|
+
import Query from '../../dream/Query.js';
|
|
8
|
+
import { ExplicitForeignKeyRequired, InvalidComputedForeignKey, } from '../../errors/associations/InvalidComputedForeignKey.js';
|
|
9
|
+
import FailedToIdentifyAssociation from '../../errors/schema-builder/FailedToIdentifyAssociation.js';
|
|
10
|
+
import intersection from '../intersection.js';
|
|
11
|
+
import sortBy from '../sortBy.js';
|
|
12
|
+
import uniq from '../uniq.js';
|
|
13
|
+
import ASTBuilder from './ASTBuilder.js';
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*
|
|
17
|
+
* This is a base class, which is inherited by the ASTSchemaBuilder and
|
|
18
|
+
* the ASTKyselyCodegenEnhancer, both of which is responsible for building
|
|
19
|
+
* up the output of the various type files consumed by dream internally.
|
|
20
|
+
*
|
|
21
|
+
* This base class is just a container for common methods used by both
|
|
22
|
+
* classes. It requires a connectionName to be provided, unlike the underlying
|
|
23
|
+
* ASTBuilder class, and provides methods which leverage the connectionName
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
export default class ASTConnectionBuilder extends ASTBuilder {
|
|
27
|
+
connectionName;
|
|
28
|
+
hasForeignKeyError = false;
|
|
29
|
+
constructor(connectionName) {
|
|
30
|
+
super();
|
|
31
|
+
this.connectionName = connectionName;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @internal
|
|
35
|
+
*
|
|
36
|
+
* returns the path from project root to the dream.ts file
|
|
37
|
+
* for the particular connection. If the connectionName is anything
|
|
38
|
+
* other than default, the path will represent that by injecting
|
|
39
|
+
* the connectionName into the file name, i.e. dream.alternate.ts
|
|
40
|
+
*/
|
|
41
|
+
schemaPath() {
|
|
42
|
+
const dreamApp = DreamApp.getOrFail();
|
|
43
|
+
return path.join(dreamApp.projectRoot, dreamApp.paths.types, dreamSchemaTypesFilenameForConnection(this.connectionName));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* @internal
|
|
47
|
+
*
|
|
48
|
+
* returns the path from project root to the db.ts file
|
|
49
|
+
* for the particular connection. If the connectionName is anything
|
|
50
|
+
* other than default, the path will represent that by injecting
|
|
51
|
+
* the connectionName into the file name, i.e. db.alternate.ts
|
|
52
|
+
*/
|
|
53
|
+
dbPath() {
|
|
54
|
+
const dreamApp = DreamApp.getOrFail();
|
|
55
|
+
return path.join(dreamApp.projectRoot, dreamApp.paths.types, dbTypesFilenameForConnection(this.connectionName));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* @internal
|
|
59
|
+
*
|
|
60
|
+
* returns the db source file for the given connectionName, injecting
|
|
61
|
+
* the source file with the actual file contents, so that AST nodes
|
|
62
|
+
* can be built through ingesting.
|
|
63
|
+
*/
|
|
64
|
+
async getDbSourceFile() {
|
|
65
|
+
const fileContent = await this.loadDbSyncFile();
|
|
66
|
+
return ts.createSourceFile('./db.js', fileContent, ts.ScriptTarget.Latest, true);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* @internal
|
|
70
|
+
*
|
|
71
|
+
* reads the db source file for the given connection, returning the contents
|
|
72
|
+
* as a raw string
|
|
73
|
+
*/
|
|
74
|
+
async loadDbSyncFile() {
|
|
75
|
+
return (await fs.readFile(this.dbPath())).toString();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* @internal
|
|
79
|
+
*
|
|
80
|
+
* builds up the schema data for every table into an object, which
|
|
81
|
+
* can be read and injected into AST nodes.
|
|
82
|
+
*/
|
|
83
|
+
async getSchemaData() {
|
|
84
|
+
const tables = await this.getTables();
|
|
85
|
+
const schemaData = {};
|
|
86
|
+
for (const table of tables) {
|
|
87
|
+
schemaData[table] = await this.tableData(table);
|
|
88
|
+
}
|
|
89
|
+
return schemaData;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* @internal
|
|
93
|
+
*
|
|
94
|
+
* used by getSchemaData to build up all table data
|
|
95
|
+
*/
|
|
96
|
+
async getTables() {
|
|
97
|
+
const fileContents = await this.loadDbSyncFile();
|
|
98
|
+
const tableLines = /export interface DB {([^}]*)}/.exec(fileContents)[1];
|
|
99
|
+
if (tableLines === undefined)
|
|
100
|
+
return [];
|
|
101
|
+
const tables = tableLines
|
|
102
|
+
.split('\n')
|
|
103
|
+
.map(line => {
|
|
104
|
+
const stingArray = line.split(':');
|
|
105
|
+
const substring = stingArray[0];
|
|
106
|
+
if (substring === undefined)
|
|
107
|
+
return '';
|
|
108
|
+
return substring.replace(/\s*/, '');
|
|
109
|
+
})
|
|
110
|
+
.filter(line => !!line);
|
|
111
|
+
return tables;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* @internal
|
|
115
|
+
*
|
|
116
|
+
* finds all enums used by the app, and returns information
|
|
117
|
+
* about those enums that can be used for type generating purpposes
|
|
118
|
+
*/
|
|
119
|
+
async getAllEnumValueNames() {
|
|
120
|
+
const schemaData = await this.getSchemaData();
|
|
121
|
+
const enumValueNames = Object.values(schemaData)
|
|
122
|
+
.map(tableData => Object.keys(tableData.columns)
|
|
123
|
+
.filter(columnName => !!tableData.columns[columnName]?.enumValues)
|
|
124
|
+
.map(columnName => ({
|
|
125
|
+
enumValues: tableData.columns[columnName].enumValues,
|
|
126
|
+
enumType: tableData.columns[columnName].enumType,
|
|
127
|
+
})))
|
|
128
|
+
.flat();
|
|
129
|
+
return enumValueNames;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* @internal
|
|
133
|
+
*
|
|
134
|
+
* returns a tuple, where the first value is the global name, and the second value
|
|
135
|
+
* is the table that that global name points to. Used to build up our global
|
|
136
|
+
* model name exports within type files.
|
|
137
|
+
*/
|
|
138
|
+
globalModelNames() {
|
|
139
|
+
const dreamApp = DreamApp.getOrFail();
|
|
140
|
+
const models = dreamApp.models;
|
|
141
|
+
return Object.keys(models)
|
|
142
|
+
.filter(key => models[key]?.prototype?.connectionName === this.connectionName)
|
|
143
|
+
.map(key => [key, models[key].prototype.table]);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* @internal
|
|
147
|
+
*
|
|
148
|
+
* retrieves useful association data for a given association and table, which
|
|
149
|
+
* can be used to build up types
|
|
150
|
+
*/
|
|
151
|
+
getAssociationData(tableName, targetAssociationType) {
|
|
152
|
+
const dreamApp = DreamApp.getOrFail();
|
|
153
|
+
const models = sortBy(Object.values(dreamApp.models), m => m.table);
|
|
154
|
+
const tableAssociationData = {};
|
|
155
|
+
for (const model of models.filter(model => model.table === tableName)) {
|
|
156
|
+
for (const associationName of model.associationNames) {
|
|
157
|
+
const associationMetaData = model['associationMetadataMap']()[associationName];
|
|
158
|
+
if (associationMetaData === undefined)
|
|
159
|
+
continue;
|
|
160
|
+
if (targetAssociationType && associationMetaData.type !== targetAssociationType)
|
|
161
|
+
continue;
|
|
162
|
+
const dreamClassOrClasses = associationMetaData.modelCB();
|
|
163
|
+
if (!dreamClassOrClasses)
|
|
164
|
+
throw new FailedToIdentifyAssociation(model, associationMetaData.type, associationName, associationMetaData.globalAssociationNameOrNames);
|
|
165
|
+
const optional = associationMetaData.type === 'BelongsTo' ? associationMetaData.optional === true : null;
|
|
166
|
+
const where = associationMetaData.type === 'HasMany' || associationMetaData.type === 'HasOne'
|
|
167
|
+
? associationMetaData.and || null
|
|
168
|
+
: null;
|
|
169
|
+
// NOTE
|
|
170
|
+
// this try-catch is here because the ASTSchemaBuilder currently needs to be run twice to generate foreignKey
|
|
171
|
+
// correctly. The first time will raise, since calling Dream.columns is dependant on the schema const to
|
|
172
|
+
// introspect columns during a foreign key check. This will be repaired once kysely types have been successfully
|
|
173
|
+
// split off into a separate file from the types we diliver in types/dream.ts
|
|
174
|
+
let foreignKey = null;
|
|
175
|
+
try {
|
|
176
|
+
const isThroughAssociation = associationMetaData.through;
|
|
177
|
+
if (!isThroughAssociation) {
|
|
178
|
+
const _foreignKey = associationMetaData.foreignKey();
|
|
179
|
+
foreignKey = _foreignKey;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
this.hasForeignKeyError = true;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
tableAssociationData[associationName] ||= {
|
|
187
|
+
tables: [],
|
|
188
|
+
type: associationMetaData.type,
|
|
189
|
+
polymorphic: associationMetaData.polymorphic,
|
|
190
|
+
foreignKey,
|
|
191
|
+
foreignKeyTypeColumn: associationMetaData.polymorphic
|
|
192
|
+
? associationMetaData?.foreignKeyTypeField?.() || null
|
|
193
|
+
: null,
|
|
194
|
+
optional,
|
|
195
|
+
and: where,
|
|
196
|
+
};
|
|
197
|
+
if (foreignKey)
|
|
198
|
+
tableAssociationData[associationName]['foreignKey'] = foreignKey;
|
|
199
|
+
if (Array.isArray(dreamClassOrClasses)) {
|
|
200
|
+
const tables = dreamClassOrClasses.map(dreamClass => dreamClass.table);
|
|
201
|
+
tableAssociationData[associationName].tables = [
|
|
202
|
+
...tableAssociationData[associationName].tables,
|
|
203
|
+
...tables,
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
tableAssociationData[associationName].tables.push(dreamClassOrClasses.table);
|
|
208
|
+
}
|
|
209
|
+
// guarantee unique
|
|
210
|
+
tableAssociationData[associationName].tables = [
|
|
211
|
+
...new Set(tableAssociationData[associationName].tables),
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (!(error instanceof ExplicitForeignKeyRequired || error instanceof InvalidComputedForeignKey))
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return Object.keys(tableAssociationData)
|
|
221
|
+
.sort()
|
|
222
|
+
.reduce((acc, key) => {
|
|
223
|
+
if (tableAssociationData[key] === undefined)
|
|
224
|
+
return acc;
|
|
225
|
+
acc[key] = tableAssociationData[key];
|
|
226
|
+
return acc;
|
|
227
|
+
}, {});
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* @internal
|
|
231
|
+
*
|
|
232
|
+
* retrieves the table data for an individual table.
|
|
233
|
+
* Can be used to build up types
|
|
234
|
+
*/
|
|
235
|
+
async tableData(tableName) {
|
|
236
|
+
const dreamApp = DreamApp.getOrFail();
|
|
237
|
+
const models = Object.values(dreamApp.models).filter(model => model.table === tableName);
|
|
238
|
+
const maybeModel = models[0];
|
|
239
|
+
if (!maybeModel)
|
|
240
|
+
throw new Error(`
|
|
241
|
+
Could not find a Dream model with table "${tableName}".
|
|
242
|
+
|
|
243
|
+
If you recently changed the name of a table in a migration, you
|
|
244
|
+
may need to update the table getter in the corresponding Dream.
|
|
245
|
+
`);
|
|
246
|
+
const baseModel = maybeModel['stiBaseClassOrOwnClass'];
|
|
247
|
+
const associationData = this.getAssociationData(tableName);
|
|
248
|
+
const allStiChildren = models.filter(model => model['isSTIChild']);
|
|
249
|
+
const modelsToCheck = allStiChildren.length ? allStiChildren : [baseModel];
|
|
250
|
+
// If a table is STI, then we look only at the serializers attached to
|
|
251
|
+
// all STI children (not the STI base model because the base model may not have any serializers)
|
|
252
|
+
const eachModelSerializerKeys = modelsToCheck.map(model => {
|
|
253
|
+
let serializers = {};
|
|
254
|
+
try {
|
|
255
|
+
serializers = model?.prototype?.['serializers'] || {};
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// no-op
|
|
259
|
+
}
|
|
260
|
+
return Object.keys(serializers);
|
|
261
|
+
});
|
|
262
|
+
const serializerKeys = intersection(...eachModelSerializerKeys).sort();
|
|
263
|
+
return {
|
|
264
|
+
scopes: {
|
|
265
|
+
default: uniq(models.flatMap(model => model['scopes'].default.map(scopeStatement => scopeStatement.method))),
|
|
266
|
+
named: uniq(models.flatMap(model => model['scopes'].named.map(scopeStatement => scopeStatement.method))),
|
|
267
|
+
},
|
|
268
|
+
columns: await this.getColumnData(tableName, associationData),
|
|
269
|
+
virtualColumns: uniq(models.flatMap(model => model['virtualAttributes'].map(prop => prop.property) || [])),
|
|
270
|
+
associations: associationData,
|
|
271
|
+
serializerKeys,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* @internal
|
|
276
|
+
*
|
|
277
|
+
* retrieves the column data for an individual table and association.
|
|
278
|
+
* Can be used to build up types
|
|
279
|
+
*/
|
|
280
|
+
async getColumnData(tableName, allTableAssociationData) {
|
|
281
|
+
const dbDriverClass = Query.dbDriverClass(this.connectionName);
|
|
282
|
+
return await dbDriverClass.getColumnData(this.connectionName, tableName, allTableAssociationData);
|
|
283
|
+
}
|
|
284
|
+
}
|