@an5/orm 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -11
- package/dist/cleanup.d.ts +1 -0
- package/dist/cleanup.js +177 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +147 -0
- package/dist/generator/src/attributes.d.ts +57 -0
- package/dist/generator/src/attributes.js +225 -0
- package/dist/generator/src/code-generator.js +49 -16
- package/dist/generator/src/config.d.ts +2 -2
- package/dist/generator/src/config.js +4 -3
- package/dist/generator/src/dialect.d.ts +52 -1
- package/dist/generator/src/dialect.js +136 -3
- package/dist/generator/src/dotnet-generator.js +67 -23
- package/dist/generator/src/golang-generator.d.ts +8 -0
- package/dist/generator/src/golang-generator.js +86 -21
- package/dist/generator/src/java-generator.d.ts +8 -0
- package/dist/generator/src/java-generator.js +108 -3
- package/dist/generator/src/kotlin-generator.js +51 -0
- package/dist/generator/src/parser.d.ts +1 -0
- package/dist/generator/src/parser.js +90 -35
- package/dist/generator/src/pull-schema.d.ts +54 -0
- package/dist/generator/src/pull-schema.js +116 -0
- package/dist/generator/src/push-apply.d.ts +4 -2
- package/dist/generator/src/push-apply.js +53 -4
- package/dist/generator/src/push-schema.d.ts +21 -9
- package/dist/generator/src/push-schema.js +132 -32
- package/dist/generator/src/python-generator.js +35 -22
- package/dist/generator/src/rust-generator.js +79 -74
- package/dist/generator/src/swift-generator.js +54 -3
- package/dist/generator/src/types.d.ts +10 -0
- package/dist/generator/src/view-order.d.ts +33 -0
- package/dist/generator/src/view-order.js +123 -0
- package/dist/migrate.d.ts +12 -0
- package/dist/migrate.js +442 -0
- package/dist/migration-core.d.ts +32 -3
- package/dist/migration-core.js +213 -43
- package/dist/provider-support.d.ts +0 -1
- package/dist/provider-support.js +0 -1
- package/dist/pull.d.ts +1 -0
- package/dist/pull.js +425 -0
- package/dist/push.d.ts +1 -0
- package/dist/push.js +93 -0
- package/dist/seed.d.ts +1 -0
- package/dist/seed.js +33 -0
- package/generator/src/attributes.ts +231 -0
- package/generator/src/code-generator.ts +47 -16
- package/generator/src/config.ts +5 -3
- package/generator/src/dialect.ts +188 -5
- package/generator/src/dotnet-generator.ts +68 -24
- package/generator/src/golang-generator.ts +89 -22
- package/generator/src/java-generator.ts +109 -3
- package/generator/src/kotlin-generator.ts +51 -0
- package/generator/src/parser.ts +102 -30
- package/generator/src/pull-schema.ts +127 -0
- package/generator/src/push-apply.ts +62 -5
- package/generator/src/push-schema.ts +160 -32
- package/generator/src/python-generator.ts +33 -20
- package/generator/src/rust-generator.ts +79 -73
- package/generator/src/swift-generator.ts +55 -4
- package/generator/src/types.ts +10 -0
- package/generator/src/view-order.ts +133 -0
- package/package.json +19 -14
package/README.md
CHANGED
|
@@ -69,38 +69,58 @@ DATABASE_URL=sqlserver://localhost:1433;database=mydb;user=sa;password=Secret123
|
|
|
69
69
|
|
|
70
70
|
### CLI Commands
|
|
71
71
|
|
|
72
|
+
You can run AN5 commands directly using `npx @an5/orm` (or `npx an5`):
|
|
73
|
+
|
|
72
74
|
```bash
|
|
73
75
|
# Generate client code from .an5 schemas
|
|
74
|
-
|
|
76
|
+
npx @an5/orm generate
|
|
77
|
+
# or: npx an5 generate
|
|
75
78
|
|
|
76
79
|
# Push schema directly to database
|
|
77
|
-
|
|
80
|
+
npx @an5/orm db:push
|
|
78
81
|
|
|
79
82
|
# Pull database schema into .an5 files
|
|
80
|
-
|
|
83
|
+
npx @an5/orm db:pull
|
|
81
84
|
|
|
82
85
|
# Check schema diff
|
|
83
|
-
|
|
86
|
+
npx @an5/orm db:migrate diff
|
|
84
87
|
|
|
85
88
|
# Generate new migration script
|
|
86
|
-
|
|
89
|
+
npx @an5/orm db:migrate generate
|
|
87
90
|
|
|
88
91
|
# Apply pending migrations
|
|
89
|
-
|
|
92
|
+
npx @an5/orm db:migrate apply
|
|
90
93
|
|
|
91
94
|
# Rollback last migration
|
|
92
|
-
|
|
95
|
+
npx @an5/orm db:migrate rollback
|
|
93
96
|
|
|
94
97
|
# View migration status
|
|
95
|
-
|
|
98
|
+
npx @an5/orm db:migrate status
|
|
96
99
|
|
|
97
100
|
# Run database seed script
|
|
98
|
-
|
|
101
|
+
npx @an5/orm db:seed
|
|
99
102
|
|
|
100
|
-
# Clean up
|
|
101
|
-
|
|
103
|
+
# Clean up extra tables and views
|
|
104
|
+
npx @an5/orm db:cleanup
|
|
102
105
|
```
|
|
103
106
|
|
|
107
|
+
You can also add these commands to your project's `package.json` scripts:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"scripts": {
|
|
112
|
+
"generate": "an5 generate",
|
|
113
|
+
"db:push": "an5 db:push",
|
|
114
|
+
"db:pull": "an5 db:pull",
|
|
115
|
+
"db:migrate": "an5 db:migrate",
|
|
116
|
+
"db:seed": "an5 db:seed",
|
|
117
|
+
"db:cleanup": "an5 db:cleanup"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Then run them with `npm run generate`, `npm run db:push`, etc.
|
|
123
|
+
|
|
104
124
|
## Runtime Database Usage (via `@an5/adapters`)
|
|
105
125
|
|
|
106
126
|
To connect to your database and perform queries, use `@an5/adapters`:
|
|
@@ -169,6 +189,49 @@ model Order {
|
|
|
169
189
|
}
|
|
170
190
|
```
|
|
171
191
|
|
|
192
|
+
Views are declared with the same field syntax, so generated clients can type their
|
|
193
|
+
rows, plus the `SELECT` that defines them. A view is read-only: writes are not
|
|
194
|
+
generated, and `db:push`/`db:migrate` create the view after the tables it reads —
|
|
195
|
+
and after any view it reads. `db:pull` writes a database view back as a `view`
|
|
196
|
+
block, with its columns and the SQL body from the catalog. `db:cleanup` drops
|
|
197
|
+
views that are no longer in the schema, before the tables they might read.
|
|
198
|
+
|
|
199
|
+
`@@sql` holds the `SELECT` body; a full `CREATE VIEW ... AS ...` statement pasted
|
|
200
|
+
from a catalog is reduced to that body, so its header and object name are not
|
|
201
|
+
repeated in the generated DDL.
|
|
202
|
+
|
|
203
|
+
Generated clients expose a view through a read-only client — `ViewClient` in
|
|
204
|
+
TypeScript, Python, .NET, Java and Kotlin, `ViewModelClient` in Swift, and
|
|
205
|
+
`ViewClient[T]` in Go and Rust. Those types have the read methods only, so a write
|
|
206
|
+
to a view is rejected at compile time rather than by the database.
|
|
207
|
+
|
|
208
|
+
```an5
|
|
209
|
+
view ActiveUser {
|
|
210
|
+
id NVARCHAR(1000)
|
|
211
|
+
email NVARCHAR(255)
|
|
212
|
+
|
|
213
|
+
@@map("active_users")
|
|
214
|
+
@@sql("SELECT id, email FROM users WHERE active = 1")
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
A longer query can be written over several lines with triple quotes; the indentation
|
|
219
|
+
shared by the SQL lines is removed before it reaches the database:
|
|
220
|
+
|
|
221
|
+
```an5
|
|
222
|
+
view ActiveUser {
|
|
223
|
+
id NVARCHAR(1000)
|
|
224
|
+
email NVARCHAR(255)
|
|
225
|
+
|
|
226
|
+
@@map("active_users")
|
|
227
|
+
@@sql("""
|
|
228
|
+
SELECT id, email
|
|
229
|
+
FROM users
|
|
230
|
+
WHERE active = 1
|
|
231
|
+
""")
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
172
235
|
## License
|
|
173
236
|
|
|
174
237
|
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "dotenv/config";
|
package/dist/cleanup.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
require("dotenv/config");
|
|
7
|
+
const adapters_1 = require("@an5/adapters");
|
|
8
|
+
const config_1 = require("./generator/src/config");
|
|
9
|
+
const provider_support_1 = require("./provider-support");
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const rootDir = process.cwd();
|
|
13
|
+
const { config, outputs } = (0, config_1.loadConfig)();
|
|
14
|
+
const schemaDir = outputs.schemaDir;
|
|
15
|
+
let _adapter = null;
|
|
16
|
+
async function getDb() {
|
|
17
|
+
if (!_adapter) {
|
|
18
|
+
_adapter = new adapters_1.An5Adapter({ connectionString: (0, config_1.resolveConnectionString)(config, process.env, "db:cleanup") });
|
|
19
|
+
await _adapter.$connect();
|
|
20
|
+
}
|
|
21
|
+
return _adapter;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Prints why a command cannot run against this provider and exits.
|
|
25
|
+
*
|
|
26
|
+
* The decision and the wording live in `./provider-support`, which stays free of Node
|
|
27
|
+
* globals so it can be imported and tested anywhere; only the command files, which
|
|
28
|
+
* are Node programs, do the exiting.
|
|
29
|
+
*/
|
|
30
|
+
function refuseIfNotSqlServer(provider, command) {
|
|
31
|
+
const message = (0, provider_support_1.sqlServerOnlyError)(provider, command);
|
|
32
|
+
if (message === null)
|
|
33
|
+
return;
|
|
34
|
+
console.error(message);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The tables and views the schema files declare, lower-cased.
|
|
39
|
+
*
|
|
40
|
+
* SQL Server compares object names case-insensitively in the default collation,
|
|
41
|
+
* so `Users` and `users` are the same object here; a case-sensitive comparison
|
|
42
|
+
* would drop a table the schema still declares.
|
|
43
|
+
*/
|
|
44
|
+
function declaredObjects(schemaText) {
|
|
45
|
+
const tables = new Set();
|
|
46
|
+
const views = new Set();
|
|
47
|
+
let block = null;
|
|
48
|
+
let blockName = '';
|
|
49
|
+
for (const rawLine of schemaText.split(/\r?\n/)) {
|
|
50
|
+
const line = rawLine.trim();
|
|
51
|
+
if (!line || line.startsWith('//'))
|
|
52
|
+
continue;
|
|
53
|
+
const start = /^(model|view)\s+(\w+)\s*\{/.exec(line);
|
|
54
|
+
if (start) {
|
|
55
|
+
block = start[1];
|
|
56
|
+
blockName = start[2] ?? '';
|
|
57
|
+
if (block === 'model')
|
|
58
|
+
tables.add(blockName.toLowerCase() + 's');
|
|
59
|
+
else
|
|
60
|
+
views.add(blockName.toLowerCase() + 's');
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (line === '}') {
|
|
64
|
+
block = null;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const mapped = /^@@map\(\s*"([^"]+)"\s*\)/.exec(line);
|
|
68
|
+
if (mapped?.[1]) {
|
|
69
|
+
// A mapped name replaces the default one, but `@@map` outside a block is
|
|
70
|
+
// still read as a table name: older schema files may have it there.
|
|
71
|
+
const target = block === 'view' ? views : tables;
|
|
72
|
+
target.add(mapped[1].toLowerCase());
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { tables, views };
|
|
76
|
+
}
|
|
77
|
+
/** Quotes a catalog identifier, doubling `]` the way T-SQL expects. */
|
|
78
|
+
function quoteName(name) {
|
|
79
|
+
return `[${name.replace(/]/g, ']]')}]`;
|
|
80
|
+
}
|
|
81
|
+
async function cleanup() {
|
|
82
|
+
// Drops tables by comparing sys.tables with the schema files, so a non-SQL
|
|
83
|
+
// Server connection string is a mistake worth naming first.
|
|
84
|
+
refuseIfNotSqlServer((0, config_1.providerFromConfig)(config, process.env), "db:cleanup");
|
|
85
|
+
console.log("🧹 Starting database cleanup...");
|
|
86
|
+
let schemaText = "";
|
|
87
|
+
if (fs_1.default.existsSync(schemaDir)) {
|
|
88
|
+
const files = fs_1.default.readdirSync(schemaDir).filter(f => f.endsWith(".an5"));
|
|
89
|
+
for (const file of files) {
|
|
90
|
+
schemaText += fs_1.default.readFileSync(path_1.default.join(schemaDir, file), "utf8") + "\n";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const schemaPath = path_1.default.join(__dirname, "schema.an5");
|
|
95
|
+
if (fs_1.default.existsSync(schemaPath)) {
|
|
96
|
+
schemaText = fs_1.default.readFileSync(schemaPath, "utf8");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.error("No schema found!");
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const { tables: validTables, views: validViews } = declaredObjects(schemaText);
|
|
104
|
+
// Also include the capitalization variations from an5Metadata if we can,
|
|
105
|
+
// but schema.an5 is the source of truth.
|
|
106
|
+
console.log("Valid tables from schema:", Array.from(validTables));
|
|
107
|
+
console.log("Valid views from schema:", Array.from(validViews));
|
|
108
|
+
const dbTables = await (await getDb()).$queryRawUnsafe(`
|
|
109
|
+
SELECT name FROM sys.tables WHERE is_ms_shipped = 0
|
|
110
|
+
`);
|
|
111
|
+
const dbViews = await (await getDb()).$queryRawUnsafe(`
|
|
112
|
+
SELECT name FROM sys.views WHERE is_ms_shipped = 0
|
|
113
|
+
`);
|
|
114
|
+
const tablesToDrop = dbTables
|
|
115
|
+
.map(t => t.name)
|
|
116
|
+
.filter(name => !validTables.has(name.toLowerCase()));
|
|
117
|
+
const viewsToDrop = dbViews
|
|
118
|
+
.map(v => v.name)
|
|
119
|
+
.filter(name => !validViews.has(name.toLowerCase()));
|
|
120
|
+
// Views go first: one of them may read a table that is about to be dropped, and
|
|
121
|
+
// an orphan view is exactly what this command is meant to remove.
|
|
122
|
+
if (viewsToDrop.length > 0) {
|
|
123
|
+
console.log("Views to be DROPPED:", viewsToDrop);
|
|
124
|
+
for (const view of viewsToDrop) {
|
|
125
|
+
console.log(`Dropping view [${view}]...`);
|
|
126
|
+
try {
|
|
127
|
+
await (await getDb()).$executeRawUnsafe(`DROP VIEW IF EXISTS ${quoteName(view)}`);
|
|
128
|
+
console.log(`✅ Dropped [${view}]`);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
console.error(`❌ Failed to drop view [${view}]: ${err.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (tablesToDrop.length === 0) {
|
|
136
|
+
console.log(viewsToDrop.length === 0
|
|
137
|
+
? "✅ No extra tables found. Database is clean."
|
|
138
|
+
: "✅ No extra tables found.");
|
|
139
|
+
console.log("✅ Cleanup completed.");
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
console.log("Tables to be DROPPED:", tablesToDrop);
|
|
143
|
+
for (const table of tablesToDrop) {
|
|
144
|
+
console.log(`Dropping table [${table}]...`);
|
|
145
|
+
try {
|
|
146
|
+
// We need to handle foreign key constraints.
|
|
147
|
+
// For a thorough cleanup, we might need to drop constraints first.
|
|
148
|
+
// But for now, we'll try a simple DROP TABLE.
|
|
149
|
+
await (await getDb()).$executeRawUnsafe(`DROP TABLE ${quoteName(table)}`);
|
|
150
|
+
console.log(`✅ Dropped [${table}]`);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
console.error(`❌ Failed to drop [${table}]: ${err.message}`);
|
|
154
|
+
if (err.message.includes("FOREIGN KEY constraint")) {
|
|
155
|
+
console.log(`Attempting to find and drop constraints for [${table}]...`);
|
|
156
|
+
const constraints = await (await getDb()).$queryRawUnsafe(`
|
|
157
|
+
SELECT fk.name AS constraint_name, OBJECT_NAME(fk.parent_object_id) AS table_name
|
|
158
|
+
FROM sys.foreign_keys AS fk
|
|
159
|
+
WHERE OBJECT_NAME(fk.referenced_object_id) = @p_0 OR OBJECT_NAME(fk.parent_object_id) = @p_0
|
|
160
|
+
`, table);
|
|
161
|
+
for (const c of constraints) {
|
|
162
|
+
console.log(`Dropping constraint [${c.constraint_name}] on [${c.table_name}]...`);
|
|
163
|
+
await (await getDb()).$executeRawUnsafe(`ALTER TABLE ${quoteName(c.table_name)} DROP CONSTRAINT ${quoteName(c.constraint_name)}`);
|
|
164
|
+
}
|
|
165
|
+
// Try dropping again
|
|
166
|
+
await (await getDb()).$executeRawUnsafe(`DROP TABLE ${quoteName(table)}`);
|
|
167
|
+
console.log(`✅ Dropped [${table}] after removing constraints.`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
console.log("✅ Cleanup completed.");
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
cleanup().catch(err => {
|
|
175
|
+
console.error("❌ Cleanup failed:", err);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
});
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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
|
+
exports.main = main;
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
function printHelp() {
|
|
12
|
+
console.log(`AN5 ORM CLI
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
an5 <command> [options]
|
|
16
|
+
npx @an5/orm <command> [options]
|
|
17
|
+
|
|
18
|
+
Commands:
|
|
19
|
+
generate, codegen Generate client code from .an5 schemas
|
|
20
|
+
db:push, push Push schema directly to database
|
|
21
|
+
db:pull, pull Pull database schema into .an5 files
|
|
22
|
+
db:migrate, migrate Manage database migrations:
|
|
23
|
+
diff Show differences between schema and database
|
|
24
|
+
generate Generate a new migration script
|
|
25
|
+
apply [--preview] Apply pending migrations
|
|
26
|
+
rollback [--preview] Roll back applied migrations
|
|
27
|
+
status View migration status
|
|
28
|
+
db:seed, seed Run project seed script (seed.ts / seed.js)
|
|
29
|
+
db:cleanup, cleanup Clean up database tables
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
-v, --version Show version number
|
|
33
|
+
-h, --help Show help
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
function printVersion() {
|
|
37
|
+
try {
|
|
38
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
39
|
+
console.log(`@an5/orm v${pkg.version}`);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
console.log('@an5/orm');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function resolveCommandScript(baseName) {
|
|
46
|
+
// If running from dist/cli.js, command files are right next to it
|
|
47
|
+
const distCandidate = path_1.default.join(__dirname, `${baseName}.js`);
|
|
48
|
+
if (fs_1.default.existsSync(distCandidate)) {
|
|
49
|
+
return distCandidate;
|
|
50
|
+
}
|
|
51
|
+
if (baseName === 'generator') {
|
|
52
|
+
const genDist = path_1.default.join(__dirname, 'generator', 'src', 'index.js');
|
|
53
|
+
if (fs_1.default.existsSync(genDist))
|
|
54
|
+
return genDist;
|
|
55
|
+
const genTs = path_1.default.join(__dirname, '..', 'generator', 'src', 'index.ts');
|
|
56
|
+
if (fs_1.default.existsSync(genTs))
|
|
57
|
+
return genTs;
|
|
58
|
+
}
|
|
59
|
+
const rootTs = path_1.default.join(__dirname, '..', `${baseName}.ts`);
|
|
60
|
+
if (fs_1.default.existsSync(rootTs)) {
|
|
61
|
+
return rootTs;
|
|
62
|
+
}
|
|
63
|
+
const localTs = path_1.default.join(__dirname, `${baseName}.ts`);
|
|
64
|
+
if (fs_1.default.existsSync(localTs)) {
|
|
65
|
+
return localTs;
|
|
66
|
+
}
|
|
67
|
+
return distCandidate;
|
|
68
|
+
}
|
|
69
|
+
function runScript(scriptPath, args) {
|
|
70
|
+
const isTs = scriptPath.endsWith('.ts');
|
|
71
|
+
const runner = isTs ? (process.platform === 'win32' ? 'npx.cmd' : 'npx') : process.execPath;
|
|
72
|
+
const runnerArgs = isTs ? ['tsx', scriptPath, ...args] : [scriptPath, ...args];
|
|
73
|
+
const result = (0, child_process_1.spawnSync)(runner, runnerArgs, {
|
|
74
|
+
stdio: 'inherit',
|
|
75
|
+
env: process.env,
|
|
76
|
+
});
|
|
77
|
+
if (result.error) {
|
|
78
|
+
console.error(`❌ Failed to execute command: ${result.error.message}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
process.exit(result.status ?? (result.signal ? 1 : 0));
|
|
82
|
+
}
|
|
83
|
+
function main(rawArgs = process.argv.slice(2)) {
|
|
84
|
+
const [firstArg, ...rest] = rawArgs;
|
|
85
|
+
if (!firstArg || firstArg === '--help' || firstArg === '-h' || firstArg === 'help') {
|
|
86
|
+
printHelp();
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
if (firstArg === '--version' || firstArg === '-v' || firstArg === 'version') {
|
|
90
|
+
printVersion();
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
// Handle db:migrate:... shorthand (e.g. db:migrate:apply -> migrate apply)
|
|
94
|
+
if (firstArg.startsWith('db:migrate:')) {
|
|
95
|
+
const sub = firstArg.slice('db:migrate:'.length);
|
|
96
|
+
const script = resolveCommandScript('migrate');
|
|
97
|
+
runScript(script, [sub, ...rest]);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
switch (firstArg) {
|
|
101
|
+
case 'generate':
|
|
102
|
+
case 'codegen':
|
|
103
|
+
case 'g': {
|
|
104
|
+
const script = resolveCommandScript('generator');
|
|
105
|
+
runScript(script, rest);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case 'db:push':
|
|
109
|
+
case 'push': {
|
|
110
|
+
const script = resolveCommandScript('push');
|
|
111
|
+
runScript(script, rest);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case 'db:pull':
|
|
115
|
+
case 'pull': {
|
|
116
|
+
const script = resolveCommandScript('pull');
|
|
117
|
+
runScript(script, rest);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case 'db:seed':
|
|
121
|
+
case 'seed': {
|
|
122
|
+
const script = resolveCommandScript('seed');
|
|
123
|
+
runScript(script, rest);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'db:cleanup':
|
|
127
|
+
case 'cleanup': {
|
|
128
|
+
const script = resolveCommandScript('cleanup');
|
|
129
|
+
runScript(script, rest);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'db:migrate':
|
|
133
|
+
case 'migrate': {
|
|
134
|
+
const script = resolveCommandScript('migrate');
|
|
135
|
+
runScript(script, rest);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
default: {
|
|
139
|
+
console.error(`Unknown command: "${firstArg}"`);
|
|
140
|
+
console.error('Run "an5 --help" for a list of available commands.');
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (require.main === module) {
|
|
146
|
+
main();
|
|
147
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading `@attribute(...)` arguments from a `.an5` field or model line.
|
|
3
|
+
*
|
|
4
|
+
* The schema used to be read with regexes such as `/@default\((.*)\)/`, which
|
|
5
|
+
* ran to the *last* `)` on the line. A field with `@default(now())` followed by
|
|
6
|
+
* `@description("...")` therefore had `now()) @description("..."` as its default
|
|
7
|
+
* expression, and `db:push` emitted SQL no database accepts. The closing
|
|
8
|
+
* parenthesis has to be the one that matches the opening one — parentheses nest
|
|
9
|
+
* (`now()`, `uuid()`, `lower(...)`) and quoted strings may contain both.
|
|
10
|
+
*
|
|
11
|
+
* These helpers only read attributes; they do not validate the schema language.
|
|
12
|
+
*/
|
|
13
|
+
/** Whether `line` has the attribute, with or without arguments. */
|
|
14
|
+
export declare function hasAttribute(line: string, name: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The raw argument text of the first `name(...)` on `line`.
|
|
17
|
+
*
|
|
18
|
+
* Returns `undefined` when the attribute is absent, or when it is written
|
|
19
|
+
* without parentheses. The returned text keeps its quotes, so callers decide
|
|
20
|
+
* whether they are reading a string, a number or an expression.
|
|
21
|
+
*/
|
|
22
|
+
export declare function attributeArgs(line: string, name: string): string | undefined;
|
|
23
|
+
/** Strips one pair of surrounding double quotes, if present. */
|
|
24
|
+
export declare function unquote(value: string | undefined): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Splits schema text into logical lines, keeping a triple-quoted `@@sql` body on
|
|
27
|
+
* one line.
|
|
28
|
+
*
|
|
29
|
+
* The parsers read a schema line by line. Without this, a multi-line view body
|
|
30
|
+
* would end the directive at its first newline and the rest of the `SELECT`
|
|
31
|
+
* would be read as fields of the view. Newlines inside the quotes are preserved;
|
|
32
|
+
* only the split points change.
|
|
33
|
+
*/
|
|
34
|
+
export declare function splitSchemaLines(text: string): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Reads a named string option from an attribute argument list, e.g.
|
|
37
|
+
* `map: "UQ_users_email"` in `@unique(map: "UQ_users_email")`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function stringOption(args: string | undefined, key: string): string | undefined;
|
|
40
|
+
/** Reads a named field-list option, e.g. `include: [email, tenantId]`. */
|
|
41
|
+
export declare function fieldListOption(args: string | undefined, key: string): string[] | undefined;
|
|
42
|
+
/** One `@@unique` or `@@index` directive, with the options it accepts. */
|
|
43
|
+
export interface IndexDirective {
|
|
44
|
+
fields: string[];
|
|
45
|
+
name?: string;
|
|
46
|
+
includeFields?: string[];
|
|
47
|
+
filter?: string;
|
|
48
|
+
options?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parses the inside of `@@unique([a, b], map: "…", include: […], …)`.
|
|
52
|
+
*
|
|
53
|
+
* The directive is the one place a schema can carry raw SQL (`filter`,
|
|
54
|
+
* `options`), so its argument list is balanced rather than cut at the first
|
|
55
|
+
* `)`. A filter such as `(deletedAt IS NULL)` used to truncate the directive.
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseIndexDirective(args: string | undefined): IndexDirective | null;
|