@query-graph/core 1.1.4 → 1.1.5

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
 
32
+ /** @deprecated Import authoring primitives from `@query-graph/core/dsl`. */
28
33
  export const GRAPH_DEFINITION_VERSION: 10
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') {
@@ -0,0 +1,161 @@
1
+ # Authoring DSL
2
+
3
+ Authoring API находится в <code>@query-graph/core/dsl</code>. Он создает
4
+ семантический definition версии 10, который не содержит таблиц, 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 = requiredParameter('id', 'int64')
26
+ const status = optionalParameter('status', 'string')
27
+ const ids = requiredListParameter('ids', 'int64')
28
+ ```
29
+
30
+ Scalar parameter используется через <code>param</code>, list parameter через
31
+ <code>inParameter</code>. Optional parameter не активирует constraint с
32
+ <code>when</code>, пока значение отсутствует.
33
+
34
+ ## Expressions
35
+
36
+ DSL предоставляет сравнения, boolean groups, null tests, semantic functions,
37
+ агрегаты и literals:
38
+
39
+ ```ts
40
+ const predicate = and(eq(users.field('organisationId'), param(organisationId)), isNull(users.field('dateDelete')))
41
+ ```
42
+
43
+ Типы operands проверяются TypeScript во время authoring и повторно Rust при
44
+ регистрации.
45
+
46
+ ## Relations
47
+
48
+ ```ts
49
+ const profileRelation = relation({
50
+ name: 'profile',
51
+ from: users,
52
+ to: profiles,
53
+ on: eq(users.field('id'), profiles.field('userId')),
54
+ cardinality: 'one',
55
+ required: false,
56
+ selection: firstBy(asc(profiles.field('id'))),
57
+ })
58
+ ```
59
+
60
+ - <code>cardinality</code> описывает форму связи: <code>one</code> или
61
+ <code>many</code>.
62
+ - <code>required</code> определяет, может ли отсутствие связанной строки удалить
63
+ root из результата.
64
+ - <code>firstBy</code> детерминированно выбирает одну строку среди кандидатов и
65
+ компилируется через dialect-specific APPLY/LATERAL семантику.
66
+
67
+ У каждого source должен быть единственный путь от root. Planner использует этот
68
+ инвариант для минимального набора JOIN.
69
+
70
+ ## Constraints и exists
71
+
72
+ ```ts
73
+ constraint({
74
+ when: organisationId,
75
+ predicate: eq(users.field('organisationId'), param(organisationId)),
76
+ })
77
+
78
+ constraint({
79
+ predicate: exists(memberships, eq(memberships.field('active'), true)),
80
+ })
81
+ ```
82
+
83
+ <code>exists</code> является semijoin: он фильтрует множество root и не
84
+ материализует many relation. В relation predicate корреляция задается явно:
85
+
86
+ ```ts
87
+ exists(flags, flagPredicate, { from: profiles })
88
+ ```
89
+
90
+ ## Projection и presence
91
+
92
+ ```ts
93
+ const projection = [
94
+ project({ path: 'id', expression: users.field('id'), default: true }),
95
+ projectObject({ path: 'profile', presence: profiles.field('id') }),
96
+ project({
97
+ path: 'profile.name',
98
+ expression: profiles.field('name'),
99
+ default: true,
100
+ }),
101
+ ]
102
+ ```
103
+
104
+ <code>projectObject</code> позволяет executor отличить отсутствующий to-one
105
+ объект от существующего объекта, все поля которого равны NULL.
106
+
107
+ ## Ordering
108
+
109
+ ```ts
110
+ const byName = ordering({
111
+ name: 'nameAsc',
112
+ by: [asc(users.field('name'), { nulls: 'last' })],
113
+ default: true,
114
+ })
115
+ ```
116
+
117
+ Operation выбирает ordering по имени. SQL expression остается частью
118
+ definition, поэтому потребитель не передает произвольный ORDER BY.
119
+
120
+ ## GraphModule
121
+
122
+ <code>defineGraphModule</code> объединяет sources, parameters, relations,
123
+ constraints, projection objects и orderings без собственного root:
124
+
125
+ ```ts
126
+ const auditModule = defineGraphModule({
127
+ name: 'audit',
128
+ sources: [audit],
129
+ relations: [auditRelation],
130
+ projection: [createdAtProjection],
131
+ })
132
+
133
+ const definition = defineGraph({
134
+ name: 'users',
135
+ root: users,
136
+ modules: [auditModule],
137
+ sources: [users],
138
+ projection: [idProjection],
139
+ })
140
+ ```
141
+
142
+ Повторно подключенный тот же constraint object дедуплицируется по identity.
143
+ Конфликтующие sources, parameters, relations и projection paths отклоняются.
144
+
145
+ ## Summary
146
+
147
+ <code>defineSummaryGraph</code> принимает dimensions и measures:
148
+
149
+ ```ts
150
+ const summary = defineSummaryGraph({
151
+ name: 'ordersByStatus',
152
+ root: orders,
153
+ sources: [orders],
154
+ dimensions: [dimension({ path: 'status', expression: orders.field('status'), default: true })],
155
+ measures: [measure({ path: 'count', expression: count(), default: true })],
156
+ })
157
+ ```
158
+
159
+ Dimensions становятся GROUP BY, aggregate constraints становятся HAVING. Rust
160
+ отклоняет nested aggregates, неагрегированные поля вне dimensions и независимые
161
+ 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 = requiredListParameter('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 остается версии 10. В совместимых выпусках сохраняются diagnostic
51
+ codes и locations. Message предназначен человеку и не должен использоваться как
52
+ machine-readable contract.
package/docs/sql.md ADDED
@@ -0,0 +1,68 @@
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
+ ## Bindings
37
+
38
+ Compiler никогда не вставляет operation values в SQL. Каждый binding содержит:
39
+
40
+ - SQL placeholder name;
41
+ - имя parameter;
42
+ - scalar type;
43
+ - index элемента для list parameter.
44
+
45
+ Повторное использование parameter переиспользует binding. Пустой list
46
+ компилируется в ложное условие без placeholders.
47
+
48
+ ## Result metadata
49
+
50
+ SQL columns получают технические aliases <code>c0</code>,
51
+ <code>c1</code> и так далее. <code>statement.columns</code> хранит logical path,
52
+ scalar type, nullability и relation path. <code>statement.objects</code> хранит
53
+ presence columns для nullable объектов.
54
+
55
+ Executor должен опираться на metadata, а не анализировать SQL.
56
+
57
+ ## Граница исполнения
58
+
59
+ query-graph не владеет:
60
+
61
+ - connection pool и transaction;
62
+ - timeout, cancellation и retry;
63
+ - tracing и logging;
64
+ - преобразованием driver-specific bind values;
65
+ - hydration rows и batch attachment.
66
+
67
+ Эти обязанности остаются у приложения. Один и тот же graph можно использовать
68
+ с разными 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.