@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.
Files changed (74) hide show
  1. package/dist/PostgresAdapter-DCF8T4vh.js +3 -0
  2. package/dist/PostgresAdapter-_X36-mLL.js +73 -0
  3. package/dist/PostgresAdapter-_X36-mLL.js.map +1 -0
  4. package/dist/QuerySet-BzR5QzGi.js +411 -0
  5. package/dist/QuerySet-BzR5QzGi.js.map +1 -0
  6. package/dist/SqliteAdapter-CBnxCznk.js +3 -0
  7. package/dist/SqliteAdapter-J03fEjmr.js +70 -0
  8. package/dist/SqliteAdapter-J03fEjmr.js.map +1 -0
  9. package/dist/chunk-DLY2FNSh.js +12 -0
  10. package/dist/connection/adapters/Adapter.d.ts +20 -0
  11. package/dist/connection/adapters/AdapterRegistry.d.ts +17 -0
  12. package/dist/connection/adapters/dialects/PostgresAdapter.d.ts +22 -0
  13. package/dist/connection/adapters/dialects/SqliteAdapter.d.ts +14 -0
  14. package/dist/connection/adapters/dialects/index.d.ts +5 -0
  15. package/dist/connection/adapters/index.d.ts +8 -0
  16. package/dist/connection/clients/DBClient.d.ts +9 -0
  17. package/dist/connection/clients/DBClient.js +1 -0
  18. package/dist/connection/clients/dialects/PostgresClient.d.ts +17 -0
  19. package/dist/connection/clients/dialects/PostgresClient.js +32 -0
  20. package/dist/connection/clients/dialects/SqliteClient.d.ts +17 -0
  21. package/dist/connection/clients/dialects/SqliteClient.js +44 -0
  22. package/dist/connection/clients/dialects/index.d.ts +5 -0
  23. package/dist/connection/clients/index.d.ts +7 -0
  24. package/dist/connection/index.d.ts +11 -0
  25. package/dist/connection/index.js +5 -0
  26. package/dist/connection-DytAsjC9.js +102 -0
  27. package/dist/connection-DytAsjC9.js.map +1 -0
  28. package/dist/index.d.ts +18 -0
  29. package/dist/index.js +9 -0
  30. package/dist/query/QBuilder.d.ts +11 -0
  31. package/dist/query/QuerySet.d.ts +64 -0
  32. package/dist/query/QuerySet.js +108 -0
  33. package/dist/query/compiler/QueryCompiler.d.ts +20 -0
  34. package/dist/query/compiler/QueryCompiler.js +183 -0
  35. package/dist/query/compiler/index.d.ts +4 -0
  36. package/dist/query/domain/CompiledQuery.d.ts +4 -0
  37. package/dist/query/domain/CompiledQuery.js +1 -0
  38. package/dist/query/domain/Dialect.d.ts +2 -0
  39. package/dist/query/domain/Direction.d.ts +2 -0
  40. package/dist/query/domain/FilterInput.d.ts +3 -0
  41. package/dist/query/domain/FilterKey.d.ts +2 -0
  42. package/dist/query/domain/FilterValue.d.ts +1 -0
  43. package/dist/query/domain/LookupType.d.ts +2 -0
  44. package/dist/query/domain/OrderSpec.d.ts +5 -0
  45. package/dist/query/domain/OrderToken.d.ts +1 -0
  46. package/dist/query/domain/QNode.d.ts +9 -0
  47. package/dist/query/domain/QueryResult.d.ts +4 -0
  48. package/dist/query/domain/QuerySetState.d.ts +13 -0
  49. package/dist/query/domain/RelationMeta.d.ts +10 -0
  50. package/dist/query/domain/RepositoryMeta.d.ts +7 -0
  51. package/dist/query/domain/WhereClause.d.ts +4 -0
  52. package/dist/query/domain/WhereClause.js +1 -0
  53. package/dist/query/domain/index.d.ts +18 -0
  54. package/dist/query/domain/internal/InternalDialect.d.ts +4 -0
  55. package/dist/query/domain/internal/InternalDirection.d.ts +4 -0
  56. package/dist/query/domain/internal/InternalLookupType.d.ts +15 -0
  57. package/dist/query/domain/internal/InternalQNodeType.d.ts +6 -0
  58. package/dist/query/domain/internal/InternalRelationKind.d.ts +5 -0
  59. package/dist/query/index.d.ts +12 -0
  60. package/dist/query/index.js +4 -0
  61. package/dist/query-CQbvLeuh.js +21 -0
  62. package/dist/query-CQbvLeuh.js.map +1 -0
  63. package/dist/repository/Repository.d.ts +40 -0
  64. package/dist/repository/Repository.js +100 -0
  65. package/dist/repository/index.d.ts +4 -0
  66. package/dist/repository/index.js +4 -0
  67. package/dist/repository-DaRvsfjs.js +78 -0
  68. package/dist/repository-DaRvsfjs.js.map +1 -0
  69. package/dist/transaction/UnitOfWork.d.ts +31 -0
  70. package/dist/transaction/index.d.ts +4 -0
  71. package/dist/transaction/index.js +3 -0
  72. package/dist/transaction-DIGJnp19.js +50 -0
  73. package/dist/transaction-DIGJnp19.js.map +1 -0
  74. 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,4 @@
1
+ /**
2
+ * Domain boundary barrel: centralizes this subdomain's public contract.
3
+ */
4
+ export { QueryCompiler } from './QueryCompiler';
@@ -0,0 +1,4 @@
1
+ export interface CompiledQuery {
2
+ sql: string;
3
+ params: readonly unknown[];
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { InternalDialect } from './internal/InternalDialect';
2
+ export type Dialect = (typeof InternalDialect)[keyof typeof InternalDialect];
@@ -0,0 +1,2 @@
1
+ import type { InternalDirection } from './internal/InternalDirection';
2
+ export type Direction = (typeof InternalDirection)[keyof typeof InternalDirection];
@@ -0,0 +1,3 @@
1
+ import type { FilterKey } from './FilterKey';
2
+ import type { FilterValue } from './FilterValue';
3
+ export type FilterInput<T> = Partial<Record<FilterKey<T>, FilterValue>>;
@@ -0,0 +1,2 @@
1
+ import type { LookupType } from '.';
2
+ export type FilterKey<T> = keyof T | `${string & keyof T}__${LookupType}`;
@@ -0,0 +1 @@
1
+ export type FilterValue = string | number | boolean | Date | null | undefined | Array<string | number>;
@@ -0,0 +1,2 @@
1
+ import type { InternalLookupType } from './internal/InternalLookupType';
2
+ export type LookupType = (typeof InternalLookupType)[keyof typeof InternalLookupType];
@@ -0,0 +1,5 @@
1
+ import type { Direction } from '.';
2
+ export interface OrderSpec<T> {
3
+ by: keyof T;
4
+ dir: Direction;
5
+ }
@@ -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,4 @@
1
+ export interface QueryResult<T> {
2
+ results: T[];
3
+ nextCursor?: string | null;
4
+ }
@@ -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,7 @@
1
+ import type { RelationMeta } from './RelationMeta';
2
+ export interface RepositoryMeta {
3
+ table: string;
4
+ pk: string;
5
+ columns: Record<string, string>;
6
+ relations?: Record<string, RelationMeta>;
7
+ }
@@ -0,0 +1,4 @@
1
+ export interface WhereClause {
2
+ sql: string;
3
+ params: readonly unknown[];
4
+ }
@@ -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,4 @@
1
+ export declare const InternalDialect: {
2
+ readonly POSTGRES: "postgres";
3
+ readonly SQLITE: "sqlite";
4
+ };
@@ -0,0 +1,4 @@
1
+ export declare const InternalDirection: {
2
+ readonly ASC: "asc";
3
+ readonly DESC: "desc";
4
+ };
@@ -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,6 @@
1
+ export declare const InternalQNodeType: {
2
+ readonly ATOM: "atom";
3
+ readonly AND: "and";
4
+ readonly OR: "or";
5
+ readonly NOT: "not";
6
+ };
@@ -0,0 +1,5 @@
1
+ export declare const InternalRelationKind: {
2
+ readonly HAS_MANY: "hasMany";
3
+ readonly BELONGS_TO: "belongsTo";
4
+ readonly HAS_ONE: "hasOne";
5
+ };
@@ -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,4 @@
1
+ import { QBuilder, QueryCompiler, QuerySet, compiler_exports } from "../QuerySet-BzR5QzGi.js";
2
+ import { domain_exports } from "../query-CQbvLeuh.js";
3
+
4
+ export { QBuilder as Q, QBuilder, QueryCompiler, QuerySet, compiler_exports as compiler, domain_exports as domain };
@@ -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,4 @@
1
+ /**
2
+ * Domain boundary barrel: centralizes this subdomain's public contract.
3
+ */
4
+ export { Repository } from './Repository';
@@ -0,0 +1,4 @@
1
+ import "../QuerySet-BzR5QzGi.js";
2
+ import { Repository } from "../repository-DaRvsfjs.js";
3
+
4
+ export { Repository };
@@ -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