@prisma-next/sql-lane 0.3.0-dev.4 → 0.3.0-dev.6

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 (55) hide show
  1. package/dist/exports/sql.d.ts +5 -5
  2. package/dist/exports/sql.d.ts.map +1 -0
  3. package/dist/index.d.ts +5 -116
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/raw.d.ts +11 -0
  6. package/dist/raw.d.ts.map +1 -0
  7. package/dist/sql/builder.d.ts +11 -0
  8. package/dist/sql/builder.d.ts.map +1 -0
  9. package/dist/sql/context.d.ts +5 -0
  10. package/dist/sql/context.d.ts.map +1 -0
  11. package/dist/sql/include-builder.d.ts +35 -0
  12. package/dist/sql/include-builder.d.ts.map +1 -0
  13. package/dist/sql/join-builder.d.ts +4 -0
  14. package/dist/sql/join-builder.d.ts.map +1 -0
  15. package/dist/sql/mutation-builder.d.ts +64 -0
  16. package/dist/sql/mutation-builder.d.ts.map +1 -0
  17. package/dist/sql/plan.d.ts +4 -0
  18. package/dist/sql/plan.d.ts.map +1 -0
  19. package/dist/sql/predicate-builder.d.ts +11 -0
  20. package/dist/sql/predicate-builder.d.ts.map +1 -0
  21. package/dist/sql/projection.d.ts +18 -0
  22. package/dist/sql/projection.d.ts.map +1 -0
  23. package/dist/sql/select-builder.d.ts +35 -0
  24. package/dist/sql/select-builder.d.ts.map +1 -0
  25. package/dist/types/internal.d.ts +35 -0
  26. package/dist/types/internal.d.ts.map +1 -0
  27. package/dist/types/public.d.ts +18 -0
  28. package/dist/types/public.d.ts.map +1 -0
  29. package/dist/utils/assertions.d.ts +28 -0
  30. package/dist/utils/assertions.d.ts.map +1 -0
  31. package/dist/utils/capabilities.d.ts +4 -0
  32. package/dist/utils/capabilities.d.ts.map +1 -0
  33. package/dist/utils/errors.d.ts +30 -0
  34. package/dist/utils/errors.d.ts.map +1 -0
  35. package/dist/utils/state.d.ts +30 -0
  36. package/dist/utils/state.d.ts.map +1 -0
  37. package/package.json +10 -9
  38. package/src/exports/sql.ts +12 -0
  39. package/src/index.ts +13 -0
  40. package/src/raw.ts +230 -0
  41. package/src/sql/builder.ts +66 -0
  42. package/src/sql/context.ts +10 -0
  43. package/src/sql/include-builder.ts +248 -0
  44. package/src/sql/join-builder.ts +18 -0
  45. package/src/sql/mutation-builder.ts +494 -0
  46. package/src/sql/plan.ts +289 -0
  47. package/src/sql/predicate-builder.ts +130 -0
  48. package/src/sql/projection.ts +112 -0
  49. package/src/sql/select-builder.ts +449 -0
  50. package/src/types/internal.ts +41 -0
  51. package/src/types/public.ts +36 -0
  52. package/src/utils/assertions.ts +34 -0
  53. package/src/utils/capabilities.ts +39 -0
  54. package/src/utils/errors.ts +168 -0
  55. package/src/utils/state.ts +40 -0
@@ -0,0 +1,289 @@
1
+ import type { PlanMeta } from '@prisma-next/contract/types';
2
+ import type { OperationExpr } from '@prisma-next/sql-relational-core/ast';
3
+ import { compact } from '@prisma-next/sql-relational-core/ast';
4
+ import type { AnyColumnBuilder } from '@prisma-next/sql-relational-core/types';
5
+ import {
6
+ collectColumnRefs,
7
+ getColumnInfo,
8
+ getOperationExpr,
9
+ isColumnBuilder,
10
+ isOperationExpr,
11
+ } from '@prisma-next/sql-relational-core/utils/guards';
12
+ import type { MetaBuildArgs } from '../types/internal';
13
+ import { assertColumnBuilder } from '../utils/assertions';
14
+ import { errorMissingColumnForAlias } from '../utils/errors';
15
+
16
+ export function buildMeta(args: MetaBuildArgs): PlanMeta {
17
+ const refsColumns = new Map<string, { table: string; column: string }>();
18
+ const refsTables = new Set<string>([args.table.name]);
19
+
20
+ for (const column of args.projection.columns) {
21
+ // Skip null columns (include placeholders)
22
+ if (!column) {
23
+ continue;
24
+ }
25
+ const operationExpr = getOperationExpr(column);
26
+ if (operationExpr) {
27
+ const allRefs = collectColumnRefs(operationExpr);
28
+ for (const ref of allRefs) {
29
+ refsColumns.set(`${ref.table}.${ref.column}`, {
30
+ table: ref.table,
31
+ column: ref.column,
32
+ });
33
+ }
34
+ } else {
35
+ refsColumns.set(`${column.table}.${column.column}`, {
36
+ table: column.table,
37
+ column: column.column,
38
+ });
39
+ }
40
+ }
41
+
42
+ if (args.joins) {
43
+ for (const join of args.joins) {
44
+ refsTables.add(join.table.name);
45
+ const onLeft = assertColumnBuilder(join.on.left, 'join ON left');
46
+ const onRight = assertColumnBuilder(join.on.right, 'join ON right');
47
+ refsColumns.set(`${onLeft.table}.${onLeft.column}`, {
48
+ table: onLeft.table,
49
+ column: onLeft.column,
50
+ });
51
+ refsColumns.set(`${onRight.table}.${onRight.column}`, {
52
+ table: onRight.table,
53
+ column: onRight.column,
54
+ });
55
+ }
56
+ }
57
+
58
+ if (args.includes) {
59
+ for (const include of args.includes) {
60
+ refsTables.add(include.table.name);
61
+ // Add ON condition columns
62
+ // JoinOnPredicate.left and .right are always ColumnBuilder
63
+ const leftCol = assertColumnBuilder(include.on.left, 'include ON left');
64
+ const rightCol = assertColumnBuilder(include.on.right, 'include ON right');
65
+ refsColumns.set(`${leftCol.table}.${leftCol.column}`, {
66
+ table: leftCol.table,
67
+ column: leftCol.column,
68
+ });
69
+ refsColumns.set(`${rightCol.table}.${rightCol.column}`, {
70
+ table: rightCol.table,
71
+ column: rightCol.column,
72
+ });
73
+ // Add child projection columns
74
+ for (const column of include.childProjection.columns) {
75
+ const col = assertColumnBuilder(column, 'include child projection column');
76
+
77
+ refsColumns.set(`${col.table}.${col.column}`, {
78
+ table: col.table,
79
+ column: col.column,
80
+ });
81
+ }
82
+ // Add child WHERE columns if present
83
+ if (include.childWhere) {
84
+ const colInfo = getColumnInfo(include.childWhere.left);
85
+ refsColumns.set(`${colInfo.table}.${colInfo.column}`, {
86
+ table: colInfo.table,
87
+ column: colInfo.column,
88
+ });
89
+ // Handle right side of child WHERE clause
90
+ const childWhereRight = include.childWhere.right;
91
+ if (isColumnBuilder(childWhereRight)) {
92
+ const rightColInfo = getColumnInfo(childWhereRight);
93
+ refsColumns.set(`${rightColInfo.table}.${rightColInfo.column}`, {
94
+ table: rightColInfo.table,
95
+ column: rightColInfo.column,
96
+ });
97
+ }
98
+ }
99
+ // Add child ORDER BY columns if present
100
+ if (include.childOrderBy) {
101
+ const orderBy = include.childOrderBy as unknown as {
102
+ expr?: AnyColumnBuilder | OperationExpr;
103
+ };
104
+ if (orderBy.expr) {
105
+ const colInfo = getColumnInfo(orderBy.expr);
106
+ refsColumns.set(`${colInfo.table}.${colInfo.column}`, {
107
+ table: colInfo.table,
108
+ column: colInfo.column,
109
+ });
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ if (args.where) {
116
+ const whereLeft = args.where.left;
117
+ // Check if whereLeft is an OperationExpr directly (not wrapped in ColumnBuilder)
118
+ if (isOperationExpr(whereLeft)) {
119
+ const allRefs = collectColumnRefs(whereLeft);
120
+ for (const ref of allRefs) {
121
+ refsColumns.set(`${ref.table}.${ref.column}`, {
122
+ table: ref.table,
123
+ column: ref.column,
124
+ });
125
+ }
126
+ } else {
127
+ // Check if whereLeft is a ColumnBuilder with an _operationExpr property
128
+ const operationExpr = (whereLeft as { _operationExpr?: OperationExpr })._operationExpr;
129
+ if (operationExpr) {
130
+ const allRefs = collectColumnRefs(operationExpr);
131
+ for (const ref of allRefs) {
132
+ refsColumns.set(`${ref.table}.${ref.column}`, {
133
+ table: ref.table,
134
+ column: ref.column,
135
+ });
136
+ }
137
+ } else {
138
+ const colBuilder = assertColumnBuilder(whereLeft, 'where clause must be a ColumnBuilder');
139
+ refsColumns.set(`${colBuilder.table}.${colBuilder.column}`, {
140
+ table: colBuilder.table,
141
+ column: colBuilder.column,
142
+ });
143
+ }
144
+ }
145
+
146
+ // Handle right side of WHERE clause - can be ParamPlaceholder or AnyColumnBuilder
147
+ const whereRight = args.where.right;
148
+ if (isColumnBuilder(whereRight)) {
149
+ const colInfo = getColumnInfo(whereRight);
150
+ refsColumns.set(`${colInfo.table}.${colInfo.column}`, {
151
+ table: colInfo.table,
152
+ column: colInfo.column,
153
+ });
154
+ }
155
+ }
156
+
157
+ if (args.orderBy) {
158
+ const orderBy = args.orderBy as unknown as {
159
+ expr?: AnyColumnBuilder | OperationExpr;
160
+ };
161
+ const orderByExpr = orderBy.expr;
162
+ if (orderByExpr) {
163
+ if (isOperationExpr(orderByExpr)) {
164
+ const allRefs = collectColumnRefs(orderByExpr);
165
+ for (const ref of allRefs) {
166
+ refsColumns.set(`${ref.table}.${ref.column}`, {
167
+ table: ref.table,
168
+ column: ref.column,
169
+ });
170
+ }
171
+ } else {
172
+ refsColumns.set(`${orderByExpr.table}.${orderByExpr.column}`, {
173
+ table: orderByExpr.table,
174
+ column: orderByExpr.column,
175
+ });
176
+ }
177
+ }
178
+ }
179
+
180
+ // Build projection map - mark include aliases with special marker
181
+ const includeAliases = new Set(args.includes?.map((inc) => inc.alias) ?? []);
182
+ const projectionMap = Object.fromEntries(
183
+ args.projection.aliases.map((alias, index) => {
184
+ if (includeAliases.has(alias)) {
185
+ // Mark include alias with special marker
186
+ return [alias, `include:${alias}`];
187
+ }
188
+ const column = args.projection.columns[index];
189
+ if (!column) {
190
+ // Null column means this is an include placeholder, but alias doesn't match includes
191
+ // This shouldn't happen if projection building is correct, but handle gracefully
192
+ errorMissingColumnForAlias(alias, index);
193
+ }
194
+
195
+ const operationExpr = getOperationExpr(column);
196
+ if (operationExpr) {
197
+ return [alias, `operation:${operationExpr.method}`];
198
+ }
199
+
200
+ return [alias, `${column.table}.${column.column}`];
201
+ }),
202
+ );
203
+
204
+ // Build projectionTypes mapping: alias → column type ID
205
+ // Skip include aliases - they don't have column types
206
+ const projectionTypes: Record<string, string> = {};
207
+ for (let i = 0; i < args.projection.aliases.length; i++) {
208
+ const alias = args.projection.aliases[i];
209
+ if (!alias || includeAliases.has(alias)) {
210
+ continue;
211
+ }
212
+ const column = args.projection.columns[i];
213
+ if (!column) {
214
+ continue;
215
+ }
216
+ const operationExpr = (column as { _operationExpr?: OperationExpr })._operationExpr;
217
+ if (operationExpr) {
218
+ if (operationExpr.returns.kind === 'typeId') {
219
+ projectionTypes[alias] = operationExpr.returns.type;
220
+ } else if (operationExpr.returns.kind === 'builtin') {
221
+ projectionTypes[alias] = operationExpr.returns.type;
222
+ }
223
+ } else {
224
+ // TypeScript can't narrow ColumnBuilder properly
225
+ const col = column as unknown as { columnMeta?: { codecId: string } };
226
+ const columnMeta = col.columnMeta;
227
+ const codecId = columnMeta?.codecId;
228
+ if (codecId) {
229
+ projectionTypes[alias] = codecId;
230
+ }
231
+ }
232
+ }
233
+
234
+ // Build codec assignments from column types
235
+ // Skip include aliases - they don't need codec entries
236
+ const projectionCodecs: Record<string, string> = {};
237
+ for (let i = 0; i < args.projection.aliases.length; i++) {
238
+ const alias = args.projection.aliases[i];
239
+ if (!alias || includeAliases.has(alias)) {
240
+ continue;
241
+ }
242
+ const column = args.projection.columns[i];
243
+ if (!column) {
244
+ continue;
245
+ }
246
+ const operationExpr = (column as { _operationExpr?: OperationExpr })._operationExpr;
247
+ if (operationExpr) {
248
+ if (operationExpr.returns.kind === 'typeId') {
249
+ projectionCodecs[alias] = operationExpr.returns.type;
250
+ }
251
+ } else {
252
+ // Use columnMeta.codecId directly as typeId (already canonicalized)
253
+ // TypeScript can't narrow ColumnBuilder properly
254
+ const col = column as unknown as { columnMeta?: { codecId: string } };
255
+ const columnMeta = col.columnMeta;
256
+ const codecId = columnMeta?.codecId;
257
+ if (codecId) {
258
+ projectionCodecs[alias] = codecId;
259
+ }
260
+ }
261
+ }
262
+
263
+ // Merge projection and parameter codecs
264
+ const allCodecs: Record<string, string> = {
265
+ ...projectionCodecs,
266
+ ...(args.paramCodecs ? args.paramCodecs : {}),
267
+ };
268
+
269
+ return Object.freeze(
270
+ compact({
271
+ target: args.contract.target,
272
+ targetFamily: args.contract.targetFamily,
273
+ coreHash: args.contract.coreHash,
274
+ lane: 'dsl',
275
+ refs: {
276
+ tables: Array.from(refsTables),
277
+ columns: Array.from(refsColumns.values()),
278
+ },
279
+ projection: projectionMap,
280
+ projectionTypes: Object.keys(projectionTypes).length > 0 ? projectionTypes : undefined,
281
+ annotations:
282
+ Object.keys(allCodecs).length > 0
283
+ ? Object.freeze({ codecs: Object.freeze(allCodecs) })
284
+ : undefined,
285
+ paramDescriptors: args.paramDescriptors,
286
+ profileHash: args.contract.profileHash,
287
+ }) as PlanMeta,
288
+ );
289
+ }
@@ -0,0 +1,130 @@
1
+ import type { ParamDescriptor } from '@prisma-next/contract/types';
2
+ import type { SqlContract, SqlStorage, StorageColumn } from '@prisma-next/sql-contract/types';
3
+ import type {
4
+ BinaryExpr,
5
+ ColumnRef,
6
+ OperationExpr,
7
+ ParamRef,
8
+ } from '@prisma-next/sql-relational-core/ast';
9
+ import {
10
+ createBinaryExpr,
11
+ createColumnRef,
12
+ createParamRef,
13
+ } from '@prisma-next/sql-relational-core/ast';
14
+ import type { BinaryBuilder, ParamPlaceholder } from '@prisma-next/sql-relational-core/types';
15
+ import {
16
+ getColumnInfo,
17
+ getOperationExpr,
18
+ isColumnBuilder,
19
+ isParamPlaceholder,
20
+ } from '@prisma-next/sql-relational-core/utils/guards';
21
+ import {
22
+ errorFailedToBuildWhereClause,
23
+ errorMissingParameter,
24
+ errorUnknownColumn,
25
+ errorUnknownTable,
26
+ } from '../utils/errors';
27
+
28
+ export interface BuildWhereExprResult {
29
+ expr: BinaryExpr;
30
+ codecId: string | undefined;
31
+ paramName: string;
32
+ }
33
+
34
+ export function buildWhereExpr(
35
+ contract: SqlContract<SqlStorage>,
36
+ where: BinaryBuilder,
37
+ paramsMap: Record<string, unknown>,
38
+ descriptors: ParamDescriptor[],
39
+ values: unknown[],
40
+ ): BuildWhereExprResult {
41
+ let leftExpr: ColumnRef | OperationExpr;
42
+ let codecId: string | undefined;
43
+ let rightExpr: ColumnRef | ParamRef;
44
+ let paramName: string;
45
+
46
+ // Check if where.left is an OperationExpr directly (from operation.eq())
47
+ // or a ColumnBuilder with _operationExpr property
48
+ const operationExpr = getOperationExpr(where.left);
49
+ if (operationExpr) {
50
+ leftExpr = operationExpr;
51
+ } else if (isColumnBuilder(where.left)) {
52
+ // where.left is a ColumnBuilder - use proper type narrowing
53
+ const { table, column } = getColumnInfo(where.left);
54
+
55
+ const contractTable = contract.storage.tables[table];
56
+ if (!contractTable) {
57
+ errorUnknownTable(table);
58
+ }
59
+
60
+ const columnMeta: StorageColumn | undefined = contractTable.columns[column];
61
+ if (!columnMeta) {
62
+ errorUnknownColumn(column, table);
63
+ }
64
+
65
+ codecId = columnMeta.codecId;
66
+ leftExpr = createColumnRef(table, column);
67
+ } else {
68
+ // where.left is neither OperationExpr nor ColumnBuilder - invalid state
69
+ errorFailedToBuildWhereClause();
70
+ }
71
+
72
+ // Handle where.right - can be ParamPlaceholder or AnyColumnBuilder
73
+ if (isParamPlaceholder(where.right)) {
74
+ // Handle param placeholder (existing logic)
75
+ const placeholder: ParamPlaceholder = where.right;
76
+ paramName = placeholder.name;
77
+
78
+ if (!Object.hasOwn(paramsMap, paramName)) {
79
+ errorMissingParameter(paramName);
80
+ }
81
+
82
+ const value = paramsMap[paramName];
83
+ const index = values.push(value);
84
+
85
+ // Construct descriptor directly from validated StorageColumn
86
+ if (isColumnBuilder(where.left)) {
87
+ const { table, column } = getColumnInfo(where.left);
88
+ const contractTable = contract.storage.tables[table];
89
+ const columnMeta = contractTable?.columns[column];
90
+ if (columnMeta) {
91
+ descriptors.push({
92
+ name: paramName,
93
+ source: 'dsl',
94
+ refs: { table, column },
95
+ nullable: columnMeta.nullable,
96
+ codecId: columnMeta.codecId,
97
+ nativeType: columnMeta.nativeType,
98
+ });
99
+ }
100
+ }
101
+
102
+ rightExpr = createParamRef(index, paramName);
103
+ } else if (isColumnBuilder(where.right)) {
104
+ // Handle column builder on the right
105
+ const { table, column } = getColumnInfo(where.right);
106
+
107
+ const contractTable = contract.storage.tables[table];
108
+ if (!contractTable) {
109
+ errorUnknownTable(table);
110
+ }
111
+
112
+ const columnMeta: StorageColumn | undefined = contractTable.columns[column];
113
+ if (!columnMeta) {
114
+ errorUnknownColumn(column, table);
115
+ }
116
+
117
+ rightExpr = createColumnRef(table, column);
118
+ // Use a placeholder paramName for column references (not used for params)
119
+ paramName = '';
120
+ } else {
121
+ // where.right is neither ParamPlaceholder nor ColumnBuilder - invalid state
122
+ errorFailedToBuildWhereClause();
123
+ }
124
+
125
+ return {
126
+ expr: createBinaryExpr(where.op, leftExpr, rightExpr),
127
+ codecId,
128
+ paramName,
129
+ };
130
+ }
@@ -0,0 +1,112 @@
1
+ import type { TableRef } from '@prisma-next/sql-relational-core/ast';
2
+ import type { AnyColumnBuilder, NestedProjection } from '@prisma-next/sql-relational-core/types';
3
+ import { isColumnBuilder } from '@prisma-next/sql-relational-core/utils/guards';
4
+ import type { ProjectionInput } from '../types/internal';
5
+ import {
6
+ errorAliasCollision,
7
+ errorAliasPathEmpty,
8
+ errorIncludeAliasNotFound,
9
+ errorInvalidProjectionKey,
10
+ errorInvalidProjectionValue,
11
+ errorProjectionEmpty,
12
+ } from '../utils/errors';
13
+ import type { IncludeState, ProjectionState } from '../utils/state';
14
+
15
+ export function generateAlias(path: string[]): string {
16
+ if (path.length === 0) {
17
+ errorAliasPathEmpty();
18
+ }
19
+ return path.join('_');
20
+ }
21
+
22
+ export class AliasTracker {
23
+ private readonly aliases = new Set<string>();
24
+ private readonly aliasToPath = new Map<string, string[]>();
25
+
26
+ register(path: string[]): string {
27
+ const alias = generateAlias(path);
28
+ if (this.aliases.has(alias)) {
29
+ const existingPath = this.aliasToPath.get(alias);
30
+ errorAliasCollision(path, alias, existingPath);
31
+ }
32
+ this.aliases.add(alias);
33
+ this.aliasToPath.set(alias, path);
34
+ return alias;
35
+ }
36
+
37
+ getPath(alias: string): string[] | undefined {
38
+ return this.aliasToPath.get(alias);
39
+ }
40
+
41
+ has(alias: string): boolean {
42
+ return this.aliases.has(alias);
43
+ }
44
+ }
45
+
46
+ export function flattenProjection(
47
+ projection: NestedProjection,
48
+ tracker: AliasTracker,
49
+ currentPath: string[] = [],
50
+ ): { aliases: string[]; columns: AnyColumnBuilder[] } {
51
+ const aliases: string[] = [];
52
+ const columns: AnyColumnBuilder[] = [];
53
+
54
+ for (const [key, value] of Object.entries(projection)) {
55
+ const path = [...currentPath, key];
56
+
57
+ if (isColumnBuilder(value)) {
58
+ const alias = tracker.register(path);
59
+ aliases.push(alias);
60
+ columns.push(value);
61
+ } else if (typeof value === 'object' && value !== null) {
62
+ const nested = flattenProjection(value, tracker, path);
63
+ aliases.push(...nested.aliases);
64
+ columns.push(...nested.columns);
65
+ } else {
66
+ errorInvalidProjectionValue(path);
67
+ }
68
+ }
69
+
70
+ return { aliases, columns };
71
+ }
72
+
73
+ export function buildProjectionState(
74
+ _table: TableRef,
75
+ projection: ProjectionInput,
76
+ includes?: ReadonlyArray<IncludeState>,
77
+ ): ProjectionState {
78
+ const tracker = new AliasTracker();
79
+ const aliases: string[] = [];
80
+ const columns: (AnyColumnBuilder | null)[] = [];
81
+
82
+ for (const [key, value] of Object.entries(projection)) {
83
+ if (value === true) {
84
+ // Boolean true means this is an include reference
85
+ const matchingInclude = includes?.find((inc) => inc.alias === key);
86
+ if (!matchingInclude) {
87
+ errorIncludeAliasNotFound(key);
88
+ }
89
+ // For include references, we track the alias but use null as placeholder
90
+ // The actual handling happens in AST building where we create includeRef
91
+ // Using null instead of invalid ColumnBuilder avoids code smell
92
+ aliases.push(key);
93
+ columns.push(null);
94
+ } else if (isColumnBuilder(value)) {
95
+ const alias = tracker.register([key]);
96
+ aliases.push(alias);
97
+ columns.push(value);
98
+ } else if (typeof value === 'object' && value !== null) {
99
+ const nested = flattenProjection(value as NestedProjection, tracker, [key]);
100
+ aliases.push(...nested.aliases);
101
+ columns.push(...nested.columns);
102
+ } else {
103
+ errorInvalidProjectionKey(key);
104
+ }
105
+ }
106
+
107
+ if (aliases.length === 0) {
108
+ errorProjectionEmpty();
109
+ }
110
+
111
+ return { aliases, columns };
112
+ }