turbine-orm 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +295 -0
- package/dist/cli/config.d.ts +58 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +123 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +23 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +935 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/migrate.d.ts +94 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/migrate.js +383 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli/ui.d.ts +74 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +220 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/client.d.ts +212 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +423 -0
- package/dist/client.js.map +1 -0
- package/dist/generate.d.ts +24 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +289 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/introspect.d.ts +22 -0
- package/dist/introspect.d.ts.map +1 -0
- package/dist/introspect.js +284 -0
- package/dist/introspect.js.map +1 -0
- package/dist/pipeline.d.ts +44 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +69 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/query.d.ts +342 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +1396 -0
- package/dist/query.js.map +1 -0
- package/dist/schema-builder.d.ts +127 -0
- package/dist/schema-builder.d.ts.map +1 -0
- package/dist/schema-builder.js +164 -0
- package/dist/schema-builder.js.map +1 -0
- package/dist/schema-sql.d.ts +71 -0
- package/dist/schema-sql.d.ts.map +1 -0
- package/dist/schema-sql.js +347 -0
- package/dist/schema-sql.js.map +1 -0
- package/dist/schema.d.ts +90 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +129 -0
- package/dist/schema.js.map +1 -0
- package/dist/serverless.d.ts +162 -0
- package/dist/serverless.d.ts.map +1 -0
- package/dist/serverless.js +195 -0
- package/dist/serverless.js.map +1 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +126 -0
- package/dist/types.js.map +1 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ZVN DEV
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# @batadata/turbine
|
|
2
|
+
|
|
3
|
+
The performance-first Postgres ORM. Prisma-compatible API, 2-3x faster nested queries, zero runtime overhead beyond `pg`.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npm install @batadata/turbine
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Why Turbine?
|
|
10
|
+
|
|
11
|
+
ORMs like Prisma fetch nested relations with N+1 queries -- one query per nesting level. Turbine uses Postgres `json_agg` to resolve the entire object graph in **a single SQL query**. The result is fewer round-trips, less connection overhead, and significantly lower latency.
|
|
12
|
+
|
|
13
|
+
**One query instead of N+1.** When you write `db.users.findMany({ with: { posts: { with: { comments: true } } } })`, Turbine generates a single SQL statement that returns fully-nested JSON. Prisma sends 3 separate queries. Drizzle uses LATERAL joins which are competitive, but Turbine still wins on median latency.
|
|
14
|
+
|
|
15
|
+
## Benchmarks
|
|
16
|
+
|
|
17
|
+
Production results from Vercel Serverless hitting Neon Postgres (20 iterations, warm):
|
|
18
|
+
|
|
19
|
+
| Scenario | Turbine | Drizzle | Prisma |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| **Nested L3 (median)** | **5.3ms** | 6.5ms | 7.4ms |
|
|
22
|
+
| Nested L3 (min) | **4.4ms** | 5.7ms | 6.0ms |
|
|
23
|
+
| Nested L2 | **6.5ms** | 9.1ms | 10.2ms |
|
|
24
|
+
| Simple select | 5.6ms | 7.1ms | 3.9ms |
|
|
25
|
+
|
|
26
|
+
Local Docker results (50K iterations, HDR histograms):
|
|
27
|
+
|
|
28
|
+
| Scenario | Turbine | Drizzle | Prisma |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| **L2 nested p50** | **201us** | 523us | 835us |
|
|
31
|
+
| **L2 nested RPS (c=50)** | **24,041** | 6,360 | 3,784 |
|
|
32
|
+
| L2 nested memory | 109MB | 117MB | 233MB |
|
|
33
|
+
|
|
34
|
+
Turbine is 2.6x faster than Drizzle and 4.2x faster than Prisma on nested queries at p50. Throughput is 3.8x higher than Drizzle and 6.3x higher than Prisma.
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 1. Install
|
|
40
|
+
npm install @batadata/turbine
|
|
41
|
+
|
|
42
|
+
# 2. Initialize project
|
|
43
|
+
npx turbine init --url postgres://user:pass@localhost:5432/mydb
|
|
44
|
+
|
|
45
|
+
# 3. Generate typed client from your database
|
|
46
|
+
npx turbine generate
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This introspects your database and generates a fully-typed client at `./generated/turbine/`.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { turbine } from './generated/turbine';
|
|
53
|
+
|
|
54
|
+
const db = turbine({ connectionString: process.env.DATABASE_URL });
|
|
55
|
+
|
|
56
|
+
// Type-safe queries with autocompletion
|
|
57
|
+
const users = await db.users.findMany({
|
|
58
|
+
where: { role: 'admin' },
|
|
59
|
+
orderBy: { createdAt: 'desc' },
|
|
60
|
+
limit: 10,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await db.disconnect();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage Examples
|
|
67
|
+
|
|
68
|
+
### findMany with nested relations
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Single query -- returns users with their posts and each post's comments
|
|
72
|
+
const users = await db.users.findMany({
|
|
73
|
+
where: { orgId: 1 },
|
|
74
|
+
with: {
|
|
75
|
+
posts: {
|
|
76
|
+
with: { comments: true },
|
|
77
|
+
orderBy: { createdAt: 'desc' },
|
|
78
|
+
limit: 5,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// users[0].posts[0].comments -- fully typed, single round-trip
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### findUnique
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const user = await db.users.findUnique({
|
|
90
|
+
where: { id: 42 },
|
|
91
|
+
with: { posts: true },
|
|
92
|
+
});
|
|
93
|
+
// user.posts is Post[] -- resolved in the same query
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### create
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const newUser = await db.users.create({
|
|
100
|
+
data: {
|
|
101
|
+
email: 'alice@example.com',
|
|
102
|
+
name: 'Alice',
|
|
103
|
+
orgId: 1,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
// Returns the full row with generated id, createdAt, etc.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### createMany (batch insert with UNNEST)
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const users = await db.users.createMany({
|
|
113
|
+
data: [
|
|
114
|
+
{ email: 'a@b.com', name: 'A', orgId: 1 },
|
|
115
|
+
{ email: 'b@b.com', name: 'B', orgId: 1 },
|
|
116
|
+
{ email: 'c@b.com', name: 'C', orgId: 1 },
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
// Single INSERT with UNNEST -- not 3 separate inserts
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### update / delete
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const updated = await db.users.update({
|
|
126
|
+
where: { id: 42 },
|
|
127
|
+
data: { name: 'Alice Updated' },
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const deleted = await db.users.delete({
|
|
131
|
+
where: { id: 42 },
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Transactions
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
await db.$transaction(async (tx) => {
|
|
139
|
+
const user = await tx.users.create({
|
|
140
|
+
data: { email: 'new@example.com', name: 'New', orgId: 1 },
|
|
141
|
+
});
|
|
142
|
+
await tx.posts.create({
|
|
143
|
+
data: { userId: user.id, orgId: 1, title: 'First Post', content: '...' },
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
// Fully typed -- tx.users and tx.posts have the same API as db.users and db.posts
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Pipeline (batch queries in one round-trip)
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const [user, postCount, recentPosts] = await db.pipeline(
|
|
153
|
+
db.users.buildFindUnique({ where: { id: 1 } }),
|
|
154
|
+
db.posts.buildCount({ where: { orgId: 1 } }),
|
|
155
|
+
db.posts.buildFindMany({ where: { userId: 1 }, limit: 5 }),
|
|
156
|
+
);
|
|
157
|
+
// 3 queries, 1 database round-trip
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Raw SQL (tagged template)
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const stats = await db.raw<{ day: Date; count: number }>`
|
|
164
|
+
SELECT DATE_TRUNC('day', created_at) AS day, COUNT(*)::int AS count
|
|
165
|
+
FROM posts WHERE org_id = ${orgId}
|
|
166
|
+
GROUP BY day ORDER BY day
|
|
167
|
+
`;
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Middleware
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// Query timing
|
|
174
|
+
db.$use(async (params, next) => {
|
|
175
|
+
const start = Date.now();
|
|
176
|
+
const result = await next(params);
|
|
177
|
+
console.log(`${params.model}.${params.action} took ${Date.now() - start}ms`);
|
|
178
|
+
return result;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Soft-delete filter
|
|
182
|
+
db.$use(async (params, next) => {
|
|
183
|
+
if (params.action === 'findMany' || params.action === 'findUnique') {
|
|
184
|
+
params.args.where = { ...params.args.where, deletedAt: null };
|
|
185
|
+
}
|
|
186
|
+
return next(params);
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## CLI
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
npx turbine <command> [options]
|
|
194
|
+
|
|
195
|
+
Commands:
|
|
196
|
+
init Initialize a Turbine project (creates config, dirs, templates)
|
|
197
|
+
generate | pull Introspect database and generate TypeScript types + client
|
|
198
|
+
push Apply schema-builder definitions to database
|
|
199
|
+
migrate create <name> Create a new SQL migration file
|
|
200
|
+
migrate up Apply pending migrations
|
|
201
|
+
migrate down Rollback last migration
|
|
202
|
+
migrate status Show applied/pending migrations
|
|
203
|
+
seed Run seed file
|
|
204
|
+
status Show database schema summary
|
|
205
|
+
|
|
206
|
+
Options:
|
|
207
|
+
--url, -u <url> Postgres connection string
|
|
208
|
+
--out, -o <dir> Output directory (default: ./generated/turbine)
|
|
209
|
+
--schema, -s <name> Postgres schema (default: public)
|
|
210
|
+
--dry-run Show SQL without executing
|
|
211
|
+
--verbose, -v Detailed output
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Schema-first workflow
|
|
215
|
+
|
|
216
|
+
Define your schema in TypeScript and push it to the database:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// turbine/schema.ts
|
|
220
|
+
import { defineSchema } from '@batadata/turbine';
|
|
221
|
+
|
|
222
|
+
export default defineSchema({
|
|
223
|
+
users: {
|
|
224
|
+
id: { type: 'serial', primaryKey: true },
|
|
225
|
+
email: { type: 'text', unique: true, notNull: true },
|
|
226
|
+
name: { type: 'text', notNull: true },
|
|
227
|
+
orgId: { type: 'bigint', notNull: true, references: 'organizations.id' },
|
|
228
|
+
createdAt: { type: 'timestamp', default: 'now()' },
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
npx turbine push --dry-run # Preview SQL
|
|
235
|
+
npx turbine push # Apply to database
|
|
236
|
+
npx turbine generate # Regenerate typed client
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Migration workflow
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
npx turbine migrate create add_users_table
|
|
243
|
+
# Edit the generated .sql file with UP and DOWN sections
|
|
244
|
+
npx turbine migrate up
|
|
245
|
+
npx turbine migrate status
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Configuration
|
|
249
|
+
|
|
250
|
+
Create `turbine.config.ts` in your project root (or run `npx turbine init`):
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import type { TurbineCliConfig } from '@batadata/turbine/cli';
|
|
254
|
+
|
|
255
|
+
const config: TurbineCliConfig = {
|
|
256
|
+
url: process.env.DATABASE_URL,
|
|
257
|
+
out: './generated/turbine',
|
|
258
|
+
schema: 'public',
|
|
259
|
+
migrationsDir: './turbine/migrations',
|
|
260
|
+
seedFile: './turbine/seed.ts',
|
|
261
|
+
schemaFile: './turbine/schema.ts',
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export default config;
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Priority order: CLI flags > environment variables (`DATABASE_URL`) > config file > defaults.
|
|
268
|
+
|
|
269
|
+
## How It Works
|
|
270
|
+
|
|
271
|
+
Turbine generates a single SQL query using Postgres `json_agg` + subqueries to fetch nested relations:
|
|
272
|
+
|
|
273
|
+
```sql
|
|
274
|
+
-- db.users.findMany({ where: { orgId: 1 }, with: { posts: { with: { comments: true } } } })
|
|
275
|
+
SELECT u.*,
|
|
276
|
+
(SELECT COALESCE(json_agg(sub), '[]'::json) FROM (
|
|
277
|
+
SELECT p.*,
|
|
278
|
+
(SELECT COALESCE(json_agg(sub2), '[]'::json) FROM (
|
|
279
|
+
SELECT c.* FROM comments c WHERE c.post_id = p.id
|
|
280
|
+
) sub2) AS comments
|
|
281
|
+
FROM posts p WHERE p.user_id = u.id
|
|
282
|
+
) sub) AS posts
|
|
283
|
+
FROM users u WHERE u.org_id = 1
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
This resolves the entire 3-level object graph in one database round-trip. Prisma would send 3 queries. The performance difference scales with nesting depth and network latency.
|
|
287
|
+
|
|
288
|
+
## Requirements
|
|
289
|
+
|
|
290
|
+
- Node.js >= 22.0.0
|
|
291
|
+
- PostgreSQL >= 14
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
MIT
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @batadata/turbine CLI — Configuration file support
|
|
3
|
+
*
|
|
4
|
+
* Loads turbine.config.ts (or .js/.mjs) via dynamic import.
|
|
5
|
+
* Falls back to CLI args and environment variables.
|
|
6
|
+
*/
|
|
7
|
+
export interface TurbineCliConfig {
|
|
8
|
+
/** Postgres connection string */
|
|
9
|
+
url?: string;
|
|
10
|
+
/** Output directory for generated files (default: ./generated/turbine) */
|
|
11
|
+
out?: string;
|
|
12
|
+
/** Postgres schema to introspect (default: public) */
|
|
13
|
+
schema?: string;
|
|
14
|
+
/** Tables to include (empty = all) */
|
|
15
|
+
include?: string[];
|
|
16
|
+
/** Tables to exclude */
|
|
17
|
+
exclude?: string[];
|
|
18
|
+
/** Directory for migration files (default: ./turbine/migrations) */
|
|
19
|
+
migrationsDir?: string;
|
|
20
|
+
/** Path to seed file (default: ./turbine/seed.ts) */
|
|
21
|
+
seedFile?: string;
|
|
22
|
+
/** Schema builder file path (for push command) */
|
|
23
|
+
schemaFile?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Attempt to load a turbine config file from the current directory.
|
|
27
|
+
* Returns the config if found, or an empty object.
|
|
28
|
+
*/
|
|
29
|
+
export declare function loadConfig(cwd?: string): Promise<TurbineCliConfig>;
|
|
30
|
+
/**
|
|
31
|
+
* Find the config file path (for display purposes).
|
|
32
|
+
* Returns null if no config file is found.
|
|
33
|
+
*/
|
|
34
|
+
export declare function findConfigFile(cwd?: string): string | null;
|
|
35
|
+
export interface ResolvedConfig {
|
|
36
|
+
url: string;
|
|
37
|
+
out: string;
|
|
38
|
+
schema: string;
|
|
39
|
+
include: string[];
|
|
40
|
+
exclude: string[];
|
|
41
|
+
migrationsDir: string;
|
|
42
|
+
seedFile: string;
|
|
43
|
+
schemaFile: string;
|
|
44
|
+
}
|
|
45
|
+
export interface CliOverrides {
|
|
46
|
+
url?: string;
|
|
47
|
+
out?: string;
|
|
48
|
+
schema?: string;
|
|
49
|
+
include?: string[];
|
|
50
|
+
exclude?: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Merge config file values with CLI overrides and env vars.
|
|
54
|
+
* Priority: CLI flags > env vars > config file > defaults.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveConfig(fileConfig: TurbineCliConfig, overrides: CliOverrides): ResolvedConfig;
|
|
57
|
+
export declare function configTemplate(connectionString?: string): string;
|
|
58
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAiBD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA6BxE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO1D;AAMD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,gBAAgB,EAC5B,SAAS,EAAE,YAAY,GACtB,cAAc,CAehB;AAMD,wBAAgB,cAAc,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAqChE"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @batadata/turbine CLI — Configuration file support
|
|
3
|
+
*
|
|
4
|
+
* Loads turbine.config.ts (or .js/.mjs) via dynamic import.
|
|
5
|
+
* Falls back to CLI args and environment variables.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { resolve, join } from 'node:path';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Config file names, in priority order
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
const CONFIG_FILES = [
|
|
14
|
+
'turbine.config.ts',
|
|
15
|
+
'turbine.config.mts',
|
|
16
|
+
'turbine.config.js',
|
|
17
|
+
'turbine.config.mjs',
|
|
18
|
+
];
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Load config
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
/**
|
|
23
|
+
* Attempt to load a turbine config file from the current directory.
|
|
24
|
+
* Returns the config if found, or an empty object.
|
|
25
|
+
*/
|
|
26
|
+
export async function loadConfig(cwd) {
|
|
27
|
+
const dir = cwd ?? process.cwd();
|
|
28
|
+
for (const filename of CONFIG_FILES) {
|
|
29
|
+
const filePath = join(dir, filename);
|
|
30
|
+
if (!existsSync(filePath))
|
|
31
|
+
continue;
|
|
32
|
+
try {
|
|
33
|
+
const absPath = resolve(filePath);
|
|
34
|
+
const fileUrl = pathToFileURL(absPath).href;
|
|
35
|
+
// For .ts files, we need to rely on Node's --experimental-strip-types
|
|
36
|
+
// or the tsx loader. Dynamic import handles .js/.mjs natively.
|
|
37
|
+
const mod = await import(fileUrl);
|
|
38
|
+
const config = mod.default ?? mod;
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
// If importing a .ts file fails, try the next one
|
|
43
|
+
if (filename.endsWith('.ts') || filename.endsWith('.mts')) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Failed to load config from ${filename}: ${err instanceof Error ? err.message : String(err)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Find the config file path (for display purposes).
|
|
53
|
+
* Returns null if no config file is found.
|
|
54
|
+
*/
|
|
55
|
+
export function findConfigFile(cwd) {
|
|
56
|
+
const dir = cwd ?? process.cwd();
|
|
57
|
+
for (const filename of CONFIG_FILES) {
|
|
58
|
+
const filePath = join(dir, filename);
|
|
59
|
+
if (existsSync(filePath))
|
|
60
|
+
return filePath;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Merge config file values with CLI overrides and env vars.
|
|
66
|
+
* Priority: CLI flags > env vars > config file > defaults.
|
|
67
|
+
*/
|
|
68
|
+
export function resolveConfig(fileConfig, overrides) {
|
|
69
|
+
return {
|
|
70
|
+
url: overrides.url ??
|
|
71
|
+
process.env['DATABASE_URL'] ??
|
|
72
|
+
fileConfig.url ??
|
|
73
|
+
'',
|
|
74
|
+
out: overrides.out ?? fileConfig.out ?? './generated/turbine',
|
|
75
|
+
schema: overrides.schema ?? fileConfig.schema ?? 'public',
|
|
76
|
+
include: overrides.include ?? fileConfig.include ?? [],
|
|
77
|
+
exclude: overrides.exclude ?? fileConfig.exclude ?? [],
|
|
78
|
+
migrationsDir: fileConfig.migrationsDir ?? './turbine/migrations',
|
|
79
|
+
seedFile: fileConfig.seedFile ?? './turbine/seed.ts',
|
|
80
|
+
schemaFile: fileConfig.schemaFile ?? './turbine/schema.ts',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Config file template (for `turbine init`)
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
export function configTemplate(connectionString) {
|
|
87
|
+
const url = connectionString ?? 'process.env.DATABASE_URL';
|
|
88
|
+
const urlLine = connectionString
|
|
89
|
+
? ` url: '${connectionString}',`
|
|
90
|
+
: ` url: process.env.DATABASE_URL,`;
|
|
91
|
+
return `import type { TurbineCliConfig } from '@batadata/turbine/cli';
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Turbine configuration
|
|
95
|
+
* @see https://batadata.com/docs/turbine/config
|
|
96
|
+
*/
|
|
97
|
+
const config: TurbineCliConfig = {
|
|
98
|
+
/** Postgres connection string */
|
|
99
|
+
${urlLine}
|
|
100
|
+
|
|
101
|
+
/** Output directory for generated types + client */
|
|
102
|
+
out: './generated/turbine',
|
|
103
|
+
|
|
104
|
+
/** Postgres schema to introspect (default: public) */
|
|
105
|
+
schema: 'public',
|
|
106
|
+
|
|
107
|
+
/** Tables to exclude from generation */
|
|
108
|
+
// exclude: ['_migrations', '_sessions'],
|
|
109
|
+
|
|
110
|
+
/** Directory for SQL migration files */
|
|
111
|
+
migrationsDir: './turbine/migrations',
|
|
112
|
+
|
|
113
|
+
/** Path to seed file */
|
|
114
|
+
seedFile: './turbine/seed.ts',
|
|
115
|
+
|
|
116
|
+
/** Path to schema builder file (for turbine push) */
|
|
117
|
+
schemaFile: './turbine/schema.ts',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export default config;
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAyBzC,8EAA8E;AAC9E,uCAAuC;AACvC,8EAA8E;AAE9E,MAAM,YAAY,GAAG;IACnB,mBAAmB;IACnB,oBAAoB;IACpB,mBAAmB;IACnB,oBAAoB;CACZ,CAAC;AAEX,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAY;IAC3C,MAAM,GAAG,GAAG,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAEjC,KAAK,MAAM,QAAQ,IAAI,YAAY,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACrC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,SAAS;QAEpC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;YAClC,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;YAE5C,sEAAsE;YACtE,+DAA+D;YAC/D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;YAClC,MAAM,MAAM,GAAqB,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;YAEpD,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,kDAAkD;YAClD,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1D,SAAS;YACX,CAAC;YACD,MAAM,IAAI,KAAK,CACb,8BAA8B,QAAQ,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC9F,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,MAAM,GAAG,GAAG,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACjC,KAAK,MAAM,QAAQ,IAAI,YAAY,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACrC,IAAI,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAQ,CAAC;IAC5C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAyBD;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,UAA4B,EAC5B,SAAuB;IAEvB,OAAO;QACL,GAAG,EACD,SAAS,CAAC,GAAG;YACb,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;YAC3B,UAAU,CAAC,GAAG;YACd,EAAE;QACJ,GAAG,EAAE,SAAS,CAAC,GAAG,IAAI,UAAU,CAAC,GAAG,IAAI,qBAAqB;QAC7D,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,UAAU,CAAC,MAAM,IAAI,QAAQ;QACzD,OAAO,EAAE,SAAS,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,IAAI,EAAE;QACtD,OAAO,EAAE,SAAS,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,IAAI,EAAE;QACtD,aAAa,EAAE,UAAU,CAAC,aAAa,IAAI,sBAAsB;QACjE,QAAQ,EAAE,UAAU,CAAC,QAAQ,IAAI,mBAAmB;QACpD,UAAU,EAAE,UAAU,CAAC,UAAU,IAAI,qBAAqB;KAC3D,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,4CAA4C;AAC5C,8EAA8E;AAE9E,MAAM,UAAU,cAAc,CAAC,gBAAyB;IACtD,MAAM,GAAG,GAAG,gBAAgB,IAAI,0BAA0B,CAAC;IAC3D,MAAM,OAAO,GAAG,gBAAgB;QAC9B,CAAC,CAAC,WAAW,gBAAgB,IAAI;QACjC,CAAC,CAAC,kCAAkC,CAAC;IAEvC,OAAO;;;;;;;;EAQP,OAAO;;;;;;;;;;;;;;;;;;;;;;CAsBR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @batadata/turbine CLI
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* turbine init — Initialize a Turbine project
|
|
7
|
+
* turbine generate | pull — Introspect database and generate TypeScript types
|
|
8
|
+
* turbine push — Apply schema-builder definitions to database
|
|
9
|
+
* turbine migrate create <name> — Create a new SQL migration file
|
|
10
|
+
* turbine migrate up — Apply pending migrations
|
|
11
|
+
* turbine migrate down — Rollback last migration
|
|
12
|
+
* turbine migrate status — Show migration status
|
|
13
|
+
* turbine seed — Run seed file
|
|
14
|
+
* turbine status — Show schema summary
|
|
15
|
+
* turbine studio — Launch web UI (coming soon)
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* DATABASE_URL=postgres://... npx turbine generate
|
|
19
|
+
* npx turbine init --url postgres://...
|
|
20
|
+
* npx turbine migrate create add_users_table
|
|
21
|
+
*/
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;GAmBG"}
|