prisma-sql 1.37.0 → 1.38.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/generator.js CHANGED
@@ -54,7 +54,7 @@ var require_package = __commonJS({
54
54
  "package.json"(exports$1, module) {
55
55
  module.exports = {
56
56
  name: "prisma-sql",
57
- version: "1.37.0",
57
+ version: "1.38.0",
58
58
  description: "Convert Prisma queries to optimized SQL with type safety. 2-7x faster than Prisma Client.",
59
59
  main: "dist/index.cjs",
60
60
  module: "dist/index.js",
@@ -95,6 +95,17 @@ var require_package = __commonJS({
95
95
  "sonar-cli": "sonar-scanner -Dsonar.projectKey=prisma-sql -Dsonar.sources=./src -Dsonar.host.url=http://localhost:9000 -Dsonar.login=sqp_9fe07460d0aa83f711d0edf4f317f05019d0613b",
96
96
  sonar: "yarn sonar-cli && npx tsx scripts/sonar.ts"
97
97
  },
98
+ workspaces: {
99
+ packages: [
100
+ "demo"
101
+ ],
102
+ nohoist: [
103
+ "**/prisma",
104
+ "**/prisma/**",
105
+ "**/@prisma/**",
106
+ "**/prisma-*"
107
+ ]
108
+ },
98
109
  keywords: [
99
110
  "prisma",
100
111
  "sql",
@@ -302,7 +313,7 @@ function getArrayType(prismaType, dialect) {
302
313
  case "DateTime":
303
314
  return "timestamptz[]";
304
315
  default:
305
- return "text[]";
316
+ return `"${baseType}"[]`;
306
317
  }
307
318
  }
308
319
  function jsonAgg(content, dialect) {
@@ -1947,7 +1958,7 @@ function buildLogical(operator, value, ctx, builder) {
1947
1958
  }
1948
1959
  function buildScalarField(fieldName, value, ctx) {
1949
1960
  const field = assertFieldExists(fieldName, ctx.model, ctx.path);
1950
- const expr = col(ctx.alias, fieldName);
1961
+ const expr = col(ctx.alias, fieldName, ctx.model);
1951
1962
  if (value === null) {
1952
1963
  return freezeResult(`${expr} ${SQL_TEMPLATES.IS_NULL}`, EMPTY_JOINS);
1953
1964
  }
@@ -3435,8 +3446,8 @@ function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3435
3446
  const replacement = `${outerAlias}.`;
3436
3447
  return orderBy.split(needle).join(replacement);
3437
3448
  }
3438
- function buildDistinctColumns(distinct, fromAlias) {
3439
- return distinct.map((f) => col(fromAlias, f)).join(SQL_SEPARATORS.FIELD_LIST);
3449
+ function buildDistinctColumns(distinct, fromAlias, model) {
3450
+ return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3440
3451
  }
3441
3452
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3442
3453
  const outputCols = [...scalarNames, ...includeNames];
@@ -3450,13 +3461,13 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
3450
3461
  return formatted;
3451
3462
  }
3452
3463
  function buildWindowOrder(args) {
3453
- const { baseOrder, idField, fromAlias } = args;
3464
+ const { baseOrder, idField, fromAlias, model } = args;
3454
3465
  const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
3455
3466
  const hasIdInOrder = orderFields.some(
3456
3467
  (f) => f.startsWith(`${fromAlias}.id `) || f.startsWith(`${fromAlias}."id" `)
3457
3468
  );
3458
3469
  if (hasIdInOrder) return baseOrder;
3459
- const idTiebreaker = idField ? `, ${col(fromAlias, "id")} ASC` : "";
3470
+ const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
3460
3471
  return `${baseOrder}${idTiebreaker}`;
3461
3472
  }
3462
3473
  function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
@@ -3473,14 +3484,17 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3473
3484
  includeNames,
3474
3485
  hasCount
3475
3486
  );
3476
- const distinctCols = buildDistinctColumns([...distinct], from.alias);
3477
- const fallbackOrder = [...distinct].map((f) => `${col(from.alias, f)} ASC`).join(SQL_SEPARATORS.FIELD_LIST);
3478
- const idField = model.fields.find((f) => f.name === "id" && !f.isRelation);
3487
+ const distinctCols = buildDistinctColumns([...distinct], from.alias, model);
3488
+ const fallbackOrder = [...distinct].map((f) => `${col(from.alias, f, model)} ASC`).join(SQL_SEPARATORS.FIELD_LIST);
3489
+ const idField = model.fields.find(
3490
+ (f) => f.name === "id" && !f.isRelation
3491
+ );
3479
3492
  const baseOrder = isNonEmptyString(orderBy) ? orderBy : fallbackOrder;
3480
3493
  const windowOrder = buildWindowOrder({
3481
3494
  baseOrder,
3482
3495
  idField,
3483
- fromAlias: from.alias
3496
+ fromAlias: from.alias,
3497
+ model
3484
3498
  });
3485
3499
  const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
3486
3500
  const joins = buildJoinsSql(whereJoins, countJoins);
@@ -3591,9 +3605,9 @@ function withCountJoins(spec, countJoins, whereJoins) {
3591
3605
  whereJoins: [...whereJoins || [], ...countJoins || []]
3592
3606
  });
3593
3607
  }
3594
- function buildPostgresDistinctOnClause(fromAlias, distinct) {
3608
+ function buildPostgresDistinctOnClause(fromAlias, distinct, model) {
3595
3609
  if (!isNonEmptyArray(distinct)) return null;
3596
- const distinctCols = buildDistinctColumns([...distinct], fromAlias);
3610
+ const distinctCols = buildDistinctColumns([...distinct], fromAlias, model);
3597
3611
  return `${SQL_TEMPLATES.DISTINCT_ON} (${distinctCols})`;
3598
3612
  }
3599
3613
  function pushJoinGroups(parts, ...groups) {
@@ -3623,7 +3637,8 @@ function constructFinalSql(spec) {
3623
3637
  method,
3624
3638
  cursorClause,
3625
3639
  params,
3626
- dialect
3640
+ dialect,
3641
+ model
3627
3642
  } = spec;
3628
3643
  const useWindowDistinct = hasWindowDistinct(spec);
3629
3644
  assertDistinctAllowed(method, useWindowDistinct);
@@ -3637,7 +3652,7 @@ function constructFinalSql(spec) {
3637
3652
  return finalizeSql(sql2, params);
3638
3653
  }
3639
3654
  const parts = [SQL_TEMPLATES.SELECT];
3640
- const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct) : null;
3655
+ const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
3641
3656
  if (distinctOn) parts.push(distinctOn);
3642
3657
  const baseSelect = (select != null ? select : "").trim();
3643
3658
  const fullSelectList = buildSelectList(baseSelect, includeCols);
@@ -3897,17 +3912,17 @@ function getModelFieldMap(model) {
3897
3912
  function isTruthySelection(v) {
3898
3913
  return v === true;
3899
3914
  }
3900
- function aggExprForField(aggKey, field, alias) {
3915
+ function aggExprForField(aggKey, field, alias, model) {
3901
3916
  if (aggKey === "_count") {
3902
- return field === "_all" ? `COUNT(*)` : `COUNT(${col(alias, field)})`;
3917
+ return field === "_all" ? `COUNT(*)` : `COUNT(${col(alias, field, model)})`;
3903
3918
  }
3904
3919
  if (field === "_all") {
3905
3920
  throw new Error(`'${aggKey}' does not support '_all'`);
3906
3921
  }
3907
- if (aggKey === "_sum") return `SUM(${col(alias, field)})`;
3908
- if (aggKey === "_avg") return `AVG(${col(alias, field)})`;
3909
- if (aggKey === "_min") return `MIN(${col(alias, field)})`;
3910
- return `MAX(${col(alias, field)})`;
3922
+ if (aggKey === "_sum") return `SUM(${col(alias, field, model)})`;
3923
+ if (aggKey === "_avg") return `AVG(${col(alias, field, model)})`;
3924
+ if (aggKey === "_min") return `MIN(${col(alias, field, model)})`;
3925
+ return `MAX(${col(alias, field, model)})`;
3911
3926
  }
3912
3927
  function buildComparisonOp(op) {
3913
3928
  const sqlOp = COMPARISON_OPS[op];
@@ -4088,7 +4103,7 @@ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialec
4088
4103
  for (const [field, filter] of Object.entries(target)) {
4089
4104
  assertHavingAggTarget(aggKey, field, model);
4090
4105
  if (!isPlainObject(filter) || Object.keys(filter).length === 0) continue;
4091
- const expr = aggExprForField(aggKey, field, alias);
4106
+ const expr = aggExprForField(aggKey, field, alias, model);
4092
4107
  out.push(...buildHavingOpsForExpr(expr, filter, params, dialect));
4093
4108
  }
4094
4109
  return out;
@@ -4105,7 +4120,7 @@ function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect
4105
4120
  assertAggregateFieldType(aggKey, field.type, field.name, model.name);
4106
4121
  const entries = Object.entries(aggFilter);
4107
4122
  if (entries.length === 0) continue;
4108
- const expr = aggExprForField(aggKey, fieldName, alias);
4123
+ const expr = aggExprForField(aggKey, fieldName, alias, model);
4109
4124
  for (const [op, val] of entries) {
4110
4125
  if (op === "mode") continue;
4111
4126
  const built = buildSimpleComparison(expr, op, val, params, dialect);
@@ -4144,10 +4159,10 @@ function assertCountableScalarField(fieldMap, model, fieldName) {
4144
4159
  );
4145
4160
  }
4146
4161
  }
4147
- function pushCountField(fields, alias, fieldName) {
4162
+ function pushCountField(fields, alias, fieldName, model) {
4148
4163
  const outAlias = `_count.${fieldName}`;
4149
4164
  fields.push(
4150
- `COUNT(${col(alias, fieldName)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4165
+ `COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4151
4166
  );
4152
4167
  }
4153
4168
  function addCountFields(fields, countArg, alias, model, fieldMap) {
@@ -4165,7 +4180,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
4165
4180
  );
4166
4181
  for (const [f] of selected) {
4167
4182
  assertCountableScalarField(fieldMap, model, f);
4168
- pushCountField(fields, alias, f);
4183
+ pushCountField(fields, alias, f, model);
4169
4184
  }
4170
4185
  }
4171
4186
  function getAggregateSelectionObject(args, agg) {
@@ -4186,10 +4201,10 @@ function assertAggregatableScalarField(fieldMap, model, agg, fieldName) {
4186
4201
  }
4187
4202
  return field;
4188
4203
  }
4189
- function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName) {
4204
+ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4190
4205
  const outAlias = `${agg}.${fieldName}`;
4191
4206
  fields.push(
4192
- `${aggFn}(${col(alias, fieldName)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4207
+ `${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4193
4208
  );
4194
4209
  }
4195
4210
  function addAggregateFields(fields, args, alias, model, fieldMap) {
@@ -4207,7 +4222,7 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
4207
4222
  fieldName
4208
4223
  );
4209
4224
  assertAggregateFieldType(agg, field.type, fieldName, model.name);
4210
- pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName);
4225
+ pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
4211
4226
  }
4212
4227
  }
4213
4228
  }
@@ -4270,7 +4285,7 @@ function assertGroupByBy(args, model) {
4270
4285
  return byFields;
4271
4286
  }
4272
4287
  function buildGroupBySelectParts(args, alias, model, byFields) {
4273
- const groupCols = byFields.map((f) => col(alias, f));
4288
+ const groupCols = byFields.map((f) => col(alias, f, model));
4274
4289
  const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4275
4290
  const aggFields = buildAggregateFields(args, alias, model);
4276
4291
  const selectFields = isNonEmptyArray(aggFields) ? groupCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : groupCols.join(SQL_SEPARATORS.FIELD_LIST);
@@ -4563,6 +4578,29 @@ function generateSQL2(directive) {
4563
4578
  }
4564
4579
 
4565
4580
  // src/code-emitter.ts
4581
+ function extractEnumMappings(datamodel) {
4582
+ const mappings = {};
4583
+ const fieldTypes = {};
4584
+ for (const enumDef of datamodel.enums) {
4585
+ const enumMapping = {};
4586
+ for (const value of enumDef.values) {
4587
+ enumMapping[value.name] = value.dbName || value.name;
4588
+ }
4589
+ if (Object.keys(enumMapping).length > 0) {
4590
+ mappings[enumDef.name] = enumMapping;
4591
+ }
4592
+ }
4593
+ for (const model of datamodel.models) {
4594
+ fieldTypes[model.name] = {};
4595
+ for (const field of model.fields) {
4596
+ const baseType = field.type.replace(/\[\]|\?/g, "");
4597
+ if (mappings[baseType]) {
4598
+ fieldTypes[model.name][field.name] = baseType;
4599
+ }
4600
+ }
4601
+ }
4602
+ return { mappings, fieldTypes };
4603
+ }
4566
4604
  function generateClient(options) {
4567
4605
  return __async(this, null, function* () {
4568
4606
  const { datamodel, outputDir, config } = options;
@@ -4604,7 +4642,7 @@ function generateClient(options) {
4604
4642
  }
4605
4643
  const absoluteOutputDir = resolve(process.cwd(), outputDir);
4606
4644
  yield mkdir(absoluteOutputDir, { recursive: true });
4607
- const code = generateCode(models, queries, config.dialect);
4645
+ const code = generateCode(models, queries, config.dialect, datamodel);
4608
4646
  const outputPath = join(absoluteOutputDir, "index.ts");
4609
4647
  yield writeFile(outputPath, code);
4610
4648
  const totalQueries = Array.from(queries.values()).reduce(
@@ -4632,15 +4670,20 @@ function createQueryKey(processedQuery) {
4632
4670
  return value;
4633
4671
  });
4634
4672
  }
4635
- function generateCode(models, queries, dialect) {
4673
+ function generateCode(models, queries, dialect, datamodel) {
4636
4674
  const cleanModels = models.map((model) => __spreadProps(__spreadValues({}, model), {
4637
4675
  fields: model.fields.filter((f) => f !== void 0 && f !== null)
4638
4676
  }));
4677
+ const { mappings, fieldTypes } = extractEnumMappings(datamodel);
4639
4678
  return `// Generated by @prisma-sql/generator - DO NOT EDIT
4640
4679
  import { buildSQL, transformQueryResults, type PrismaMethod, type Model } from 'prisma-sql'
4641
4680
 
4642
4681
  export const MODELS: Model[] = ${JSON.stringify(cleanModels, null, 2)}
4643
4682
 
4683
+ const ENUM_MAPPINGS: Record<string, Record<string, string>> = ${JSON.stringify(mappings, null, 2)}
4684
+
4685
+ const ENUM_FIELDS: Record<string, Record<string, string>> = ${JSON.stringify(fieldTypes, null, 2)}
4686
+
4644
4687
  const QUERIES: Record<string, Record<string, Record<string, {
4645
4688
  sql: string
4646
4689
  params: unknown[]
@@ -4654,6 +4697,79 @@ function isDynamicParam(key: string): boolean {
4654
4697
  return key === 'skip' || key === 'take' || key === 'cursor'
4655
4698
  }
4656
4699
 
4700
+ function transformEnumInValue(value: unknown, enumType: string | undefined): unknown {
4701
+ if (!enumType || value === null || value === undefined) {
4702
+ return value
4703
+ }
4704
+
4705
+ const mapping = ENUM_MAPPINGS[enumType]
4706
+ if (!mapping) {
4707
+ return value
4708
+ }
4709
+
4710
+ // Handle array of enum values
4711
+ if (Array.isArray(value)) {
4712
+ return value.map(v => {
4713
+ if (typeof v === 'string' && mapping[v] !== undefined) {
4714
+ return mapping[v]
4715
+ }
4716
+ return v
4717
+ })
4718
+ }
4719
+
4720
+ // Handle single enum value
4721
+ if (typeof value === 'string' && mapping[value] !== undefined) {
4722
+ return mapping[value]
4723
+ }
4724
+
4725
+ return value
4726
+ }
4727
+
4728
+ function transformEnumValues(modelName: string, obj: any, currentPath: string[] = []): any {
4729
+ if (obj === null || obj === undefined) {
4730
+ return obj
4731
+ }
4732
+
4733
+ if (Array.isArray(obj)) {
4734
+ return obj.map(item => transformEnumValues(modelName, item, currentPath))
4735
+ }
4736
+
4737
+ if (typeof obj === 'object') {
4738
+ const transformed: any = {}
4739
+ const modelFields = ENUM_FIELDS[modelName] || {}
4740
+
4741
+ for (const [key, value] of Object.entries(obj)) {
4742
+ const newPath = [...currentPath, key]
4743
+
4744
+ // Check if current key is an enum field at root level
4745
+ const enumType = modelFields[key]
4746
+
4747
+ if (enumType) {
4748
+ // This is an enum field - check if value is direct or has operators
4749
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
4750
+ // Has operators like { equals: "ACTIVE" }, { in: ["ACTIVE"] }, etc.
4751
+ const transformedOperators: any = {}
4752
+ for (const [op, opValue] of Object.entries(value)) {
4753
+ transformedOperators[op] = transformEnumInValue(opValue, enumType)
4754
+ }
4755
+ transformed[key] = transformedOperators
4756
+ } else {
4757
+ // Direct value like { status: "ACTIVE" }
4758
+ transformed[key] = transformEnumInValue(value, enumType)
4759
+ }
4760
+ } else if (typeof value === 'object' && value !== null) {
4761
+ // Recursively transform nested objects (relations, logical operators, etc)
4762
+ transformed[key] = transformEnumValues(modelName, value, newPath)
4763
+ } else {
4764
+ transformed[key] = value
4765
+ }
4766
+ }
4767
+ return transformed
4768
+ }
4769
+
4770
+ return obj
4771
+ }
4772
+
4657
4773
  function normalizeQuery(args: any): string {
4658
4774
  if (!args) return '{}'
4659
4775
 
@@ -4769,7 +4885,10 @@ export function speedExtension(config: {
4769
4885
  const modelName = this?.name || this?.$name
4770
4886
  const startTime = Date.now()
4771
4887
 
4772
- const queryKey = normalizeQuery(args)
4888
+ // Transform enum values before processing
4889
+ const transformedArgs = transformEnumValues(modelName, args || {})
4890
+
4891
+ const queryKey = normalizeQuery(transformedArgs)
4773
4892
  const prebakedQuery = QUERIES[modelName]?.[method]?.[queryKey]
4774
4893
 
4775
4894
  let sql: string
@@ -4778,7 +4897,7 @@ export function speedExtension(config: {
4778
4897
 
4779
4898
  if (prebakedQuery) {
4780
4899
  sql = prebakedQuery.sql
4781
- params = [...prebakedQuery.params, ...extractDynamicParams(args, prebakedQuery.dynamicKeys)]
4900
+ params = [...prebakedQuery.params, ...extractDynamicParams(transformedArgs, prebakedQuery.dynamicKeys)]
4782
4901
  prebaked = true
4783
4902
  } else {
4784
4903
  const model = MODELS.find((m) => m.name === modelName)
@@ -4787,7 +4906,7 @@ export function speedExtension(config: {
4787
4906
  return this.$parent[modelName][method](args)
4788
4907
  }
4789
4908
 
4790
- const result = buildSQL(model, MODELS, method, args || {}, DIALECT)
4909
+ const result = buildSQL(model, MODELS, method, transformedArgs, DIALECT)
4791
4910
  sql = result.sql
4792
4911
  params = result.params
4793
4912
  }