@rvoh/dream 2.36.0 → 2.37.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.
Files changed (242) hide show
  1. package/dist/cjs/src/Dream.js +3 -1
  2. package/dist/cjs/src/db/dataTypes.js +0 -8
  3. package/dist/cjs/src/decorators/field/Encrypted.js +8 -3
  4. package/dist/cjs/src/dream/QueryDriver/Base.js +17 -24
  5. package/dist/cjs/src/dream/QueryDriver/Kysely.js +22 -23
  6. package/dist/cjs/src/dream/QueryDriver/Postgres.js +113 -31
  7. package/dist/cjs/src/encrypt/InternalEncrypt.js +28 -0
  8. package/dist/cjs/src/helpers/cli/ASTBuilder.js +11 -202
  9. package/dist/cjs/src/helpers/cli/ASTConnectionBuilder.js +97 -75
  10. package/dist/cjs/src/helpers/cli/ASTDbTypesBuilder.js +132 -0
  11. package/dist/cjs/src/helpers/cli/ASTSchemaBuilder.js +37 -81
  12. package/dist/cjs/src/helpers/cli/dbTypesNaming.js +90 -0
  13. package/dist/cjs/src/helpers/cli/tableMatchesPattern.js +21 -0
  14. package/dist/esm/src/Dream.js +3 -1
  15. package/dist/esm/src/db/dataTypes.js +0 -8
  16. package/dist/esm/src/decorators/field/Encrypted.js +8 -3
  17. package/dist/esm/src/dream/QueryDriver/Base.js +17 -24
  18. package/dist/esm/src/dream/QueryDriver/Kysely.js +22 -23
  19. package/dist/esm/src/dream/QueryDriver/Postgres.js +113 -31
  20. package/dist/esm/src/encrypt/InternalEncrypt.js +28 -0
  21. package/dist/esm/src/helpers/cli/ASTBuilder.js +11 -202
  22. package/dist/esm/src/helpers/cli/ASTConnectionBuilder.js +97 -75
  23. package/dist/esm/src/helpers/cli/ASTDbTypesBuilder.js +132 -0
  24. package/dist/esm/src/helpers/cli/ASTSchemaBuilder.js +37 -81
  25. package/dist/esm/src/helpers/cli/dbTypesNaming.js +90 -0
  26. package/dist/esm/src/helpers/cli/tableMatchesPattern.js +21 -0
  27. package/dist/types/src/Dream.d.ts +3 -1
  28. package/dist/types/src/db/dataTypes.d.ts +0 -2
  29. package/dist/types/src/dream/QueryDriver/Base.d.ts +16 -18
  30. package/dist/types/src/dream/QueryDriver/Kysely.d.ts +0 -9
  31. package/dist/types/src/dream/QueryDriver/Postgres.d.ts +9 -9
  32. package/dist/types/src/dream-app/index.d.ts +12 -12
  33. package/dist/types/src/encrypt/InternalEncrypt.d.ts +9 -0
  34. package/dist/types/src/helpers/cli/ASTBuilder.d.ts +11 -101
  35. package/dist/types/src/helpers/cli/ASTConnectionBuilder.d.ts +40 -37
  36. package/dist/types/src/helpers/cli/ASTDbTypesBuilder.d.ts +26 -0
  37. package/dist/types/src/helpers/cli/ASTSchemaBuilder.d.ts +5 -15
  38. package/dist/types/src/helpers/cli/dbTypesNaming.d.ts +37 -0
  39. package/dist/types/src/helpers/cli/tableMatchesPattern.d.ts +15 -0
  40. package/dist/types/src/package-exports/types.d.ts +1 -1
  41. package/dist/types/src/types/db.d.ts +71 -0
  42. package/dist/types/src/types/types/db.ts +91 -0
  43. package/docs/assets/hierarchy.js +1 -1
  44. package/docs/assets/navigation.js +1 -1
  45. package/docs/assets/search.js +1 -1
  46. package/docs/classes/db.DreamMigrationHelpers.html +11 -11
  47. package/docs/classes/db.KyselyQueryDriver.html +55 -54
  48. package/docs/classes/db.PostgresQueryDriver.html +56 -56
  49. package/docs/classes/db.QueryDriverBase.html +60 -59
  50. package/docs/classes/errors.CannotSaveMissingDream.html +2 -2
  51. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  52. package/docs/classes/errors.ColumnOverflow.html +3 -3
  53. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  54. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  55. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  56. package/docs/classes/errors.DecryptionError.html +2 -2
  57. package/docs/classes/errors.DecryptionParseError.html +2 -2
  58. package/docs/classes/errors.DecryptionRotationError.html +3 -3
  59. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  60. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  61. package/docs/classes/errors.InvalidClockTime.html +2 -2
  62. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  63. package/docs/classes/errors.InvalidDateTime.html +2 -2
  64. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  65. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  66. package/docs/classes/errors.NotNullViolation.html +3 -3
  67. package/docs/classes/errors.RecordNotFound.html +3 -3
  68. package/docs/classes/errors.SortableScopeDidNotStabilize.html +3 -3
  69. package/docs/classes/errors.SortableScopeLockWaitTimedOut.html +3 -3
  70. package/docs/classes/errors.ValidationError.html +3 -3
  71. package/docs/classes/index.CalendarDate.html +33 -33
  72. package/docs/classes/index.ClockTime.html +32 -32
  73. package/docs/classes/index.ClockTimeTz.html +35 -35
  74. package/docs/classes/index.DateTime.html +86 -86
  75. package/docs/classes/index.Decorators.html +19 -19
  76. package/docs/classes/index.Dream.html +125 -125
  77. package/docs/classes/index.DreamApp.html +11 -11
  78. package/docs/classes/index.DreamTransaction.html +3 -3
  79. package/docs/classes/index.Env.html +5 -5
  80. package/docs/classes/index.Query.html +65 -65
  81. package/docs/classes/system.CliFileWriter.html +4 -4
  82. package/docs/classes/system.DreamBin.html +3 -3
  83. package/docs/classes/system.DreamCLI.html +7 -7
  84. package/docs/classes/system.DreamImporter.html +2 -2
  85. package/docs/classes/system.DreamLogos.html +2 -2
  86. package/docs/classes/system.DreamSerializerBuilder.html +11 -11
  87. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  88. package/docs/classes/system.PathHelpers.html +3 -3
  89. package/docs/classes/utils.Encrypt.html +4 -4
  90. package/docs/classes/utils.Range.html +2 -2
  91. package/docs/functions/db.closeAllDbConnections.html +1 -1
  92. package/docs/functions/db.dreamDbConnections.html +1 -1
  93. package/docs/functions/db.untypedDb.html +1 -1
  94. package/docs/functions/db.validateColumn.html +1 -1
  95. package/docs/functions/db.validateTable.html +1 -1
  96. package/docs/functions/errors.pgErrorType.html +1 -1
  97. package/docs/functions/index.DreamSerializer.html +1 -1
  98. package/docs/functions/index.ObjectSerializer.html +1 -1
  99. package/docs/functions/index.ReplicaSafe.html +1 -1
  100. package/docs/functions/index.STI.html +1 -1
  101. package/docs/functions/index.SoftDelete.html +1 -1
  102. package/docs/functions/utils.camelize.html +1 -1
  103. package/docs/functions/utils.capitalize.html +1 -1
  104. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  105. package/docs/functions/utils.compact.html +1 -1
  106. package/docs/functions/utils.groupBy.html +1 -1
  107. package/docs/functions/utils.hyphenize.html +1 -1
  108. package/docs/functions/utils.intersection.html +1 -1
  109. package/docs/functions/utils.isEmpty.html +1 -1
  110. package/docs/functions/utils.normalizeUnicode.html +1 -1
  111. package/docs/functions/utils.pascalize.html +1 -1
  112. package/docs/functions/utils.percent.html +1 -1
  113. package/docs/functions/utils.range.html +1 -1
  114. package/docs/functions/utils.round.html +1 -1
  115. package/docs/functions/utils.sanitizeString.html +1 -1
  116. package/docs/functions/utils.snakeify.html +1 -1
  117. package/docs/functions/utils.sort.html +1 -1
  118. package/docs/functions/utils.sortBy.html +1 -1
  119. package/docs/functions/utils.sortObjectByKey.html +1 -1
  120. package/docs/functions/utils.sortObjectByValue.html +1 -1
  121. package/docs/functions/utils.uncapitalize.html +1 -1
  122. package/docs/functions/utils.uniq.html +1 -1
  123. package/docs/hierarchy.html +1 -1
  124. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  125. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  126. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  127. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  128. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  129. package/docs/interfaces/types.DecoratorContext.html +2 -2
  130. package/docs/interfaces/types.DecryptCallbacks.html +2 -2
  131. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  132. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  133. package/docs/interfaces/types.DreamDbConfig.html +6 -6
  134. package/docs/interfaces/types.DurationObject.html +2 -2
  135. package/docs/interfaces/types.EncryptOptions.html +2 -2
  136. package/docs/interfaces/types.EncryptionLegacyKeyUsedEvent.html +4 -4
  137. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  138. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  139. package/docs/interfaces/types.IntrospectedColumn.html +17 -0
  140. package/docs/interfaces/types.IntrospectedDatabase.html +7 -0
  141. package/docs/interfaces/types.IntrospectedEnum.html +5 -0
  142. package/docs/interfaces/types.IntrospectedTable.html +11 -0
  143. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  144. package/docs/modules/types.html +1 -1
  145. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  146. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  147. package/docs/types/openapi.OpenapiFormats.html +1 -1
  148. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  149. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  150. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  151. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  152. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  153. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  154. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  155. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  156. package/docs/types/openapi.OpenapiSchemaCombinatorFields.html +1 -1
  157. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  158. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +1 -1
  159. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -1
  160. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -1
  161. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  162. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  163. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  164. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  165. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  166. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  167. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  168. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  169. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  170. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  171. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  172. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  173. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  174. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  175. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  176. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  177. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  178. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  179. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  180. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  181. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  182. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  183. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  184. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  185. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  186. package/docs/types/types.CalendarDateObject.html +1 -1
  187. package/docs/types/types.Camelized.html +1 -1
  188. package/docs/types/types.ClockTimeObject.html +1 -1
  189. package/docs/types/types.DbConnectionType.html +1 -1
  190. package/docs/types/types.DbTypes.html +1 -1
  191. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  192. package/docs/types/types.DreamAttributes.html +1 -1
  193. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  194. package/docs/types/types.DreamClassColumn.html +1 -1
  195. package/docs/types/types.DreamColumn.html +1 -1
  196. package/docs/types/types.DreamColumnNames.html +1 -1
  197. package/docs/types/types.DreamLogLevel.html +1 -1
  198. package/docs/types/types.DreamLogger.html +2 -2
  199. package/docs/types/types.DreamModelSerializerType.html +1 -1
  200. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  201. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  202. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  203. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  204. package/docs/types/types.DreamSerializable.html +1 -1
  205. package/docs/types/types.DreamSerializableArray.html +1 -1
  206. package/docs/types/types.DreamSerializerKey.html +1 -1
  207. package/docs/types/types.DreamSerializers.html +1 -1
  208. package/docs/types/types.DreamVirtualColumns.html +1 -1
  209. package/docs/types/types.DurationUnit.html +1 -1
  210. package/docs/types/types.EncryptAlgorithm.html +1 -1
  211. package/docs/types/types.HasManyStatement.html +1 -1
  212. package/docs/types/types.HasOneStatement.html +1 -1
  213. package/docs/types/types.Hyphenized.html +1 -1
  214. package/docs/types/types.IntrospectedValueType.html +5 -0
  215. package/docs/types/types.Pascalized.html +1 -1
  216. package/docs/types/types.PrimaryKeyType.html +1 -1
  217. package/docs/types/types.RoundingPrecision.html +1 -1
  218. package/docs/types/types.SerializerCasing.html +1 -1
  219. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  220. package/docs/types/types.Snakeified.html +1 -1
  221. package/docs/types/types.StrictInterface.html +1 -1
  222. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  223. package/docs/types/types.UpdateableProperties.html +1 -1
  224. package/docs/types/types.ValidationType.html +1 -1
  225. package/docs/types/types.ViewModel.html +2 -2
  226. package/docs/types/types.ViewModelClass.html +1 -1
  227. package/docs/types/types.WeekdayName.html +1 -1
  228. package/docs/types/types.WhereStatementForDream.html +1 -1
  229. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  230. package/docs/variables/index.DreamConst.html +1 -1
  231. package/docs/variables/index.ops.html +1 -1
  232. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  233. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  234. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  235. package/docs/variables/system.primaryKeyTypes.html +1 -1
  236. package/package.json +6 -3
  237. package/dist/cjs/src/db/helpers/syncDbTypesFiles.js +0 -41
  238. package/dist/cjs/src/helpers/cli/ASTKyselyCodegenEnhancer.js +0 -380
  239. package/dist/esm/src/db/helpers/syncDbTypesFiles.js +0 -41
  240. package/dist/esm/src/helpers/cli/ASTKyselyCodegenEnhancer.js +0 -380
  241. package/dist/types/src/db/helpers/syncDbTypesFiles.d.ts +0 -1
  242. package/dist/types/src/helpers/cli/ASTKyselyCodegenEnhancer.d.ts +0 -94
@@ -3019,7 +3019,9 @@ export default class Dream {
3019
3019
  * Writes the value into the current attributes as given, with none of
3020
3020
  * setAttribute's checks. Only for values Dream already trusts: a row read
3021
3021
  * from the database, and the ciphertext the @Encrypted property setter
3022
- * has just produced. Every caller-supplied value goes through setAttribute.
3022
+ * writes (freshly encrypted, or the ciphertext as loaded or last saved,
3023
+ * which it keeps when that already holds the assigned plaintext). Every
3024
+ * caller-supplied value goes through setAttribute.
3023
3025
  */
3024
3026
  setAttributeUnchecked(column, val) {
3025
3027
  this.currentAttributes[column] = val;
@@ -1,11 +1,3 @@
1
- export default function dataTypes() {
2
- // In the future, when we support multiple db drivers,
3
- // this will need to be updated
4
- return postgresDatatypes;
5
- }
6
- export function isPrimitiveDataType(type) {
7
- return dataTypes().includes(type.replace(/\[\]$/, ''));
8
- }
9
1
  export const postgresDatatypes = [
10
2
  'bigint',
11
3
  'bigserial',
@@ -96,9 +96,14 @@ export default function Encrypted(columnOrOptions) {
96
96
  */
97
97
  if (this.columnSetterGuardActivated)
98
98
  return;
99
- // the value was just encrypted, so it bypasses setAttribute's check
100
- // that a backing column receives only decryptable ciphertext
101
- this.setAttributeUnchecked(encryptedKey, InternalEncrypt.encryptColumn(val));
99
+ // On a persisted record, the ciphertext as loaded or last saved is
100
+ // kept when the current key opens it to the assigned value, so
101
+ // assigning a property the plaintext it already holds leaves the
102
+ // record clean; any other value is encrypted fresh. Either way the
103
+ // value is ciphertext Dream produced, so it bypasses setAttribute's
104
+ // check that a backing column receives only decryptable ciphertext
105
+ const dream = this;
106
+ this.setAttributeUnchecked(encryptedKey, InternalEncrypt.reuseOrEncryptColumn(val, dream.isPersisted ? dream['frozenAttributes'][encryptedKey] : undefined));
102
107
  },
103
108
  configurable: false,
104
109
  });
@@ -91,14 +91,12 @@ export default class QueryDriverBase {
91
91
  * comlpex to override. You will need to do the following
92
92
  * when overriding this method:
93
93
  *
94
- * 1. introspect the db and use it to generate a db.ts file in the
95
- * same shape as the existing one. Currently, the process for generating
96
- * this file is extremely complex and messy, and will be difficult
97
- * to achieve.
94
+ * 1. generate a types/db.ts file in the same shape as the existing one.
95
+ * KyselyQueryDriver does this with the ASTDbTypesBuilder, which writes
96
+ * it from {@link introspectDatabase}.
98
97
  * 2. generate a types/dream.ts file in the same shape as the existing
99
- * one. This is normally done using the ASTSchemaBuilder
100
- * but this will likely need to be overridden to tailor to your custom
101
- * database engine.
98
+ * one. KyselyQueryDriver does this with the ASTSchemaBuilder, which
99
+ * also reads the connection through {@link introspectDatabase}.
102
100
  */
103
101
  // eslint-disable-next-line @typescript-eslint/require-await
104
102
  static async sync(connectionName, _,
@@ -292,15 +290,20 @@ export default class QueryDriverBase {
292
290
  keys) {
293
291
  throw new Error('override acquireAdvisoryTransactionLocks in child class');
294
292
  }
293
+ /**
294
+ * Reads the tables, columns and enums of the connection's database, from
295
+ * which `sync` writes the connection's `types/db.ts` and `types/dream.ts`.
296
+ * It returns every table the connection can read, sorted by schema and
297
+ * name; `sync` applies `tableIncludePattern` and `tableExcludePattern`
298
+ * itself.
299
+ *
300
+ * @param connectionName - the connection to read
301
+ */
295
302
  // eslint-disable-next-line @typescript-eslint/require-await
296
- static async getColumnData(
297
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
298
- connectionName,
303
+ static async introspectDatabase(
299
304
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
300
- tableName,
301
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
302
- allTableAssociationData) {
303
- throw new Error('implement getColumnData in child class');
305
+ connectionName) {
306
+ throw new Error('implement introspectDatabase in child class');
304
307
  }
305
308
  /**
306
309
  * @internal
@@ -695,16 +698,6 @@ export default class QueryDriverBase {
695
698
  reallyDestroy) {
696
699
  throw new Error('implement destroyDream in child class');
697
700
  }
698
- static get syncDialect() {
699
- return 'postgres';
700
- }
701
- /** @internal Resolve the credential used by the database type generator. */
702
- static codegenPassword(password) {
703
- if (typeof password === 'function') {
704
- throw new Error('This database adapter does not support a password provider for type generation');
705
- }
706
- return Promise.resolve(password);
707
- }
708
701
  /**
709
702
  * Returns the sql that would be executed by this Query
710
703
  *
@@ -12,7 +12,6 @@ import pluralize from 'pluralize-esm';
12
12
  import { CliFileWriter } from '../../cli/CliFileWriter.js';
13
13
  import DreamCLI from '../../cli/index.js';
14
14
  import { CHECK_VIOLATION, COLUMN_OVERFLOW, INVALID_INPUT_SYNTAX, NOT_NULL_VIOLATION, pgErrorType, } from '../../db/errors.js';
15
- import syncDbTypesFiles from '../../db/helpers/syncDbTypesFiles.js';
16
15
  import { default as _db } from '../../db/index.js';
17
16
  import validateColumn from '../../db/validators/validateColumn.js';
18
17
  import associationToGetterSetterProp from '../../decorators/field/association/associationToGetterSetterProp.js';
@@ -39,8 +38,6 @@ import ColumnOverflow from '../../errors/db/ColumnOverflow.js';
39
38
  import DataTypeColumnTypeMismatch from '../../errors/db/DataTypeColumnTypeMismatch.js';
40
39
  import NotNullViolation from '../../errors/db/NotNullViolation.js';
41
40
  import UnexpectedUndefined from '../../errors/UnexpectedUndefined.js';
42
- import ASTGlobalSchemaBuilder from '../../helpers/cli/ASTGlobalSchemaBuilder.js';
43
- import ASTSchemaBuilder from '../../helpers/cli/ASTSchemaBuilder.js';
44
41
  import generateMigration from '../../helpers/cli/generateMigration.js';
45
42
  import compact from '../../helpers/compact.js';
46
43
  import { normalizeDataForDb } from '../../helpers/db/normalizeDataForDb.js';
@@ -51,7 +48,6 @@ import isEmpty from '../../helpers/isEmpty.js';
51
48
  import maybeNamespacedColumnNameToColumnName from '../../helpers/maybeNamespacedColumnNameToColumnName.js';
52
49
  import namespaceColumn from '../../helpers/namespaceColumn.js';
53
50
  import objectPathsToArrays from '../../helpers/objectPathsToArrays.js';
54
- import pascalize from '../../helpers/pascalize.js';
55
51
  import protectAgainstPollutingAssignment from '../../helpers/protectAgainstPollutingAssignment.js';
56
52
  import { Range } from '../../helpers/range.js';
57
53
  import snakeify from '../../helpers/snakeify.js';
@@ -200,13 +196,16 @@ export default class KyselyQueryDriver extends QueryDriverBase {
200
196
  * */
201
197
  static async sync(connectionName, onSync, options = {}) {
202
198
  try {
203
- if (!options?.schemaOnly) {
199
+ const { ASTDbTypesBuilder, ASTSchemaBuilder, ASTGlobalSchemaBuilder } = await loadTypeFileBuilders();
200
+ // the schema builder takes the db types builder's reading of the
201
+ // connection, so that a sync reads each connection once for both files
202
+ const dbTypesBuilder = options?.schemaOnly ? undefined : new ASTDbTypesBuilder(connectionName);
203
+ if (dbTypesBuilder) {
204
204
  await DreamCLI.logger.logProgress(`introspecting db for connection: ${connectionName}...`, async () => {
205
- // this calls kysely-codegen under the hood
206
- await syncDbTypesFiles(connectionName);
205
+ await dbTypesBuilder.build();
207
206
  });
208
207
  }
209
- const newSchemaBuilder = new ASTSchemaBuilder(connectionName);
208
+ const newSchemaBuilder = new ASTSchemaBuilder(connectionName, { introspectionFrom: dbTypesBuilder });
210
209
  await DreamCLI.logger.logProgress(`building dream schema for connection ${connectionName}...`, async () => {
211
210
  await newSchemaBuilder.build();
212
211
  });
@@ -243,12 +242,6 @@ export default class KyselyQueryDriver extends QueryDriverBase {
243
242
  throw error;
244
243
  }
245
244
  }
246
- static get syncDialect() {
247
- return 'postgres';
248
- }
249
- static async codegenPassword(password) {
250
- return typeof password === 'function' ? await password() : password;
251
- }
252
245
  /**
253
246
  * @internal
254
247
  *
@@ -369,15 +362,6 @@ export default class KyselyQueryDriver extends QueryDriverBase {
369
362
  await dreamTransaction.runAfterCommitHooks();
370
363
  return callbackResponse;
371
364
  }
372
- /**
373
- * @internal
374
- *
375
- * this is used by getColumnData to serialize enums
376
- */
377
- static enumType(row) {
378
- const enumName = pascalize(row.udtName.replace(/\[\]$/, ''));
379
- return enumName;
380
- }
381
365
  /**
382
366
  * @internal
383
367
  *
@@ -2828,3 +2812,18 @@ function shouldSoftDelete(dream, reallyDestroy) {
2828
2812
  const dreamClass = dream.constructor;
2829
2813
  return dreamClass['softDelete'] && !reallyDestroy;
2830
2814
  }
2815
+ /**
2816
+ * The type file builders import `typescript`, a devDependency that a
2817
+ * production install does not have, and this driver is reached from the
2818
+ * package's main entry, so they are loaded only when `sync` runs: in
2819
+ * development, and never in production, where migrations run with
2820
+ * `--skip-sync`.
2821
+ */
2822
+ async function loadTypeFileBuilders() {
2823
+ const [{ default: ASTDbTypesBuilder }, { default: ASTSchemaBuilder }, { default: ASTGlobalSchemaBuilder }] = await Promise.all([
2824
+ import('../../helpers/cli/ASTDbTypesBuilder.js'),
2825
+ import('../../helpers/cli/ASTSchemaBuilder.js'),
2826
+ import('../../helpers/cli/ASTGlobalSchemaBuilder.js'),
2827
+ ]);
2828
+ return { ASTDbTypesBuilder, ASTSchemaBuilder, ASTGlobalSchemaBuilder };
2829
+ }
@@ -9,11 +9,9 @@
9
9
  import pg from 'pg';
10
10
  import { sql } from 'kysely';
11
11
  import DreamCLI from '../../cli/index.js';
12
- import { isPrimitiveDataType } from '../../db/dataTypes.js';
13
12
  import { DEADLOCK_DETECTED, LOCK_NOT_AVAILABLE, pgErrorType, UNIQUE_VIOLATION } from '../../db/errors.js';
14
13
  import DreamApp from '../../dream-app/index.js';
15
14
  import SortableScopeLockWaitTimedOut from '../../errors/SortableScopeLockWaitTimedOut.js';
16
- import camelize from '../../helpers/camelize.js';
17
15
  import { findCitextArrayOid, findCorrespondingArrayOid, findEnumArrayOids, parsePostgresBigint, parsePostgresDate, parsePostgresDatetime, parsePostgresDatetimeTz, parsePostgresDecimal, parsePostgresTime, parsePostgresTimeTz, } from '../../helpers/customPgParsers.js';
18
16
  import { testDatabasePoolSize } from '../../db/testDatabasePool.js';
19
17
  import EnvInternal from '../../helpers/EnvInternal.js';
@@ -139,38 +137,96 @@ export default class PostgresQueryDriver extends KyselyQueryDriver {
139
137
  }
140
138
  }
141
139
  /**
142
- * @internal
140
+ * Reads the tables, views, materialized views, and partitioned and foreign
141
+ * tables of every schema the connection's user can use, except Postgres's
142
+ * own schemas, tables that inherit from another (partitions included) and
143
+ * Kysely's migration tables. A domain column reads as the type at the
144
+ * bottom of its domains. An enum outside the `public` schema is named
145
+ * `<schema>.<name>`.
143
146
  *
144
- * this is used by the SchemaBuilder to store column data permanently
145
- * within the types/dream.ts file.
147
+ * @param connectionName - the connection to read
146
148
  */
147
- static async getColumnData(connectionName, tableName, allTableAssociationData) {
149
+ static async introspectDatabase(connectionName) {
148
150
  const db = this.dbFor(connectionName, 'primary');
149
- const sqlQuery = sql `SELECT column_name, udt_name::regtype, is_nullable, data_type FROM information_schema.columns WHERE table_name = ${tableName}`;
150
- const columnToDBTypeMap = await sqlQuery.execute(db);
151
- const rows = columnToDBTypeMap.rows;
152
- const columnData = {};
153
- rows.forEach(row => {
154
- const isEnum = ['USER-DEFINED', 'ARRAY'].includes(row.dataType) && !isPrimitiveDataType(row.udtName);
155
- const isArray = ['ARRAY'].includes(row.dataType);
156
- const associationMetadata = allTableAssociationData[row.columnName];
157
- columnData[camelize(row.columnName)] = {
158
- dbType: row.udtName,
159
- allowNull: row.isNullable === 'YES',
160
- enumType: isEnum ? this.enumType(row) : null,
161
- enumValues: isEnum ? `${this.enumType(row)}Values` : null,
162
- isArray,
163
- foreignKey: associationMetadata?.foreignKey || null,
164
- };
165
- });
166
- return Object.keys(columnData)
167
- .sort()
168
- .reduce((acc, key) => {
169
- if (columnData[key] === undefined)
170
- return acc;
171
- acc[key] = columnData[key];
172
- return acc;
173
- }, {});
151
+ const columnRows = (await sql `
152
+ WITH RECURSIVE domain_base(oid, base, domainnotnull) AS (
153
+ SELECT dom.oid, dom.typbasetype, dom.typnotnull
154
+ FROM pg_catalog.pg_type AS dom
155
+ JOIN pg_catalog.pg_type AS typ ON typ.oid = dom.typbasetype
156
+ WHERE typ.typtype <> 'd'
157
+ UNION ALL
158
+ SELECT dom.oid, parent.base, parent.domainnotnull OR dom.typnotnull
159
+ FROM domain_base AS parent
160
+ JOIN pg_catalog.pg_type AS dom ON dom.typbasetype = parent.oid
161
+ )
162
+ SELECT
163
+ ns.nspname AS schemaname,
164
+ cls.relname AS tablename,
165
+ att.attname AS columnname,
166
+ format_type(val.oid, NULL) || CASE WHEN base.typcategory = 'A' THEN '[]' ELSE '' END AS dbtype,
167
+ base.typcategory = 'A' AS isarray,
168
+ format_type(val.oid, NULL) AS valuedbtype,
169
+ CASE WHEN val.typtype = 'e' THEN
170
+ CASE WHEN valns.nspname = 'public' THEN val.typname ELSE valns.nspname || '.' || val.typname END
171
+ END AS enumname,
172
+ NOT (att.attnotnull OR coalesce(columndomain.domainnotnull, false)) AS nullable,
173
+ (att.atthasdef OR att.attidentity <> '') AS hasdefault
174
+ FROM pg_catalog.pg_class AS cls
175
+ JOIN pg_catalog.pg_namespace AS ns ON ns.oid = cls.relnamespace
176
+ JOIN pg_catalog.pg_attribute AS att ON att.attrelid = cls.oid
177
+ LEFT JOIN domain_base AS columndomain ON columndomain.oid = att.atttypid
178
+ JOIN pg_catalog.pg_type AS base ON base.oid = coalesce(columndomain.base, att.atttypid)
179
+ LEFT JOIN domain_base AS elemdomain ON elemdomain.oid = base.typelem
180
+ JOIN pg_catalog.pg_type AS val
181
+ ON val.oid = CASE WHEN base.typcategory = 'A' THEN coalesce(elemdomain.base, base.typelem) ELSE base.oid END
182
+ JOIN pg_catalog.pg_namespace AS valns ON valns.oid = val.typnamespace
183
+ WHERE cls.relkind IN ('r', 'p', 'v', 'm', 'f')
184
+ AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_inherits AS inh WHERE inh.inhrelid = cls.oid)
185
+ AND ns.nspname !~ '^pg_'
186
+ AND ns.nspname <> 'information_schema'
187
+ AND has_schema_privilege(ns.oid, 'USAGE')
188
+ AND cls.relname NOT IN ('kysely_migration', 'kysely_migration_lock')
189
+ AND att.attnum > 0
190
+ AND NOT att.attisdropped
191
+ ORDER BY ns.nspname, cls.relname, att.attnum
192
+ `.execute(db)).rows;
193
+ const enumRows = (await sql `
194
+ SELECT
195
+ CASE WHEN ns.nspname = 'public' THEN typ.typname ELSE ns.nspname || '.' || typ.typname END AS enumname,
196
+ enm.enumlabel AS label
197
+ FROM pg_catalog.pg_enum AS enm
198
+ JOIN pg_catalog.pg_type AS typ ON typ.oid = enm.enumtypid
199
+ JOIN pg_catalog.pg_namespace AS ns ON ns.oid = typ.typnamespace
200
+ `.execute(db)).rows;
201
+ const tables = [];
202
+ for (const row of columnRows) {
203
+ let table = tables.at(-1);
204
+ if (table?.schema !== row.schemaname || table.name !== row.tablename) {
205
+ table = {
206
+ schema: row.schemaname,
207
+ name: row.tablename,
208
+ inDefaultSchema: row.schemaname === 'public',
209
+ columns: [],
210
+ };
211
+ tables.push(table);
212
+ }
213
+ table.columns.push({
214
+ name: row.columnname,
215
+ dbType: row.dbtype,
216
+ valueType: row.enumname ? 'enum' : (POSTGRES_VALUE_TYPES[row.valuedbtype] ?? 'string'),
217
+ enumName: row.enumname,
218
+ isArray: row.isarray,
219
+ allowNull: row.nullable,
220
+ hasDefault: row.hasdefault,
221
+ });
222
+ }
223
+ const enumValues = {};
224
+ for (const row of enumRows)
225
+ (enumValues[row.enumname] ||= []).push(row.label);
226
+ return {
227
+ tables,
228
+ enums: Object.keys(enumValues).map(name => ({ name, values: enumValues[name] })),
229
+ };
174
230
  }
175
231
  static async duplicateDatabase(connectionName) {
176
232
  const dreamApp = DreamApp.getOrFail();
@@ -353,3 +409,29 @@ export default class PostgresQueryDriver extends KyselyQueryDriver {
353
409
  };
354
410
  }
355
411
  }
412
+ /**
413
+ * The `types/db.ts` type of one value of each Postgres type Dream reads as
414
+ * something other than a string, keyed by the type's name as `format_type`
415
+ * gives it. Every other type, enums aside, reads as a string.
416
+ */
417
+ const POSTGRES_VALUE_TYPES = {
418
+ smallint: 'number',
419
+ integer: 'number',
420
+ real: 'number',
421
+ 'double precision': 'number',
422
+ oid: 'number',
423
+ bigint: 'Int8',
424
+ numeric: 'Numeric',
425
+ boolean: 'boolean',
426
+ date: 'Timestamp',
427
+ 'timestamp without time zone': 'Timestamp',
428
+ 'timestamp with time zone': 'Timestamp',
429
+ 'time without time zone': 'ClockTime',
430
+ 'time with time zone': 'ClockTimeTz',
431
+ json: 'Json',
432
+ jsonb: 'Json',
433
+ bytea: 'Buffer',
434
+ interval: 'Interval',
435
+ point: 'Point',
436
+ circle: 'Circle',
437
+ };
@@ -14,6 +14,34 @@ export default class InternalEncrypt {
14
14
  return null;
15
15
  return this.doEncryption(data, encryptOpts.current);
16
16
  }
17
+ /**
18
+ * Returns `existingCiphertext` when the current column key opens it to a
19
+ * value whose JSON serialization equals `data`'s, the plaintext
20
+ * `encryptColumn(data)` would encrypt, and otherwise `encryptColumn(data)`.
21
+ * A value only the legacy key opens, ciphertext no key opens, and a
22
+ * missing `existingCiphertext` all get fresh ciphertext. It never calls
23
+ * `onLegacyKeyUsed`.
24
+ */
25
+ static reuseOrEncryptColumn(data, existingCiphertext) {
26
+ if (data === null || data === undefined || typeof existingCiphertext !== 'string')
27
+ return this.encryptColumn(data);
28
+ const dreamApp = DreamApp.getOrFail();
29
+ const encryptOpts = dreamApp.encryption?.columns;
30
+ if (!encryptOpts)
31
+ throw new MissingColumnEncryptionOpts();
32
+ let existingPlaintext;
33
+ try {
34
+ existingPlaintext = Encrypt.decrypt(existingCiphertext, encryptOpts.current);
35
+ }
36
+ catch (err) {
37
+ if (err instanceof DecryptionError || err instanceof DecryptionParseError)
38
+ return this.encryptColumn(data);
39
+ throw err;
40
+ }
41
+ return JSON.stringify(existingPlaintext) === JSON.stringify(data)
42
+ ? existingCiphertext
43
+ : this.encryptColumn(data);
44
+ }
17
45
  /**
18
46
  * Decrypts `data` with the column keys, current then legacy.
19
47
  * `onLegacyKeyUsed` is passed to `Encrypt.decrypt`, so it is called only
@@ -7,7 +7,7 @@ const f = ts.factory;
7
7
  * @internal
8
8
  *
9
9
  * This is a base class, which is inherited by the ASTSchemaBuilder,
10
- * the ASTKyselyCodegenEnhancer, and the ASTGlobalSchemaBuilder,
10
+ * the ASTDbTypesBuilder, and the ASTGlobalSchemaBuilder,
11
11
  * each of which is responsible for building up the output of the various
12
12
  * type files consumed by dream internally.
13
13
  *
@@ -23,131 +23,6 @@ export default class ASTBuilder {
23
23
  newLine() {
24
24
  return f.createIdentifier('\n');
25
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
26
  /**
152
27
  * @internal
153
28
  *
@@ -189,25 +64,6 @@ export default class ASTBuilder {
189
64
  return output;
190
65
  }
191
66
  }
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
67
  /**
212
68
  * @internal
213
69
  *
@@ -251,28 +107,6 @@ export default class ASTBuilder {
251
107
  return [importDeclaration];
252
108
  }
253
109
  }
254
- /**
255
- * @internal
256
- *
257
- * for a given table name (i.e. balloon_lines), it will return the exported
258
- * `BalloonLines` interface within the dbSourceFile
259
- */
260
- getTableInterfaceDeclaration(dbSourceFile, tableName) {
261
- const DB = this.findDbExport(dbSourceFile, 'DB');
262
- let targetProperty = null;
263
- for (const member of DB.members) {
264
- if (ts.isPropertySignature(member) && member.name.getText(dbSourceFile) === tableName) {
265
- targetProperty = member;
266
- break;
267
- }
268
- }
269
- const tableInterfaceName = (targetProperty?.type)
270
- .typeName?.escapedText;
271
- if (!tableInterfaceName)
272
- throw new Error(`failed to find table interface for table: ${tableName}`);
273
- const tableInterface = this.findDbExport(dbSourceFile, tableInterfaceName);
274
- return tableInterface;
275
- }
276
110
  /**
277
111
  * @internal
278
112
  *
@@ -291,39 +125,14 @@ export default class ASTBuilder {
291
125
  isDateDbType(dbType) {
292
126
  return /^date[[\]]*$/.test(dbType);
293
127
  }
294
- /**
295
- * @internal
296
- *
297
- * checks if a database type is a time without time zone type (with optional array suffix)
298
- */
299
- isTimeWithoutTimeZoneDbType(dbType) {
300
- return /^time without time zone[[\]]*$/.test(dbType);
301
- }
302
- /**
303
- * @internal
304
- *
305
- * checks if a database type is a time with time zone type (with optional array suffix)
306
- */
307
- isTimeWithTimeZoneDbType(dbType) {
308
- return /^time with time zone[[\]]*$/.test(dbType);
309
- }
310
- /**
311
- * @internal
312
- *
313
- * checks if a database type is any time type (with or without time zone, with optional array suffix)
314
- */
315
- isTimeDbType(dbType) {
316
- return /^time (without|with) time zone(\[\])?$/.test(dbType);
317
- }
318
- /**
319
- * @internal
320
- *
321
- * checks if a TypeScript type node represents a string-like type
322
- * (string keyword, string type reference, or string literal)
323
- */
324
- isStringLikeType(node, sourceFile) {
325
- return (node.kind === ts.SyntaxKind.StringKeyword ||
326
- (ts.isTypeReferenceNode(node) && node.typeName.getText(sourceFile) === 'string') ||
327
- (ts.isLiteralTypeNode(node) && ts.isStringLiteral(node.literal)));
328
- }
128
+ }
129
+ /**
130
+ * @internal
131
+ *
132
+ * A property name for an object literal, interface or class: the name itself
133
+ * when it is an identifier, and a string literal otherwise, such as a table
134
+ * outside the default schema (`inventory.parts`).
135
+ */
136
+ export function propertyName(name) {
137
+ return /^[A-Za-z_$][\w$]*$/.test(name) ? f.createIdentifier(name) : f.createStringLiteral(name);
329
138
  }