@prisma-next/sql-builder 0.13.0 → 0.14.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.
@@ -4,7 +4,7 @@ import type { RawCodecInferer } from '@prisma-next/sql-relational-core/expressio
4
4
  import type { ExecutionContext } from '@prisma-next/sql-relational-core/query-lane-context';
5
5
  import type { Db, TableProxyContract } from '../types/db';
6
6
  import type { BuilderContext } from './builder-base';
7
- import { resolveTableForFlatName, resolveTableInNamespace } from './resolve-table';
7
+ import { resolveTableInNamespace } from './resolve-table';
8
8
  import { TableProxyImpl } from './table-proxy-impl';
9
9
 
10
10
  export interface SqlOptions<C extends Contract<SqlStorage> & TableProxyContract> {
@@ -29,27 +29,32 @@ export function sql<C extends Contract<SqlStorage> & TableProxyContract>(
29
29
  const { storage } = context.contract;
30
30
 
31
31
  return new Proxy({} as Db<C>, {
32
- get(_target, prop: string) {
33
- if (Object.hasOwn(storage.namespaces, prop)) {
34
- const namespaceId = prop;
35
- return new Proxy(
36
- {},
37
- {
38
- get(_facetTarget, tableName: string) {
39
- const table = resolveTableInNamespace(storage, namespaceId, tableName);
40
- if (table) {
41
- return new TableProxyImpl(tableName, table, tableName, ctx, namespaceId);
42
- }
43
- return undefined;
44
- },
45
- },
46
- );
32
+ get(_target, prop: string | symbol) {
33
+ if (typeof prop !== 'string') {
34
+ return undefined;
47
35
  }
48
- const resolved = resolveTableForFlatName(storage, prop);
49
- if (resolved) {
50
- return new TableProxyImpl(prop, resolved.table, prop, ctx, resolved.namespaceId);
36
+ if (!Object.hasOwn(storage.namespaces, prop)) {
37
+ return undefined;
51
38
  }
52
- return undefined;
39
+ const namespaceId = prop;
40
+ return new Proxy(
41
+ {},
42
+ {
43
+ get(_facetTarget, tableName: string | symbol) {
44
+ if (typeof tableName !== 'string') {
45
+ return undefined;
46
+ }
47
+ const table = resolveTableInNamespace(storage, namespaceId, tableName);
48
+ if (table) {
49
+ // `namespaceId` is a dynamic Proxy key with no static literal here, so the
50
+ // proxy's `NsId` type param lands on its `string` default at this boundary.
51
+ // `TableProxyImpl` still forwards `NsId` through its `as()`/join chain.
52
+ return new TableProxyImpl(tableName, table, tableName, ctx, namespaceId);
53
+ }
54
+ return undefined;
55
+ },
56
+ },
57
+ );
53
58
  },
54
59
  });
55
60
  }
@@ -25,7 +25,7 @@ import type {
25
25
  StorageTableToScopeTable,
26
26
  Subquery,
27
27
  } from '../scope';
28
- import type { TableProxyContract, UnboundTables } from '../types/db';
28
+ import type { NamespaceTable, TableProxyContract } from '../types/db';
29
29
  import type { JoinedTables } from '../types/joined-tables';
30
30
  import type { DeleteQuery, InsertQuery, UpdateQuery } from '../types/mutation-query';
31
31
  import type { SelectQuery } from '../types/select-query';
@@ -47,16 +47,17 @@ import { tableSourceForProxy } from './table-source-for-proxy';
47
47
 
48
48
  export class TableProxyImpl<
49
49
  C extends TableProxyContract,
50
- Name extends string & keyof UnboundTables<C>,
50
+ Name extends string,
51
51
  Alias extends string,
52
52
  AvailableScope extends Scope,
53
53
  QC extends QueryContext,
54
+ NsId extends string = string,
54
55
  >
55
56
  extends BuilderBase<C['capabilities']>
56
- implements TableProxy<C, Name, Alias, AvailableScope, QC>
57
+ implements TableProxy<C, NsId, Name, Alias, AvailableScope, QC>
57
58
  {
58
59
  declare readonly [JoinOuterScope]: JoinSource<
59
- StorageTableToScopeTable<UnboundTables<C>[Name]>,
60
+ StorageTableToScopeTable<NamespaceTable<C, NsId, Name>>,
60
61
  Alias
61
62
  >[typeof JoinOuterScope];
62
63
 
@@ -97,7 +98,7 @@ export class TableProxyImpl<
97
98
  > => {
98
99
  return this.#toJoined().lateralJoin(alias, builder);
99
100
  },
100
- ) as TableProxy<C, Name, Alias, AvailableScope, QC>['lateralJoin'];
101
+ ) as TableProxy<C, NsId, Name, Alias, AvailableScope, QC>['lateralJoin'];
101
102
 
102
103
  outerLateralJoin = this._gate(
103
104
  { sql: { lateral: true } },
@@ -114,7 +115,7 @@ export class TableProxyImpl<
114
115
  > => {
115
116
  return this.#toJoined().outerLateralJoin(alias, builder);
116
117
  },
117
- ) as TableProxy<C, Name, Alias, AvailableScope, QC>['outerLateralJoin'];
118
+ ) as TableProxy<C, NsId, Name, Alias, AvailableScope, QC>['outerLateralJoin'];
118
119
 
119
120
  getJoinOuterScope(): Scope {
120
121
  return this.#scope;
@@ -126,8 +127,15 @@ export class TableProxyImpl<
126
127
 
127
128
  as<NewAlias extends string>(
128
129
  newAlias: NewAlias,
129
- ): TableProxy<C, Name, NewAlias, RebindScope<AvailableScope, Alias, NewAlias>, QC> {
130
- return new TableProxyImpl(this.#tableName, this.#table, newAlias, this.ctx, this.#namespaceId);
130
+ ): TableProxy<C, NsId, Name, NewAlias, RebindScope<AvailableScope, Alias, NewAlias>, QC> {
131
+ return new TableProxyImpl<
132
+ C,
133
+ Name,
134
+ NewAlias,
135
+ RebindScope<AvailableScope, Alias, NewAlias>,
136
+ QC,
137
+ NsId
138
+ >(this.#tableName, this.#table, newAlias, this.ctx, this.#namespaceId);
131
139
  }
132
140
 
133
141
  select<Columns extends (keyof AvailableScope['topLevel'] & string)[]>(
package/src/types/db.ts CHANGED
@@ -3,24 +3,33 @@ import type { TableProxy } from './table-proxy';
3
3
 
4
4
  export type CapabilitiesBase = Record<string, Record<string, boolean>>;
5
5
 
6
- // The sql-builder DSL is flat by table name across every declared
7
- // namespace. Two namespaces declaring tables with the same name produce
8
- // a union at the DSL surface (which collapses to a type error at the
9
- // first call site); landing the namespace-aware DSL surface (db.<ns>.<table>)
10
- // is tracked separately. Within scope here: the DSL accepts the
11
- // namespaced storage shape directly and walks every namespace.
6
+ type NamespaceEntries = Readonly<Record<string, Readonly<Record<string, unknown>>>>;
7
+
8
+ // The application-domain models the table-proxy helpers read to map a storage
9
+ // table -> model and column -> field within a namespace coordinate. The index
10
+ // signature lets the helpers index `C['domain']['namespaces'][NsId]` by a
11
+ // generic `NsId` directly, so `FindModelForTable` / `FindFieldForColumn` no
12
+ // longer need a `C extends Contract<SqlStorage>` guard to reach the per-namespace
13
+ // models; concrete emitted contracts (whose models are richer) satisfy it.
14
+ type NamespaceDomain = Readonly<
15
+ Record<string, { readonly models: Readonly<Record<string, unknown>> }>
16
+ >;
17
+
12
18
  export type TableProxyContract = {
19
+ readonly domain: {
20
+ readonly namespaces: NamespaceDomain;
21
+ };
13
22
  readonly storage: {
14
- readonly namespaces: Readonly<
15
- Record<
16
- string,
17
- { readonly entries: { readonly table: Readonly<Record<string, StorageTable>> } }
18
- >
19
- >;
23
+ readonly namespaces: Readonly<Record<string, { readonly entries: NamespaceEntries }>>;
20
24
  };
21
25
  readonly capabilities: CapabilitiesBase;
22
26
  };
23
27
 
28
+ type TablesInNamespace<NS extends { readonly entries: NamespaceEntries }> =
29
+ NS['entries']['table'] extends Readonly<Record<string, StorageTable>>
30
+ ? NS['entries']['table']
31
+ : Readonly<Record<string, StorageTable>>;
32
+
24
33
  // Union of every table name declared in any namespace of `C`. Replaces
25
34
  // the prior `UnboundTables<C>` indexing (which only saw `__unbound__`).
26
35
  export type UnboundTables<C extends TableProxyContract> = {
@@ -28,31 +37,48 @@ export type UnboundTables<C extends TableProxyContract> = {
28
37
  };
29
38
 
30
39
  export type TableNamesAcrossNamespaces<C extends TableProxyContract> = {
31
- [NSId in keyof C['storage']['namespaces']]: keyof C['storage']['namespaces'][NSId]['entries']['table'] &
40
+ [NSId in keyof C['storage']['namespaces']]: keyof TablesInNamespace<
41
+ C['storage']['namespaces'][NSId]
42
+ > &
32
43
  string;
33
44
  }[keyof C['storage']['namespaces']];
34
45
 
35
46
  export type TableInAnyNamespace<C extends TableProxyContract, Name extends string> = {
36
- [NSId in keyof C['storage']['namespaces']]: Name extends keyof C['storage']['namespaces'][NSId]['entries']['table']
37
- ? C['storage']['namespaces'][NSId]['entries']['table'][Name]
47
+ [NSId in keyof C['storage']['namespaces']]: Name extends keyof TablesInNamespace<
48
+ C['storage']['namespaces'][NSId]
49
+ >
50
+ ? TablesInNamespace<C['storage']['namespaces'][NSId]>[Name]
38
51
  : never;
39
52
  }[keyof C['storage']['namespaces']];
40
53
 
54
+ // The exact storage table at a single namespace coordinate. Resolving through
55
+ // the coordinate (rather than the cross-namespace `UnboundTables` union) keeps
56
+ // a bare table name shared across namespaces resolving to each namespace's own
57
+ // table — no per-namespace column intersection. `TablesInNamespace` narrows the
58
+ // open-dict `entries['table']` (`Record<string, unknown>`) back to the typed
59
+ // `StorageTable` map before indexing by the bare name.
60
+ export type NamespaceTable<
61
+ C extends TableProxyContract,
62
+ NsId extends string,
63
+ Name extends string,
64
+ > = TablesInNamespace<C['storage']['namespaces'][NsId]>[Name];
65
+
41
66
  // The tables of a single storage namespace, keyed by bare table name. Lets
42
67
  // callers reach a table by its namespace coordinate (`db.<ns>.<table>`) when
43
- // the same bare name is declared in more than one namespace.
68
+ // the same bare name is declared in more than one namespace. The `NsId`
69
+ // coordinate is threaded into each `TableProxy` so its column/field resolution
70
+ // is a function of `(NsId, Name)`, not `Name` alone.
44
71
  export type Namespace<
45
72
  C extends TableProxyContract,
46
- NsId extends keyof C['storage']['namespaces'],
73
+ NsId extends string & keyof C['storage']['namespaces'],
47
74
  > = {
48
- readonly [Name in keyof C['storage']['namespaces'][NsId]['entries']['table'] &
49
- string]: TableProxy<C, Name>;
75
+ readonly [Name in keyof TablesInNamespace<C['storage']['namespaces'][NsId]> & string]: TableProxy<
76
+ C,
77
+ NsId,
78
+ Name
79
+ >;
50
80
  };
51
81
 
52
- // Additive intersection: the flat by-bare-name surface retained alongside a
53
- // per-namespace facet keyed by namespace id.
54
82
  export type Db<C extends TableProxyContract> = {
55
- readonly [Name in TableNamesAcrossNamespaces<C>]: TableProxy<C, Name>;
56
- } & {
57
- readonly [Ns in keyof C['storage']['namespaces']]: Namespace<C, Ns>;
83
+ readonly [Ns in keyof C['storage']['namespaces'] & string]: Namespace<C, Ns>;
58
84
  };
@@ -1,11 +1,10 @@
1
- import type { Contract, ContractModelDefinitions } from '@prisma-next/contract/types';
2
1
  import type {
3
2
  ExtractCodecTypes,
4
3
  ExtractFieldInputTypes,
5
- ExtractFieldOutputTypes,
6
4
  ExtractQueryOperationTypes,
7
5
  StorageTable,
8
6
  } from '@prisma-next/sql-contract/types';
7
+ import type { ComputeColumnJsType } from '@prisma-next/sql-relational-core/types';
9
8
  import type { Expression, FieldProxy, Functions } from '../expression';
10
9
  import type {
11
10
  DefaultScope,
@@ -16,98 +15,111 @@ import type {
16
15
  Scope,
17
16
  StorageTableToScopeTable,
18
17
  } from '../scope';
19
- import type { TableProxyContract, UnboundTables } from './db';
18
+ import type { NamespaceTable, TableProxyContract } from './db';
20
19
  import type { DeleteQuery, InsertQuery, InsertValues, UpdateQuery } from './mutation-query';
21
20
  import type { WithJoin, WithSelect } from './shared';
22
21
 
23
- type FindModelForTable<C, TableName extends string> = C extends Contract
22
+ type FindModelForTable<
23
+ C extends TableProxyContract,
24
+ NsId extends string,
25
+ TableName extends string,
26
+ > = C['domain']['namespaces'][NsId]['models'] extends infer Models extends Record<string, unknown>
24
27
  ? {
25
- [M in keyof ContractModelDefinitions<C> & string]: ContractModelDefinitions<C>[M] extends {
28
+ [M in keyof Models & string]: Models[M] extends {
26
29
  readonly storage: { readonly table: TableName };
27
30
  }
28
31
  ? M
29
32
  : never;
30
- }[keyof ContractModelDefinitions<C> & string]
33
+ }[keyof Models & string]
31
34
  : never;
32
35
 
33
- type FindFieldForColumn<C, ModelName extends string, ColumnName extends string> = C extends Contract
34
- ? ModelName extends keyof ContractModelDefinitions<C>
35
- ? {
36
- [F in keyof ContractModelDefinitions<C>[ModelName]['storage']['fields'] &
37
- string]: ContractModelDefinitions<C>[ModelName]['storage']['fields'][F] extends {
38
- readonly column: ColumnName;
39
- }
40
- ? F
41
- : never;
42
- }[keyof ContractModelDefinitions<C>[ModelName]['storage']['fields'] & string]
36
+ type FindFieldForColumn<
37
+ C extends TableProxyContract,
38
+ NsId extends string,
39
+ ModelName extends string,
40
+ ColumnName extends string,
41
+ > = C['domain']['namespaces'][NsId]['models'] extends infer Models extends Record<string, unknown>
42
+ ? ModelName extends keyof Models
43
+ ? Models[ModelName] extends {
44
+ readonly storage: { readonly fields: infer Fields extends Record<string, unknown> };
45
+ }
46
+ ? {
47
+ [F in keyof Fields & string]: Fields[F] extends { readonly column: ColumnName }
48
+ ? F
49
+ : never;
50
+ }[keyof Fields & string]
51
+ : never
43
52
  : never
44
53
  : never;
45
54
 
55
+ // The select-result row's column types for a table at a namespace coordinate.
56
+ // Each column resolves through `ComputeColumnJsType` with the coordinate, so
57
+ // refined per-namespace output types (e.g. `Vector<N>`) are preserved and
58
+ // same-named tables across namespaces resolve to each namespace's own columns.
59
+ // `ComputeColumnJsType`'s constraint is the minimal `ColumnResolutionContract`,
60
+ // which `TableProxyContract` satisfies, so `C` is indexed directly — no
61
+ // `C extends Contract<SqlStorage>` guard.
46
62
  type ResolvedColumnTypes<
47
- C,
63
+ C extends TableProxyContract,
64
+ NsId extends string,
48
65
  TableName extends string,
49
- FieldTypes extends Record<string, Record<string, unknown>>,
50
- > = string extends keyof FieldTypes
51
- ? Record<string, never>
52
- : FindModelForTable<C, TableName> extends infer ModelName extends string
53
- ? ModelName extends keyof FieldTypes
54
- ? C extends TableProxyContract
55
- ? UnboundTables<C> extends infer Tables extends Record<string, StorageTable>
56
- ? TableName extends keyof Tables
57
- ? {
58
- [ColName in keyof Tables[TableName]['columns'] & string]: FindFieldForColumn<
59
- C,
60
- ModelName,
61
- ColName
62
- > extends infer FieldName extends string
63
- ? FieldName extends keyof FieldTypes[ModelName]
64
- ? FieldTypes[ModelName][FieldName]
65
- : unknown
66
- : unknown;
67
- }
68
- : Record<string, never>
69
- : Record<string, never>
70
- : Record<string, never>
71
- : Record<string, never>
66
+ > =
67
+ NamespaceTable<C, NsId, TableName> extends infer Table extends StorageTable
68
+ ? {
69
+ [ColName in keyof Table['columns'] & string]: ComputeColumnJsType<
70
+ C,
71
+ NsId,
72
+ TableName,
73
+ ColName,
74
+ ExtractCodecTypes<C>
75
+ >;
76
+ }
72
77
  : Record<string, never>;
73
78
 
74
79
  type ResolvedInsertValues<
75
- C,
80
+ C extends TableProxyContract,
81
+ NsId extends string,
76
82
  Table extends StorageTable,
77
83
  TableName extends string,
78
84
  CT extends Record<string, { readonly input: unknown }>,
79
- FieldInputs extends Record<string, Record<string, unknown>>,
85
+ FieldInputs extends Record<string, Record<string, Record<string, unknown>>>,
80
86
  > = string extends keyof FieldInputs
81
87
  ? InsertValues<Table, CT>
82
- : FindModelForTable<C, TableName> extends infer ModelName extends string
83
- ? ModelName extends keyof FieldInputs
84
- ? {
85
- [K in keyof Table['columns']]?: FindFieldForColumn<
86
- C,
87
- ModelName,
88
- K & string
89
- > extends infer FieldName extends string
90
- ? FieldName extends keyof FieldInputs[ModelName]
91
- ? Table['columns'][K]['nullable'] extends true
92
- ? NonNullable<FieldInputs[ModelName][FieldName]> | null
93
- : NonNullable<FieldInputs[ModelName][FieldName]>
94
- : Table['columns'][K]['codecId'] extends keyof CT
95
- ? CT[Table['columns'][K]['codecId']]['input']
96
- : unknown
97
- : Table['columns'][K]['codecId'] extends keyof CT
98
- ? CT[Table['columns'][K]['codecId']]['input']
99
- : unknown;
100
- }
88
+ : NsId extends keyof FieldInputs
89
+ ? FieldInputs[NsId] extends infer NsInputs extends Record<string, Record<string, unknown>>
90
+ ? FindModelForTable<C, NsId, TableName> extends infer ModelName extends string
91
+ ? ModelName extends keyof NsInputs
92
+ ? {
93
+ [K in keyof Table['columns']]?: FindFieldForColumn<
94
+ C,
95
+ NsId,
96
+ ModelName,
97
+ K & string
98
+ > extends infer FieldName extends string
99
+ ? FieldName extends keyof NsInputs[ModelName]
100
+ ? Table['columns'][K]['nullable'] extends true
101
+ ? NonNullable<NsInputs[ModelName][FieldName]> | null
102
+ : NonNullable<NsInputs[ModelName][FieldName]>
103
+ : Table['columns'][K]['codecId'] extends keyof CT
104
+ ? CT[Table['columns'][K]['codecId']]['input']
105
+ : unknown
106
+ : Table['columns'][K]['codecId'] extends keyof CT
107
+ ? CT[Table['columns'][K]['codecId']]['input']
108
+ : unknown;
109
+ }
110
+ : InsertValues<Table, CT>
111
+ : InsertValues<Table, CT>
101
112
  : InsertValues<Table, CT>
102
113
  : InsertValues<Table, CT>;
103
114
 
104
115
  type ResolvedUpdateValues<
105
- C,
116
+ C extends TableProxyContract,
117
+ NsId extends string,
106
118
  Table extends StorageTable,
107
119
  TableName extends string,
108
120
  CT extends Record<string, { readonly input: unknown }>,
109
- FieldInputs extends Record<string, Record<string, unknown>>,
110
- > = ResolvedInsertValues<C, Table, TableName, CT, FieldInputs>;
121
+ FieldInputs extends Record<string, Record<string, Record<string, unknown>>>,
122
+ > = ResolvedInsertValues<C, NsId, Table, TableName, CT, FieldInputs>;
111
123
 
112
124
  type ResolvedUpdateExpressions<Table extends StorageTable> = {
113
125
  [K in keyof Table['columns']]?: Expression<{
@@ -116,31 +128,37 @@ type ResolvedUpdateExpressions<Table extends StorageTable> = {
116
128
  }>;
117
129
  };
118
130
 
119
- export type ContractToQC<C extends TableProxyContract, Name extends string = string> = {
131
+ export type ContractToQC<
132
+ C extends TableProxyContract,
133
+ NsId extends string = string,
134
+ Name extends string = string,
135
+ > = {
120
136
  readonly codecTypes: ExtractCodecTypes<C>;
121
137
  readonly capabilities: C['capabilities'];
122
138
  readonly queryOperationTypes: ExtractQueryOperationTypes<C>;
123
- readonly resolvedColumnOutputTypes: ResolvedColumnTypes<C, Name, ExtractFieldOutputTypes<C>>;
139
+ readonly resolvedColumnOutputTypes: ResolvedColumnTypes<C, NsId, Name>;
124
140
  };
125
141
 
126
142
  export interface TableProxy<
127
143
  C extends TableProxyContract,
128
- Name extends string & keyof UnboundTables<C>,
144
+ NsId extends string,
145
+ Name extends string,
129
146
  Alias extends string = Name,
130
- AvailableScope extends Scope = DefaultScope<Name, UnboundTables<C>[Name]>,
131
- QC extends QueryContext = ContractToQC<C, Name>,
132
- > extends JoinSource<StorageTableToScopeTable<UnboundTables<C>[Name]>, Alias>,
147
+ AvailableScope extends Scope = DefaultScope<Name, NamespaceTable<C, NsId, Name>>,
148
+ QC extends QueryContext = ContractToQC<C, NsId, Name>,
149
+ > extends JoinSource<StorageTableToScopeTable<NamespaceTable<C, NsId, Name>>, Alias>,
133
150
  WithSelect<QC, AvailableScope, EmptyRow>,
134
151
  WithJoin<QC, AvailableScope, C['capabilities']> {
135
152
  as<NewAlias extends string>(
136
153
  newAlias: NewAlias,
137
- ): TableProxy<C, Name, NewAlias, RebindScope<AvailableScope, Alias, NewAlias>, QC>;
154
+ ): TableProxy<C, NsId, Name, NewAlias, RebindScope<AvailableScope, Alias, NewAlias>, QC>;
138
155
 
139
156
  insert(
140
157
  rows: ReadonlyArray<
141
158
  ResolvedInsertValues<
142
159
  C,
143
- UnboundTables<C>[Name],
160
+ NsId,
161
+ NamespaceTable<C, NsId, Name>,
144
162
  Name,
145
163
  QC['codecTypes'],
146
164
  ExtractFieldInputTypes<C>
@@ -151,7 +169,8 @@ export interface TableProxy<
151
169
  update(
152
170
  set: ResolvedUpdateValues<
153
171
  C,
154
- UnboundTables<C>[Name],
172
+ NsId,
173
+ NamespaceTable<C, NsId, Name>,
155
174
  Name,
156
175
  QC['codecTypes'],
157
176
  ExtractFieldInputTypes<C>
@@ -162,7 +181,7 @@ export interface TableProxy<
162
181
  callback: (
163
182
  fields: FieldProxy<AvailableScope>,
164
183
  fns: Functions<QC>,
165
- ) => ResolvedUpdateExpressions<UnboundTables<C>[Name]>,
184
+ ) => ResolvedUpdateExpressions<NamespaceTable<C, NsId, Name>>,
166
185
  ): UpdateQuery<QC, AvailableScope, EmptyRow>;
167
186
 
168
187
  delete(): DeleteQuery<QC, AvailableScope, EmptyRow>;