@query-graph/core 1.0.0-next.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +189 -91
  2. package/definition.d.ts +117 -39
  3. package/dsl.d.ts +789 -98
  4. package/index.js +50 -50
  5. package/package.json +5 -5
package/README.md CHANGED
@@ -55,51 +55,65 @@ import {
55
55
  source,
56
56
  } from '@query-graph/core/dsl'
57
57
 
58
- const link = source('link', {
59
- idOwner: 'int64',
60
- idControllerObjectValue: 'int64',
58
+ const users = source('users', {
59
+ id: 'int64',
60
+ email: 'string',
61
61
  })
62
62
 
63
- const value = source('value', {
63
+ const profiles = source('profiles', {
64
64
  id: 'int64',
65
- value: nullable('string'),
65
+ userId: 'int64',
66
+ displayName: nullable('string'),
66
67
  })
67
68
 
68
- const idOwner = requiredParameter('idOwner', 'int64')
69
- const valueRelation = relation({
70
- name: 'value',
71
- from: link,
72
- to: value,
73
- on: eq(link.field('idControllerObjectValue'), value.field('id')),
69
+ const userId = requiredParameter('userId', 'int64')
70
+ const profileRelation = relation({
71
+ name: 'profile',
72
+ from: users,
73
+ to: profiles,
74
+ on: eq(users.field('id'), profiles.field('userId')),
75
+ cardinality: 'one',
74
76
  required: true,
77
+ selection: firstBy(asc(profiles.field('id'))),
75
78
  })
76
79
 
77
- const attributeValuesModule = defineGraphModule({
78
- name: 'attributeValues',
79
- sources: [link, value],
80
- parameters: [idOwner],
81
- relations: [valueRelation],
80
+ const userProfileModule = defineGraphModule({
81
+ name: 'userProfile',
82
+ sources: [users, profiles],
83
+ parameters: [userId],
84
+ relations: [profileRelation],
82
85
  constraints: [
83
86
  constraint({
84
- predicate: eq(link.field('idOwner'), param(idOwner)),
87
+ predicate: eq(users.field('id'), param(userId)),
85
88
  }),
86
89
  constraint({
87
- predicate: exists(value, isNotNull(value.field('value'))),
90
+ predicate: exists(profiles, isNotNull(profiles.field('displayName'))),
88
91
  }),
89
92
  ],
90
93
  projection: [
91
94
  project({
92
- path: 'value.value',
93
- expression: value.field('value'),
95
+ path: 'id',
96
+ expression: users.field('id'),
97
+ default: true,
98
+ }),
99
+ project({
100
+ path: 'profile.displayName',
101
+ expression: profiles.field('displayName'),
102
+ }),
103
+ ],
104
+ orderings: [
105
+ ordering({
106
+ name: 'idAsc',
107
+ by: [asc(users.field('id'))],
94
108
  default: true,
95
109
  }),
96
110
  ],
97
111
  })
98
112
 
99
113
  const definition = defineGraph({
100
- name: 'attributeValues',
101
- root: link,
102
- modules: [attributeValuesModule],
114
+ name: 'userProfile',
115
+ root: users,
116
+ modules: [userProfileModule],
103
117
  })
104
118
 
105
119
  const graph = registerDefinition(definition)
@@ -149,30 +163,30 @@ npm install @query-graph/core @query-graph/dsl-object
149
163
  import { registerDefinition } from '@query-graph/core'
150
164
  import { asc, defineGraph, eq, firstBy, project, relation, source } from '@query-graph/dsl-object'
151
165
 
152
- const staff = source('staff', { id: 'int64' })
153
- const personStaff = source('personStaff', {
166
+ const users = source('users', { id: 'int64' })
167
+ const profiles = source('profiles', {
154
168
  id: 'int64',
155
- idStaff: 'int64',
156
- idPerson: 'int64',
169
+ userId: 'int64',
170
+ displayName: 'string',
157
171
  })
158
172
 
159
173
  const definition = defineGraph({
160
- name: 'staff',
161
- root: staff,
162
- sources: [staff, personStaff],
174
+ name: 'users',
175
+ root: users,
176
+ sources: [users, profiles],
163
177
  relations: [
164
178
  relation({
165
- name: 'credentials',
166
- from: staff,
167
- to: personStaff,
168
- on: eq(staff.field('id'), personStaff.field('idStaff')),
179
+ name: 'profile',
180
+ from: users,
181
+ to: profiles,
182
+ on: eq(users.field('id'), profiles.field('userId')),
169
183
  cardinality: 'one',
170
- selection: firstBy(asc(personStaff.field('idPerson')), asc(personStaff.field('id'))),
184
+ selection: firstBy(asc(profiles.field('id'))),
171
185
  }),
172
186
  ],
173
187
  projection: [
174
- project({ path: 'id', expression: staff.field('id'), default: true }),
175
- project({ path: 'credentials.idPerson', expression: personStaff.field('idPerson') }),
188
+ project({ path: 'id', expression: users.field('id'), default: true }),
189
+ project({ path: 'profile.displayName', expression: profiles.field('displayName') }),
176
190
  ],
177
191
  })
178
192
 
@@ -194,7 +208,7 @@ const graph = registerDefinition(definition)
194
208
 
195
209
  ```ts
196
210
  constraint({
197
- predicate: exists(businessServiceStaff, eq(businessServiceStaff.field('idService'), param(idService))),
211
+ predicate: exists(memberships, eq(memberships.field('teamId'), param(teamId))),
198
212
  })
199
213
  ```
200
214
 
@@ -213,7 +227,7 @@ Oracle и SQL Server compiler строят коррелированный `EXIST
213
227
 
214
228
  ```ts
215
229
  constraint({
216
- predicate: exists(categoryLinks, categoryPredicate, { from: attributeValues }),
230
+ predicate: exists(permissions, permissionPredicate, { from: memberships }),
217
231
  })
218
232
  ```
219
233
 
@@ -228,28 +242,38 @@ constraint({
228
242
  `number | number[]`.
229
243
 
230
244
  ```ts
231
- const idServices = requiredListParameter('idServices', 'int64')
245
+ const users = source('users', {
246
+ id: 'int64',
247
+ })
248
+
249
+ const memberships = source('memberships', {
250
+ id: 'int64',
251
+ userId: 'int64',
252
+ teamId: 'int64',
253
+ })
254
+
255
+ const teamIds = requiredListParameter('teamIds', 'int64')
232
256
 
233
257
  const definition = defineGraph({
234
- name: 'staffByServices',
235
- root: staff,
236
- sources: [staff, businessServiceStaff],
237
- parameters: [idServices],
258
+ name: 'usersByTeams',
259
+ root: users,
260
+ sources: [users, memberships],
261
+ parameters: [teamIds],
238
262
  relations: [
239
263
  relation({
240
- name: 'staffServices',
241
- from: staff,
242
- to: businessServiceStaff,
243
- on: eq(staff.field('id'), businessServiceStaff.field('idStaff')),
264
+ name: 'memberships',
265
+ from: users,
266
+ to: memberships,
267
+ on: eq(users.field('id'), memberships.field('userId')),
244
268
  cardinality: 'many',
245
269
  }),
246
270
  ],
247
271
  constraints: [
248
272
  constraint({
249
- predicate: exists(businessServiceStaff, inParameter(businessServiceStaff.field('idService'), idServices)),
273
+ predicate: exists(memberships, inParameter(memberships.field('teamId'), teamIds)),
250
274
  }),
251
275
  ],
252
- projection: [project({ path: 'id', expression: staff.field('id'), default: true })],
276
+ projection: [project({ path: 'id', expression: users.field('id'), default: true })],
253
277
  })
254
278
  ```
255
279
 
@@ -257,15 +281,15 @@ Operation передает обычный массив:
257
281
 
258
282
  ```ts
259
283
  const statement = relationalGraph.compileSqlServer({
260
- parameters: { idServices: [12, 18, 24] },
284
+ parameters: { teamIds: [12, 18, 24] },
261
285
  })
262
286
  ```
263
287
 
264
288
  Compiler разворачивает его в `IN (@p0, @p1, @p2)`. Каждый элемент получает
265
- binding `{ parameter: 'idServices', index, scalarType: 'int64' }`, по которому
289
+ binding `{ parameter: 'teamIds', index, scalarType: 'int64' }`, по которому
266
290
  adapter исполнения берет значение из исходного массива. Rust проверяет каждый
267
291
  элемент отдельно и возвращает путь ошибки наподобие
268
- `parameters.idServices[2]`.
292
+ `parameters.teamIds[2]`.
269
293
 
270
294
  Отсутствующий optional list отключает constraint с `when`. Переданный пустой
271
295
  список остается присутствующим параметром и компилируется в ложный предикат
@@ -302,12 +326,12 @@ TypeScript выводит union имен из definition и подключенн
302
326
  одной строки:
303
327
 
304
328
  ```ts
305
- const credentials = relation({
306
- name: 'credentials',
307
- from: staff,
308
- to: personStaff,
309
- on: eq(staff.field('id'), personStaff.field('idStaff')),
310
- selection: firstBy(asc(personStaff.field('idPerson')), asc(personStaff.field('id'))),
329
+ const profile = relation({
330
+ name: 'profile',
331
+ from: users,
332
+ to: profiles,
333
+ on: eq(users.field('id'), profiles.field('userId')),
334
+ selection: firstBy(asc(profiles.field('id'))),
311
335
  })
312
336
  ```
313
337
 
@@ -327,46 +351,60 @@ SQL Server renderer использует `OUTER/CROSS APPLY` и `TOP (1)`, Oracl
327
351
  SQL-конструкции `GROUP BY` и `HAVING`:
328
352
 
329
353
  ```ts
330
- const serviceId = dimension({
331
- path: 'serviceId',
332
- expression: service.field('id'),
354
+ const teams = source('teams', {
355
+ id: 'int64',
356
+ workspaceId: 'int64',
357
+ })
358
+
359
+ const memberships = source('memberships', {
360
+ id: 'int64',
361
+ userId: 'int64',
362
+ teamId: 'int64',
363
+ })
364
+
365
+ const workspaceId = requiredParameter('workspaceId', 'int64')
366
+ const minimumMembers = requiredParameter('minimumMembers', 'int64')
367
+
368
+ const teamId = dimension({
369
+ path: 'teamId',
370
+ expression: teams.field('id'),
333
371
  default: true,
334
372
  })
335
373
 
336
- const staffCount = measure({
337
- path: 'staffCount',
338
- expression: countDistinct(serviceStaff.field('idStaff')),
374
+ const memberCount = measure({
375
+ path: 'memberCount',
376
+ expression: countDistinct(memberships.field('userId')),
339
377
  default: true,
340
378
  })
341
379
 
342
380
  const definition = defineSummaryGraph({
343
- name: 'serviceSummary',
344
- root: service,
345
- sources: [service, serviceStaff],
346
- parameters: [idOrganisation, minimumStaff],
381
+ name: 'teamSummary',
382
+ root: teams,
383
+ sources: [teams, memberships],
384
+ parameters: [workspaceId, minimumMembers],
347
385
  relations: [
348
386
  relation({
349
- name: 'staff',
350
- from: service,
351
- to: serviceStaff,
352
- on: eq(service.field('id'), serviceStaff.field('idService')),
387
+ name: 'members',
388
+ from: teams,
389
+ to: memberships,
390
+ on: eq(teams.field('id'), memberships.field('teamId')),
353
391
  cardinality: 'many',
354
392
  }),
355
393
  ],
356
394
  constraints: [
357
395
  constraint({
358
- predicate: eq(service.field('idOrganisation'), param(idOrganisation)),
396
+ predicate: eq(teams.field('workspaceId'), param(workspaceId)),
359
397
  }),
360
398
  constraint({
361
- predicate: gte(staffCount, param(minimumStaff)),
399
+ predicate: gte(memberCount, param(minimumMembers)),
362
400
  }),
363
401
  ],
364
- dimensions: [serviceId],
365
- measures: [staffCount],
402
+ dimensions: [teamId],
403
+ measures: [memberCount],
366
404
  orderings: [
367
405
  ordering({
368
- name: 'staffCountDesc',
369
- by: [desc(staffCount)],
406
+ name: 'memberCountDesc',
407
+ by: [desc(memberCount)],
370
408
  default: true,
371
409
  }),
372
410
  ],
@@ -443,10 +481,9 @@ TypeScript проверяет имена источников, полей и с
443
481
  const relationalGraph = registerDefinition(definition).withRelationalMapping(mapping)
444
482
 
445
483
  relationalGraph.compileSqlServer({
446
- select: ['id', 'credentials.idPerson'],
484
+ select: ['id', 'profile.displayName'],
447
485
  parameters: {
448
- idOrganisation: 42,
449
- personIds: [7, 11],
486
+ userId: 42,
450
487
  },
451
488
  })
452
489
  ```
@@ -459,6 +496,63 @@ JavaScript number и десятичная строка, а list-параметр
459
496
  типа. Эти проверки существуют только в декларациях TypeScript; сериализация
460
497
  definition, N-API вызов, runtime-валидация Rust и SQL compilation не меняются.
461
498
 
499
+ ### Тип результата проекции
500
+
501
+ DSL сохраняет scalar type и nullability каждого projection expression в compile-time metadata.
502
+ `ResultOf` строит из этих сведений вложенный тип результата по projection paths:
503
+
504
+ ```ts
505
+ import type { QueryOperation, ResultOf } from '@query-graph/core/dsl'
506
+
507
+ const operation = {
508
+ select: ['id', 'profile.displayName'],
509
+ parameters: { userId: 42 },
510
+ } as const satisfies QueryOperation<typeof definition>
511
+
512
+ type UserRow = ResultOf<typeof definition, typeof operation>
513
+ // {
514
+ // id: number | string
515
+ // profile: { displayName: string | null }
516
+ // }
517
+ ```
518
+
519
+ Если второй аргумент не передан, в результат входят только поля с `default: true`.
520
+ `ResultOf` также принимает тип, возвращенный `registerDefinition`, или mapped graph:
521
+
522
+ ```ts
523
+ type DefaultUserRow = ResultOf<typeof graph>
524
+ // { id: number | string }
525
+ ```
526
+
527
+ Nullability учитывает nullable-поле и всю цепочку optional relations до его source.
528
+ Required relation после optional relation не делает значение обязательным. Семантика
529
+ выражений тоже сохраняется: например, `coalesce(nullableField, 'fallback')` выводится
530
+ как non-null `string`.
531
+
532
+ Типы значений по умолчанию нейтральны к DB driver: `int64` и `decimal` представлены
533
+ как `number | string`, а `date`, `dateTime` и `binary` - как `string`. Адаптер
534
+ исполнения может передать третьим аргументом собственную полную scalar map:
535
+
536
+ ```ts
537
+ import type { DefaultScalarOutputTypeMap, ResultOf } from '@query-graph/core/dsl'
538
+
539
+ type DriverScalars = Omit<DefaultScalarOutputTypeMap, 'int64' | 'dateTime'> & {
540
+ int64: bigint
541
+ dateTime: Date
542
+ }
543
+
544
+ type DriverUserRow = ResultOf<typeof definition, typeof operation, DriverScalars>
545
+ ```
546
+
547
+ Для точного учета явного `select` operation должна сохранять literal tuple через
548
+ `as const` или `satisfies`. Это только TypeScript API: wire definition, N-API и
549
+ Rust planner не получают дополнительного runtime state.
550
+
551
+ `ResultOf` описывает логическую форму одной строки, восстановленную из путей
552
+ проекции. Он не выполняет запрос и не превращает relation `many` в массив.
553
+ Группировка строк, дедупликация и гидратация коллекций остаются ответственностью
554
+ потребителя модуля.
555
+
462
556
  ## Диагностика
463
557
 
464
558
  Ошибки N-API имеют имя `QueryGraphError` и сохраняют обычный `message` и stack.
@@ -494,18 +588,22 @@ Wire errors, semantic validation и SQL compilation имеют разные ве
494
588
  ```ts
495
589
  const relationalGraph = graph.withRelationalMapping({
496
590
  sources: {
497
- link: {
591
+ users: {
498
592
  table: {
499
593
  schema: 'dbo',
500
- name: 'ControllerAttributeValueLink',
594
+ name: 'users',
595
+ },
596
+ },
597
+ profiles: {
598
+ table: {
599
+ schema: 'dbo',
600
+ name: 'user_profiles',
501
601
  },
502
602
  columns: {
503
- idOwner: 'owner_id',
603
+ userId: 'user_id',
604
+ displayName: 'display_name',
504
605
  },
505
606
  },
506
- value: {
507
- table: 'ControllerObjectValue',
508
- },
509
607
  },
510
608
  })
511
609
  ```
@@ -515,9 +613,9 @@ const relationalGraph = graph.withRelationalMapping({
515
613
 
516
614
  ```ts
517
615
  const operation = {
518
- select: ['value.value'],
616
+ select: ['id', 'profile.displayName'],
519
617
  parameters: {
520
- idOwner: 42,
618
+ userId: 42,
521
619
  },
522
620
  offset: 0,
523
621
  limit: 25,
@@ -532,7 +630,7 @@ console.table(statement.relations)
532
630
  ```
533
631
 
534
632
  `statement.bindings` связывает сгенерированные имена, например `p0`, с
535
- логическими параметрами, например `idOwner`, и содержит их scalar type.
633
+ логическими параметрами, например `userId`, и содержит их scalar type.
536
634
  Потребитель берет значения из `operation.parameters` и передает их
537
635
  своему драйверу базы данных.
538
636
 
package/definition.d.ts CHANGED
@@ -2,19 +2,27 @@ export * from './dsl.js'
2
2
 
3
3
  import type {
4
4
  ConstraintDefinition,
5
- DimensionDefinition,
6
5
  Expression,
7
6
  ExpressionInput,
7
+ ExpressionNullability,
8
+ ExpressionScalarType,
8
9
  FieldExpression,
10
+ FieldSpecIsNullable,
9
11
  FieldSpecMap,
12
+ FieldSpecScalarType,
10
13
  JoinProjectionPath,
11
- MeasureDefinition,
12
14
  ParameterRef,
13
- ProjectionFieldDefinition,
14
15
  RelationCardinality,
15
16
  RelationRef,
16
17
  RelationSelection,
18
+ ScalarType,
19
+ SourceFieldNullability,
20
+ SourceReferenceKey,
17
21
  SourceRef,
22
+ TypedExpression,
23
+ TypedDimensionDefinition,
24
+ TypedMeasureDefinition,
25
+ TypedProjectionField,
18
26
  } from './dsl.js'
19
27
 
20
28
  export const GRAPH_DEFINITION_VERSION: 9
@@ -23,25 +31,41 @@ export function field<
23
31
  const Key extends string,
24
32
  const Fields extends FieldSpecMap,
25
33
  const Name extends Extract<keyof Fields, string>,
26
- >(source: SourceRef<Key, Fields>, name: Name): FieldExpression<Key, Name>
34
+ >(
35
+ source: SourceRef<Key, Fields>,
36
+ name: Name,
37
+ ): TypedExpression<
38
+ FieldExpression<Key, Name>,
39
+ FieldSpecScalarType<Fields[Name]>,
40
+ SourceFieldNullability<Key, FieldSpecIsNullable<Fields[Name]>>
41
+ >
27
42
  export function field<const Source extends string, const Name extends string>(
28
43
  source: Source,
29
44
  name: Name,
30
- ): FieldExpression<Source, Name>
45
+ ): TypedExpression<FieldExpression<Source, Name>, ScalarType, SourceFieldNullability<Source, boolean>>
31
46
 
32
- export interface RelationOptions {
33
- required?: boolean
34
- cardinality?: RelationCardinality
47
+ export interface RelationOptions<
48
+ Required extends boolean = boolean,
49
+ Cardinality extends RelationCardinality = RelationCardinality,
50
+ > {
51
+ required?: Required
52
+ cardinality?: Cardinality
35
53
  selection?: RelationSelection
36
54
  }
37
55
 
38
- export function relation<const Name extends string>(
56
+ export function relation<
57
+ const Name extends string,
58
+ const From extends string | SourceRef,
59
+ const To extends string | SourceRef,
60
+ const Required extends boolean = false,
61
+ const Cardinality extends RelationCardinality = 'one',
62
+ >(
39
63
  name: Name,
40
- from: string | SourceRef,
41
- to: string | SourceRef,
64
+ from: From,
65
+ to: To,
42
66
  on: Expression,
43
- options?: RelationOptions,
44
- ): RelationRef<Name>
67
+ options?: RelationOptions<Required, Cardinality>,
68
+ ): RelationRef<Name, SourceReferenceKey<From>, SourceReferenceKey<To>, Required, Cardinality>
45
69
 
46
70
  export interface ConstraintOptions {
47
71
  when?: string | ParameterRef
@@ -49,39 +73,93 @@ export interface ConstraintOptions {
49
73
 
50
74
  export function constraint(predicate: Expression, options?: ConstraintOptions): ConstraintDefinition
51
75
 
52
- export interface ProjectionOptions {
53
- default?: boolean
76
+ export interface ProjectionOptions<SelectedByDefault extends boolean = boolean> {
77
+ default?: SelectedByDefault
54
78
  }
55
79
 
56
- export function project<const Path extends string>(
80
+ export function project<
81
+ const Path extends string,
82
+ const Value extends ExpressionInput,
83
+ const SelectedByDefault extends boolean = false,
84
+ >(
57
85
  path: Path,
58
- expression: ExpressionInput,
59
- options?: ProjectionOptions,
60
- ): ProjectionFieldDefinition<Path>
61
- export function project<const Path extends readonly string[]>(
86
+ expression: Value,
87
+ options?: ProjectionOptions<SelectedByDefault>,
88
+ ): TypedProjectionField<
89
+ Path,
90
+ Extract<ExpressionScalarType<Value>, ScalarType>,
91
+ ExpressionNullability<Value>,
92
+ SelectedByDefault
93
+ >
94
+ export function project<
95
+ const Path extends readonly string[],
96
+ const Value extends ExpressionInput,
97
+ const SelectedByDefault extends boolean = false,
98
+ >(
62
99
  path: Path,
63
- expression: ExpressionInput,
64
- options?: ProjectionOptions,
65
- ): ProjectionFieldDefinition<JoinProjectionPath<Path>>
100
+ expression: Value,
101
+ options?: ProjectionOptions<SelectedByDefault>,
102
+ ): TypedProjectionField<
103
+ JoinProjectionPath<Path>,
104
+ Extract<ExpressionScalarType<Value>, ScalarType>,
105
+ ExpressionNullability<Value>,
106
+ SelectedByDefault
107
+ >
66
108
 
67
- export function dimension<const Path extends string>(
109
+ export function dimension<
110
+ const Path extends string,
111
+ const Value extends ExpressionInput,
112
+ const SelectedByDefault extends boolean = false,
113
+ >(
68
114
  path: Path,
69
- expression: ExpressionInput,
70
- options?: ProjectionOptions,
71
- ): DimensionDefinition<Path>
72
- export function dimension<const Path extends readonly string[]>(
115
+ expression: Value,
116
+ options?: ProjectionOptions<SelectedByDefault>,
117
+ ): TypedDimensionDefinition<
118
+ Path,
119
+ Extract<ExpressionScalarType<Value>, ScalarType>,
120
+ ExpressionNullability<Value>,
121
+ SelectedByDefault
122
+ >
123
+ export function dimension<
124
+ const Path extends readonly string[],
125
+ const Value extends ExpressionInput,
126
+ const SelectedByDefault extends boolean = false,
127
+ >(
73
128
  path: Path,
74
- expression: ExpressionInput,
75
- options?: ProjectionOptions,
76
- ): DimensionDefinition<JoinProjectionPath<Path>>
129
+ expression: Value,
130
+ options?: ProjectionOptions<SelectedByDefault>,
131
+ ): TypedDimensionDefinition<
132
+ JoinProjectionPath<Path>,
133
+ Extract<ExpressionScalarType<Value>, ScalarType>,
134
+ ExpressionNullability<Value>,
135
+ SelectedByDefault
136
+ >
77
137
 
78
- export function measure<const Path extends string>(
138
+ export function measure<
139
+ const Path extends string,
140
+ const Value extends ExpressionInput,
141
+ const SelectedByDefault extends boolean = false,
142
+ >(
79
143
  path: Path,
80
- expression: ExpressionInput,
81
- options?: ProjectionOptions,
82
- ): MeasureDefinition<Path>
83
- export function measure<const Path extends readonly string[]>(
144
+ expression: Value,
145
+ options?: ProjectionOptions<SelectedByDefault>,
146
+ ): TypedMeasureDefinition<
147
+ Path,
148
+ Extract<ExpressionScalarType<Value>, ScalarType>,
149
+ ExpressionNullability<Value>,
150
+ SelectedByDefault
151
+ >
152
+ export function measure<
153
+ const Path extends readonly string[],
154
+ const Value extends ExpressionInput,
155
+ const SelectedByDefault extends boolean = false,
156
+ >(
84
157
  path: Path,
85
- expression: ExpressionInput,
86
- options?: ProjectionOptions,
87
- ): MeasureDefinition<JoinProjectionPath<Path>>
158
+ expression: Value,
159
+ options?: ProjectionOptions<SelectedByDefault>,
160
+ ): TypedMeasureDefinition<
161
+ JoinProjectionPath<Path>,
162
+ Extract<ExpressionScalarType<Value>, ScalarType>,
163
+ ExpressionNullability<Value>,
164
+ SelectedByDefault
165
+ >