@query-graph/core 1.1.4 → 1.1.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.
package/definition.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @deprecated Import the object authoring API from `@query-graph/core/dsl`.
3
+ * This compatibility entrypoint will be removed in 2.0.
4
+ */
1
5
  export * from './dsl.js'
2
6
 
3
7
  import type {
@@ -25,8 +29,10 @@ import type {
25
29
  TypedProjectionField,
26
30
  } from './dsl.js'
27
31
 
28
- export const GRAPH_DEFINITION_VERSION: 10
32
+ /** @deprecated Import authoring primitives from `@query-graph/core/dsl`. */
33
+ export const GRAPH_DEFINITION_VERSION: 11
29
34
 
35
+ /** @deprecated Use `source.field(name)` from `@query-graph/core/dsl`. */
30
36
  export function field<
31
37
  const Key extends string,
32
38
  const Fields extends FieldSpecMap,
@@ -53,6 +59,7 @@ export interface RelationOptions<
53
59
  selection?: RelationSelection
54
60
  }
55
61
 
62
+ /** @deprecated Use `relation({ name, from, to, on, ...options })`. */
56
63
  export function relation<
57
64
  const Name extends string,
58
65
  const From extends string | SourceRef,
@@ -71,12 +78,14 @@ export interface ConstraintOptions {
71
78
  when?: string | ParameterRef
72
79
  }
73
80
 
81
+ /** @deprecated Use `constraint({ predicate, when })`. */
74
82
  export function constraint(predicate: Expression, options?: ConstraintOptions): ConstraintDefinition
75
83
 
76
84
  export interface ProjectionOptions<SelectedByDefault extends boolean = boolean> {
77
85
  default?: SelectedByDefault
78
86
  }
79
87
 
88
+ /** @deprecated Use `project({ path, expression, default })`. */
80
89
  export function project<
81
90
  const Path extends string,
82
91
  const Value extends ExpressionInput,
@@ -106,6 +115,7 @@ export function project<
106
115
  SelectedByDefault
107
116
  >
108
117
 
118
+ /** @deprecated Use `dimension({ path, expression, default })`. */
109
119
  export function dimension<
110
120
  const Path extends string,
111
121
  const Value extends ExpressionInput,
@@ -135,6 +145,7 @@ export function dimension<
135
145
  SelectedByDefault
136
146
  >
137
147
 
148
+ /** @deprecated Use `measure({ path, expression, default })`. */
138
149
  export function measure<
139
150
  const Path extends string,
140
151
  const Value extends ExpressionInput,
package/definition.js CHANGED
@@ -1,8 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const objectDsl = require('./dsl.js')
4
-
5
- const GRAPH_DEFINITION_VERSION = 10
4
+ const { GRAPH_DEFINITION_VERSION } = require('./lib/authoring/definition.js')
6
5
 
7
6
  function field(sourceReference, name) {
8
7
  if (typeof sourceReference === 'string') {
@@ -66,13 +65,17 @@ exports.hidden = objectDsl.hidden
66
65
  exports.source = objectDsl.source
67
66
  exports.field = field
68
67
  exports.requiredParameter = objectDsl.requiredParameter
68
+ exports.requiredParam = objectDsl.requiredParam
69
69
  exports.optionalParameter = objectDsl.optionalParameter
70
+ exports.optionalParam = objectDsl.optionalParam
70
71
  exports.param = objectDsl.param
71
72
  exports.literal = objectDsl.literal
72
73
  exports.integer = objectDsl.integer
73
74
  exports.decimal = objectDsl.decimal
74
75
  exports.requiredListParameter = objectDsl.requiredListParameter
76
+ exports.requiredListParam = objectDsl.requiredListParam
75
77
  exports.optionalListParameter = objectDsl.optionalListParameter
78
+ exports.optionalListParam = objectDsl.optionalListParam
76
79
  exports.eq = objectDsl.eq
77
80
  exports.neq = objectDsl.neq
78
81
  exports.lt = objectDsl.lt
@@ -87,11 +90,15 @@ exports.not = objectDsl.not
87
90
  exports.isNull = objectDsl.isNull
88
91
  exports.isNotNull = objectDsl.isNotNull
89
92
  exports.inParameter = objectDsl.inParameter
93
+ exports.inParam = objectDsl.inParam
90
94
  exports.exists = objectDsl.exists
91
95
  exports.lower = objectDsl.lower
92
96
  exports.upper = objectDsl.upper
93
97
  exports.coalesce = objectDsl.coalesce
94
98
  exports.concat = objectDsl.concat
99
+ exports.divide = objectDsl.divide
100
+ exports.truncate = objectDsl.truncate
101
+ exports.modulo = objectDsl.modulo
95
102
  exports.count = objectDsl.count
96
103
  exports.countDistinct = objectDsl.countDistinct
97
104
  exports.sum = objectDsl.sum
@@ -0,0 +1,189 @@
1
+ # Authoring DSL
2
+
3
+ Authoring API находится в <code>@query-graph/core/dsl</code>. Он создает
4
+ семантический definition версии 11, который не содержит таблиц, SQL aliases или
5
+ синтаксиса диалекта.
6
+
7
+ ## Sources и поля
8
+
9
+ ```ts
10
+ import { fieldType, hidden, nullable, source } from '@query-graph/core/dsl'
11
+
12
+ const users = source('users', {
13
+ id: 'int64',
14
+ email: nullable('string'),
15
+ internalRank: hidden(fieldType('int32', { nullable: true })),
16
+ })
17
+ ```
18
+
19
+ Поле имеет scalar type, nullability и флаг selectable. Hidden-поля можно
20
+ использовать в constraints и relations, но нельзя вывести в projection.
21
+
22
+ ## Параметры
23
+
24
+ ```ts
25
+ const id = requiredParam('id', 'int64')
26
+ const status = optionalParam('status', 'string')
27
+ const ids = requiredListParam('ids', 'int64')
28
+ const optionalIds = optionalListParam('optionalIds', 'int64')
29
+ ```
30
+
31
+ Scalar parameter передается в expression напрямую, list parameter используется
32
+ через <code>inParam</code>. Явная форма <code>param(id)</code> поддерживается
33
+ для совместимости. Полные имена <code>requiredParameter</code>,
34
+ <code>optionalParameter</code>, <code>requiredListParameter</code>,
35
+ <code>optionalListParameter</code> и <code>inParameter</code> остаются алиасами
36
+ тех же factories. Optional parameter не активирует constraint с
37
+ <code>when</code>, пока значение отсутствует.
38
+
39
+ ## Expressions
40
+
41
+ DSL предоставляет сравнения, boolean groups, null tests, semantic functions,
42
+ агрегаты и literals:
43
+
44
+ ```ts
45
+ const predicate = and(eq(users.field('organisationId'), organisationId), isNull(users.field('dateDelete')))
46
+ ```
47
+
48
+ Semantic functions позволяют оставить вычисляемые поля частью графа, не
49
+ привязывая definition к синтаксису Oracle или SQL Server:
50
+
51
+ ```ts
52
+ const durationProjection = [
53
+ project({
54
+ path: 'hoursDuration',
55
+ expression: truncate(divide(services.field('durationMinutes'), 60)),
56
+ }),
57
+ project({
58
+ path: 'minutesDuration',
59
+ expression: modulo(services.field('durationMinutes'), 60),
60
+ }),
61
+ ]
62
+ ```
63
+
64
+ <code>divide</code> возвращает decimal для точных числовых типов и float64,
65
+ если хотя бы один operand имеет тип float64. <code>truncate</code> отбрасывает
66
+ дробную часть по направлению к нулю и сохраняет scalar type. <code>modulo</code>
67
+ принимает int32, int64 и decimal; float64 отклоняется, чтобы definition одинаково
68
+ работал в поддерживаемых диалектах. Nullability распространяется от operands к
69
+ результату.
70
+
71
+ TypeScript выводит тип и nullability результата во время authoring. Rust при
72
+ регистрации повторно проверяет arity и совместимость типов operands.
73
+
74
+ ## Relations
75
+
76
+ ```ts
77
+ const profileRelation = relation({
78
+ name: 'profile',
79
+ from: users,
80
+ to: profiles,
81
+ on: eq(users.field('id'), profiles.field('userId')),
82
+ cardinality: 'one',
83
+ required: false,
84
+ selection: firstBy(asc(profiles.field('id'))),
85
+ })
86
+ ```
87
+
88
+ - <code>cardinality</code> описывает форму связи: <code>one</code> или
89
+ <code>many</code>.
90
+ - <code>required</code> определяет, может ли отсутствие связанной строки удалить
91
+ root из результата.
92
+ - <code>firstBy</code> детерминированно выбирает одну строку среди кандидатов и
93
+ компилируется через dialect-specific APPLY/LATERAL семантику.
94
+
95
+ У каждого source должен быть единственный путь от root. Planner использует этот
96
+ инвариант для минимального набора JOIN.
97
+
98
+ ## Constraints и exists
99
+
100
+ ```ts
101
+ constraint({
102
+ when: organisationId,
103
+ predicate: eq(users.field('organisationId'), organisationId),
104
+ })
105
+
106
+ constraint({
107
+ predicate: exists(memberships, eq(memberships.field('active'), true)),
108
+ })
109
+ ```
110
+
111
+ <code>exists</code> является semijoin: он фильтрует множество root и не
112
+ материализует many relation. В relation predicate корреляция задается явно:
113
+
114
+ ```ts
115
+ exists(flags, flagPredicate, { from: profiles })
116
+ ```
117
+
118
+ ## Projection и presence
119
+
120
+ ```ts
121
+ const projection = [
122
+ project({ path: 'id', expression: users.field('id'), default: true }),
123
+ projectObject({ path: 'profile', presence: profiles.field('id') }),
124
+ project({
125
+ path: 'profile.name',
126
+ expression: profiles.field('name'),
127
+ default: true,
128
+ }),
129
+ ]
130
+ ```
131
+
132
+ <code>projectObject</code> позволяет executor отличить отсутствующий to-one
133
+ объект от существующего объекта, все поля которого равны NULL.
134
+
135
+ ## Ordering
136
+
137
+ ```ts
138
+ const byName = ordering({
139
+ name: 'nameAsc',
140
+ by: [asc(users.field('name'), { nulls: 'last' })],
141
+ default: true,
142
+ })
143
+ ```
144
+
145
+ Operation выбирает ordering по имени. SQL expression остается частью
146
+ definition, поэтому потребитель не передает произвольный ORDER BY.
147
+
148
+ ## GraphModule
149
+
150
+ <code>defineGraphModule</code> объединяет sources, parameters, relations,
151
+ constraints, projection objects и orderings без собственного root:
152
+
153
+ ```ts
154
+ const auditModule = defineGraphModule({
155
+ name: 'audit',
156
+ sources: [audit],
157
+ relations: [auditRelation],
158
+ projection: [createdAtProjection],
159
+ })
160
+
161
+ const definition = defineGraph({
162
+ name: 'users',
163
+ root: users,
164
+ modules: [auditModule],
165
+ sources: [users],
166
+ projection: [idProjection],
167
+ })
168
+ ```
169
+
170
+ Повторно подключенный тот же constraint object дедуплицируется по identity.
171
+ Конфликтующие sources, parameters, relations и projection paths отклоняются.
172
+
173
+ ## Summary
174
+
175
+ <code>defineSummaryGraph</code> принимает dimensions и measures:
176
+
177
+ ```ts
178
+ const summary = defineSummaryGraph({
179
+ name: 'ordersByStatus',
180
+ root: orders,
181
+ sources: [orders],
182
+ dimensions: [dimension({ path: 'status', expression: orders.field('status'), default: true })],
183
+ measures: [measure({ path: 'count', expression: count(), default: true })],
184
+ })
185
+ ```
186
+
187
+ Dimensions становятся GROUP BY, aggregate constraints становятся HAVING. Rust
188
+ отклоняет nested aggregates, неагрегированные поля вне dimensions и независимые
189
+ many-ветви, которым нужен отдельный aggregate subquery plan.
@@ -0,0 +1,100 @@
1
+ # Composition
2
+
3
+ В query-graph есть два разных вида композиции.
4
+
5
+ - <code>GraphModule</code> расширяет один SQL-граф до регистрации.
6
+ - <code>batchRelation</code> связывает уже зарегистрированные графы несколькими
7
+ SQL-запросами.
8
+
9
+ ## Когда использовать GraphModule
10
+
11
+ GraphModule подходит, когда данные безопасно входят в один relational plan:
12
+ to-one связи, общие constraints, набор projection fields или ordering.
13
+
14
+ Many-связь в одном SQL может размножить root-строки и нарушить пагинацию. Для
15
+ таких полей нужен batch.
16
+
17
+ ## Batch query
18
+
19
+ Child-модуль один раз закрепляет контракт ключа:
20
+
21
+ ```ts
22
+ const itemIds = requiredListParam('itemIds', 'int64')
23
+
24
+ const childrenQuery = batchQuery({
25
+ graph: childrenGraph,
26
+ key: {
27
+ path: 'itemId',
28
+ parameter: itemIds,
29
+ },
30
+ })
31
+ ```
32
+
33
+ <code>key.path</code> должен быть projection child-графа.
34
+ <code>key.parameter</code> должен быть list parameter того же scalar type.
35
+
36
+ ## Batch relation
37
+
38
+ Root-граф подключает child query:
39
+
40
+ ```ts
41
+ const children = batchRelation({
42
+ name: 'children',
43
+ from: 'id',
44
+ query: childrenQuery,
45
+ cardinality: 'many',
46
+ parameters: {
47
+ state: 'active',
48
+ },
49
+ })
50
+
51
+ const graph = composeGraph({
52
+ root: rootGraph,
53
+ relations: [children],
54
+ })
55
+ ```
56
+
57
+ <code>from</code> является projection path root-графа. Static parameters
58
+ проверяются при композиции. Сам list parameter ключей в <code>parameters</code>
59
+ передавать нельзя: compiled plan заполняет его автоматически.
60
+
61
+ ## Компиляция плана
62
+
63
+ ```ts
64
+ const plan = graph.compileOraclePlan({
65
+ select: ['id', 'children.id', 'children.name'],
66
+ parameters: rootParameters,
67
+ })
68
+ ```
69
+
70
+ Planner:
71
+
72
+ 1. отделяет root selection от child selections;
73
+ 2. добавляет root key в select, если потребитель его не запросил;
74
+ 3. добавляет child key в child select;
75
+ 4. компилирует только root statement;
76
+ 5. возвращает metadata выбранных batch steps.
77
+
78
+ Batch relation не компилируется и не выполняется, если ее поля не выбраны.
79
+
80
+ ## Deferred batch
81
+
82
+ После выполнения root executor собирает уникальные ключи и компилирует один
83
+ child statement:
84
+
85
+ ```ts
86
+ for (const batch of plan.batches) {
87
+ const keys = collectKeys(rootRows, batch.parentKey)
88
+ const statement = plan.compileBatch(batch.name, keys)
89
+ const childRows = await driver.query(statement.sql, bind(statement, keys))
90
+ attach(rootRows, childRows, batch)
91
+ }
92
+ ```
93
+
94
+ При <code>cardinality: 'many'</code> executor присоединяет массив. При
95
+ <code>cardinality: 'one'</code> он присоединяет объект или NULL. Cardinality
96
+ описывает результат, а не количество SQL-запросов.
97
+
98
+ Metadata сообщает <code>parentKeyInjected</code> и
99
+ <code>childKeyInjected</code>. Executor может удалить технически добавленные
100
+ ключи после сборки результата.
@@ -0,0 +1,52 @@
1
+ # Diagnostics
2
+
3
+ Ошибки Node API представлены как <code>QueryGraphError</code>:
4
+
5
+ ```ts
6
+ try {
7
+ registerDefinition(definition)
8
+ } catch (error) {
9
+ if (error instanceof Error && error.name === 'QueryGraphError') {
10
+ console.table(error.issues)
11
+ }
12
+ }
13
+ ```
14
+
15
+ Поля ошибки:
16
+
17
+ - <code>name</code>: всегда <code>QueryGraphError</code>;
18
+ - <code>phase</code>: definition, mapping, composition, operation или sql;
19
+ - <code>code</code>: основной код ошибки;
20
+ - <code>issues</code>: все diagnostics текущей фазы.
21
+
22
+ Каждый diagnostic содержит stable code, location и message. Location указывает
23
+ на wire path, например
24
+ <code>relations[1].selection.orderBy[0].expression</code>.
25
+
26
+ ## Границы проверки
27
+
28
+ JavaScript DSL выполняет shallow validation сразу:
29
+
30
+ - configuration должна быть object;
31
+ - неизвестные ключи отклоняются;
32
+ - boolean и enum options проверяются до построения wire object;
33
+ - пустые обязательные строки отклоняются.
34
+
35
+ Rust выполняет semantic validation:
36
+
37
+ - уникальность и topology;
38
+ - ссылки и expression scope;
39
+ - scalar types и parameter shapes;
40
+ - projection conflicts и visibility;
41
+ - aggregate semantics;
42
+ - mapping completeness;
43
+ - operation select, ordering и parameter values.
44
+
45
+ Такое разделение дает ранние ошибки опечаток, не дублируя planner rules в
46
+ JavaScript.
47
+
48
+ ## Совместимость diagnostics
49
+
50
+ Wire schema остается версии 11. В совместимых выпусках сохраняются diagnostic
51
+ codes и locations. Message предназначен человеку и не должен использоваться как
52
+ machine-readable contract.
package/docs/sql.md ADDED
@@ -0,0 +1,80 @@
1
+ # SQL Compilation
2
+
3
+ Definition не содержит физическую схему БД. Mapping добавляется после
4
+ регистрации:
5
+
6
+ ```ts
7
+ const graph = registerDefinition(definition).withRelationalMapping({
8
+ sources: {
9
+ users: {
10
+ table: { catalog: 'app', schema: 'dbo', name: 'Users' },
11
+ columns: { displayName: 'display_name' },
12
+ },
13
+ },
14
+ })
15
+ ```
16
+
17
+ Можно передать несколько fragments через
18
+ <code>withRelationalMappings</code>. Одинаковые mappings объединяются,
19
+ конфликтующие table/column names возвращают ошибку фазы mapping.
20
+
21
+ ## Диалекты
22
+
23
+ ```ts
24
+ const sqlServer = graph.compileSqlServer(operation, { version: '2022' })
25
+ const oracle = graph.compileOracle(operation, { version: '19c' })
26
+ ```
27
+
28
+ Поддерживаемые версии:
29
+
30
+ - SQL Server: 2008, 2012, 2016, 2019, 2022;
31
+ - Oracle: 11g, 12c, 19c, 21c, 23ai.
32
+
33
+ Version capabilities проверяются до возврата SQL. Например, pagination и
34
+ <code>firstBy</code> могут требовать более новую версию диалекта.
35
+
36
+ Semantic functions рендерятся с одинаковым поведением через разные конструкции
37
+ диалектов:
38
+
39
+ | DSL | Oracle | SQL Server |
40
+ | -------------------------------- | ----------------------------- | --------------------------------- |
41
+ | <code>divide(left, right)</code> | <code>left / right</code> | дробное <code>left / right</code> |
42
+ | <code>truncate(value)</code> | <code>TRUNC(value)</code> | <code>ROUND(value, 0, 1)</code> |
43
+ | <code>modulo(left, right)</code> | <code>MOD(left, right)</code> | <code>left % right</code> |
44
+
45
+ SQL Server compiler принудительно переводит <code>divide</code> в дробную
46
+ арифметику, чтобы два integer operands не дали integer division.
47
+
48
+ ## Bindings
49
+
50
+ Compiler никогда не вставляет operation values в SQL. Каждый binding содержит:
51
+
52
+ - SQL placeholder name;
53
+ - имя parameter;
54
+ - scalar type;
55
+ - index элемента для list parameter.
56
+
57
+ Повторное использование parameter переиспользует binding. Пустой list
58
+ компилируется в ложное условие без placeholders.
59
+
60
+ ## Result metadata
61
+
62
+ SQL columns получают технические aliases <code>c0</code>,
63
+ <code>c1</code> и так далее. <code>statement.columns</code> хранит logical path,
64
+ scalar type, nullability и relation path. <code>statement.objects</code> хранит
65
+ presence columns для nullable объектов.
66
+
67
+ Executor должен опираться на metadata, а не анализировать SQL.
68
+
69
+ ## Граница исполнения
70
+
71
+ query-graph не владеет:
72
+
73
+ - connection pool и transaction;
74
+ - timeout, cancellation и retry;
75
+ - tracing и logging;
76
+ - преобразованием driver-specific bind values;
77
+ - hydration rows и batch attachment.
78
+
79
+ Эти обязанности остаются у приложения. Один и тот же graph можно использовать
80
+ с разными executors, пока они соблюдают контракт statement metadata.
@@ -0,0 +1,86 @@
1
+ # Type System
2
+
3
+ TypeScript помогает автору до регистрации, Rust остается авторитетной runtime
4
+ проверкой wire definition.
5
+
6
+ ## Scalar types
7
+
8
+ | Scalar | Operation value | Результат по умолчанию |
9
+ | ------------------------------ | ----------------- | ---------------------- |
10
+ | boolean | boolean | boolean |
11
+ | int32, float64 | number | number |
12
+ | int64, decimal | number или string | number или string |
13
+ | string, date, dateTime, binary | string | string |
14
+ | json | JSON value | JSON value |
15
+
16
+ <code>int64</code> принимает number только в безопасном диапазоне JavaScript.
17
+ Decimal string проверяется как конечная десятичная запись.
18
+
19
+ ## QueryOperation
20
+
21
+ Definition передает свои параметры, projection paths и ordering names в
22
+ operation:
23
+
24
+ ```ts
25
+ const operation = {
26
+ select: ['id', 'profile.name'],
27
+ ordering: 'nameAsc',
28
+ parameters: {
29
+ organisationId: 42,
30
+ },
31
+ } as const satisfies QueryOperation<typeof definition>
32
+ ```
33
+
34
+ Required parameters обязательны, optional параметры могут отсутствовать,
35
+ list parameters принимают readonly arrays.
36
+
37
+ ## Nullability
38
+
39
+ Nullability выражения складывается из:
40
+
41
+ - nullable исходного поля;
42
+ - optional relation на пути к source;
43
+ - семантики функции;
44
+ - projection object presence.
45
+
46
+ Required relation под optional ancestor остается nullable. Это свойство
47
+ вычисляется из topology один раз при регистрации.
48
+
49
+ ## ResultOf
50
+
51
+ ```ts
52
+ type Row = ResultOf<typeof definition, typeof operation>
53
+ ```
54
+
55
+ <code>ResultOf</code> строит вложенный объект из выбранных projection paths.
56
+ Если operation не передана, используются поля с <code>default: true</code>.
57
+ Для composed graph выбранные batch relations добавляются как массивы либо
58
+ nullable объекты согласно cardinality.
59
+
60
+ Driver может заменить представление scalar values:
61
+
62
+ ```ts
63
+ interface DriverScalars extends DefaultScalarOutputTypeMap {
64
+ int64: bigint
65
+ decimal: string
66
+ dateTime: Date
67
+ }
68
+
69
+ type DriverRow = ResultOf<typeof definition, typeof operation, DriverScalars>
70
+ ```
71
+
72
+ Этот mapping существует только на уровне TypeScript. Wire schema и SQL
73
+ compilation не меняются.
74
+
75
+ ## Runtime checker
76
+
77
+ Rust проверяет:
78
+
79
+ - ссылки на sources, fields и parameters;
80
+ - scalar/list shape параметров;
81
+ - совместимость сравнений и функций;
82
+ - boolean тип predicates;
83
+ - orderable и groupable expressions;
84
+ - aggregate context и concrete projection types.
85
+
86
+ Ошибки собираются в diagnostics с location исходного definition.