@danceroutine/tango-orm 0.1.0 → 1.0.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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -0
  3. package/dist/{PostgresAdapter-_X36-mLL.js → PostgresAdapter-C9a1XJRx.js} +31 -7
  4. package/dist/PostgresAdapter-C9a1XJRx.js.map +1 -0
  5. package/dist/PostgresAdapter-CBc1u8eT.js +3 -0
  6. package/dist/SqliteAdapter-BJKNxCvS.js +3 -0
  7. package/dist/SqliteAdapter-Dp6VRXmz.js +118 -0
  8. package/dist/SqliteAdapter-Dp6VRXmz.js.map +1 -0
  9. package/dist/connection/adapters/Adapter.d.ts +9 -0
  10. package/dist/connection/adapters/AdapterRegistry.d.ts +28 -1
  11. package/dist/connection/adapters/dialects/PostgresAdapter.d.ts +10 -1
  12. package/dist/connection/adapters/dialects/SqliteAdapter.d.ts +11 -1
  13. package/dist/connection/clients/DBClient.d.ts +8 -0
  14. package/dist/connection/clients/dialects/PostgresClient.d.ts +22 -1
  15. package/dist/connection/clients/dialects/SqliteClient.d.ts +26 -1
  16. package/dist/connection/index.js +3 -3
  17. package/dist/{connection-DytAsjC9.js → connection-CVvycXus.js} +21 -6
  18. package/dist/connection-CVvycXus.js.map +1 -0
  19. package/dist/index.d.ts +7 -4
  20. package/dist/index.js +9 -8
  21. package/dist/manager/ManagerLike.d.ts +15 -0
  22. package/dist/manager/ModelManager.d.ts +48 -0
  23. package/dist/manager/index.d.ts +6 -0
  24. package/dist/manager/index.js +8 -0
  25. package/dist/manager/internal/MutationCompiler.d.ts +15 -0
  26. package/dist/manager/internal/RuntimeBoundClient.d.ts +16 -0
  27. package/dist/manager/registerModelObjects.d.ts +5 -0
  28. package/dist/manager-D6tU8xTO.js +13 -0
  29. package/dist/manager-D6tU8xTO.js.map +1 -0
  30. package/dist/query/QBuilder.d.ts +18 -0
  31. package/dist/query/QuerySet.d.ts +60 -9
  32. package/dist/query/compiler/QueryCompiler.d.ts +19 -2
  33. package/dist/query/domain/{RepositoryMeta.d.ts → TableMeta.d.ts} +1 -1
  34. package/dist/query/domain/index.d.ts +1 -1
  35. package/dist/query/index.d.ts +2 -2
  36. package/dist/query/index.js +1 -2
  37. package/dist/query-wnl4h2o7.js +671 -0
  38. package/dist/query-wnl4h2o7.js.map +1 -0
  39. package/dist/registerModelObjects-emX7Hja9.js +354 -0
  40. package/dist/registerModelObjects-emX7Hja9.js.map +1 -0
  41. package/dist/runtime/TangoRuntime.d.ts +34 -0
  42. package/dist/runtime/defaultRuntime.d.ts +13 -0
  43. package/dist/runtime/index.d.ts +13 -0
  44. package/dist/runtime/index.js +8 -0
  45. package/dist/runtime-7U5_XDad.js +17 -0
  46. package/dist/runtime-7U5_XDad.js.map +1 -0
  47. package/dist/transaction/UnitOfWork.d.ts +21 -3
  48. package/dist/transaction/index.js +1 -1
  49. package/dist/{transaction-DIGJnp19.js → transaction-DooTMuAl.js} +29 -11
  50. package/dist/transaction-DooTMuAl.js.map +1 -0
  51. package/dist/validation/OrmSqlSafetyAdapter.d.ts +22 -0
  52. package/dist/validation/SQLValidationEngine.d.ts +51 -0
  53. package/dist/validation/SqlValidationPlan.d.ts +42 -0
  54. package/dist/validation/index.d.ts +3 -0
  55. package/package.json +81 -74
  56. package/dist/PostgresAdapter-DCF8T4vh.js +0 -3
  57. package/dist/PostgresAdapter-_X36-mLL.js.map +0 -1
  58. package/dist/QuerySet-BzR5QzGi.js +0 -411
  59. package/dist/QuerySet-BzR5QzGi.js.map +0 -1
  60. package/dist/SqliteAdapter-CBnxCznk.js +0 -3
  61. package/dist/SqliteAdapter-J03fEjmr.js +0 -70
  62. package/dist/SqliteAdapter-J03fEjmr.js.map +0 -1
  63. package/dist/connection/clients/DBClient.js +0 -1
  64. package/dist/connection/clients/dialects/PostgresClient.js +0 -32
  65. package/dist/connection/clients/dialects/SqliteClient.js +0 -44
  66. package/dist/connection-DytAsjC9.js.map +0 -1
  67. package/dist/query/QuerySet.js +0 -108
  68. package/dist/query/compiler/QueryCompiler.js +0 -183
  69. package/dist/query/domain/CompiledQuery.js +0 -1
  70. package/dist/query/domain/WhereClause.js +0 -1
  71. package/dist/query-CQbvLeuh.js +0 -21
  72. package/dist/query-CQbvLeuh.js.map +0 -1
  73. package/dist/repository/Repository.d.ts +0 -40
  74. package/dist/repository/Repository.js +0 -100
  75. package/dist/repository/index.d.ts +0 -4
  76. package/dist/repository/index.js +0 -4
  77. package/dist/repository-DaRvsfjs.js +0 -78
  78. package/dist/repository-DaRvsfjs.js.map +0 -1
  79. package/dist/transaction-DIGJnp19.js.map +0 -1
@@ -1,108 +0,0 @@
1
- import { InternalQNodeType } from './domain/internal/InternalQNodeType';
2
- import { InternalDirection } from './domain/internal/InternalDirection';
3
- import { QBuilder as Q } from './QBuilder';
4
- import { QueryCompiler } from './compiler';
5
- /**
6
- * Django-inspired query builder for constructing and executing database queries.
7
- * Provides a fluent API for filtering, ordering, pagination, and eager loading.
8
- *
9
- * @template T - The model type being queried
10
- *
11
- * @example
12
- * ```typescript
13
- * const users = await repository
14
- * .query()
15
- * .filter({ active: true })
16
- * .filter(Q.or({ role: 'admin' }, { role: 'moderator' }))
17
- * .orderBy('-createdAt')
18
- * .limit(10)
19
- * .fetchAll();
20
- * ```
21
- */
22
- export class QuerySet {
23
- repo;
24
- state;
25
- static BRAND = 'tango.orm.query_set';
26
- __tangoBrand = QuerySet.BRAND;
27
- static isQuerySet(value) {
28
- return (typeof value === 'object' &&
29
- value !== null &&
30
- value.__tangoBrand === QuerySet.BRAND);
31
- }
32
- constructor(repo, state = {}) {
33
- this.repo = repo;
34
- this.state = state;
35
- }
36
- filter(q) {
37
- const wrapped = q.kind
38
- ? q
39
- : { kind: InternalQNodeType.ATOM, where: q };
40
- const merged = this.state.q ? Q.and(this.state.q, wrapped) : wrapped;
41
- return new QuerySet(this.repo, { ...this.state, q: merged });
42
- }
43
- exclude(q) {
44
- const wrapped = q.kind
45
- ? q
46
- : { kind: InternalQNodeType.ATOM, where: q };
47
- const excludes = [...(this.state.excludes ?? []), wrapped];
48
- return new QuerySet(this.repo, { ...this.state, excludes });
49
- }
50
- orderBy(...tokens) {
51
- const order = tokens.map((t) => {
52
- const str = String(t);
53
- if (str.startsWith('-')) {
54
- return { by: str.slice(1), dir: InternalDirection.DESC };
55
- }
56
- return { by: t, dir: InternalDirection.ASC };
57
- });
58
- return new QuerySet(this.repo, { ...this.state, order });
59
- }
60
- limit(n) {
61
- return new QuerySet(this.repo, { ...this.state, limit: n });
62
- }
63
- offset(n) {
64
- return new QuerySet(this.repo, { ...this.state, offset: n });
65
- }
66
- select(cols) {
67
- return new QuerySet(this.repo, { ...this.state, select: cols });
68
- }
69
- selectRelated(...rels) {
70
- return new QuerySet(this.repo, { ...this.state, selectRelated: rels });
71
- }
72
- prefetchRelated(...rels) {
73
- return new QuerySet(this.repo, { ...this.state, prefetchRelated: rels });
74
- }
75
- async fetch(shape) {
76
- const compiler = new QueryCompiler(this.repo.meta, this.repo.dialect);
77
- const compiled = compiler.compile(this.state);
78
- const rows = await this.repo.run(compiled);
79
- const results = !shape
80
- ? rows
81
- : typeof shape === 'function'
82
- ? rows.map(shape)
83
- : rows.map((r) => shape.parse(r));
84
- return {
85
- results,
86
- nextCursor: null,
87
- };
88
- }
89
- async fetchOne(shape) {
90
- const limited = this.limit(1);
91
- const result = await limited.fetch(shape);
92
- return result.results[0] ?? null;
93
- }
94
- async count() {
95
- const compiler = new QueryCompiler(this.repo.meta, this.repo.dialect);
96
- const compiled = compiler.compile({
97
- ...this.state,
98
- select: ['COUNT(*) as count'],
99
- });
100
- const countQuery = compiled.sql.replace(/SELECT .+ FROM/, 'SELECT COUNT(*) as count FROM');
101
- const rows = await this.repo.client.query(countQuery, compiled.params);
102
- return Number(rows.rows[0]?.count ?? 0);
103
- }
104
- async exists() {
105
- const count = await this.count();
106
- return count > 0;
107
- }
108
- }
@@ -1,183 +0,0 @@
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
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,21 +0,0 @@
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
@@ -1 +0,0 @@
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":""}
@@ -1,40 +0,0 @@
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
- }
@@ -1,100 +0,0 @@
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
- }
@@ -1,4 +0,0 @@
1
- /**
2
- * Domain boundary barrel: centralizes this subdomain's public contract.
3
- */
4
- export { Repository } from './Repository';
@@ -1,4 +0,0 @@
1
- import "../QuerySet-BzR5QzGi.js";
2
- import { Repository } from "../repository-DaRvsfjs.js";
3
-
4
- export { Repository };
@@ -1,78 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"repository-DaRvsfjs.js","names":["value: unknown","client: DBClient","dialect: 'postgres' | 'sqlite'","id: T[keyof T]","input: Partial<T>","patch: Partial<T>","inputs: Partial<T>[]"],"sources":["../src/repository/Repository.ts","../src/repository/index.ts"],"sourcesContent":["import type { DBClient } from '../connection/clients/DBClient';\nimport { QuerySet } from '../query/QuerySet';\nimport type { RepositoryMeta } from '../query/domain/RepositoryMeta';\n\n/**\n * Base repository class providing CRUD operations and query building capabilities.\n * Extend this class to create model-specific repositories with custom business logic.\n *\n * @template T - The model type this repository manages\n *\n * @example\n * ```typescript\n * class UserRepository extends Repository<User> {\n * meta = {\n * table: 'users',\n * pk: 'id',\n * columns: { id: 'int', email: 'text', name: 'text' },\n * };\n *\n * async findByEmail(email: string): Promise<User | null> {\n * return this.query().filter({ email }).fetchOne();\n * }\n * }\n * ```\n */\nexport abstract class Repository<T extends Record<string, any>> {\n static readonly BRAND = 'tango.orm.repository' as const;\n readonly __tangoBrand: typeof Repository.BRAND = Repository.BRAND;\n abstract meta: RepositoryMeta;\n protected client: DBClient;\n protected dialect: 'postgres' | 'sqlite';\n\n static isRepository<T extends Record<string, any>>(value: unknown): value is Repository<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === Repository.BRAND\n );\n }\n\n constructor(client: DBClient, dialect: 'postgres' | 'sqlite' = 'postgres') {\n this.client = client;\n this.dialect = dialect;\n }\n\n query(): QuerySet<T> {\n return new QuerySet<T>(\n {\n meta: this.meta,\n client: this.client,\n dialect: this.dialect,\n run: async (compiled) => {\n const result = await this.client.query<T>(compiled.sql, compiled.params);\n return result.rows;\n },\n },\n {}\n );\n }\n\n async findById(id: T[keyof T]): Promise<T | null> {\n const result = await this.query()\n .filter({ [this.meta.pk]: id } as any)\n .fetchOne();\n return result;\n }\n\n async getOrThrow(id: T[keyof T]): Promise<T> {\n const result = await this.findById(id);\n if (!result) {\n throw new Error(`${this.meta.table} with ${this.meta.pk}=${String(id)} not found`);\n }\n return result;\n }\n\n async create(input: Partial<T>): Promise<T> {\n const keys = Object.keys(input);\n const values = Object.values(input);\n\n const placeholder =\n this.dialect === 'postgres' ? keys.map((_, i) => `$${i + 1}`).join(', ') : keys.map(() => '?').join(', ');\n\n const sql = `INSERT INTO ${this.meta.table} (${keys.join(', ')}) VALUES (${placeholder}) RETURNING *`;\n\n const result = await this.client.query<T>(sql, values);\n return result.rows[0]!;\n }\n\n async update(id: T[keyof T], patch: Partial<T>): Promise<T> {\n const keys = Object.keys(patch);\n const values = Object.values(patch);\n\n const sets =\n this.dialect === 'postgres'\n ? keys.map((k, i) => `${k} = $${i + 1}`).join(', ')\n : keys.map((k) => `${k} = ?`).join(', ');\n\n const whereParam = this.dialect === 'postgres' ? `$${keys.length + 1}` : '?';\n\n const sql = `UPDATE ${this.meta.table} SET ${sets} WHERE ${this.meta.pk} = ${whereParam} RETURNING *`;\n\n const result = await this.client.query<T>(sql, [...values, id]);\n return result.rows[0]!;\n }\n\n async delete(id: T[keyof T]): Promise<void> {\n const placeholder = this.dialect === 'postgres' ? '$1' : '?';\n const sql = `DELETE FROM ${this.meta.table} WHERE ${this.meta.pk} = ${placeholder}`;\n await this.client.query(sql, [id]);\n }\n\n async bulkCreate(inputs: Partial<T>[]): Promise<T[]> {\n if (inputs.length === 0) return [];\n\n const keys = Object.keys(inputs[0]!);\n const valueRows = inputs.map((input) => Object.values(input));\n\n const placeholders =\n this.dialect === 'postgres'\n ? valueRows\n .map(\n (_, rowIdx) =>\n `(${keys.map((_, colIdx) => `$${rowIdx * keys.length + colIdx + 1}`).join(', ')})`\n )\n .join(', ')\n : valueRows.map(() => `(${keys.map(() => '?').join(', ')})`).join(', ');\n\n const sql = `INSERT INTO ${this.meta.table} (${keys.join(', ')}) VALUES ${placeholders} RETURNING *`;\n\n const allValues = valueRows.flat();\n const result = await this.client.query<T>(sql, allValues);\n return result.rows;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { Repository } from './Repository';\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"transaction-DIGJnp19.js","names":["value: unknown","client: DBClient"],"sources":["../src/transaction/UnitOfWork.ts","../src/transaction/index.ts"],"sourcesContent":["import type { DBClient } from '../connection/clients/DBClient';\n\n/**\n * Unit of Work pattern implementation for managing database transactions.\n * Ensures that a set of operations either all succeed or all fail together.\n *\n * @example\n * ```typescript\n * const uow = await UnitOfWork.start(dbClient);\n * try {\n * await userRepo.create({ email: 'test@example.com' });\n * await postRepo.create({ title: 'Hello' });\n * await uow.commit();\n * } catch (error) {\n * await uow.rollback();\n * throw error;\n * }\n * ```\n */\nexport class UnitOfWork {\n static readonly BRAND = 'tango.orm.unit_of_work' as const;\n readonly __tangoBrand: typeof UnitOfWork.BRAND = UnitOfWork.BRAND;\n protected client: DBClient;\n protected isActive = false;\n\n static isUnitOfWork(value: unknown): value is UnitOfWork {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === UnitOfWork.BRAND\n );\n }\n\n constructor(client: DBClient) {\n this.client = client;\n }\n\n async begin(): Promise<void> {\n if (!this.isActive) {\n await this.client.begin();\n this.isActive = true;\n }\n }\n\n async commit(): Promise<void> {\n if (this.isActive) {\n await this.client.commit();\n this.isActive = false;\n }\n }\n\n async rollback(): Promise<void> {\n if (this.isActive) {\n await this.client.rollback();\n this.isActive = false;\n }\n }\n\n getClient(): DBClient {\n return this.client;\n }\n\n static async start(client: DBClient): Promise<UnitOfWork> {\n const uow = new UnitOfWork(client);\n await uow.begin();\n return uow;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { UnitOfWork } from './UnitOfWork';\n"],"mappings":";;;IAmBa,aAAN,MAAM,WAAW;CACpB,OAAgB,QAAQ;CACxB,eAAiD,WAAW;CAC5D;CACA,WAAqB;CAErB,OAAO,aAAaA,OAAqC;AACrD,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,WAAW;CAEzE;CAED,YAAYC,QAAkB;AAC1B,OAAK,SAAS;CACjB;CAED,MAAM,QAAuB;AACzB,OAAK,KAAK,UAAU;AAChB,SAAM,KAAK,OAAO,OAAO;AACzB,QAAK,WAAW;EACnB;CACJ;CAED,MAAM,SAAwB;AAC1B,MAAI,KAAK,UAAU;AACf,SAAM,KAAK,OAAO,QAAQ;AAC1B,QAAK,WAAW;EACnB;CACJ;CAED,MAAM,WAA0B;AAC5B,MAAI,KAAK,UAAU;AACf,SAAM,KAAK,OAAO,UAAU;AAC5B,QAAK,WAAW;EACnB;CACJ;CAED,YAAsB;AAClB,SAAO,KAAK;CACf;CAED,aAAa,MAAMA,QAAuC;EACtD,MAAM,MAAM,IAAI,WAAW;AAC3B,QAAM,IAAI,OAAO;AACjB,SAAO;CACV;AACJ"}