@danceroutine/tango-orm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/PostgresAdapter-DCF8T4vh.js +3 -0
- package/dist/PostgresAdapter-_X36-mLL.js +73 -0
- package/dist/PostgresAdapter-_X36-mLL.js.map +1 -0
- package/dist/QuerySet-BzR5QzGi.js +411 -0
- package/dist/QuerySet-BzR5QzGi.js.map +1 -0
- package/dist/SqliteAdapter-CBnxCznk.js +3 -0
- package/dist/SqliteAdapter-J03fEjmr.js +70 -0
- package/dist/SqliteAdapter-J03fEjmr.js.map +1 -0
- package/dist/chunk-DLY2FNSh.js +12 -0
- package/dist/connection/adapters/Adapter.d.ts +20 -0
- package/dist/connection/adapters/AdapterRegistry.d.ts +17 -0
- package/dist/connection/adapters/dialects/PostgresAdapter.d.ts +22 -0
- package/dist/connection/adapters/dialects/SqliteAdapter.d.ts +14 -0
- package/dist/connection/adapters/dialects/index.d.ts +5 -0
- package/dist/connection/adapters/index.d.ts +8 -0
- package/dist/connection/clients/DBClient.d.ts +9 -0
- package/dist/connection/clients/DBClient.js +1 -0
- package/dist/connection/clients/dialects/PostgresClient.d.ts +17 -0
- package/dist/connection/clients/dialects/PostgresClient.js +32 -0
- package/dist/connection/clients/dialects/SqliteClient.d.ts +17 -0
- package/dist/connection/clients/dialects/SqliteClient.js +44 -0
- package/dist/connection/clients/dialects/index.d.ts +5 -0
- package/dist/connection/clients/index.d.ts +7 -0
- package/dist/connection/index.d.ts +11 -0
- package/dist/connection/index.js +5 -0
- package/dist/connection-DytAsjC9.js +102 -0
- package/dist/connection-DytAsjC9.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +9 -0
- package/dist/query/QBuilder.d.ts +11 -0
- package/dist/query/QuerySet.d.ts +64 -0
- package/dist/query/QuerySet.js +108 -0
- package/dist/query/compiler/QueryCompiler.d.ts +20 -0
- package/dist/query/compiler/QueryCompiler.js +183 -0
- package/dist/query/compiler/index.d.ts +4 -0
- package/dist/query/domain/CompiledQuery.d.ts +4 -0
- package/dist/query/domain/CompiledQuery.js +1 -0
- package/dist/query/domain/Dialect.d.ts +2 -0
- package/dist/query/domain/Direction.d.ts +2 -0
- package/dist/query/domain/FilterInput.d.ts +3 -0
- package/dist/query/domain/FilterKey.d.ts +2 -0
- package/dist/query/domain/FilterValue.d.ts +1 -0
- package/dist/query/domain/LookupType.d.ts +2 -0
- package/dist/query/domain/OrderSpec.d.ts +5 -0
- package/dist/query/domain/OrderToken.d.ts +1 -0
- package/dist/query/domain/QNode.d.ts +9 -0
- package/dist/query/domain/QueryResult.d.ts +4 -0
- package/dist/query/domain/QuerySetState.d.ts +13 -0
- package/dist/query/domain/RelationMeta.d.ts +10 -0
- package/dist/query/domain/RepositoryMeta.d.ts +7 -0
- package/dist/query/domain/WhereClause.d.ts +4 -0
- package/dist/query/domain/WhereClause.js +1 -0
- package/dist/query/domain/index.d.ts +18 -0
- package/dist/query/domain/internal/InternalDialect.d.ts +4 -0
- package/dist/query/domain/internal/InternalDirection.d.ts +4 -0
- package/dist/query/domain/internal/InternalLookupType.d.ts +15 -0
- package/dist/query/domain/internal/InternalQNodeType.d.ts +6 -0
- package/dist/query/domain/internal/InternalRelationKind.d.ts +5 -0
- package/dist/query/index.d.ts +12 -0
- package/dist/query/index.js +4 -0
- package/dist/query-CQbvLeuh.js +21 -0
- package/dist/query-CQbvLeuh.js.map +1 -0
- package/dist/repository/Repository.d.ts +40 -0
- package/dist/repository/Repository.js +100 -0
- package/dist/repository/index.d.ts +4 -0
- package/dist/repository/index.js +4 -0
- package/dist/repository-DaRvsfjs.js +78 -0
- package/dist/repository-DaRvsfjs.js.map +1 -0
- package/dist/transaction/UnitOfWork.d.ts +31 -0
- package/dist/transaction/index.d.ts +4 -0
- package/dist/transaction/index.js +3 -0
- package/dist/transaction-DIGJnp19.js +50 -0
- package/dist/transaction-DIGJnp19.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { InternalDialect } from '../domain/internal/InternalDialect';
|
|
2
|
+
import { InternalQNodeType } from '../domain/internal/InternalQNodeType';
|
|
3
|
+
import { InternalLookupType } from '../domain/internal/InternalLookupType';
|
|
4
|
+
export class QueryCompiler {
|
|
5
|
+
meta;
|
|
6
|
+
dialect;
|
|
7
|
+
static BRAND = 'tango.orm.query_compiler';
|
|
8
|
+
__tangoBrand = QueryCompiler.BRAND;
|
|
9
|
+
static isQueryCompiler(value) {
|
|
10
|
+
return (typeof value === 'object' &&
|
|
11
|
+
value !== null &&
|
|
12
|
+
value.__tangoBrand === QueryCompiler.BRAND);
|
|
13
|
+
}
|
|
14
|
+
constructor(meta, dialect = InternalDialect.POSTGRES) {
|
|
15
|
+
this.meta = meta;
|
|
16
|
+
this.dialect = dialect;
|
|
17
|
+
}
|
|
18
|
+
compile(state) {
|
|
19
|
+
const whereParts = [];
|
|
20
|
+
const params = [];
|
|
21
|
+
if (state.q) {
|
|
22
|
+
const result = this.compileQNode(state.q, params.length + 1);
|
|
23
|
+
if (result.sql) {
|
|
24
|
+
whereParts.push(result.sql);
|
|
25
|
+
params.push(...result.params);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
state.excludes?.forEach((exclude) => {
|
|
29
|
+
const result = this.compileQNode({ kind: InternalQNodeType.NOT, node: exclude }, params.length + 1);
|
|
30
|
+
if (result.sql) {
|
|
31
|
+
whereParts.push(result.sql);
|
|
32
|
+
params.push(...result.params);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const select = state.select?.map(String).join(', ') || `${this.meta.table}.*`;
|
|
36
|
+
const joins = (state.selectRelated ?? [])
|
|
37
|
+
.map((rel) => {
|
|
38
|
+
const r = this.meta.relations?.[rel];
|
|
39
|
+
if (!r || r.kind !== 'belongsTo') {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
return `LEFT JOIN ${r.table} ${r.alias} ON ${r.alias}.${r.targetPk} = ${this.meta.table}.${r.localKey}`;
|
|
43
|
+
})
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join(' ');
|
|
46
|
+
const whereSQL = whereParts.length ? ` WHERE ${whereParts.join(' AND ')}` : '';
|
|
47
|
+
const orderSQL = ` ORDER BY ${state.order?.length
|
|
48
|
+
? state.order.map((o) => `${this.meta.table}.${String(o.by)} ${o.dir.toUpperCase()}`).join(', ')
|
|
49
|
+
: `${this.meta.table}.${this.meta.pk} ASC`}`;
|
|
50
|
+
const limitSQL = state.limit ? ` LIMIT ${state.limit}` : '';
|
|
51
|
+
const offsetSQL = state.offset ? ` OFFSET ${state.offset}` : '';
|
|
52
|
+
const sql = `SELECT ${select} FROM ${this.meta.table}${joins ? ' ' + joins : ''}${whereSQL}${orderSQL}${limitSQL}${offsetSQL}`;
|
|
53
|
+
return { sql, params };
|
|
54
|
+
}
|
|
55
|
+
compileQNode(node, paramIndex) {
|
|
56
|
+
switch (node.kind) {
|
|
57
|
+
case InternalQNodeType.ATOM:
|
|
58
|
+
return this.compileAtom(node.where || {}, paramIndex);
|
|
59
|
+
case InternalQNodeType.AND:
|
|
60
|
+
return this.compileAnd(node.nodes || [], paramIndex);
|
|
61
|
+
case InternalQNodeType.OR:
|
|
62
|
+
return this.compileOr(node.nodes || [], paramIndex);
|
|
63
|
+
case InternalQNodeType.NOT:
|
|
64
|
+
return this.compileNot(node.node, paramIndex);
|
|
65
|
+
default:
|
|
66
|
+
return { sql: '', params: [] };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
compileAtom(where, paramIndex) {
|
|
70
|
+
const entries = Object.entries(where).filter(([, value]) => value !== undefined);
|
|
71
|
+
const { parts, params } = entries.reduce((accumulator, [key, value]) => {
|
|
72
|
+
const [field, lookup = InternalLookupType.EXACT] = String(key).split('__');
|
|
73
|
+
const col = `${this.meta.table}.${field}`;
|
|
74
|
+
const idx = paramIndex + accumulator.params.length;
|
|
75
|
+
const clause = this.lookupToSQL(col, lookup, value, idx);
|
|
76
|
+
accumulator.parts.push(clause.sql);
|
|
77
|
+
accumulator.params.push(...clause.params);
|
|
78
|
+
return accumulator;
|
|
79
|
+
}, { parts: [], params: [] });
|
|
80
|
+
return {
|
|
81
|
+
sql: parts.length ? `(${parts.join(' AND ')})` : '',
|
|
82
|
+
params,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
compileAnd(nodes, paramIndex) {
|
|
86
|
+
const { parts, params } = nodes.reduce((accumulator, node) => {
|
|
87
|
+
const result = this.compileQNode(node, paramIndex + accumulator.params.length);
|
|
88
|
+
if (result.sql) {
|
|
89
|
+
accumulator.parts.push(result.sql);
|
|
90
|
+
accumulator.params.push(...result.params);
|
|
91
|
+
}
|
|
92
|
+
return accumulator;
|
|
93
|
+
}, { parts: [], params: [] });
|
|
94
|
+
return {
|
|
95
|
+
sql: parts.length ? `(${parts.join(' AND ')})` : '',
|
|
96
|
+
params,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
compileOr(nodes, paramIndex) {
|
|
100
|
+
const { parts, params } = nodes.reduce((accumulator, node) => {
|
|
101
|
+
const result = this.compileQNode(node, paramIndex + accumulator.params.length);
|
|
102
|
+
if (result.sql) {
|
|
103
|
+
accumulator.parts.push(result.sql);
|
|
104
|
+
accumulator.params.push(...result.params);
|
|
105
|
+
}
|
|
106
|
+
return accumulator;
|
|
107
|
+
}, { parts: [], params: [] });
|
|
108
|
+
return {
|
|
109
|
+
sql: parts.length ? `(${parts.join(' OR ')})` : '',
|
|
110
|
+
params,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
compileNot(node, paramIndex) {
|
|
114
|
+
const result = this.compileQNode(node, paramIndex);
|
|
115
|
+
if (!result.sql) {
|
|
116
|
+
return { sql: '', params: [] };
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
sql: `(NOT ${result.sql})`,
|
|
120
|
+
params: result.params,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
lookupToSQL(col, lookup, value, paramIndex) {
|
|
124
|
+
const placeholder = this.dialect === InternalDialect.POSTGRES ? `$${paramIndex}` : '?';
|
|
125
|
+
const normalized = this.normalizeParam(value);
|
|
126
|
+
switch (lookup) {
|
|
127
|
+
case InternalLookupType.EXACT:
|
|
128
|
+
if (value === null) {
|
|
129
|
+
return { sql: `${col} IS NULL`, params: [] };
|
|
130
|
+
}
|
|
131
|
+
return { sql: `${col} = ${placeholder}`, params: [normalized] };
|
|
132
|
+
case InternalLookupType.LT:
|
|
133
|
+
return { sql: `${col} < ${placeholder}`, params: [normalized] };
|
|
134
|
+
case InternalLookupType.LTE:
|
|
135
|
+
return { sql: `${col} <= ${placeholder}`, params: [normalized] };
|
|
136
|
+
case InternalLookupType.GT:
|
|
137
|
+
return { sql: `${col} > ${placeholder}`, params: [normalized] };
|
|
138
|
+
case InternalLookupType.GTE:
|
|
139
|
+
return { sql: `${col} >= ${placeholder}`, params: [normalized] };
|
|
140
|
+
case InternalLookupType.IN: {
|
|
141
|
+
const arr = (Array.isArray(value) ? value : [value]).map((entry) => this.normalizeParam(entry));
|
|
142
|
+
if (arr.length === 0) {
|
|
143
|
+
return { sql: '1=0', params: [] };
|
|
144
|
+
}
|
|
145
|
+
const placeholders = this.dialect === InternalDialect.POSTGRES
|
|
146
|
+
? arr.map((_, i) => `$${paramIndex + i}`).join(', ')
|
|
147
|
+
: arr.map(() => '?').join(', ');
|
|
148
|
+
return { sql: `${col} IN (${placeholders})`, params: arr };
|
|
149
|
+
}
|
|
150
|
+
case InternalLookupType.ISNULL:
|
|
151
|
+
return { sql: value ? `${col} IS NULL` : `${col} IS NOT NULL`, params: [] };
|
|
152
|
+
case InternalLookupType.CONTAINS:
|
|
153
|
+
return { sql: `${col} LIKE ${placeholder}`, params: [`%${value}%`] };
|
|
154
|
+
case InternalLookupType.ICONTAINS: {
|
|
155
|
+
const lowerCol = this.dialect === InternalDialect.POSTGRES ? `LOWER(${col})` : `${col}`;
|
|
156
|
+
const lowerValue = String(value).toLowerCase();
|
|
157
|
+
return { sql: `${lowerCol} LIKE ${placeholder}`, params: [`%${lowerValue}%`] };
|
|
158
|
+
}
|
|
159
|
+
case InternalLookupType.STARTSWITH:
|
|
160
|
+
return { sql: `${col} LIKE ${placeholder}`, params: [`${value}%`] };
|
|
161
|
+
case InternalLookupType.ISTARTSWITH: {
|
|
162
|
+
const lowerCol = this.dialect === InternalDialect.POSTGRES ? `LOWER(${col})` : `${col}`;
|
|
163
|
+
const lowerValue = String(value).toLowerCase();
|
|
164
|
+
return { sql: `${lowerCol} LIKE ${placeholder}`, params: [`${lowerValue}%`] };
|
|
165
|
+
}
|
|
166
|
+
case InternalLookupType.ENDSWITH:
|
|
167
|
+
return { sql: `${col} LIKE ${placeholder}`, params: [`%${value}`] };
|
|
168
|
+
case InternalLookupType.IENDSWITH: {
|
|
169
|
+
const lowerCol = this.dialect === InternalDialect.POSTGRES ? `LOWER(${col})` : `${col}`;
|
|
170
|
+
const lowerValue = String(value).toLowerCase();
|
|
171
|
+
return { sql: `${lowerCol} LIKE ${placeholder}`, params: [`%${lowerValue}`] };
|
|
172
|
+
}
|
|
173
|
+
default:
|
|
174
|
+
throw new Error(`Unknown lookup: ${lookup}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
normalizeParam(value) {
|
|
178
|
+
if (this.dialect === InternalDialect.SQLITE && typeof value === 'boolean') {
|
|
179
|
+
return value ? 1 : 0;
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type FilterValue = string | number | boolean | Date | null | undefined | Array<string | number>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type OrderToken<T> = keyof T | `-${string & keyof T}`;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FilterInput } from './FilterInput';
|
|
2
|
+
import type { InternalQNodeType } from './internal/InternalQNodeType';
|
|
3
|
+
export type QNodeType = (typeof InternalQNodeType)[keyof typeof InternalQNodeType];
|
|
4
|
+
export interface QNode<T> {
|
|
5
|
+
kind: QNodeType;
|
|
6
|
+
where?: FilterInput<T>;
|
|
7
|
+
nodes?: QNode<T>[];
|
|
8
|
+
node?: QNode<T>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { OrderSpec } from './OrderSpec';
|
|
2
|
+
import type { QNode } from './QNode';
|
|
3
|
+
export interface QuerySetState<T> {
|
|
4
|
+
q?: QNode<T>;
|
|
5
|
+
excludes?: QNode<T>[];
|
|
6
|
+
order?: OrderSpec<T>[];
|
|
7
|
+
limit?: number;
|
|
8
|
+
offset?: number;
|
|
9
|
+
cursor?: string | null;
|
|
10
|
+
selectRelated?: string[];
|
|
11
|
+
prefetchRelated?: string[];
|
|
12
|
+
select?: (keyof T)[];
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { InternalRelationKind } from './internal/InternalRelationKind';
|
|
2
|
+
export type RelationKind = (typeof InternalRelationKind)[keyof typeof InternalRelationKind];
|
|
3
|
+
export interface RelationMeta {
|
|
4
|
+
kind: RelationKind;
|
|
5
|
+
table: string;
|
|
6
|
+
targetPk: string;
|
|
7
|
+
localKey?: string;
|
|
8
|
+
foreignKey?: string;
|
|
9
|
+
alias: string;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain boundary barrel: centralizes this subdomain's public contract.
|
|
3
|
+
*/
|
|
4
|
+
export type { CompiledQuery } from './CompiledQuery';
|
|
5
|
+
export type { Dialect } from './Dialect';
|
|
6
|
+
export type { Direction } from './Direction';
|
|
7
|
+
export type { FilterInput } from './FilterInput';
|
|
8
|
+
export type { FilterKey } from './FilterKey';
|
|
9
|
+
export type { FilterValue } from './FilterValue';
|
|
10
|
+
export type { LookupType } from './LookupType';
|
|
11
|
+
export type { OrderSpec } from './OrderSpec';
|
|
12
|
+
export type { OrderToken } from './OrderToken';
|
|
13
|
+
export type { QNode } from './QNode';
|
|
14
|
+
export type { QueryResult } from './QueryResult';
|
|
15
|
+
export type { QuerySetState } from './QuerySetState';
|
|
16
|
+
export type { RelationMeta } from './RelationMeta';
|
|
17
|
+
export type { RepositoryMeta as RepoMeta } from './RepositoryMeta';
|
|
18
|
+
export type { WhereClause } from './WhereClause';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const InternalLookupType: {
|
|
2
|
+
readonly EXACT: "exact";
|
|
3
|
+
readonly LT: "lt";
|
|
4
|
+
readonly LTE: "lte";
|
|
5
|
+
readonly GT: "gt";
|
|
6
|
+
readonly GTE: "gte";
|
|
7
|
+
readonly IN: "in";
|
|
8
|
+
readonly ISNULL: "isnull";
|
|
9
|
+
readonly CONTAINS: "contains";
|
|
10
|
+
readonly ICONTAINS: "icontains";
|
|
11
|
+
readonly STARTSWITH: "startswith";
|
|
12
|
+
readonly ISTARTSWITH: "istartswith";
|
|
13
|
+
readonly ENDSWITH: "endswith";
|
|
14
|
+
readonly IENDSWITH: "iendswith";
|
|
15
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain boundary barrel: exposes namespaced exports for Django-style drill-down
|
|
3
|
+
* imports and curated flat exports for TS-native ergonomics.
|
|
4
|
+
*/
|
|
5
|
+
export * as compiler from './compiler/index';
|
|
6
|
+
export * as domain from './domain/index';
|
|
7
|
+
export type * from './domain/index';
|
|
8
|
+
export type { RepoMeta } from './domain/index';
|
|
9
|
+
export { QuerySet } from './QuerySet';
|
|
10
|
+
export type { RepositoryLike } from './QuerySet';
|
|
11
|
+
export { QBuilder, QBuilder as Q } from './QBuilder';
|
|
12
|
+
export { QueryCompiler } from './compiler/index';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { __export } from "./chunk-DLY2FNSh.js";
|
|
2
|
+
import { QBuilder, QueryCompiler, QuerySet, compiler_exports } from "./QuerySet-BzR5QzGi.js";
|
|
3
|
+
|
|
4
|
+
//#region src/query/domain/index.ts
|
|
5
|
+
var domain_exports = {};
|
|
6
|
+
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/query/index.ts
|
|
9
|
+
var query_exports = {};
|
|
10
|
+
__export(query_exports, {
|
|
11
|
+
Q: () => QBuilder,
|
|
12
|
+
QBuilder: () => QBuilder,
|
|
13
|
+
QueryCompiler: () => QueryCompiler,
|
|
14
|
+
QuerySet: () => QuerySet,
|
|
15
|
+
compiler: () => compiler_exports,
|
|
16
|
+
domain: () => domain_exports
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { domain_exports, query_exports };
|
|
21
|
+
//# sourceMappingURL=query-CQbvLeuh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-CQbvLeuh.js","names":[],"sources":["../src/query/domain/index.ts","../src/query/index.ts"],"sourcesContent":["/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport type { CompiledQuery } from './CompiledQuery';\nexport type { Dialect } from './Dialect';\nexport type { Direction } from './Direction';\nexport type { FilterInput } from './FilterInput';\nexport type { FilterKey } from './FilterKey';\nexport type { FilterValue } from './FilterValue';\nexport type { LookupType } from './LookupType';\nexport type { OrderSpec } from './OrderSpec';\nexport type { OrderToken } from './OrderToken';\nexport type { QNode } from './QNode';\nexport type { QueryResult } from './QueryResult';\nexport type { QuerySetState } from './QuerySetState';\nexport type { RelationMeta } from './RelationMeta';\nexport type { RepositoryMeta as RepoMeta } from './RepositoryMeta';\nexport type { WhereClause } from './WhereClause';\n","/**\n * Domain boundary barrel: exposes namespaced exports for Django-style drill-down\n * imports and curated flat exports for TS-native ergonomics.\n */\n\nexport * as compiler from './compiler/index';\nexport * as domain from './domain/index';\n\nexport type * from './domain/index';\nexport type { RepoMeta } from './domain/index';\nexport { QuerySet } from './QuerySet';\nexport type { RepositoryLike } from './QuerySet';\nexport { QBuilder, QBuilder as Q } from './QBuilder';\nexport { QueryCompiler } from './compiler/index';\n"],"mappings":""}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DBClient } from '../connection/clients/DBClient';
|
|
2
|
+
import { QuerySet } from '../query/QuerySet';
|
|
3
|
+
import type { RepositoryMeta } from '../query/domain/RepositoryMeta';
|
|
4
|
+
/**
|
|
5
|
+
* Base repository class providing CRUD operations and query building capabilities.
|
|
6
|
+
* Extend this class to create model-specific repositories with custom business logic.
|
|
7
|
+
*
|
|
8
|
+
* @template T - The model type this repository manages
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* class UserRepository extends Repository<User> {
|
|
13
|
+
* meta = {
|
|
14
|
+
* table: 'users',
|
|
15
|
+
* pk: 'id',
|
|
16
|
+
* columns: { id: 'int', email: 'text', name: 'text' },
|
|
17
|
+
* };
|
|
18
|
+
*
|
|
19
|
+
* async findByEmail(email: string): Promise<User | null> {
|
|
20
|
+
* return this.query().filter({ email }).fetchOne();
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare abstract class Repository<T extends Record<string, unknown>> {
|
|
26
|
+
static readonly BRAND: "tango.orm.repository";
|
|
27
|
+
readonly __tangoBrand: typeof Repository.BRAND;
|
|
28
|
+
abstract meta: RepositoryMeta;
|
|
29
|
+
protected client: DBClient;
|
|
30
|
+
protected dialect: 'postgres' | 'sqlite';
|
|
31
|
+
static isRepository<T extends Record<string, unknown>>(value: unknown): value is Repository<T>;
|
|
32
|
+
constructor(client: DBClient, dialect?: 'postgres' | 'sqlite');
|
|
33
|
+
query(): QuerySet<T>;
|
|
34
|
+
findById(id: T[keyof T]): Promise<T | null>;
|
|
35
|
+
getOrThrow(id: T[keyof T]): Promise<T>;
|
|
36
|
+
create(input: Partial<T>): Promise<T>;
|
|
37
|
+
update(id: T[keyof T], patch: Partial<T>): Promise<T>;
|
|
38
|
+
delete(id: T[keyof T]): Promise<void>;
|
|
39
|
+
bulkCreate(inputs: Partial<T>[]): Promise<T[]>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { NotFoundError } from '@danceroutine/tango-core';
|
|
2
|
+
import { QuerySet } from '../query/QuerySet';
|
|
3
|
+
/**
|
|
4
|
+
* Base repository class providing CRUD operations and query building capabilities.
|
|
5
|
+
* Extend this class to create model-specific repositories with custom business logic.
|
|
6
|
+
*
|
|
7
|
+
* @template T - The model type this repository manages
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* class UserRepository extends Repository<User> {
|
|
12
|
+
* meta = {
|
|
13
|
+
* table: 'users',
|
|
14
|
+
* pk: 'id',
|
|
15
|
+
* columns: { id: 'int', email: 'text', name: 'text' },
|
|
16
|
+
* };
|
|
17
|
+
*
|
|
18
|
+
* async findByEmail(email: string): Promise<User | null> {
|
|
19
|
+
* return this.query().filter({ email }).fetchOne();
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class Repository {
|
|
25
|
+
static BRAND = 'tango.orm.repository';
|
|
26
|
+
__tangoBrand = Repository.BRAND;
|
|
27
|
+
client;
|
|
28
|
+
dialect;
|
|
29
|
+
static isRepository(value) {
|
|
30
|
+
return (typeof value === 'object' &&
|
|
31
|
+
value !== null &&
|
|
32
|
+
value.__tangoBrand === Repository.BRAND);
|
|
33
|
+
}
|
|
34
|
+
constructor(client, dialect = 'postgres') {
|
|
35
|
+
this.client = client;
|
|
36
|
+
this.dialect = dialect;
|
|
37
|
+
}
|
|
38
|
+
query() {
|
|
39
|
+
return new QuerySet({
|
|
40
|
+
meta: this.meta,
|
|
41
|
+
client: this.client,
|
|
42
|
+
dialect: this.dialect,
|
|
43
|
+
run: async (compiled) => {
|
|
44
|
+
const result = await this.client.query(compiled.sql, compiled.params);
|
|
45
|
+
return result.rows;
|
|
46
|
+
},
|
|
47
|
+
}, {});
|
|
48
|
+
}
|
|
49
|
+
async findById(id) {
|
|
50
|
+
const filter = { [this.meta.pk]: id };
|
|
51
|
+
const result = await this.query().filter(filter).fetchOne();
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
async getOrThrow(id) {
|
|
55
|
+
const result = await this.findById(id);
|
|
56
|
+
if (!result) {
|
|
57
|
+
throw new NotFoundError(`${this.meta.table} with ${this.meta.pk}=${String(id)} not found`);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
async create(input) {
|
|
62
|
+
const keys = Object.keys(input);
|
|
63
|
+
const values = Object.values(input);
|
|
64
|
+
const placeholder = this.dialect === 'postgres' ? keys.map((_, i) => `$${i + 1}`).join(', ') : keys.map(() => '?').join(', ');
|
|
65
|
+
const sql = `INSERT INTO ${this.meta.table} (${keys.join(', ')}) VALUES (${placeholder}) RETURNING *`;
|
|
66
|
+
const result = await this.client.query(sql, values);
|
|
67
|
+
return result.rows[0];
|
|
68
|
+
}
|
|
69
|
+
async update(id, patch) {
|
|
70
|
+
const keys = Object.keys(patch);
|
|
71
|
+
const values = Object.values(patch);
|
|
72
|
+
const sets = this.dialect === 'postgres'
|
|
73
|
+
? keys.map((k, i) => `${k} = $${i + 1}`).join(', ')
|
|
74
|
+
: keys.map((k) => `${k} = ?`).join(', ');
|
|
75
|
+
const whereParam = this.dialect === 'postgres' ? `$${keys.length + 1}` : '?';
|
|
76
|
+
const sql = `UPDATE ${this.meta.table} SET ${sets} WHERE ${this.meta.pk} = ${whereParam} RETURNING *`;
|
|
77
|
+
const result = await this.client.query(sql, [...values, id]);
|
|
78
|
+
return result.rows[0];
|
|
79
|
+
}
|
|
80
|
+
async delete(id) {
|
|
81
|
+
const placeholder = this.dialect === 'postgres' ? '$1' : '?';
|
|
82
|
+
const sql = `DELETE FROM ${this.meta.table} WHERE ${this.meta.pk} = ${placeholder}`;
|
|
83
|
+
await this.client.query(sql, [id]);
|
|
84
|
+
}
|
|
85
|
+
async bulkCreate(inputs) {
|
|
86
|
+
if (inputs.length === 0)
|
|
87
|
+
return [];
|
|
88
|
+
const keys = Object.keys(inputs[0]);
|
|
89
|
+
const valueRows = inputs.map((input) => Object.values(input));
|
|
90
|
+
const placeholders = this.dialect === 'postgres'
|
|
91
|
+
? valueRows
|
|
92
|
+
.map((_, rowIdx) => `(${keys.map((_, colIdx) => `$${rowIdx * keys.length + colIdx + 1}`).join(', ')})`)
|
|
93
|
+
.join(', ')
|
|
94
|
+
: valueRows.map(() => `(${keys.map(() => '?').join(', ')})`).join(', ');
|
|
95
|
+
const sql = `INSERT INTO ${this.meta.table} (${keys.join(', ')}) VALUES ${placeholders} RETURNING *`;
|
|
96
|
+
const allValues = valueRows.flat();
|
|
97
|
+
const result = await this.client.query(sql, allValues);
|
|
98
|
+
return result.rows;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { __export } from "./chunk-DLY2FNSh.js";
|
|
2
|
+
import { QuerySet } from "./QuerySet-BzR5QzGi.js";
|
|
3
|
+
|
|
4
|
+
//#region src/repository/Repository.ts
|
|
5
|
+
var Repository = class Repository {
|
|
6
|
+
static BRAND = "tango.orm.repository";
|
|
7
|
+
__tangoBrand = Repository.BRAND;
|
|
8
|
+
client;
|
|
9
|
+
dialect;
|
|
10
|
+
static isRepository(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === Repository.BRAND;
|
|
12
|
+
}
|
|
13
|
+
constructor(client, dialect = "postgres") {
|
|
14
|
+
this.client = client;
|
|
15
|
+
this.dialect = dialect;
|
|
16
|
+
}
|
|
17
|
+
query() {
|
|
18
|
+
return new QuerySet({
|
|
19
|
+
meta: this.meta,
|
|
20
|
+
client: this.client,
|
|
21
|
+
dialect: this.dialect,
|
|
22
|
+
run: async (compiled) => {
|
|
23
|
+
const result = await this.client.query(compiled.sql, compiled.params);
|
|
24
|
+
return result.rows;
|
|
25
|
+
}
|
|
26
|
+
}, {});
|
|
27
|
+
}
|
|
28
|
+
async findById(id) {
|
|
29
|
+
const result = await this.query().filter({ [this.meta.pk]: id }).fetchOne();
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
async getOrThrow(id) {
|
|
33
|
+
const result = await this.findById(id);
|
|
34
|
+
if (!result) throw new Error(`${this.meta.table} with ${this.meta.pk}=${String(id)} not found`);
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
async create(input) {
|
|
38
|
+
const keys = Object.keys(input);
|
|
39
|
+
const values = Object.values(input);
|
|
40
|
+
const placeholder = this.dialect === "postgres" ? keys.map((_, i) => `$${i + 1}`).join(", ") : keys.map(() => "?").join(", ");
|
|
41
|
+
const sql = `INSERT INTO ${this.meta.table} (${keys.join(", ")}) VALUES (${placeholder}) RETURNING *`;
|
|
42
|
+
const result = await this.client.query(sql, values);
|
|
43
|
+
return result.rows[0];
|
|
44
|
+
}
|
|
45
|
+
async update(id, patch) {
|
|
46
|
+
const keys = Object.keys(patch);
|
|
47
|
+
const values = Object.values(patch);
|
|
48
|
+
const sets = this.dialect === "postgres" ? keys.map((k, i) => `${k} = $${i + 1}`).join(", ") : keys.map((k) => `${k} = ?`).join(", ");
|
|
49
|
+
const whereParam = this.dialect === "postgres" ? `$${keys.length + 1}` : "?";
|
|
50
|
+
const sql = `UPDATE ${this.meta.table} SET ${sets} WHERE ${this.meta.pk} = ${whereParam} RETURNING *`;
|
|
51
|
+
const result = await this.client.query(sql, [...values, id]);
|
|
52
|
+
return result.rows[0];
|
|
53
|
+
}
|
|
54
|
+
async delete(id) {
|
|
55
|
+
const placeholder = this.dialect === "postgres" ? "$1" : "?";
|
|
56
|
+
const sql = `DELETE FROM ${this.meta.table} WHERE ${this.meta.pk} = ${placeholder}`;
|
|
57
|
+
await this.client.query(sql, [id]);
|
|
58
|
+
}
|
|
59
|
+
async bulkCreate(inputs) {
|
|
60
|
+
if (inputs.length === 0) return [];
|
|
61
|
+
const keys = Object.keys(inputs[0]);
|
|
62
|
+
const valueRows = inputs.map((input) => Object.values(input));
|
|
63
|
+
const placeholders = this.dialect === "postgres" ? valueRows.map((_, rowIdx) => `(${keys.map((_$1, colIdx) => `$${rowIdx * keys.length + colIdx + 1}`).join(", ")})`).join(", ") : valueRows.map(() => `(${keys.map(() => "?").join(", ")})`).join(", ");
|
|
64
|
+
const sql = `INSERT INTO ${this.meta.table} (${keys.join(", ")}) VALUES ${placeholders} RETURNING *`;
|
|
65
|
+
const allValues = valueRows.flat();
|
|
66
|
+
const result = await this.client.query(sql, allValues);
|
|
67
|
+
return result.rows;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/repository/index.ts
|
|
73
|
+
var repository_exports = {};
|
|
74
|
+
__export(repository_exports, { Repository: () => Repository });
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { Repository, repository_exports };
|
|
78
|
+
//# sourceMappingURL=repository-DaRvsfjs.js.map
|