@an5/orm 1.0.8 → 1.0.9

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 (38) hide show
  1. package/README.md +167 -175
  2. package/dist/an5Metadata.js +64 -0
  3. package/dist/an5Orm.d.ts +92 -3
  4. package/dist/an5Orm.js +697 -206
  5. package/dist/errors.d.ts +1 -1
  6. package/dist/errors.js +1 -1
  7. package/dist/generator/src/code-generator.d.ts +14 -13
  8. package/dist/generator/src/code-generator.js +249 -225
  9. package/dist/generator/src/dotnet-generator.d.ts +13 -11
  10. package/dist/generator/src/dotnet-generator.js +872 -546
  11. package/dist/generator/src/index.d.ts +1 -1
  12. package/dist/generator/src/index.js +104 -88
  13. package/dist/generator/src/metadata-generator.d.ts +11 -8
  14. package/dist/generator/src/metadata-generator.js +104 -90
  15. package/dist/generator/src/parser.d.ts +11 -11
  16. package/dist/generator/src/parser.js +206 -206
  17. package/dist/generator/src/python-generator.d.ts +19 -9
  18. package/dist/generator/src/python-generator.js +345 -105
  19. package/dist/generator/src/types.d.ts +27 -27
  20. package/dist/generator/src/types.js +2 -2
  21. package/dist/index.d.ts +3 -2
  22. package/dist/index.js +19 -8
  23. package/dist/logger.js +1 -1
  24. package/dist/migration-core.d.ts +87 -0
  25. package/dist/migration-core.js +692 -0
  26. package/dist/scripts/release-cli.d.ts +2 -2
  27. package/dist/scripts/release-cli.js +115 -115
  28. package/dist/sql-utils.d.ts +3 -1
  29. package/dist/sql-utils.js +94 -24
  30. package/generator/src/code-generator.ts +259 -230
  31. package/generator/src/dotnet-generator.ts +882 -544
  32. package/generator/src/golang-generator.ts +1242 -0
  33. package/generator/src/index.ts +111 -116
  34. package/generator/src/metadata-generator.ts +116 -101
  35. package/generator/src/parser.ts +202 -202
  36. package/generator/src/python-generator.ts +424 -114
  37. package/generator/src/types.ts +29 -29
  38. package/package.json +12 -6
@@ -1,3 +1,3 @@
1
- #!/usr/bin/env tsx
2
- export {};
1
+ #!/usr/bin/env tsx
2
+ export {};
3
3
  //# sourceMappingURL=release-cli.d.ts.map
@@ -1,116 +1,116 @@
1
- #!/usr/bin/env tsx
2
- "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const child_process_1 = require("child_process");
8
- const fs_1 = __importDefault(require("fs"));
9
- const path_1 = __importDefault(require("path"));
10
- function run(cmd, allowFail = false) {
11
- try {
12
- return (0, child_process_1.execSync)(cmd, { encoding: 'utf8' }).trim();
13
- }
14
- catch (error) {
15
- if (allowFail)
16
- return error.stdout?.toString()?.trim() || '';
17
- throw error;
18
- }
19
- }
20
- function inferComponent(file) {
21
- const normalized = file.replace('\\', '/').toLowerCase();
22
- if (normalized.includes('/generator/'))
23
- return 'generator';
24
- if (normalized.includes('/an5client/'))
25
- return 'client';
26
- if (normalized.includes('/an5schema/'))
27
- return 'schema';
28
- if (normalized.includes('/.github/'))
29
- return 'ci';
30
- if (normalized.includes('package.json') || normalized.includes('tsconfig'))
31
- return 'build';
32
- if (normalized.includes('.md'))
33
- return 'docs';
34
- return 'misc';
35
- }
36
- function collectChanges() {
37
- const status = run('git status --porcelain');
38
- const files = status
39
- .split('\n')
40
- .map(l => l.trim())
41
- .filter(Boolean)
42
- .map(l => l.slice(3).trim())
43
- .filter(Boolean);
44
- const groups = new Map();
45
- for (const file of files) {
46
- const component = inferComponent(file);
47
- if (!groups.has(component)) {
48
- groups.set(component, { component, files: [], summary: '' });
49
- }
50
- groups.get(component).files.push(file);
51
- }
52
- return Array.from(groups.values()).map(group => ({
53
- ...group,
54
- summary: `${group.component} updates`
55
- }));
56
- }
57
- function generateChangelog(groups) {
58
- const lines = ['# Changelog', ''];
59
- for (const group of groups) {
60
- lines.push(`## ${group.component}`);
61
- lines.push(`- ${group.summary}`);
62
- for (const file of group.files) {
63
- lines.push(` - ${file}`);
64
- }
65
- lines.push('');
66
- }
67
- return lines.join('\n').trim();
68
- }
69
- async function maybeUseLLM(summary) {
70
- let apiKey = '';
71
- try {
72
- const configPath = require('path').join(__dirname, '..', '..', 'an5Adapters', 'typescript', 'config');
73
- const mod = require(configPath);
74
- if (mod?.getLlmConfig) {
75
- const db = await mod.getLlmConfig();
76
- if (db)
77
- apiKey = db.apiKey;
78
- }
79
- }
80
- catch { }
81
- if (!apiKey)
82
- return summary;
83
- try {
84
- const prompt = `Summarize these changes into one concise release note in English:\n${summary}`;
85
- const payload = JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'system', content: 'You are a helpful release note writer.' }, { role: 'user', content: prompt }] });
86
- const response = run(`curl -s https://api.openai.com/v1/chat/completions -H "Authorization: Bearer ${apiKey}" -H "Content-Type: application/json" -d '${payload}'`, true);
87
- const parsed = JSON.parse(response);
88
- return parsed.choices?.[0]?.message?.content?.trim() || summary;
89
- }
90
- catch {
91
- return summary;
92
- }
93
- }
94
- async function main() {
95
- const groups = collectChanges();
96
- if (groups.length === 0) {
97
- console.log('No modified files detected.');
98
- process.exit(0);
99
- }
100
- const changelog = generateChangelog(groups);
101
- const outputPath = path_1.default.resolve(process.cwd(), 'CHANGELOG.md');
102
- fs_1.default.writeFileSync(outputPath, changelog + '\n');
103
- const summary = groups.map(g => `${g.component}: ${g.files.join(', ')}`).join('\n');
104
- const llmSummary = await maybeUseLLM(summary);
105
- console.log('\nGenerated changelog:\n');
106
- console.log(changelog);
107
- console.log('\nLLM summary:\n');
108
- console.log(llmSummary);
109
- const message = groups.map(g => g.component).join(', ');
110
- const commitMessage = `chore: update ${message}`;
111
- run(`git add -A`);
112
- run(`git commit -m "${commitMessage}"`, true);
113
- console.log(`\nCommitted with: ${commitMessage}`);
114
- }
115
- main().catch(console.error);
1
+ #!/usr/bin/env tsx
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const child_process_1 = require("child_process");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ function run(cmd, allowFail = false) {
11
+ try {
12
+ return (0, child_process_1.execSync)(cmd, { encoding: 'utf8' }).trim();
13
+ }
14
+ catch (error) {
15
+ if (allowFail)
16
+ return error.stdout?.toString()?.trim() || '';
17
+ throw error;
18
+ }
19
+ }
20
+ function inferComponent(file) {
21
+ const normalized = file.replace('\\', '/').toLowerCase();
22
+ if (normalized.includes('/generator/'))
23
+ return 'generator';
24
+ if (normalized.includes('/an5client/'))
25
+ return 'client';
26
+ if (normalized.includes('/an5schema/'))
27
+ return 'schema';
28
+ if (normalized.includes('/.github/'))
29
+ return 'ci';
30
+ if (normalized.includes('package.json') || normalized.includes('tsconfig'))
31
+ return 'build';
32
+ if (normalized.includes('.md'))
33
+ return 'docs';
34
+ return 'misc';
35
+ }
36
+ function collectChanges() {
37
+ const status = run('git status --porcelain');
38
+ const files = status
39
+ .split('\n')
40
+ .map(l => l.trim())
41
+ .filter(Boolean)
42
+ .map(l => l.slice(3).trim())
43
+ .filter(Boolean);
44
+ const groups = new Map();
45
+ for (const file of files) {
46
+ const component = inferComponent(file);
47
+ if (!groups.has(component)) {
48
+ groups.set(component, { component, files: [], summary: '' });
49
+ }
50
+ groups.get(component).files.push(file);
51
+ }
52
+ return Array.from(groups.values()).map(group => ({
53
+ ...group,
54
+ summary: `${group.component} updates`
55
+ }));
56
+ }
57
+ function generateChangelog(groups) {
58
+ const lines = ['# Changelog', ''];
59
+ for (const group of groups) {
60
+ lines.push(`## ${group.component}`);
61
+ lines.push(`- ${group.summary}`);
62
+ for (const file of group.files) {
63
+ lines.push(` - ${file}`);
64
+ }
65
+ lines.push('');
66
+ }
67
+ return lines.join('\n').trim();
68
+ }
69
+ async function maybeUseLLM(summary) {
70
+ let apiKey = '';
71
+ try {
72
+ const configPath = require('path').join(__dirname, '..', '..', 'an5Adapters', 'typescript', 'config');
73
+ const mod = require(configPath);
74
+ if (mod?.getLlmConfig) {
75
+ const db = await mod.getLlmConfig();
76
+ if (db)
77
+ apiKey = db.apiKey;
78
+ }
79
+ }
80
+ catch { }
81
+ if (!apiKey)
82
+ return summary;
83
+ try {
84
+ const prompt = `Summarize these changes into one concise release note in English:\n${summary}`;
85
+ const payload = JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'system', content: 'You are a helpful release note writer.' }, { role: 'user', content: prompt }] });
86
+ const response = run(`curl -s https://api.openai.com/v1/chat/completions -H "Authorization: Bearer ${apiKey}" -H "Content-Type: application/json" -d '${payload}'`, true);
87
+ const parsed = JSON.parse(response);
88
+ return parsed.choices?.[0]?.message?.content?.trim() || summary;
89
+ }
90
+ catch {
91
+ return summary;
92
+ }
93
+ }
94
+ async function main() {
95
+ const groups = collectChanges();
96
+ if (groups.length === 0) {
97
+ console.log('No modified files detected.');
98
+ process.exit(0);
99
+ }
100
+ const changelog = generateChangelog(groups);
101
+ const outputPath = path_1.default.resolve(process.cwd(), 'CHANGELOG.md');
102
+ fs_1.default.writeFileSync(outputPath, changelog + '\n');
103
+ const summary = groups.map(g => `${g.component}: ${g.files.join(', ')}`).join('\n');
104
+ const llmSummary = await maybeUseLLM(summary);
105
+ console.log('\nGenerated changelog:\n');
106
+ console.log(changelog);
107
+ console.log('\nLLM summary:\n');
108
+ console.log(llmSummary);
109
+ const message = groups.map(g => g.component).join(', ');
110
+ const commitMessage = `chore: update ${message}`;
111
+ run(`git add -A`);
112
+ run(`git commit -m "${commitMessage}"`, true);
113
+ console.log(`\nCommitted with: ${commitMessage}`);
114
+ }
115
+ main().catch(console.error);
116
116
  //# sourceMappingURL=release-cli.js.map
@@ -8,6 +8,8 @@ import type { RelationDef } from "./metadata";
8
8
  * user-supplied column / alias names.
9
9
  */
10
10
  export declare function quoteIdentifier(name: string): string;
11
+ /** Quotes one-part or multipart SQL Server table identifiers safely. */
12
+ export declare function quoteTableIdentifier(name: string): string;
11
13
  /** Sanitizes a parameter name to a safe T-SQL identifier fragment. */
12
14
  export declare function sanitizeParamName(name: string): string;
13
15
  /** Validates a direction token, defaulting to ASC. */
@@ -20,7 +22,7 @@ export interface QueryContext {
20
22
  }
21
23
  export declare function buildOrderBy(orderBy: any): string;
22
24
  /**
23
- * Recursively builds a WHERE clause from a Prisma-style filter object.
25
+ * Recursively builds a WHERE clause from a structured filter object.
24
26
  *
25
27
  * Values are always bound as parameters (`@name`); only identifiers are
26
28
  * interpolated directly, and they are run through {@link quoteIdentifier}.
package/dist/sql-utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.quoteIdentifier = quoteIdentifier;
4
+ exports.quoteTableIdentifier = quoteTableIdentifier;
4
5
  exports.sanitizeParamName = sanitizeParamName;
5
6
  exports.normalizeSortDirection = normalizeSortDirection;
6
7
  exports.toNonNegativeInt = toNonNegativeInt;
@@ -18,6 +19,48 @@ function quoteIdentifier(name) {
18
19
  const s = String(name);
19
20
  return `[${s.replace(/\]/g, "]]")}]`;
20
21
  }
22
+ function unquoteBracketIdentifier(part) {
23
+ const trimmed = String(part).trim();
24
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
25
+ return trimmed.slice(1, -1).replace(/\]\]/g, "]");
26
+ }
27
+ return trimmed;
28
+ }
29
+ function splitMultipartIdentifier(name) {
30
+ const parts = [];
31
+ let current = "";
32
+ let inBracket = false;
33
+ const input = String(name).trim();
34
+ for (let i = 0; i < input.length; i += 1) {
35
+ const ch = input[i];
36
+ if (ch === "[")
37
+ inBracket = true;
38
+ if (ch === "]") {
39
+ if (input[i + 1] === "]") {
40
+ current += "]]";
41
+ i += 1;
42
+ continue;
43
+ }
44
+ inBracket = false;
45
+ }
46
+ if (ch === "." && !inBracket) {
47
+ if (current.trim())
48
+ parts.push(current.trim());
49
+ current = "";
50
+ continue;
51
+ }
52
+ current += ch;
53
+ }
54
+ if (current.trim())
55
+ parts.push(current.trim());
56
+ return parts.length > 0 ? parts : [input];
57
+ }
58
+ /** Quotes one-part or multipart SQL Server table identifiers safely. */
59
+ function quoteTableIdentifier(name) {
60
+ return splitMultipartIdentifier(name)
61
+ .map(part => quoteIdentifier(unquoteBracketIdentifier(part)))
62
+ .join(".");
63
+ }
21
64
  /** Sanitizes a parameter name to a safe T-SQL identifier fragment. */
22
65
  function sanitizeParamName(name) {
23
66
  const s = String(name);
@@ -34,7 +77,7 @@ function toNonNegativeInt(value, fallback = 0) {
34
77
  const n = Number.parseInt(String(value), 10);
35
78
  return Number.isFinite(n) && n >= 0 ? n : fallback;
36
79
  }
37
- const OPERATOR_KEYS = ["in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt"];
80
+ const OPERATOR_KEYS = ["equals", "in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt"];
38
81
  function isOperatorValue(value) {
39
82
  if (!value || typeof value !== "object" || value instanceof Date)
40
83
  return false;
@@ -55,7 +98,7 @@ function buildOrderBy(orderBy) {
55
98
  return orderClauses.length > 0 ? ` ORDER BY ${orderClauses.join(", ")}` : "";
56
99
  }
57
100
  /**
58
- * Recursively builds a WHERE clause from a Prisma-style filter object.
101
+ * Recursively builds a WHERE clause from a structured filter object.
59
102
  *
60
103
  * Values are always bound as parameters (`@name`); only identifiers are
61
104
  * interpolated directly, and they are run through {@link quoteIdentifier}.
@@ -83,18 +126,27 @@ function parseWhere(modelName, where, params, prefix = "", ctx = {}) {
83
126
  conditions.push(`(${filtered.join(" OR ")})`);
84
127
  }
85
128
  }
86
- else if (key === "AND" && Array.isArray(value)) {
87
- const andConditions = value.map((subWhere, idx) => parseWhere(modelName, subWhere, params, `${prefix}and_${idx}_`, ctx));
129
+ else if (key === "AND") {
130
+ const andItems = Array.isArray(value) ? value : [value];
131
+ const andConditions = andItems.map((subWhere, idx) => parseWhere(modelName, subWhere, params, `${prefix}and_${idx}_`, ctx));
88
132
  const filtered = andConditions.filter(Boolean);
89
133
  if (filtered.length > 0) {
90
134
  conditions.push(`(${filtered.join(" AND ")})`);
91
135
  }
92
136
  }
137
+ else if (key === "NOT") {
138
+ const notItems = Array.isArray(value) ? value : [value];
139
+ const notConditions = notItems.map((subWhere, idx) => parseWhere(modelName, subWhere, params, `${prefix}not_${idx}_`, ctx));
140
+ const filtered = notConditions.filter(Boolean);
141
+ if (filtered.length > 0) {
142
+ conditions.push(`NOT (${filtered.join(" AND ")})`);
143
+ }
144
+ }
93
145
  else {
94
146
  const modelRelations = relationMap[modelName];
95
147
  const relation = modelRelations?.[key];
96
148
  if (relation) {
97
- const relationTable = modelToTable[relation.modelName] || relation.modelName;
149
+ const relationTable = quoteTableIdentifier(modelToTable[relation.modelName] || relation.modelName);
98
150
  const subParams = {};
99
151
  let subWhere = value;
100
152
  let op = "some";
@@ -114,26 +166,23 @@ function parseWhere(modelName, where, params, prefix = "", ctx = {}) {
114
166
  }
115
167
  const subWhereSql = parseWhere(relation.modelName, subWhere, subParams, `${prefix}${key}_`, ctx);
116
168
  Object.assign(params, subParams);
117
- if (subWhereSql) {
118
- const fk = quoteIdentifier(relation.foreignKey);
119
- const lk = quoteIdentifier(relation.localKey);
120
- if (relation.relationType === "one") {
121
- if (op === "none") {
122
- conditions.push(`${fk} NOT IN (SELECT ${lk} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
123
- }
124
- else {
125
- conditions.push(`${fk} IN (SELECT ${lk} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
126
- }
169
+ const fk = quoteIdentifier(relation.foreignKey);
170
+ const lk = quoteIdentifier(relation.localKey);
171
+ const outerKey = relation.relationType === "one" ? fk : lk;
172
+ const innerKey = relation.relationType === "one" ? lk : fk;
173
+ if (op === "every") {
174
+ if (subWhereSql) {
175
+ conditions.push(`${outerKey} NOT IN (SELECT ${innerKey} FROM ${relationTable} WITH (NOLOCK) WHERE NOT (${subWhereSql}))`);
127
176
  }
128
177
  else {
129
- if (op === "none") {
130
- conditions.push(`${lk} NOT IN (SELECT ${fk} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
131
- }
132
- else {
133
- conditions.push(`${lk} IN (SELECT ${fk} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
134
- }
178
+ conditions.push("1 = 1");
135
179
  }
136
180
  }
181
+ else {
182
+ const relationPredicate = subWhereSql ? ` WHERE ${subWhereSql}` : "";
183
+ const operator = op === "none" ? "NOT IN" : "IN";
184
+ conditions.push(`${outerKey} ${operator} (SELECT ${innerKey} FROM ${relationTable} WITH (NOLOCK)${relationPredicate})`);
185
+ }
137
186
  }
138
187
  else {
139
188
  const col = quoteIdentifier(key);
@@ -141,7 +190,16 @@ function parseWhere(modelName, where, params, prefix = "", ctx = {}) {
141
190
  if (value && typeof value === "object" && !(value instanceof Date)) {
142
191
  const ops = Object.entries(value);
143
192
  for (const [op, opVal] of ops) {
144
- if (op === "in" && Array.isArray(opVal)) {
193
+ if (op === "equals") {
194
+ if (opVal === null) {
195
+ conditions.push(`${col} IS NULL`);
196
+ }
197
+ else {
198
+ conditions.push(`${col} = @${paramName}_equals`);
199
+ params[`${paramName}_equals`] = opVal;
200
+ }
201
+ }
202
+ else if (op === "in" && Array.isArray(opVal)) {
145
203
  if (opVal.length === 0) {
146
204
  conditions.push("1 = 0");
147
205
  }
@@ -182,8 +240,20 @@ function parseWhere(modelName, where, params, prefix = "", ctx = {}) {
182
240
  params[`${paramName}_endsWith`] = `%${opVal}`;
183
241
  }
184
242
  else if (op === "not") {
185
- conditions.push(`${col} <> @${paramName}_not`);
186
- params[`${paramName}_not`] = opVal;
243
+ if (opVal === null) {
244
+ conditions.push(`${col} IS NOT NULL`);
245
+ }
246
+ else if (opVal && typeof opVal === "object" && !(opVal instanceof Date) && !Array.isArray(opVal)) {
247
+ const nestedParams = {};
248
+ const nestedSql = parseWhere(modelName, { [key]: opVal }, nestedParams, `${prefix}${key}_not_`, ctx);
249
+ Object.assign(params, nestedParams);
250
+ if (nestedSql)
251
+ conditions.push(`NOT (${nestedSql})`);
252
+ }
253
+ else {
254
+ conditions.push(`${col} <> @${paramName}_not`);
255
+ params[`${paramName}_not`] = opVal;
256
+ }
187
257
  }
188
258
  else if (op === "gte") {
189
259
  conditions.push(`${col} >= @${paramName}_gte`);