@danceroutine/tango-orm 1.7.0 → 1.8.1

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 (82) hide show
  1. package/dist/InternalDialect-ClSaUNso.js +10 -0
  2. package/dist/InternalDialect-ClSaUNso.js.map +1 -0
  3. package/dist/PostgresAdapter-CXKdKBG-.js +4 -0
  4. package/dist/PostgresAdapter-DySFW6vy.js +128 -0
  5. package/dist/PostgresAdapter-DySFW6vy.js.map +1 -0
  6. package/dist/{SqliteClient-CjOK9-ki.js → SqliteAdapter-CDdOjRmW.js} +57 -3
  7. package/dist/SqliteAdapter-CDdOjRmW.js.map +1 -0
  8. package/dist/SqliteAdapter-mjtXuVTg.js +4 -0
  9. package/dist/connection/adapters/Adapter.d.ts +32 -1
  10. package/dist/connection/adapters/dialects/PostgresAdapter.d.ts +5 -6
  11. package/dist/connection/adapters/dialects/SqliteAdapter.d.ts +4 -6
  12. package/dist/connection/adapters/index.d.ts +1 -1
  13. package/dist/connection/index.d.ts +1 -1
  14. package/dist/connection/index.js +4 -5
  15. package/dist/{connection-B_K2ZAf7.js → connection-Dmhgx31M.js} +5 -7
  16. package/dist/{connection-B_K2ZAf7.js.map → connection-Dmhgx31M.js.map} +1 -1
  17. package/dist/{defaultRuntime-BPK9kWEW.js → defaultRuntime-DzqBQ9Hb.js} +63 -16
  18. package/dist/defaultRuntime-DzqBQ9Hb.js.map +1 -0
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +11 -12
  21. package/dist/manager/ModelManager.d.ts +25 -5
  22. package/dist/manager/index.d.ts +6 -0
  23. package/dist/manager/index.js +8 -7
  24. package/dist/manager/internal/MutationCompiler.d.ts +14 -6
  25. package/dist/manager/relations/ManyToManyRelatedManager.d.ts +147 -0
  26. package/dist/manager/relations/ManyToManyRelatedQuerySet.d.ts +62 -0
  27. package/dist/manager/relations/MaterializedModelRecord.d.ts +28 -0
  28. package/dist/manager/relations/index.d.ts +9 -0
  29. package/dist/manager/relations/internal/ThroughTableManager.d.ts +79 -0
  30. package/dist/manager-DrDTiCAz.js +24 -0
  31. package/dist/manager-DrDTiCAz.js.map +1 -0
  32. package/dist/query/ModelQuerySet.d.ts +20 -0
  33. package/dist/query/QBuilder.d.ts +3 -3
  34. package/dist/query/QuerySet.d.ts +49 -21
  35. package/dist/query/compiler/QueryCompiler.d.ts +13 -4
  36. package/dist/query/domain/CompiledQuery.d.ts +169 -2
  37. package/dist/query/domain/FilterInput.d.ts +1 -1
  38. package/dist/query/domain/FilterKey.d.ts +4 -2
  39. package/dist/query/domain/QNode.d.ts +4 -4
  40. package/dist/query/domain/QuerySetState.d.ts +3 -3
  41. package/dist/query/domain/RelationMeta.d.ts +9 -0
  42. package/dist/query/domain/RelationTyping.d.ts +47 -0
  43. package/dist/query/domain/TableMetaFactory.d.ts +1 -14
  44. package/dist/query/domain/index.d.ts +1 -1
  45. package/dist/query/domain/internal/InternalPrefetchQueryKind.d.ts +20 -0
  46. package/dist/query/index.d.ts +1 -0
  47. package/dist/query/index.js +3 -2
  48. package/dist/query/planning/QueryPlanner.d.ts +1 -1
  49. package/dist/{query-FZJoSCg4.js → query-DUZnBFhf.js} +425 -166
  50. package/dist/query-DUZnBFhf.js.map +1 -0
  51. package/dist/registerModelObjects-DxlBfuUN.js +797 -0
  52. package/dist/registerModelObjects-DxlBfuUN.js.map +1 -0
  53. package/dist/runtime/TangoRuntime.d.ts +9 -0
  54. package/dist/runtime/index.d.ts +3 -2
  55. package/dist/runtime/index.js +7 -6
  56. package/dist/runtime/internal/SqliteDBClientProvider.d.ts +3 -0
  57. package/dist/{runtime-ByXbpVBS.js → runtime-1H88J3nN.js} +3 -3
  58. package/dist/runtime-1H88J3nN.js.map +1 -0
  59. package/dist/transaction/index.js +5 -4
  60. package/dist/{transaction-Cs0Z9tbW.js → transaction-ZhfDf-f8.js} +2 -2
  61. package/dist/{transaction-Cs0Z9tbW.js.map → transaction-ZhfDf-f8.js.map} +1 -1
  62. package/dist/validation/SQLValidationEngine.d.ts +22 -5
  63. package/dist/validation/SqlValidationPlan.d.ts +5 -4
  64. package/dist/validation/internal/InternalSqlValidationPlanKind.d.ts +25 -0
  65. package/dist/validation/internal/InternalValidatedFilterDescriptorKind.d.ts +4 -0
  66. package/package.json +6 -6
  67. package/dist/PostgresAdapter-BFdo_nIt.js +0 -4
  68. package/dist/PostgresAdapter-CMiEpHya.js +0 -49
  69. package/dist/PostgresAdapter-CMiEpHya.js.map +0 -1
  70. package/dist/PostgresClient-BQJZfEOT.js +0 -68
  71. package/dist/PostgresClient-BQJZfEOT.js.map +0 -1
  72. package/dist/SqliteAdapter-A-P9zUhP.js +0 -4
  73. package/dist/SqliteAdapter-CeqhyrPC.js +0 -44
  74. package/dist/SqliteAdapter-CeqhyrPC.js.map +0 -1
  75. package/dist/SqliteClient-CjOK9-ki.js.map +0 -1
  76. package/dist/defaultRuntime-BPK9kWEW.js.map +0 -1
  77. package/dist/manager-C6oJ2tAF.js +0 -13
  78. package/dist/manager-C6oJ2tAF.js.map +0 -1
  79. package/dist/query-FZJoSCg4.js.map +0 -1
  80. package/dist/registerModelObjects-C-1RbUHS.js +0 -385
  81. package/dist/registerModelObjects-C-1RbUHS.js.map +0 -1
  82. package/dist/runtime-ByXbpVBS.js.map +0 -1
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Maintainer note: model-backed and specialized relation-backed querysets
3
+ * share the fluent API through `QuerySet.spawn(...)`. This concrete class
4
+ * owns the direct "compile the accumulated state and execute it" path, while
5
+ * subclasses preserve specialized execution behavior by returning their own
6
+ * queryset family from `spawn(...)`.
7
+ */
8
+ import type { QuerySetState } from './domain/QuerySetState';
9
+ import type { QueryExecutor } from './QuerySet';
10
+ import { QuerySet } from './QuerySet';
11
+ /**
12
+ * Concrete `QuerySet` implementation returned by `Model.objects.query()`.
13
+ *
14
+ * It executes the accumulated queryset state directly against the bound
15
+ * model executor.
16
+ */
17
+ export declare class ModelQuerySet<TModel extends Record<string, unknown>, TBaseResult extends Record<string, unknown> = TModel, TSourceModel = unknown, THydrated extends Record<string, unknown> = Record<never, never>> extends QuerySet<TModel, TBaseResult, TSourceModel, THydrated> {
18
+ constructor(executor: QueryExecutor<TModel>, state?: QuerySetState<TModel, TSourceModel>);
19
+ protected spawn<TNextBaseResult extends Record<string, unknown> = TBaseResult, TNextHydrated extends Record<string, unknown> = THydrated>(state: QuerySetState<TModel, TSourceModel>): ModelQuerySet<TModel, TNextBaseResult, TSourceModel, TNextHydrated>;
20
+ }
@@ -16,14 +16,14 @@ export declare class QBuilder {
16
16
  /**
17
17
  * Combine multiple filter fragments using logical `AND`.
18
18
  */
19
- static and<T>(...nodes: Array<FilterInput<T> | QNode<T>>): QNode<T>;
19
+ static and<T, TSourceModel = unknown>(...nodes: Array<FilterInput<T, TSourceModel> | QNode<T, TSourceModel>>): QNode<T, TSourceModel>;
20
20
  /**
21
21
  * Combine multiple filter fragments using logical `OR`.
22
22
  */
23
- static or<T>(...nodes: Array<FilterInput<T> | QNode<T>>): QNode<T>;
23
+ static or<T, TSourceModel = unknown>(...nodes: Array<FilterInput<T, TSourceModel> | QNode<T, TSourceModel>>): QNode<T, TSourceModel>;
24
24
  /**
25
25
  * Negate a filter fragment using logical `NOT`.
26
26
  */
27
- static not<T>(node: FilterInput<T> | QNode<T>): QNode<T>;
27
+ static not<T, TSourceModel = unknown>(node: FilterInput<T, TSourceModel> | QNode<T, TSourceModel>): QNode<T, TSourceModel>;
28
28
  private static wrapNode;
29
29
  }
@@ -1,12 +1,13 @@
1
1
  import type { DBClient } from '../connection/clients/DBClient';
2
- import type { Dialect } from './domain/Dialect';
2
+ import type { Adapter } from '../connection/adapters/Adapter';
3
3
  import type { QuerySetState } from './domain/QuerySetState';
4
4
  import type { TableMeta } from './domain/TableMeta';
5
5
  import type { QNode } from './domain/QNode';
6
+ import { QueryResult } from './domain/QueryResult';
6
7
  import type { OrderToken } from './domain/OrderToken';
8
+ import type { OrderSpec } from './domain/OrderSpec';
7
9
  import type { FilterInput } from './domain/FilterInput';
8
10
  import type { CompiledQuery } from './domain/CompiledQuery';
9
- import { QueryResult } from './domain/QueryResult';
10
11
  import type { GeneratedHydratedRelationMap, GeneratedPrefetchRelatedPathKeys, GeneratedSelectRelatedPathKeys, HydratedQueryResult, ManyRelationHydrationCardinality, MaybeHydratedRelationMap, PrefetchRelatedRelations, RelationKeys, SelectRelatedRelations, SingleRelationHydrationCardinality } from './domain/RelationTyping';
11
12
  /**
12
13
  * Query execution seam consumed by `QuerySet`.
@@ -19,8 +20,20 @@ import type { GeneratedHydratedRelationMap, GeneratedPrefetchRelatedPathKeys, Ge
19
20
  export interface QueryExecutor<TModel> {
20
21
  meta: TableMeta;
21
22
  client: DBClient;
22
- dialect: Dialect;
23
+ adapter: Adapter;
23
24
  run(compiled: CompiledQuery): Promise<TModel[]>;
25
+ /**
26
+ * Optional hook invoked by `QuerySet` after a record has been
27
+ * materialized so executors can attach related-manager accessors
28
+ * (such as the many-to-many related manager) onto the record.
29
+ *
30
+ * The optional `modelKey` argument lets the executor route the attach
31
+ * call to the correct model when the record belongs to a related
32
+ * model rather than the executor's own source model. Implementations
33
+ * must not overwrite existing properties on the record so prior
34
+ * hydration assignments survive the attach pass.
35
+ */
36
+ attachPersistedRecordAccessors?(record: Record<string, unknown>, modelKey?: string): void;
24
37
  }
25
38
  type QueryShapeFunction<TInput, TOutput> = (row: TInput) => TOutput;
26
39
  type QueryShapeParser<TInput, TOutput> = {
@@ -34,12 +47,6 @@ type ProjectedResult<TModel extends Record<string, unknown>, TKeys extends reado
34
47
  * Provides a fluent API for filtering, ordering, pagination, projection, and
35
48
  * nested relation hydration.
36
49
  *
37
- * Refinements such as `filter`, `orderBy`, `select`, and relation loaders build
38
- * query state only. SQL runs when you call an evaluation method (`fetch`,
39
- * `fetchOne`, `count`, `exists`, or `for await` over this queryset). After the
40
- * first row-returning evaluation, this queryset instance reuses its cached
41
- * materialized result on later `fetch()` or async-iteration calls.
42
- *
43
50
  * @template TModel - The full model row type used for query composition
44
51
  * @template TBaseResult - The selected base-row shape returned by execution methods
45
52
  * @template TSourceModel - The source Tango model used for typed relation metadata
@@ -56,31 +63,47 @@ type ProjectedResult<TModel extends Record<string, unknown>, TKeys extends reado
56
63
  * .fetch();
57
64
  * ```
58
65
  */
59
- export declare class QuerySet<TModel extends Record<string, unknown>, TBaseResult extends Record<string, unknown> = TModel, TSourceModel = unknown, THydrated extends Record<string, unknown> = Record<never, never>> implements AsyncIterable<HydratedQueryResult<TBaseResult, THydrated>> {
66
+ export declare abstract class QuerySet<TModel extends Record<string, unknown>, TBaseResult extends Record<string, unknown> = TModel, TSourceModel = unknown, THydrated extends Record<string, unknown> = Record<never, never>> implements AsyncIterable<HydratedQueryResult<TBaseResult, THydrated>> {
60
67
  [Symbol.asyncIterator]: () => AsyncIterator<HydratedQueryResult<TBaseResult, THydrated>>;
61
- private executor;
62
- private state;
68
+ protected executor: QueryExecutor<TModel>;
69
+ protected state: QuerySetState<TModel, TSourceModel>;
63
70
  static readonly BRAND: "tango.orm.query_set";
64
71
  readonly __tangoBrand: typeof QuerySet.BRAND;
65
72
  private evaluationCache?;
66
- constructor(executor: QueryExecutor<TModel>, state?: QuerySetState<TModel>);
73
+ constructor(executor: QueryExecutor<TModel>, state?: QuerySetState<TModel, TSourceModel>);
74
+ /**
75
+ * Create another queryset of the same runtime family with the supplied
76
+ * query state. Concrete subclasses implement this so fluent calls keep
77
+ * their subclass-specific execution behavior instead of falling back to
78
+ * the standard queryset implementation.
79
+ */
80
+ protected abstract spawn<TNextBaseResult extends Record<string, unknown> = TBaseResult, TNextHydrated extends Record<string, unknown> = THydrated>(state: QuerySetState<TModel, TSourceModel>): QuerySet<TModel, TNextBaseResult, TSourceModel, TNextHydrated>;
67
81
  /**
68
82
  * Narrow an unknown value to `QuerySet`.
69
83
  */
70
84
  static isQuerySet<TModel extends Record<string, unknown>, TResult extends Record<string, unknown> = TModel>(value: unknown): value is QuerySet<TModel, TResult>;
85
+ /**
86
+ * Translate user-facing order tokens like `'name'` or `'-createdAt'` into
87
+ * the internal `OrderSpec` array used by `QuerySetState`.
88
+ *
89
+ * Exposed as `protected` so subclasses can compose the same parse logic
90
+ * when they need to return their own concrete type from `orderBy` without
91
+ * reaching into a base-class instance's protected state.
92
+ */
93
+ protected static buildOrderSpecs<T extends Record<string, unknown>>(tokens: readonly OrderToken<T>[]): OrderSpec<T>[];
71
94
  private static invertOrderSpec;
72
95
  /**
73
96
  * Add a filter expression to the query.
74
97
  *
75
98
  * Multiple `filter()` calls are composed with `AND`.
76
99
  */
77
- filter(q: FilterInput<TModel> | QNode<TModel>): QuerySet<TModel, TBaseResult, TSourceModel, THydrated>;
100
+ filter(q: FilterInput<TModel, TSourceModel> | QNode<TModel, TSourceModel>): QuerySet<TModel, TBaseResult, TSourceModel, THydrated>;
78
101
  /**
79
102
  * Add an exclusion expression to the query.
80
103
  *
81
104
  * Exclusions are translated to `NOT (...)` predicates.
82
105
  */
83
- exclude(q: FilterInput<TModel> | QNode<TModel>): QuerySet<TModel, TBaseResult, TSourceModel, THydrated>;
106
+ exclude(q: FilterInput<TModel, TSourceModel> | QNode<TModel, TSourceModel>): QuerySet<TModel, TBaseResult, TSourceModel, THydrated>;
84
107
  /**
85
108
  * Apply ordering tokens such as `'name'` or `'-createdAt'`.
86
109
  */
@@ -136,9 +159,10 @@ export declare class QuerySet<TModel extends Record<string, unknown>, TBaseResul
136
159
  /**
137
160
  * Async iterable surface for `for await (... of queryset)`.
138
161
  *
139
- * Evaluates this queryset on first use by awaiting {@link QuerySet.fetch} without arguments, then
140
- * yields each element from that {@link QueryResult}. Later async iterations over the same queryset
141
- * instance reuse the cached materialized result instead of issuing another database round-trip.
162
+ * Evaluates this queryset on first use by awaiting `fetch()` without
163
+ * arguments, then yields each element from that materialized result.
164
+ * Later async iterations over the same queryset instance reuse the cached
165
+ * materialized result instead of issuing another database round-trip.
142
166
  */
143
167
  [Symbol.asyncIterator](): AsyncIterator<HydratedQueryResult<TBaseResult, THydrated>>;
144
168
  /**
@@ -157,9 +181,9 @@ export declare class QuerySet<TModel extends Record<string, unknown>, TBaseResul
157
181
  last(): Promise<HydratedQueryResult<TBaseResult, THydrated> | null>;
158
182
  last<Out>(shape: QueryShapeFunction<HydratedQueryResult<TBaseResult, THydrated>, Out>): Promise<Out | null>;
159
183
  last<Out>(shape: QueryShapeParser<HydratedQueryResult<TBaseResult, THydrated>, Out>): Promise<Out | null>;
160
- get(q: FilterInput<TModel> | QNode<TModel>): Promise<HydratedQueryResult<TBaseResult, THydrated>>;
161
- get<Out>(q: FilterInput<TModel> | QNode<TModel>, shape: QueryShapeFunction<HydratedQueryResult<TBaseResult, THydrated>, Out>): Promise<Out>;
162
- get<Out>(q: FilterInput<TModel> | QNode<TModel>, shape: QueryShapeParser<HydratedQueryResult<TBaseResult, THydrated>, Out>): Promise<Out>;
184
+ get(q: FilterInput<TModel, TSourceModel> | QNode<TModel, TSourceModel>): Promise<HydratedQueryResult<TBaseResult, THydrated>>;
185
+ get<Out>(q: FilterInput<TModel, TSourceModel> | QNode<TModel, TSourceModel>, shape: QueryShapeFunction<HydratedQueryResult<TBaseResult, THydrated>, Out>): Promise<Out>;
186
+ get<Out>(q: FilterInput<TModel, TSourceModel> | QNode<TModel, TSourceModel>, shape: QueryShapeParser<HydratedQueryResult<TBaseResult, THydrated>, Out>): Promise<Out>;
163
187
  /**
164
188
  * Execute a `COUNT(*)` query for the current filtered state.
165
189
  */
@@ -171,10 +195,14 @@ export declare class QuerySet<TModel extends Record<string, unknown>, TBaseResul
171
195
  private shapeFetchedRow;
172
196
  private getOrCreateEvaluationCache;
173
197
  private evaluateRows;
198
+ private normalizeRowsForSchemaParsing;
174
199
  private normalizeHydratedRowsForParserShape;
175
200
  private hydrateRows;
201
+ private primeManyToManyOwnerCache;
202
+ private attachRootRecordAccessors;
176
203
  private hydrateJoinNodesForOwner;
177
204
  private hydratePrefetchNode;
205
+ private chunkValues;
178
206
  private groupOwnersByAccessor;
179
207
  private canonicalizeEntity;
180
208
  private normalizeTargetRow;
@@ -1,20 +1,26 @@
1
1
  import type { QuerySetState } from '../domain/QuerySetState';
2
2
  import type { TableMeta } from '../domain/TableMeta';
3
3
  import type { CompiledHydrationNode, CompiledPrefetchQuery, CompiledQuery } from '../domain/CompiledQuery';
4
- import type { Dialect } from '../domain/Dialect';
4
+ import type { Adapter } from '../../connection/adapters/Adapter';
5
5
  /**
6
6
  * Compiles immutable `QuerySet` state into parameterized SQL and recursive
7
7
  * hydration execution artifacts.
8
8
  */
9
9
  export declare class QueryCompiler {
10
10
  private meta;
11
- private dialect;
11
+ private adapter;
12
12
  static readonly BRAND: "tango.orm.query_compiler";
13
13
  readonly __tangoBrand: typeof QueryCompiler.BRAND;
14
- constructor(meta: TableMeta, dialect?: Dialect);
14
+ private readonly placeholders;
15
+ constructor(meta: TableMeta, adapter: Adapter);
15
16
  static isQueryCompiler(value: unknown): value is QueryCompiler;
16
- compile<T>(state: QuerySetState<T>): CompiledQuery;
17
+ compile<T, TSourceModel = unknown>(state: QuerySetState<T, TSourceModel>): CompiledQuery;
17
18
  compilePrefetch(node: CompiledHydrationNode, sourceValues: readonly (string | number)[]): CompiledPrefetchQuery;
19
+ compileManyToManyTargets(node: CompiledHydrationNode, targetIds: readonly (string | number)[]): {
20
+ sql: string;
21
+ params: readonly unknown[];
22
+ };
23
+ private compileManyToManyPrefetch;
18
24
  private compileHydrationNode;
19
25
  private validateHydrationRelation;
20
26
  private buildRootHiddenSelects;
@@ -26,6 +32,7 @@ export declare class QueryCompiler {
26
32
  private buildPrefetchBaseAlias;
27
33
  private buildHydrationColumnAlias;
28
34
  private buildPrefetchSourceAlias;
35
+ private buildFilterAlias;
29
36
  private sanitizeRelationPath;
30
37
  private assertInternalAliasDoesNotCollide;
31
38
  private compileQNode;
@@ -33,6 +40,8 @@ export declare class QueryCompiler {
33
40
  private compileAnd;
34
41
  private compileOr;
35
42
  private compileNot;
43
+ private compileRelationFilter;
44
+ private buildRelationFilterExists;
36
45
  private lookupToSQL;
37
46
  private normalizeParam;
38
47
  private collectStateFilterKeys;
@@ -1,42 +1,209 @@
1
1
  import type { RelationHydrationLoadMode } from './RelationMeta';
2
2
  import type { RelationHydrationCardinality } from './RelationTyping';
3
+ import { InternalPrefetchQueryKind } from './internal/InternalPrefetchQueryKind';
4
+ /**
5
+ * Result of compiling a {@link QuerySet} into an executable SQL statement plus
6
+ * the hydration plan the executor needs to reshape flat rows into nested
7
+ * relation graphs.
8
+ */
3
9
  export interface CompiledQuery {
10
+ /**
11
+ * Parameterized SQL string ready for `client.query(...)`. Identifiers and
12
+ * relation names are pre-validated; values never appear inline.
13
+ */
4
14
  sql: string;
15
+ /**
16
+ * Parameter values bound to the SQL statement in the same order the
17
+ * placeholders appear.
18
+ */
5
19
  params: readonly unknown[];
20
+ /**
21
+ * Optional hydration plan produced when the query declared selected or
22
+ * prefetched relations. Absent for plain row reads that do no
23
+ * relation-shaping work.
24
+ */
6
25
  hydrationPlan?: CompiledHydrationPlanRoot;
7
26
  }
27
+ /**
28
+ * Top-level hydration plan for a compiled query. Groups the join-time
29
+ * hydration nodes (emitted inline with the root query) with the prefetch
30
+ * hydration nodes (executed as follow-up queries after the root rows land).
31
+ */
8
32
  export interface CompiledHydrationPlanRoot {
33
+ /**
34
+ * The relation paths the caller originally requested via
35
+ * `selectRelated(...)` / `prefetchRelated(...)`. Retained for diagnostic
36
+ * messages and snapshot stability.
37
+ */
9
38
  requestedPaths: readonly string[];
39
+ /**
40
+ * Column aliases on the root query that exist solely to support hydration
41
+ * (for example, join-mirrored primary keys). The hydrator strips these
42
+ * before handing rows back to application code.
43
+ */
10
44
  hiddenRootAliases: readonly string[];
45
+ /**
46
+ * Hydration nodes whose rows arrive joined into the root query's result
47
+ * set. Each node describes how to fold those joined columns back into a
48
+ * nested relation attribute.
49
+ */
11
50
  joinNodes: readonly CompiledHydrationNode[];
51
+ /**
52
+ * Hydration nodes that require a follow-up prefetch query after the root
53
+ * rows land. Each node drives one or more `compilePrefetch(...)` calls.
54
+ */
12
55
  prefetchNodes: readonly CompiledHydrationNode[];
13
56
  }
57
+ /**
58
+ * Recursive description of how to hydrate one relation edge and its
59
+ * descendants. The same node shape is used for join-loaded and
60
+ * prefetch-loaded relations; {@link loadMode} distinguishes them.
61
+ */
14
62
  export interface CompiledHydrationNode {
63
+ /** Stable identifier for this node in the plan. */
15
64
  nodeId: string;
65
+ /**
66
+ * Public relation name as declared in the schema, mirrored onto the
67
+ * hydrated record (for example, `author` or `tags`).
68
+ */
16
69
  relationName: string;
70
+ /**
71
+ * Dot-less path expression that reaches this node from the root, used in
72
+ * error messages and plan diagnostics.
73
+ */
17
74
  relationPath: string;
75
+ /** Model key of the owner side of this edge. */
18
76
  ownerModelKey: string;
77
+ /** Model key of the target side of this edge. */
19
78
  targetModelKey: string;
79
+ /** Join-inline vs follow-up prefetch load strategy. */
20
80
  loadMode: RelationHydrationLoadMode;
81
+ /** Whether this edge yields one target or many. */
21
82
  cardinality: RelationHydrationCardinality;
83
+ /**
84
+ * Owner-side column whose value scopes the hydration read. For
85
+ * foreign-key relations this is the local column; for many-to-many this
86
+ * is the owner primary key.
87
+ */
22
88
  sourceKey: string;
89
+ /**
90
+ * Column alias on the root SQL row that surfaces `sourceKey` to the
91
+ * hydrator after projection.
92
+ */
23
93
  ownerSourceAccessor: string;
94
+ /**
95
+ * Target-side column that matches `sourceKey` during resolution.
96
+ */
24
97
  targetKey: string;
98
+ /** Target table name for the related rows. */
25
99
  targetTable: string;
100
+ /** Primary-key column name on the target table. */
26
101
  targetPrimaryKey: string;
102
+ /** Join table name for many-to-many edges. */
103
+ throughTable?: string;
104
+ /**
105
+ * Join-table column storing the owner-side primary key for many-to-many
106
+ * edges.
107
+ */
108
+ throughSourceKey?: string;
109
+ /**
110
+ * Join-table column storing the target-side primary key for many-to-many
111
+ * edges.
112
+ */
113
+ throughTargetKey?: string;
114
+ /**
115
+ * SQL type of the owner-side join column, used to validate compiled
116
+ * placeholder values.
117
+ */
118
+ throughSourceColumnType?: string;
119
+ /**
120
+ * SQL type of the target-side join column, used to validate compiled
121
+ * placeholder values.
122
+ */
123
+ throughTargetColumnType?: string;
124
+ /**
125
+ * Column map for the target table keyed by column name. Used by the
126
+ * hydrator to materialize target rows and by the compiler to emit a
127
+ * stable projection.
128
+ */
27
129
  targetColumns: Record<string, string>;
130
+ /**
131
+ * Ordered trail of relation names that led to this node. Used to produce
132
+ * precise error messages when a hydration plan fails to compile.
133
+ */
28
134
  provenance: readonly string[];
135
+ /** Child nodes whose rows arrive joined with this node's rows. */
29
136
  joinChildren: readonly CompiledHydrationNode[];
137
+ /** Child nodes that require their own follow-up prefetch. */
30
138
  prefetchChildren: readonly CompiledHydrationNode[];
139
+ /** SQL join descriptor attached when this node is loaded inline. */
31
140
  join?: CompiledJoinHydrationDescriptor;
32
141
  }
142
+ /**
143
+ * Join-specific details for a hydration node loaded inline with its parent
144
+ * query. Captures the alias the compiler emitted for the joined table and
145
+ * the column aliases under which each projected column appears in the result
146
+ * row.
147
+ */
33
148
  export interface CompiledJoinHydrationDescriptor {
149
+ /** SQL alias emitted for the joined table. */
34
150
  alias: string;
151
+ /**
152
+ * Aliases for each projected target column, keyed by column name.
153
+ * Ensures the hydrator can pull the column out of the flat SQL row.
154
+ */
35
155
  columns: Record<string, string>;
36
156
  }
37
- export interface CompiledPrefetchQuery {
157
+ /**
158
+ * Discriminated union describing a prefetch query that must run after the
159
+ * root rows load. `direct` is a one-shot select against the target table;
160
+ * `manyToMany` is a two-phase plan that first reads join rows, then loads
161
+ * the target rows by primary key.
162
+ */
163
+ export type CompiledPrefetchQuery = {
164
+ /** Marks a single-query prefetch against the target table. */
165
+ kind: typeof InternalPrefetchQueryKind.DIRECT;
166
+ /** Parameterized select statement to execute. */
38
167
  sql: string;
168
+ /** Parameter values bound to the statement. */
39
169
  params: readonly unknown[];
170
+ /**
171
+ * Target column whose value matches the owner-side `sourceKey`. The
172
+ * hydrator buckets the returned rows by this column.
173
+ */
40
174
  targetKey: string;
175
+ /** Column map for the target rows the hydrator will materialize. */
41
176
  targetColumns: Record<string, string>;
42
- }
177
+ } | {
178
+ /**
179
+ * Marks a two-phase many-to-many prefetch: the join-row query runs
180
+ * first, then a follow-up target query resolves the actual rows by
181
+ * primary key.
182
+ */
183
+ kind: typeof InternalPrefetchQueryKind.MANY_TO_MANY;
184
+ /**
185
+ * SQL for the first phase: select owner/target id pairs from the
186
+ * join table.
187
+ */
188
+ throughSql: string;
189
+ /** Parameter values bound to the join-row statement. */
190
+ throughParams: readonly unknown[];
191
+ /**
192
+ * Alias the compiler assigned to the owner-side join column in the
193
+ * join-row result set. The hydrator uses it to group targets by
194
+ * owner.
195
+ */
196
+ ownerAlias: string;
197
+ /**
198
+ * Alias the compiler assigned to the target-side join column in
199
+ * the join-row result set. The hydrator uses it to assemble the
200
+ * target primary-key list for phase two.
201
+ */
202
+ targetAlias: string;
203
+ /** Target table name for the phase-two read. */
204
+ targetTable: string;
205
+ /** Primary-key column on the target table for the phase-two read. */
206
+ targetPrimaryKey: string;
207
+ /** Column map for the target rows the hydrator will materialize. */
208
+ targetColumns: Record<string, string>;
209
+ };
@@ -1,3 +1,3 @@
1
1
  import type { FilterKey } from './FilterKey';
2
2
  import type { FilterValue } from './FilterValue';
3
- export type FilterInput<T> = Partial<Record<FilterKey<T>, FilterValue>>;
3
+ export type FilterInput<T, TSourceModel = unknown> = Partial<Record<FilterKey<T, TSourceModel>, FilterValue>>;
@@ -1,2 +1,4 @@
1
- import type { LookupType } from '.';
2
- export type FilterKey<T> = keyof T | `${string & keyof T}__${LookupType}`;
1
+ import type { LookupType } from './LookupType';
2
+ type ScalarFilterKey<T> = Extract<keyof T, string> | `${Extract<keyof T, string>}__${LookupType}`;
3
+ export type FilterKey<T, _TSourceModel = unknown> = ScalarFilterKey<T> | string;
4
+ export {};
@@ -1,9 +1,9 @@
1
1
  import type { FilterInput } from './FilterInput';
2
2
  import type { InternalQNodeType } from './internal/InternalQNodeType';
3
3
  export type QNodeType = (typeof InternalQNodeType)[keyof typeof InternalQNodeType];
4
- export interface QNode<T> {
4
+ export interface QNode<T, TSourceModel = unknown> {
5
5
  kind: QNodeType;
6
- where?: FilterInput<T>;
7
- nodes?: QNode<T>[];
8
- node?: QNode<T>;
6
+ where?: FilterInput<T, TSourceModel>;
7
+ nodes?: QNode<T, TSourceModel>[];
8
+ node?: QNode<T, TSourceModel>;
9
9
  }
@@ -1,8 +1,8 @@
1
1
  import type { OrderSpec } from './OrderSpec';
2
2
  import type { QNode } from './QNode';
3
- export interface QuerySetState<T> {
4
- q?: QNode<T>;
5
- excludes?: QNode<T>[];
3
+ export interface QuerySetState<T, TSourceModel = unknown> {
4
+ q?: QNode<T, TSourceModel>;
5
+ excludes?: QNode<T, TSourceModel>[];
6
6
  order?: OrderSpec<T>[];
7
7
  limit?: number;
8
8
  offset?: number;
@@ -36,6 +36,15 @@ export interface RelationMeta {
36
36
  sourceKey: string;
37
37
  /** Target-side column matched against the source key. */
38
38
  targetKey: string;
39
+ /** Many-to-many through table name when applicable. */
40
+ throughTable?: string;
41
+ throughModelKey?: string;
42
+ /** Many-to-many through column that matches the owner source key. */
43
+ throughSourceKey?: string;
44
+ /** Many-to-many through column that matches the target primary key. */
45
+ throughTargetKey?: string;
46
+ throughSourceColumnType?: string;
47
+ throughTargetColumnType?: string;
39
48
  /** Primary key column for the target model. */
40
49
  targetPrimaryKey: string;
41
50
  /** Target model columns and their storage types. */
@@ -1,6 +1,7 @@
1
1
  import type { z } from 'zod';
2
2
  import type { Model, PersistedModelOutput } from '@danceroutine/tango-schema/domain';
3
3
  import type { DecoratedFieldKind, InternalDecoratedFieldKind, RelationDecoratedSchema } from '@danceroutine/tango-schema/model';
4
+ import type { ManyToManyRelatedManager } from '../../manager/relations/ManyToManyRelatedManager';
4
5
  export declare const InternalRelationHydrationCardinality: {
5
6
  readonly SINGLE: "single";
6
7
  readonly MANY: "many";
@@ -37,22 +38,68 @@ type NextSeenModels<TSeen extends readonly string[], TTargetModel> = ModelKey<TT
37
38
  type CanTraverseGeneratedTarget<TTargetModel, TSeen extends readonly string[], TCycleBudget extends readonly unknown[]> = ModelKey<TTargetModel> extends TSeen[number] ? (TCycleBudget extends [] ? false : true) : true;
38
39
  type NextGeneratedCycleBudget<TTargetModel, TSeen extends readonly string[], TCycleBudget extends readonly unknown[]> = ModelKey<TTargetModel> extends TSeen[number] ? TailTuple<TCycleBudget> : TCycleBudget;
39
40
  type GeneratedCardinalityIncludesMany<TCardinality extends RelationHydrationCardinality> = TCardinality extends typeof InternalRelationHydrationCardinality.MANY ? true : false;
41
+ /**
42
+ * Generated path keys accepted by `selectRelated(...)`.
43
+ *
44
+ * Walks the ambient relation registry one hop at a time. Each hop contributes:
45
+ * (a) the bare relation key (e.g. `author`) as a valid path terminus, and
46
+ * (b) a dunder-joined extension (`author__profile`, ...) that recurses into
47
+ * the target model.
48
+ *
49
+ * Because `selectRelated` only composes single-valued hops, the mapped type
50
+ * filters relations whose `cardinality` is not `SINGLE`. The `TSeen` tuple
51
+ * tracks which model keys the recursion has visited so cyclic schemas
52
+ * (`manager -> manager`) are allowed a bounded number of revisits before
53
+ * typing falls back to `never`. `TCycleBudget` is that revisit budget; see
54
+ * `CanTraverseGeneratedTarget` / `NextGeneratedCycleBudget` for how it
55
+ * shrinks only when the next hop actually re-enters a seen model.
56
+ */
40
57
  export type GeneratedSelectRelatedPathKeys<TSourceModel, TSeen extends readonly string[] = [ModelKey<TSourceModel>], TCycleBudget extends readonly unknown[] = DefaultGeneratedCycleBudget> = {
41
58
  [TKey in GeneratedRelationKeys<TSourceModel>]: GeneratedRelations<TSourceModel>[TKey] extends {
42
59
  target: infer TTarget extends AnyModel;
43
60
  cardinality: typeof InternalRelationHydrationCardinality.SINGLE;
44
61
  } ? TKey | (CanTraverseGeneratedTarget<TTarget, TSeen, TCycleBudget> extends true ? `${TKey}__${GeneratedSelectRelatedPathKeys<TTarget, NextSeenModels<TSeen, TTarget>, NextGeneratedCycleBudget<TTarget, TSeen, TCycleBudget>>}` : never) : never;
45
62
  }[GeneratedRelationKeys<TSourceModel>];
63
+ /**
64
+ * Generated path keys accepted by `prefetchRelated(...)`.
65
+ *
66
+ * Similar in shape to {@link GeneratedSelectRelatedPathKeys}, but relaxes two
67
+ * constraints because prefetch can cross and continue past collection edges:
68
+ * 1. Any hop whose cardinality is `MANY` is a valid terminus. The
69
+ * `THasMany` flag threads through recursion so once a collection edge
70
+ * has been crossed, every subsequent hop is also a valid terminus
71
+ * (matches Django's `prefetch_related` semantics).
72
+ * 2. Single-valued hops are still accepted as terminators so paths like
73
+ * `posts__author` survive the join.
74
+ *
75
+ * Cycle handling reuses the same `TSeen` / `TCycleBudget` machinery described
76
+ * in {@link GeneratedSelectRelatedPathKeys}: cyclic schemas are typed up to
77
+ * the bound, then fall back to weaker typing rather than failing.
78
+ */
46
79
  export type GeneratedPrefetchRelatedPathKeys<TSourceModel, THasMany extends boolean = false, TSeen extends readonly string[] = [ModelKey<TSourceModel>], TCycleBudget extends readonly unknown[] = DefaultGeneratedCycleBudget> = {
47
80
  [TKey in GeneratedRelationKeys<TSourceModel>]: GeneratedRelations<TSourceModel>[TKey] extends {
48
81
  target: infer TTarget extends AnyModel;
49
82
  cardinality: infer TCardinality extends RelationHydrationCardinality;
50
83
  } ? (THasMany extends true ? TKey : GeneratedCardinalityIncludesMany<TCardinality> extends true ? TKey : never) | (CanTraverseGeneratedTarget<TTarget, TSeen, TCycleBudget> extends true ? `${TKey}__${GeneratedPrefetchRelatedPathKeys<TTarget, THasMany extends true ? true : GeneratedCardinalityIncludesMany<TCardinality>, NextSeenModels<TSeen, TTarget>, NextGeneratedCycleBudget<TTarget, TSeen, TCycleBudget>>}` : never) : never;
51
84
  }[GeneratedRelationKeys<TSourceModel>];
85
+ /**
86
+ * Generated relation-path filter keys accepted by `filter(...)`, `exclude(...)`,
87
+ * and `Q(...)`.
88
+ *
89
+ * This stays intentionally lighter than the eager-loading path typing:
90
+ * applications with generated relation registries get completion for the
91
+ * relation path prefix, while the terminal field and lookup suffix remain a
92
+ * string tail so the compiler does not explode on deep recursive field
93
+ * extraction.
94
+ */
95
+ export type GeneratedRelationFilterKeys<TSourceModel> = `${GeneratedSelectRelatedPathKeys<TSourceModel>}__${string}` | `${GeneratedPrefetchRelatedPathKeys<TSourceModel>}__${string}`;
52
96
  type GeneratedHydratedTarget<TDescriptor, TPath extends string | never> = TDescriptor extends {
53
97
  target: infer TTarget extends AnyModel;
54
98
  } ? [TPath] extends [never] ? ModelRow<TTarget> : HydratedQueryResult<ModelRow<TTarget>, GeneratedHydratedRelationMap<TTarget, TPath>> : never;
55
99
  type GeneratedHydratedValue<TDescriptor, TPath extends string | never> = TDescriptor extends {
100
+ target: infer TTarget extends AnyModel;
101
+ kind: 'manyToMany';
102
+ } ? ManyToManyRelatedManager<ModelRow<TTarget>> : TDescriptor extends {
56
103
  cardinality: infer TCardinality extends RelationHydrationCardinality;
57
104
  } ? TCardinality extends typeof InternalRelationHydrationCardinality.SINGLE ? GeneratedHydratedTarget<TDescriptor, TPath> | null : GeneratedHydratedTarget<TDescriptor, TPath>[] : never;
58
105
  type GeneratedHydrationForPath<TSourceModel, TPath extends string> = SplitPath<TPath> extends [infer THead extends string, ...infer TRest extends string[]] ? THead extends GeneratedRelationKeys<TSourceModel> ? {
@@ -1,23 +1,10 @@
1
1
  import type { Model as SchemaModel } from '@danceroutine/tango-schema/domain';
2
2
  import type { TableMeta } from './TableMeta';
3
- type ModelMetadataLike = Omit<SchemaModel['metadata'], 'key' | 'namespace' | 'fields'> & {
4
- key?: string;
5
- namespace?: string;
6
- fields: Array<{
7
- name: string;
8
- type: string;
9
- primaryKey?: boolean;
10
- }>;
11
- };
12
- type TableMetaModel = {
13
- metadata: ModelMetadataLike;
14
- };
15
3
  /**
16
4
  * Build registry-backed recursive table metadata for query planning and
17
5
  * hydration execution.
18
6
  */
19
7
  export declare class TableMetaFactory {
20
- static create(model: TableMetaModel): TableMeta;
8
+ static create(model: SchemaModel): TableMeta;
21
9
  private static createWithCache;
22
10
  }
23
- export {};
@@ -14,7 +14,7 @@ export type { QNode } from './QNode';
14
14
  export { QueryResult } from './QueryResult';
15
15
  export type { QuerySetState } from './QuerySetState';
16
16
  export type { RelationMeta } from './RelationMeta';
17
- export type { ForwardSingleRelations, GeneratedHydratedRelationMap, GeneratedPrefetchRelatedPathKeys, GeneratedSelectRelatedPathKeys, HydratedQueryResult, HydratedRelationMap, ManyRelationHydrationCardinality, MaybeHydratedRelationMap, PrefetchRelatedRelations, RelationKeys, RelationHydrationCardinality, ReverseCollectionRelations, ReverseSingleRelations, SelectRelatedRelations, SingleRelationHydrationCardinality, } from './RelationTyping';
17
+ export type { ForwardSingleRelations, GeneratedRelationFilterKeys, GeneratedHydratedRelationMap, GeneratedPrefetchRelatedPathKeys, GeneratedSelectRelatedPathKeys, HydratedQueryResult, HydratedRelationMap, ManyRelationHydrationCardinality, MaybeHydratedRelationMap, PrefetchRelatedRelations, RelationKeys, RelationHydrationCardinality, ReverseCollectionRelations, ReverseSingleRelations, SelectRelatedRelations, SingleRelationHydrationCardinality, } from './RelationTyping';
18
18
  export { InternalRelationHydrationCardinality } from './RelationTyping';
19
19
  export type { TableMeta } from './TableMeta';
20
20
  export { TableMetaFactory } from './TableMetaFactory';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Discriminator strings for compiled prefetch hydration: how `QueryCompiler.compilePrefetch`
3
+ * batches related rows for hydration (`CompiledPrefetchQuery`).
4
+ */
5
+ export declare const InternalPrefetchQueryKind: {
6
+ /**
7
+ * Single-query prefetch against the related table (`SELECT … FROM target WHERE fk IN (...)`).
8
+ * Used when each related row is reachable from one physical table via a foreign key or
9
+ * symmetric join path (belongsTo, hasMany, hasOne, reverse one-to-one).
10
+ */
11
+ readonly DIRECT: "direct";
12
+ /**
13
+ * Two-phase many-to-many prefetch: first query reads join (through) rows pairing owner ids to
14
+ * target ids (`throughSql`), then target rows load by primary key (`compileManyToManyTargets`).
15
+ * Distinct from relation metadata’s `manyToMany` relation *kind* on an endpoint—this marks the
16
+ * compiled prefetch *strategy*, not the schema edge type.
17
+ */
18
+ readonly MANY_TO_MANY: "manyToMany";
19
+ };
20
+ export type PrefetchQueryKind = (typeof InternalPrefetchQueryKind)[keyof typeof InternalPrefetchQueryKind];
@@ -8,6 +8,7 @@ export type * from './domain/index';
8
8
  export type { TableMeta } from './domain/index';
9
9
  export { QueryResult } from './domain/index';
10
10
  export { QuerySet } from './QuerySet';
11
+ export { ModelQuerySet } from './ModelQuerySet';
11
12
  export type { QueryExecutor } from './QuerySet';
12
13
  export { QBuilder, QBuilder as Q } from './QBuilder';
13
14
  export { QueryCompiler } from './compiler/index';