durcno 1.0.0-alpha.7 → 1.0.0-alpha.8
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/README.md +3 -2
- package/dist/bin.cjs +1 -1
- package/dist/src/connectors/common.d.mts +4 -4
- package/dist/src/index.d.mts +2 -2
- package/dist/src/logger.d.mts +3 -3
- package/dist/src/query-builders/rq.d.mts +16 -3
- package/dist/src/query-builders/rq.mjs +44 -81
- package/dist/src/table.d.mts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
|
|
23
23
|
- **🔗 Relation Mapping** — Intuitive `many`, `one`, and `fk` relations with full type inference.
|
|
24
24
|
- **🦾 Robust Migrations** — Auto-generated, reversible, and squashable migrations for production applications.
|
|
25
|
-
-
|
|
25
|
+
- **⚡ Zero Runtime Overhead** — Thin abstraction layer that compiles to efficient SQL.
|
|
26
26
|
- **🔌 Multiple Drivers** — Support for `pg`, `postgres`, `bun`, and `pglite` drivers.
|
|
27
|
+
- **🛡️ Zod Integration** — Built-in Zod validators for schema validation and type inference.
|
|
27
28
|
- **🌍 PostGIS Support** — First-class geographic column types for spatial queries.
|
|
28
29
|
|
|
29
30
|
## Setup
|
|
@@ -40,7 +41,7 @@ npm exec durcno init
|
|
|
40
41
|
|
|
41
42
|
Get started with Durcno by following our comprehensive documentation.
|
|
42
43
|
|
|
43
|
-
**[
|
|
44
|
+
**[Getting Started | Durcno](https://durcno.dev/docs/latest/getting-started)**
|
|
44
45
|
|
|
45
46
|
> [!WARNING]
|
|
46
47
|
> Durcno is currently in the alpha stage.
|
package/dist/bin.cjs
CHANGED
|
@@ -13603,7 +13603,7 @@ async function status(options) {
|
|
|
13603
13603
|
}
|
|
13604
13604
|
|
|
13605
13605
|
// src/cli/index.ts
|
|
13606
|
-
program.version("1.0.0-alpha.
|
|
13606
|
+
program.version("1.0.0-alpha.7");
|
|
13607
13607
|
var Options = {
|
|
13608
13608
|
config: ["--config <path>", "Path to the config file"]
|
|
13609
13609
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QueryLogger } from "../logger.mjs";
|
|
2
2
|
import { Query } from "../query-builders/query.mjs";
|
|
3
3
|
import { MigrationOptions } from "../migration/index.mjs";
|
|
4
4
|
import { ConnectionOptions } from "node:tls";
|
|
@@ -43,7 +43,7 @@ type ConnectorOptions = {
|
|
|
43
43
|
* When set, all executed queries will be logged at the `info` level with
|
|
44
44
|
* structured `{ sql, arguments }` metadata.
|
|
45
45
|
*/
|
|
46
|
-
logger?:
|
|
46
|
+
logger?: QueryLogger;
|
|
47
47
|
};
|
|
48
48
|
/**
|
|
49
49
|
* Abstract base class for all database connectors.
|
|
@@ -83,7 +83,7 @@ declare abstract class Connector {
|
|
|
83
83
|
/** Connection pool size override (can be mutated by CLI commands before `getPool()` is called). */
|
|
84
84
|
pool?: ConnectorOptions["pool"];
|
|
85
85
|
/** Optional logger instance for query logging. */
|
|
86
|
-
logger?:
|
|
86
|
+
logger?: QueryLogger;
|
|
87
87
|
constructor(options: ConnectorOptions);
|
|
88
88
|
/**
|
|
89
89
|
* Creates a single-connection client.
|
|
@@ -116,7 +116,7 @@ declare abstract class $QueryExecutor {
|
|
|
116
116
|
/** The connector options used to create this executor. */
|
|
117
117
|
options: ConnectorOptions;
|
|
118
118
|
/** Optional logger instance for query logging. */
|
|
119
|
-
logger?:
|
|
119
|
+
logger?: QueryLogger;
|
|
120
120
|
constructor(options: ConnectorOptions);
|
|
121
121
|
/**
|
|
122
122
|
* Executes a SQL query with optional parameterized arguments.
|
package/dist/src/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QueryLogger } from "./logger.mjs";
|
|
2
2
|
import { Is, Key, Prettify, SelfOrArray, SelfOrReadonly, UnionToIntersection, Valueof } from "./types.mjs";
|
|
3
3
|
import { Column, ColumnConfig, notNull, primaryKey, unique } from "./columns/common.mjs";
|
|
4
4
|
import { PrimaryKeyConstraint, primaryKeyConstraint } from "./constraints/primary-key.mjs";
|
|
@@ -93,4 +93,4 @@ type Config<T extends Connector = Connector> = {
|
|
|
93
93
|
connector: T;
|
|
94
94
|
};
|
|
95
95
|
//#endregion
|
|
96
|
-
export { $, type $Client, type AnyColumn, type AnyTableColumn, Arg, Column, type ColumnConfig, Config, type ConnectorOptions,
|
|
96
|
+
export { $, type $Client, type AnyColumn, type AnyTableColumn, Arg, Column, type ColumnConfig, Config, type ConnectorOptions, Filter, type Is, type Key, Migrations, type Prettify, PrimaryKeyConstraint, Query, type QueryLogger, type SelfOrArray, type SelfOrReadonly, Sql, type TableColumn, type UnionToIntersection, UniqueConstraint, type UuidVersion, type Valueof, and, arrayAll, arrayContainedBy, arrayContains, arrayHas, arrayOverlaps, asc, bigint, bigserial, boolean, bytea, char, cidr, database, date, defineConfig, desc, enumed, enumtype, eq, fk, geography, gt, gte, index, inet, integer, isIn, isNotNull, isNull, json, jsonb, lt, lte, macaddr, many, ne, notNull, now, numeric, one, or, pk, prequery, primaryKey, primaryKeyConstraint, relations, sequence, serial, smallint, smallserial, sql, table, text, time, timestamp, unique, uniqueConstraint, uniqueIndex, uuid, uuidv4, uuidv7, varchar };
|
package/dist/src/logger.d.mts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Any object satisfying this contract can be used as a Durcno query logger.
|
|
6
6
|
*/
|
|
7
|
-
interface
|
|
7
|
+
interface QueryLogger {
|
|
8
8
|
info(message: string, meta?: Record<string, unknown>): void;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
@@ -27,10 +27,10 @@ interface DurcnoLogger {
|
|
|
27
27
|
* });
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
|
-
declare function createQueryLogger():
|
|
30
|
+
declare function createQueryLogger(): QueryLogger;
|
|
31
31
|
/**
|
|
32
32
|
* @deprecated Use `createQueryLogger` instead.
|
|
33
33
|
*/
|
|
34
34
|
declare const createDurcnoLogger: typeof createQueryLogger;
|
|
35
35
|
//#endregion
|
|
36
|
-
export {
|
|
36
|
+
export { QueryLogger, createDurcnoLogger, createQueryLogger };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyColumn, AnyRelation, Fk, Many, Relations, StdRelations, TColsToLeftRight, TableWithColumns } from "../table.mjs";
|
|
1
|
+
import { AnyColumn, AnyRelation, Fk, Many, One, Relations, StdRelations, TColsToLeftRight, TableWithColumns } from "../table.mjs";
|
|
2
2
|
import { OrderBy } from "./orderby-clause.mjs";
|
|
3
3
|
import { QueryPromise } from "./query-promise.mjs";
|
|
4
4
|
import { BuildFilterExpression } from "../filters/index.mjs";
|
|
@@ -7,12 +7,25 @@ import { QueryExecutor } from "../connectors/common.mjs";
|
|
|
7
7
|
|
|
8
8
|
//#region src/query-builders/rq.d.ts
|
|
9
9
|
type RelationReturnType<O, TRelation extends AnyRelation> = TRelation extends Many<any, any, any, any> ? O[] : TRelation extends Fk<any, any, any, infer TCol> ? TCol["isNotNull"] extends true ? O : O | null : O | null;
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Shared column-selection fields used across all option types.
|
|
12
|
+
*/
|
|
13
|
+
type ColumnsOption<TTSchema extends string, TTName extends string, TTColumns extends Record<string, AnyColumn>> = {
|
|
11
14
|
columns?: Partial<Record<keyof TableWithColumns<TTSchema, TTName, TTColumns>["_"]["columns"], true>> | Partial<Record<keyof TableWithColumns<TTSchema, TTName, TTColumns>["_"]["columns"], false>>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Options allowed for a nested `Fk` or `One` relation.
|
|
18
|
+
* `where`, `orderBy`, and `limit` are excluded because the join condition
|
|
19
|
+
* already uniquely identifies the row — further filtering is meaningless.
|
|
20
|
+
*/
|
|
21
|
+
type NestedOptionsFkOne<TTSchema extends string, TTName extends string, TTColumns extends Record<string, AnyColumn>, TAllRelations extends Record<string, StdRelations>> = ColumnsOption<TTSchema, TTName, TTColumns> & {
|
|
22
|
+
with?: keyof TAllRelations[`"${TTSchema}"."${TTName}"`]["map"] extends never ? never : { [TRelationName in keyof TAllRelations[`"${TTSchema}"."${TTName}"`]["map"]]?: TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName] extends Fk<any, any, any, any> | One<any, any, any, any> ? NestedOptionsFkOne<TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["schema"], TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["name"], TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["cols"], TAllRelations> : OptionsBase<TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["schema"], TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["name"], TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["cols"], TAllRelations> };
|
|
23
|
+
};
|
|
24
|
+
type OptionsBase<TTSchema extends string, TTName extends string, TTColumns extends Record<string, AnyColumn>, TAllRelations extends Record<string, StdRelations>> = ColumnsOption<TTSchema, TTName, TTColumns> & {
|
|
12
25
|
where?: BuildFilterExpression<TColsToLeftRight<TableWithColumns<TTSchema, TTName, TTColumns>["_"]["columns"]>>;
|
|
13
26
|
orderBy?: OrderBy<TableWithColumns<TTSchema, TTName, TTColumns>> | OrderBy<TableWithColumns<TTSchema, TTName, TTColumns>>[];
|
|
14
27
|
limit?: number;
|
|
15
|
-
with?: keyof TAllRelations[`"${TTSchema}"."${TTName}"`]["map"] extends never ? never : { [TRelationName in keyof TAllRelations[`"${TTSchema}"."${TTName}"`]["map"]]?: OptionsBase<TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["schema"], TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["name"], TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["cols"], TAllRelations> };
|
|
28
|
+
with?: keyof TAllRelations[`"${TTSchema}"."${TTName}"`]["map"] extends never ? never : { [TRelationName in keyof TAllRelations[`"${TTSchema}"."${TTName}"`]["map"]]?: TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName] extends Fk<any, any, any, any> | One<any, any, any, any> ? NestedOptionsFkOne<TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["schema"], TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["name"], TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["cols"], TAllRelations> : OptionsBase<TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["schema"], TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["name"], TAllRelations[`"${TTSchema}"."${TTName}"`]["map"][TRelationName]["table"]["_"]["cols"], TAllRelations> };
|
|
16
29
|
};
|
|
17
30
|
type Options<TTSchema extends string, TTName extends string, TTColumns extends Record<string, AnyColumn>, TAllRelations extends Record<string, StdRelations>, TOffset extends boolean = false> = OptionsBase<TTSchema, TTName, TTColumns, TAllRelations> & {
|
|
18
31
|
offset?: TOffset extends true ? number : never;
|
|
@@ -41,9 +41,7 @@ var RelationQuery = class extends QueryPromise {
|
|
|
41
41
|
const options = this.#options;
|
|
42
42
|
const query = new Query("SELECT ", this.handleRows.bind(this));
|
|
43
43
|
const selects = [];
|
|
44
|
-
|
|
45
|
-
else if (Object.values(options.columns).at(0) === true) for (const [colName, column] of Object.entries(this.#table._.columns)) colName in options.columns && selects.push(column.fullName);
|
|
46
|
-
else for (const [colName, column] of Object.entries(this.#table._.columns)) !(colName in options.columns) && selects.push(column.fullName);
|
|
44
|
+
for (const [, column] of getSelectedColumns(options.columns, this.#table._.columns)) selects.push(column.fullName);
|
|
47
45
|
const relations = this.#allRelations[this.#table._.fullName];
|
|
48
46
|
if (relations) {
|
|
49
47
|
if (options.with) {
|
|
@@ -61,7 +59,7 @@ var RelationQuery = class extends QueryPromise {
|
|
|
61
59
|
const relations = this.#allRelations[this.#table._.fullName];
|
|
62
60
|
if (relations) {
|
|
63
61
|
const relation = relations.map[key];
|
|
64
|
-
if (relation) query
|
|
62
|
+
if (relation) buildRelationSubquery(query, key, this.#table._.name, o, relation, this.#allRelations);
|
|
65
63
|
}
|
|
66
64
|
}
|
|
67
65
|
if (options.where) {
|
|
@@ -92,6 +90,15 @@ var RelationQuery = class extends QueryPromise {
|
|
|
92
90
|
}
|
|
93
91
|
};
|
|
94
92
|
/**
|
|
93
|
+
* Returns the [colName, column] entries to include based on the columns filter option.
|
|
94
|
+
*/
|
|
95
|
+
function getSelectedColumns(columns, tableColumns) {
|
|
96
|
+
const entries = Object.entries(tableColumns);
|
|
97
|
+
if (columns === void 0 || Object.keys(columns).length === 0) return entries;
|
|
98
|
+
if (Object.values(columns).at(0) === true) return entries.filter(([colName]) => colName in columns);
|
|
99
|
+
return entries.filter(([colName]) => !(colName in columns));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
95
102
|
* Build the json_build_object selects for a relation, including nested relations.
|
|
96
103
|
* @param alias - The alias used for the inner subquery (e.g., "posts", "posts__comments")
|
|
97
104
|
* @param options - The options for this relation
|
|
@@ -100,9 +107,7 @@ var RelationQuery = class extends QueryPromise {
|
|
|
100
107
|
*/
|
|
101
108
|
function getJsonBuildObjectSelects(alias, options, table, allRelations) {
|
|
102
109
|
const selects = [];
|
|
103
|
-
|
|
104
|
-
else if (Object.values(options.columns).at(0) === true) for (const [colName, column] of Object.entries(table._.columns)) colName in options.columns && selects.push(`'${colName}', "${alias}"."${column.nameSnake}"`);
|
|
105
|
-
else for (const [colName, column] of Object.entries(table._.columns)) !(colName in options.columns) && selects.push(`'${colName}', "${alias}"."${column.nameSnake}"`);
|
|
110
|
+
for (const [colName, column] of getSelectedColumns(options.columns, table._.columns)) selects.push(`'${colName}', "${alias}"."${column.nameSnake}"`);
|
|
106
111
|
if (options.with) {
|
|
107
112
|
const tableRelations = allRelations[table._.fullName];
|
|
108
113
|
if (tableRelations) {
|
|
@@ -113,101 +118,59 @@ function getJsonBuildObjectSelects(alias, options, table, allRelations) {
|
|
|
113
118
|
}
|
|
114
119
|
/**
|
|
115
120
|
* Build a LATERAL JOIN subquery for a relation, recursively handling nested relations.
|
|
121
|
+
* Mutates query.sql directly to avoid intermediate string allocations.
|
|
116
122
|
* Alias path format: "relationKey" for top-level, "parent__child" for nested (debuggable).
|
|
117
123
|
*
|
|
124
|
+
* @param query - The query object to mutate
|
|
118
125
|
* @param aliasPath - The full alias path (e.g., "posts", "posts__comments")
|
|
119
126
|
* @param parentTableAlias - The alias of the parent table (e.g., "users" for top-level, "posts" for nested)
|
|
120
127
|
* @param options - The options for this relation
|
|
121
128
|
* @param relation - The relation definition (Many, One, or Fk)
|
|
122
129
|
* @param allRelations - All relations in the schema
|
|
123
130
|
*/
|
|
124
|
-
function buildRelationSubquery(aliasPath, parentTableAlias, options, relation, allRelations) {
|
|
125
|
-
|
|
131
|
+
function buildRelationSubquery(query, aliasPath, parentTableAlias, options, relation, allRelations) {
|
|
132
|
+
query.sql += " LEFT JOIN LATERAL (";
|
|
126
133
|
const jsonSelects = getJsonBuildObjectSelects(aliasPath, options, relation.table, allRelations);
|
|
127
|
-
if (relation.t === "Many") sql += `SELECT coalesce(json_agg(json_build_object(${jsonSelects.join(", ")})), '[]'::json) AS "data"`;
|
|
128
|
-
else sql += `SELECT json_build_object(${jsonSelects.join(", ")}) AS "data"`;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
sql += `, "${nestedAliasPath}"."data" AS "${nestedKey}_data"`;
|
|
136
|
-
}
|
|
134
|
+
if (relation.t === "Many") query.sql += `SELECT coalesce(json_agg(json_build_object(${jsonSelects.join(", ")})), '[]'::json) AS "data"`;
|
|
135
|
+
else query.sql += `SELECT json_build_object(${jsonSelects.join(", ")}) AS "data"`;
|
|
136
|
+
const nestedTableRelations = options.with ? allRelations[relation.table._.fullName] : void 0;
|
|
137
|
+
query.sql += ` FROM (SELECT "${aliasPath}".*`;
|
|
138
|
+
if (nestedTableRelations) {
|
|
139
|
+
for (const nestedKey in options.with) if (nestedTableRelations.map[nestedKey]) {
|
|
140
|
+
const nestedAliasPath = `${aliasPath}__${nestedKey}`;
|
|
141
|
+
query.sql += `, "${nestedAliasPath}"."data" AS "${nestedKey}_data"`;
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
|
-
sql += ` FROM ${relation.table._.fullName} "${aliasPath}"`;
|
|
140
|
-
if (options.with) {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const nestedRelation = tableRelations.map[nestedKey];
|
|
145
|
-
if (nestedRelation && nestedOptions) {
|
|
146
|
-
const nestedAliasPath = `${aliasPath}__${nestedKey}`;
|
|
147
|
-
sql += buildNestedRelationSubquery(nestedAliasPath, aliasPath, nestedOptions, nestedRelation, allRelations);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
144
|
+
query.sql += ` FROM ${relation.table._.fullName} "${aliasPath}"`;
|
|
145
|
+
if (nestedTableRelations) for (const nestedKey in options.with) {
|
|
146
|
+
const nestedOptions = options.with[nestedKey];
|
|
147
|
+
const nestedRelation = nestedTableRelations.map[nestedKey];
|
|
148
|
+
if (nestedRelation && nestedOptions) buildRelationSubquery(query, `${aliasPath}__${nestedKey}`, aliasPath, nestedOptions, nestedRelation, allRelations);
|
|
150
149
|
}
|
|
151
150
|
if (relation.t === "Many" || relation.t === "One") {
|
|
152
151
|
const referencedCol = relation.col.referencesCol;
|
|
153
152
|
if (!referencedCol) throw new Error(`Relation column "${relation.col.name}" has no .references() definition. Columns used in 'many' or 'one' relations must call .references() to define the join target.`);
|
|
154
|
-
sql += ` WHERE "${aliasPath}"."${relation.col.nameSnake}" = "${parentTableAlias}"."${referencedCol.nameSnake}"`;
|
|
155
|
-
if (relation.t === "
|
|
153
|
+
query.sql += ` WHERE "${aliasPath}"."${relation.col.nameSnake}" = "${parentTableAlias}"."${referencedCol.nameSnake}"`;
|
|
154
|
+
if (relation.t === "Many" && options.where) {
|
|
155
|
+
query.sql += " AND ";
|
|
156
|
+
options.where.toQuery(query);
|
|
157
|
+
}
|
|
158
|
+
if (relation.t === "One") query.sql += ` LIMIT 1`;
|
|
156
159
|
} else if (relation.t === "Fk") {
|
|
157
160
|
const referencedCol = relation.col.referencesCol;
|
|
158
161
|
if (!referencedCol) throw new Error(`Relation column "${relation.col.name}" has no .references() definition. Columns used in 'fk' relations must call .references() to define the join target.`);
|
|
159
|
-
sql += ` WHERE "${aliasPath}"."${referencedCol.nameSnake}" = "${parentTableAlias}"."${relation.col.nameSnake}"`;
|
|
160
|
-
sql += ` LIMIT 1`;
|
|
162
|
+
query.sql += ` WHERE "${aliasPath}"."${referencedCol.nameSnake}" = "${parentTableAlias}"."${relation.col.nameSnake}"`;
|
|
163
|
+
query.sql += ` LIMIT 1`;
|
|
161
164
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Build a nested LATERAL JOIN subquery (for relations within relations).
|
|
168
|
-
* Similar to buildRelationSubquery but with parent alias reference for WHERE conditions.
|
|
169
|
-
*/
|
|
170
|
-
function buildNestedRelationSubquery(aliasPath, parentAliasPath, options, relation, allRelations) {
|
|
171
|
-
let sql = " LEFT JOIN LATERAL (";
|
|
172
|
-
const jsonSelects = getJsonBuildObjectSelects(aliasPath, options, relation.table, allRelations);
|
|
173
|
-
if (relation.t === "Many") sql += `SELECT coalesce(json_agg(json_build_object(${jsonSelects.join(", ")})), '[]'::json) AS "data"`;
|
|
174
|
-
else sql += `SELECT json_build_object(${jsonSelects.join(", ")}) AS "data"`;
|
|
175
|
-
sql += ` FROM (SELECT "${aliasPath}".*`;
|
|
176
|
-
if (options.with) {
|
|
177
|
-
const tableRelations = allRelations[relation.table._.fullName];
|
|
178
|
-
if (tableRelations) {
|
|
179
|
-
for (const nestedKey in options.with) if (tableRelations.map[nestedKey]) {
|
|
180
|
-
const nestedAliasPath = `${aliasPath}__${nestedKey}`;
|
|
181
|
-
sql += `, "${nestedAliasPath}"."data" AS "${nestedKey}_data"`;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
sql += ` FROM ${relation.table._.fullName} "${aliasPath}"`;
|
|
186
|
-
if (options.with) {
|
|
187
|
-
const tableRelations = allRelations[relation.table._.fullName];
|
|
188
|
-
if (tableRelations) for (const nestedKey in options.with) {
|
|
189
|
-
const nestedOptions = options.with[nestedKey];
|
|
190
|
-
const nestedRelation = tableRelations.map[nestedKey];
|
|
191
|
-
if (nestedRelation && nestedOptions) {
|
|
192
|
-
const nestedAliasPath = `${aliasPath}__${nestedKey}`;
|
|
193
|
-
sql += buildNestedRelationSubquery(nestedAliasPath, aliasPath, nestedOptions, nestedRelation, allRelations);
|
|
194
|
-
}
|
|
165
|
+
if (relation.t === "Many") {
|
|
166
|
+
if (options.orderBy) {
|
|
167
|
+
const orders = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy];
|
|
168
|
+
query.sql += ` ORDER BY ${orders.map((o) => o.toSQL()).join(", ")}`;
|
|
195
169
|
}
|
|
170
|
+
if (options.limit) query.sql += ` LIMIT ${options.limit}`;
|
|
196
171
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (!referencedCol) throw new Error(`Relation column "${relation.col.nameSnake}" has no .references() definition. Columns used in 'many' or 'one' relations must call .references() to define the join target.`);
|
|
200
|
-
sql += ` WHERE "${aliasPath}"."${relation.col.nameSnake}" = "${parentAliasPath}"."${referencedCol.nameSnake}"`;
|
|
201
|
-
if (relation.t === "One") sql += ` LIMIT 1`;
|
|
202
|
-
} else if (relation.t === "Fk") {
|
|
203
|
-
const referencedCol = relation.col.referencesCol;
|
|
204
|
-
if (!referencedCol) throw new Error(`Relation column "${relation.col.nameSnake}" has no .references() definition. Columns used in 'fk' relations must call .references() to define the join target.`);
|
|
205
|
-
sql += ` WHERE "${aliasPath}"."${referencedCol.nameSnake}" = "${parentAliasPath}"."${relation.col.nameSnake}"`;
|
|
206
|
-
sql += ` LIMIT 1`;
|
|
207
|
-
}
|
|
208
|
-
sql += `) "${aliasPath}"`;
|
|
209
|
-
sql += `) "${aliasPath}" ON true`;
|
|
210
|
-
return sql;
|
|
172
|
+
query.sql += `) "${aliasPath}"`;
|
|
173
|
+
query.sql += `) "${aliasPath}" ON true`;
|
|
211
174
|
}
|
|
212
175
|
function convert(object, table, allRelations) {
|
|
213
176
|
for (const key of Object.keys(object)) {
|
package/dist/src/table.d.mts
CHANGED
|
@@ -139,4 +139,4 @@ type StdRelations = Relations<string, string, Record<string, AnyColumn>, Record<
|
|
|
139
139
|
type AnyRelations = Relations<any, any, Record<any, any>, Record<any, any>>;
|
|
140
140
|
declare function relations<TTSchema extends string, TTName extends string, TColumns extends Record<string, AnyColumn>, TRelations extends Record<string, AnyRelation>>(table: TableWithColumns<TTSchema, TTName, TColumns>, relations: () => TRelations): () => Relations<TTSchema, TTName, TColumns, TRelations>;
|
|
141
141
|
//#endregion
|
|
142
|
-
export { AnyColumn, AnyRelation, AnyRelations, AnyTableColumn, AnyTableWithColumns, BuildScmTblColumns, Fk, IsTableWC, Many, Relations, StdRelations, StdTable, StdTableColumn, TColsToLeftRight, Table, TableColumn, TableColumnArgs, TableWCorNever, TableWithColumns, fk, many, one, relations, table };
|
|
142
|
+
export { AnyColumn, AnyRelation, AnyRelations, AnyTableColumn, AnyTableWithColumns, BuildScmTblColumns, Fk, IsTableWC, Many, One, Relations, StdRelations, StdTable, StdTableColumn, TColsToLeftRight, Table, TableColumn, TableColumnArgs, TableWCorNever, TableWithColumns, fk, many, one, relations, table };
|
package/package.json
CHANGED