neoorm 0.1.2 → 0.1.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 +259 -11
- package/dist/bin/neoorm.js +37 -7
- package/dist/bin/neoorm.js.map +1 -1
- package/dist/codegen/diff-manifest.d.ts +11 -0
- package/dist/codegen/diff-manifest.d.ts.map +1 -0
- package/dist/codegen/diff-manifest.js +504 -0
- package/dist/codegen/diff-manifest.js.map +1 -0
- package/dist/codegen/generate.d.ts +16 -6
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/codegen/generate.js +38 -34
- package/dist/codegen/generate.js.map +1 -1
- package/dist/codegen/schema-to-manifest.d.ts.map +1 -1
- package/dist/codegen/schema-to-manifest.js +17 -8
- package/dist/codegen/schema-to-manifest.js.map +1 -1
- package/dist/dialect/index.d.ts +1 -1
- package/dist/dialect/index.d.ts.map +1 -1
- package/dist/dialect/index.js.map +1 -1
- package/dist/dialect/postgres.d.ts +8 -1
- package/dist/dialect/postgres.d.ts.map +1 -1
- package/dist/dialect/postgres.js +244 -28
- package/dist/dialect/postgres.js.map +1 -1
- package/dist/dialect/types.d.ts +61 -4
- package/dist/dialect/types.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/introspect/pull.d.ts.map +1 -1
- package/dist/introspect/pull.js +48 -34
- package/dist/introspect/pull.js.map +1 -1
- package/dist/introspect/queries.d.ts +36 -0
- package/dist/introspect/queries.d.ts.map +1 -0
- package/dist/introspect/queries.js +102 -0
- package/dist/introspect/queries.js.map +1 -0
- package/dist/introspect/to-manifest.d.ts +4 -0
- package/dist/introspect/to-manifest.d.ts.map +1 -0
- package/dist/introspect/to-manifest.js +188 -0
- package/dist/introspect/to-manifest.js.map +1 -0
- package/dist/migrate/runner.d.ts +12 -1
- package/dist/migrate/runner.d.ts.map +1 -1
- package/dist/migrate/runner.js +43 -13
- package/dist/migrate/runner.js.map +1 -1
- package/dist/plugins/builtin.d.ts +4 -0
- package/dist/plugins/builtin.d.ts.map +1 -1
- package/dist/plugins/builtin.js +27 -1
- package/dist/plugins/builtin.js.map +1 -1
- package/dist/runtime/client.d.ts +1 -1
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +26 -2
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/executor.d.ts +4 -1
- package/dist/runtime/executor.d.ts.map +1 -1
- package/dist/runtime/executor.js +20 -3
- package/dist/runtime/executor.js.map +1 -1
- package/dist/runtime/query/compile.d.ts +5 -3
- package/dist/runtime/query/compile.d.ts.map +1 -1
- package/dist/runtime/query/compile.js +244 -46
- package/dist/runtime/query/compile.js.map +1 -1
- package/dist/runtime/query/count.d.ts.map +1 -1
- package/dist/runtime/query/count.js +1 -1
- package/dist/runtime/query/count.js.map +1 -1
- package/dist/runtime/query/create.d.ts.map +1 -1
- package/dist/runtime/query/create.js +61 -72
- package/dist/runtime/query/create.js.map +1 -1
- package/dist/runtime/query/delete.d.ts.map +1 -1
- package/dist/runtime/query/delete.js +2 -2
- package/dist/runtime/query/delete.js.map +1 -1
- package/dist/runtime/query/find.d.ts.map +1 -1
- package/dist/runtime/query/find.js +1 -1
- package/dist/runtime/query/find.js.map +1 -1
- package/dist/runtime/query/primary-key.d.ts +4 -0
- package/dist/runtime/query/primary-key.d.ts.map +1 -0
- package/dist/runtime/query/primary-key.js +23 -0
- package/dist/runtime/query/primary-key.js.map +1 -0
- package/dist/runtime/query/update.d.ts.map +1 -1
- package/dist/runtime/query/update.js +4 -4
- package/dist/runtime/query/update.js.map +1 -1
- package/dist/runtime/query/upsert.d.ts.map +1 -1
- package/dist/runtime/query/upsert.js +2 -12
- package/dist/runtime/query/upsert.js.map +1 -1
- package/dist/runtime/types.d.ts +10 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/schema/column-where.d.ts +36 -0
- package/dist/schema/column-where.d.ts.map +1 -0
- package/dist/schema/column-where.js +2 -0
- package/dist/schema/column-where.js.map +1 -0
- package/dist/schema/column.d.ts +6 -2
- package/dist/schema/column.d.ts.map +1 -1
- package/dist/schema/column.js +5 -2
- package/dist/schema/column.js.map +1 -1
- package/dist/schema/index.d.ts +3 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/relation-types.d.ts +65 -16
- package/dist/schema/relation-types.d.ts.map +1 -1
- package/dist/schema/relation.d.ts +13 -12
- package/dist/schema/relation.d.ts.map +1 -1
- package/dist/schema/relation.js +16 -11
- package/dist/schema/relation.js.map +1 -1
- package/dist/schema/types.d.ts +12 -38
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/utils/uuid.d.ts +6 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/dist/utils/uuid.js +34 -0
- package/dist/utils/uuid.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,24 +8,24 @@ TypeScript-first PostgreSQL ORM with a schema DSL, compile-time types, codegen,
|
|
|
8
8
|
npm install neoorm pg
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Requires Node.js 20+ and PostgreSQL.
|
|
12
12
|
|
|
13
13
|
## Quick start
|
|
14
14
|
|
|
15
15
|
**1. Define a schema** (`schema.ts`):
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
import { defineSchema, table,
|
|
18
|
+
import { defineSchema, table, uuid, text, timestamp, fk } from "neoorm/schema";
|
|
19
19
|
|
|
20
20
|
export const schema = defineSchema({
|
|
21
21
|
users: table("users", {
|
|
22
|
-
id:
|
|
22
|
+
id: uuid().primary(),
|
|
23
23
|
email: text().notNull().unique(),
|
|
24
24
|
createdAt: timestamp().notNull().defaultNow(),
|
|
25
25
|
}),
|
|
26
26
|
|
|
27
27
|
posts: table("posts", {
|
|
28
|
-
id:
|
|
28
|
+
id: uuid().primary(),
|
|
29
29
|
authorId: fk("users.id", { as: "author", inverse: "posts", nullable: false }),
|
|
30
30
|
title: text().notNull(),
|
|
31
31
|
}),
|
|
@@ -53,33 +53,281 @@ export default defineConfig({
|
|
|
53
53
|
npx neoorm generate
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
This writes `client.ts`, `manifest.ts`, `models.ts`, `includes.ts`, and migration SQL when the schema changed.
|
|
57
|
+
|
|
56
58
|
**4. Query**
|
|
57
59
|
|
|
58
60
|
```ts
|
|
59
61
|
import { db } from "./neoorm/client.js";
|
|
60
62
|
|
|
61
|
-
const user = await db.users.findById(
|
|
63
|
+
const user = await db.users.findById(userId, {
|
|
62
64
|
with: {
|
|
63
65
|
posts: { orderBy: { createdAt: "desc" }, limit: 10 },
|
|
64
66
|
},
|
|
65
67
|
});
|
|
66
68
|
```
|
|
67
69
|
|
|
70
|
+
## Schema DSL
|
|
71
|
+
|
|
72
|
+
### Tables and accessors
|
|
73
|
+
|
|
74
|
+
`defineSchema` keys are TypeScript accessors (`users`, `posts`). The first argument to `table()` is the SQL table name:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
users: table("user", { ... }) // db.users → SQL table "user"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Column field names use camelCase in TypeScript. By default, SQL column names are snake_case (`createdAt` → `created_at`).
|
|
81
|
+
|
|
82
|
+
### Column types
|
|
83
|
+
|
|
84
|
+
| Builder | SQL type | Notes |
|
|
85
|
+
|---------|----------|-------|
|
|
86
|
+
| `id.primary()` | `TEXT` | App-generated string IDs (e.g. `user_a1b2c3d4`) |
|
|
87
|
+
| `uuid()` | `UUID` | Defaults to UUID v7; pass `{ version: 4 }` for v4 |
|
|
88
|
+
| `uuid().primary()` | `UUID` | Primary key; auto-generated on create if omitted |
|
|
89
|
+
| `text()` | `TEXT` | |
|
|
90
|
+
| `bool()` | `BOOLEAN` | |
|
|
91
|
+
| `int()` | `INTEGER` | |
|
|
92
|
+
| `timestamp()` | `TIMESTAMPTZ` | Use `.defaultNow()` for `DEFAULT NOW()` |
|
|
93
|
+
|
|
94
|
+
All column builders support `.notNull()`, `.unique()`, `.default(value)`, `.defaultNow()`, `.primary()`, and `.map(name)`.
|
|
95
|
+
|
|
96
|
+
Foreign keys use `fk("target_table.target_column", { as, inverse, nullable?, onDelete? })`.
|
|
97
|
+
|
|
98
|
+
### UUID columns
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { uuid, table } from "neoorm/schema";
|
|
102
|
+
|
|
103
|
+
posts: table("posts", {
|
|
104
|
+
id: uuid().primary(), // UUID v7 (default)
|
|
105
|
+
legacyId: uuid({ version: 4 }), // UUID v4
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
When you call `create` without an `id`, NeoOrm generates a UUID using the configured version.
|
|
110
|
+
|
|
111
|
+
### Custom SQL column names (`.map()`)
|
|
112
|
+
|
|
113
|
+
Use `.map()` when the database column name differs from the default snake_case conversion:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
emailAddress: text().notNull().map("email"),
|
|
117
|
+
authorId: fk("users.id", { as: "author", inverse: "posts" }).map("author_ref"),
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`.map()` only produces a migration when the SQL name actually changes. Mapping to the default snake_case name (e.g. `.map("email_verified")` on `emailVerified`) is a no-op — `neoorm generate` warns about this.
|
|
121
|
+
|
|
122
|
+
### Indexes and composite keys
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
posts: table(
|
|
126
|
+
"posts",
|
|
127
|
+
{ /* columns */ },
|
|
128
|
+
(t) => ({
|
|
129
|
+
authorIdx: index().on(t.authorId),
|
|
130
|
+
slugUnique: unique(t.slug),
|
|
131
|
+
pk: primaryKey(t.orgId, t.localId),
|
|
132
|
+
}),
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Many-to-many
|
|
137
|
+
|
|
138
|
+
Define the junction table, then register the relation after `defineSchema`:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
export const schema = defineSchema({
|
|
142
|
+
posts: table("posts", { /* ... */ }),
|
|
143
|
+
tags: table("tags", { /* ... */ }),
|
|
144
|
+
postTags: table("post_tags", {
|
|
145
|
+
postId: fk("posts.id", { as: "post", inverse: "postTags", nullable: false }),
|
|
146
|
+
tagId: fk("tags.id", { as: "tag", inverse: "postTags", nullable: false }),
|
|
147
|
+
}, (t) => ({
|
|
148
|
+
pk: primaryKey(t.postId, t.tagId),
|
|
149
|
+
})),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
manyToMany(schema.posts, schema.tags, {
|
|
153
|
+
through: schema.postTags,
|
|
154
|
+
left: "post",
|
|
155
|
+
right: "tag",
|
|
156
|
+
as: "tags",
|
|
157
|
+
inverse: "posts",
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Where clauses
|
|
162
|
+
|
|
163
|
+
`findMany`, `findFirst`, `findUnique`, `count`, `update`, `updateMany`, `delete`, and `deleteMany` all accept a typed `where` argument.
|
|
164
|
+
|
|
165
|
+
### Column filters
|
|
166
|
+
|
|
167
|
+
Equality shorthand and explicit operators:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
// equality
|
|
171
|
+
await db.posts.findMany({ where: { published: true } });
|
|
172
|
+
|
|
173
|
+
// operators
|
|
174
|
+
await db.posts.findFirst({
|
|
175
|
+
where: {
|
|
176
|
+
title: { contains: "ORM" },
|
|
177
|
+
views: { gte: 100 },
|
|
178
|
+
id: { in: ["post_1", "post_2"] },
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
String columns support `equals`, `contains`, `startsWith`, `endsWith`, `in`, and `notIn`. Numeric, boolean, and date columns support `equals`, `gt`, `gte`, `lt`, `lte`, `in`, and `notIn`. All nullable columns also support `isNull` and `isNotNull`.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
await db.users.findMany({
|
|
187
|
+
where: {
|
|
188
|
+
name: null, // shorthand for IS NULL
|
|
189
|
+
email: { isNotNull: true },
|
|
190
|
+
id: { notIn: ["user_1", "user_2"] },
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Logical combinators
|
|
196
|
+
|
|
197
|
+
Top-level `AND`, `OR`, and `NOT` keys combine conditions. Multiple sibling keys still imply `AND`:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
await db.users.findMany({
|
|
201
|
+
where: {
|
|
202
|
+
OR: [
|
|
203
|
+
{ email: { contains: "@example.com" } },
|
|
204
|
+
{ email: { contains: "@test.com" } },
|
|
205
|
+
],
|
|
206
|
+
NOT: { name: { isNull: true } },
|
|
207
|
+
createdAt: { gte: new Date("2025-01-01") },
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Relation filters
|
|
213
|
+
|
|
214
|
+
Relation names come from `fk(..., { as: "author", inverse: "posts" })` and `manyToMany(..., { as: "tags" })` — they are not the FK column names on the table.
|
|
215
|
+
|
|
216
|
+
| Pattern | Use for |
|
|
217
|
+
|---------|---------|
|
|
218
|
+
| `authorId: "user_1"` | Filter by FK column value |
|
|
219
|
+
| `author: { email: "a@b.c" }` | Filter by related record (to-one) |
|
|
220
|
+
| `posts: { some: { ... } }` | At least one related record matches (to-many) |
|
|
221
|
+
| `posts: { every: { ... } }` | All related records match (to-many) |
|
|
222
|
+
| `posts: { none: { ... } }` | No related records match (to-many) |
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// users who have at least one published post
|
|
226
|
+
await db.users.findMany({
|
|
227
|
+
where: {
|
|
228
|
+
posts: { some: { published: true } },
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// posts whose author has a verified email
|
|
233
|
+
await db.posts.findMany({
|
|
234
|
+
where: {
|
|
235
|
+
author: { email: { contains: "@" } },
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// posts tagged "orm" (many-to-many via junction table)
|
|
240
|
+
await db.posts.findMany({
|
|
241
|
+
where: {
|
|
242
|
+
tags: { some: { slug: "orm" } },
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Relation filters compile to SQL `EXISTS` subqueries, so they work with `findMany`, `count`, `updateMany`, and `deleteMany` without duplicate rows.
|
|
248
|
+
|
|
249
|
+
## Transactions
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
// Interactive callback
|
|
253
|
+
await db.$transaction(async (tx) => {
|
|
254
|
+
const user = await tx.users.create({ data: { email: "a@b.com" } });
|
|
255
|
+
await tx.posts.create({
|
|
256
|
+
data: {
|
|
257
|
+
title: "Hello",
|
|
258
|
+
authorId: user.id,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Batch steps (sequential, one transaction)
|
|
264
|
+
const [user, post] = await db.$transaction([
|
|
265
|
+
(tx) => tx.users.create({ data: { email: "a@b.com" } }),
|
|
266
|
+
(tx) => tx.posts.create({ data: { title: "Hello" } }),
|
|
267
|
+
]);
|
|
268
|
+
|
|
269
|
+
// Options
|
|
270
|
+
await db.$transaction(fn, {
|
|
271
|
+
isolationLevel: "Serializable", // ReadUncommitted | ReadCommitted | RepeatableRead | Serializable
|
|
272
|
+
readOnly: true,
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Nested `create` calls inside a transaction do not start a separate transaction.
|
|
277
|
+
|
|
68
278
|
## CLI
|
|
69
279
|
|
|
70
280
|
| Command | Description |
|
|
71
281
|
|---------|-------------|
|
|
72
|
-
| `neoorm generate` | Emit manifest, typed client, includes, and migrations |
|
|
73
|
-
| `neoorm migrate dev` | Apply pending migrations
|
|
282
|
+
| `neoorm generate` | Emit manifest, typed client, models, includes, and migrations |
|
|
283
|
+
| `neoorm migrate dev` | Apply pending migrations, then generate a new one if the schema changed |
|
|
74
284
|
| `neoorm migrate deploy` | Apply pending migrations |
|
|
75
|
-
| `neoorm db push` | Push schema to the database |
|
|
285
|
+
| `neoorm db push` | Push the current snapshot schema to the database |
|
|
76
286
|
| `neoorm db pull` | Introspect the database into a schema file |
|
|
77
287
|
|
|
288
|
+
`generate` always rewrites generated TypeScript files. Migration SQL is created only when the schema diff produces changes (new tables/columns, column renames via `.map()`).
|
|
289
|
+
|
|
290
|
+
## Plugins
|
|
291
|
+
|
|
292
|
+
### PostGIS
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import "neoorm/plugins/postgis";
|
|
296
|
+
import { geometry, point } from "neoorm/plugins/postgis";
|
|
297
|
+
|
|
298
|
+
places: table("places", {
|
|
299
|
+
id: uuid().primary(),
|
|
300
|
+
location: geometry({ subtype: "Point", srid: 4326 }).notNull(),
|
|
301
|
+
boundary: point({ srid: 4326 }),
|
|
302
|
+
})
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Spatial `where` operators: `intersects`, `within`, `dWithin`.
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
await db.places.findMany({
|
|
309
|
+
where: {
|
|
310
|
+
location: {
|
|
311
|
+
dWithin: {
|
|
312
|
+
geometry: { type: "Point", coordinates: [-122.4, 37.8] },
|
|
313
|
+
distance: 1000,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
PostGIS columns are stored as geometry/geography in PostgreSQL and exposed as GeoJSON in TypeScript.
|
|
321
|
+
|
|
78
322
|
## API surface
|
|
79
323
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
324
|
+
| Import | Purpose |
|
|
325
|
+
|--------|---------|
|
|
326
|
+
| `neoorm` | Config helpers, `createNeoOrmClient`, client types |
|
|
327
|
+
| `neoorm/schema` | Schema DSL (`defineSchema`, `table`, column builders, `fk`, `manyToMany`) |
|
|
328
|
+
| `neoorm/sql` | Tagged SQL templates and query builder |
|
|
329
|
+
| `neoorm/plugins` | Plugin registry |
|
|
330
|
+
| `neoorm/plugins/postgis` | PostGIS column types and spatial operators |
|
|
83
331
|
|
|
84
332
|
## License
|
|
85
333
|
|
package/dist/bin/neoorm.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join, resolve } from "node:path";
|
|
|
4
4
|
import { Pool } from "pg";
|
|
5
5
|
import { loadConfig } from "../config.js";
|
|
6
6
|
import { generateFromSchema } from "../codegen/generate.js";
|
|
7
|
-
import { migrateDeploy, dbPush } from "../migrate/runner.js";
|
|
7
|
+
import { migrateDeploy, dbPush, dbPushWarnings } from "../migrate/runner.js";
|
|
8
8
|
import { introspectPostgres } from "../introspect/pull.js";
|
|
9
9
|
import { writeFile } from "node:fs/promises";
|
|
10
10
|
const program = new Command();
|
|
@@ -15,26 +15,39 @@ program
|
|
|
15
15
|
program
|
|
16
16
|
.command("generate")
|
|
17
17
|
.description("Generate manifest, client, and migrations from schema")
|
|
18
|
-
.
|
|
18
|
+
.option("--accept-data-loss", "Include destructive schema changes in generated migrations")
|
|
19
|
+
.action(async (options) => {
|
|
19
20
|
const cwd = process.cwd();
|
|
20
21
|
const config = await loadConfig(cwd);
|
|
21
22
|
const schemaPath = resolve(cwd, config.schema);
|
|
22
23
|
const outDir = resolve(cwd, config.out);
|
|
23
|
-
const { migrationName } = await generateFromSchema(schemaPath, outDir
|
|
24
|
+
const { migrationName, schemaChanged, warnings, destructiveBlocked } = await generateFromSchema(schemaPath, outDir, {
|
|
25
|
+
...(options.acceptDataLoss ? { acceptDataLoss: true } : {}),
|
|
26
|
+
});
|
|
24
27
|
console.log(`Generated client at ${outDir}/client.ts`);
|
|
25
28
|
console.log(`Generated manifest at ${outDir}/manifest.ts`);
|
|
26
29
|
if (migrationName) {
|
|
27
30
|
console.log(`Created migration: ${migrationName}`);
|
|
28
31
|
}
|
|
32
|
+
else if (destructiveBlocked) {
|
|
33
|
+
console.log("Destructive schema changes detected — no migration written");
|
|
34
|
+
}
|
|
35
|
+
else if (schemaChanged) {
|
|
36
|
+
console.log("Manifest updated (no migration SQL needed)");
|
|
37
|
+
}
|
|
29
38
|
else {
|
|
30
39
|
console.log("No schema changes detected");
|
|
31
40
|
}
|
|
41
|
+
for (const warning of warnings) {
|
|
42
|
+
console.warn(`Warning: ${warning}`);
|
|
43
|
+
}
|
|
32
44
|
});
|
|
33
45
|
program
|
|
34
46
|
.command("migrate")
|
|
35
47
|
.description("Run migrations")
|
|
36
48
|
.argument("[subcommand]", "dev | deploy")
|
|
37
|
-
.
|
|
49
|
+
.option("--accept-data-loss", "Include destructive schema changes in generated migrations")
|
|
50
|
+
.action(async (subcommand, options) => {
|
|
38
51
|
const cwd = process.cwd();
|
|
39
52
|
const config = await loadConfig(cwd);
|
|
40
53
|
const outDir = resolve(cwd, config.out);
|
|
@@ -55,11 +68,19 @@ program
|
|
|
55
68
|
if (subcommand === "dev") {
|
|
56
69
|
const { generateFromSchema } = await import("../codegen/generate.js");
|
|
57
70
|
const schemaPath = resolve(cwd, config.schema);
|
|
58
|
-
const { migrationName } = await generateFromSchema(schemaPath, outDir
|
|
71
|
+
const { migrationName, warnings, destructiveBlocked } = await generateFromSchema(schemaPath, outDir, {
|
|
72
|
+
...(options.acceptDataLoss ? { acceptDataLoss: true } : {}),
|
|
73
|
+
});
|
|
74
|
+
for (const warning of warnings) {
|
|
75
|
+
console.warn(`Warning: ${warning}`);
|
|
76
|
+
}
|
|
59
77
|
if (migrationName) {
|
|
60
78
|
await migrateDeploy(pool, join(outDir, "migrations"));
|
|
61
79
|
console.log(`Created and applied: ${migrationName}`);
|
|
62
80
|
}
|
|
81
|
+
else if (destructiveBlocked) {
|
|
82
|
+
console.log("Destructive schema changes detected — no migration created");
|
|
83
|
+
}
|
|
63
84
|
}
|
|
64
85
|
}
|
|
65
86
|
else {
|
|
@@ -76,6 +97,7 @@ program
|
|
|
76
97
|
.description("Database utilities")
|
|
77
98
|
.argument("<subcommand>", "push | pull")
|
|
78
99
|
.option("-o, --output <file>", "Output file for pull", "schema.pulled.ts")
|
|
100
|
+
.option("--accept-data-loss", "Apply destructive schema changes when pushing to the database")
|
|
79
101
|
.action(async (subcommand, options) => {
|
|
80
102
|
const cwd = process.cwd();
|
|
81
103
|
const config = await loadConfig(cwd);
|
|
@@ -89,8 +111,16 @@ program
|
|
|
89
111
|
console.error("Run neoorm generate first");
|
|
90
112
|
process.exit(1);
|
|
91
113
|
}
|
|
92
|
-
await dbPush(pool, manifest);
|
|
93
|
-
|
|
114
|
+
const { appliedStatements, destructiveBlocked } = await dbPush(pool, manifest, { ...(options.acceptDataLoss ? { acceptDataLoss: true } : {}) });
|
|
115
|
+
for (const warning of dbPushWarnings(destructiveBlocked)) {
|
|
116
|
+
console.warn(`Warning: ${warning}`);
|
|
117
|
+
}
|
|
118
|
+
if (appliedStatements === 0 && destructiveBlocked.length === 0) {
|
|
119
|
+
console.log("Database schema is up to date");
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
console.log(`Database schema pushed (${appliedStatements} statement(s) applied)`);
|
|
123
|
+
}
|
|
94
124
|
}
|
|
95
125
|
else if (subcommand === "pull") {
|
|
96
126
|
const content = await introspectPostgres(pool);
|
package/dist/bin/neoorm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"neoorm.js","sourceRoot":"","sources":["../../src/bin/neoorm.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"neoorm.js","sourceRoot":"","sources":["../../src/bin/neoorm.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,YAAY,CAAC;KACzB,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,uDAAuD,CAAC;KACpE,MAAM,CACL,oBAAoB,EACpB,4DAA4D,CAC7D;KACA,MAAM,CAAC,KAAK,EAAE,OAAqC,EAAE,EAAE;IACtD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAExC,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,kBAAkB,EAAE,GAClE,MAAM,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE;QAC3C,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5D,CAAC,CAAC;IAEL,OAAO,CAAC,GAAG,CAAC,uBAAuB,MAAM,YAAY,CAAC,CAAC;IACvD,OAAO,CAAC,GAAG,CAAC,yBAAyB,MAAM,cAAc,CAAC,CAAC;IAC3D,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,sBAAsB,aAAa,EAAE,CAAC,CAAC;IACrD,CAAC;SAAM,IAAI,kBAAkB,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,4DAA4D,CAAC,CAAC;IAC5E,CAAC;SAAM,IAAI,aAAa,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;IAC5D,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAC5C,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,OAAO,CAAC,IAAI,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC;IACtC,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,gBAAgB,CAAC;KAC7B,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;KACxC,MAAM,CACL,oBAAoB,EACpB,4DAA4D,CAC7D;KACA,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,OAAqC,EAAE,EAAE;IAClE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAEjD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,EAAE,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC;IAEnE,IAAI,CAAC;QACH,IAAI,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;YACpD,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;YACzD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;YACvC,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,WAAW,OAAO,CAAC,MAAM,gBAAgB,CAAC,CAAC;gBACvD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;oBAC3B,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;gBAC7B,CAAC;YACH,CAAC;YAED,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;gBACzB,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;gBACtE,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC/C,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,kBAAkB,EAAE,GACnD,MAAM,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE;oBAC3C,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC5D,CAAC,CAAC;gBACL,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;oBAC/B,OAAO,CAAC,IAAI,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC;gBACtC,CAAC;gBACD,IAAI,aAAa,EAAE,CAAC;oBAClB,MAAM,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;oBACtD,OAAO,CAAC,GAAG,CAAC,wBAAwB,aAAa,EAAE,CAAC,CAAC;gBACvD,CAAC;qBAAM,IAAI,kBAAkB,EAAE,CAAC;oBAC9B,OAAO,CAAC,GAAG,CACT,4DAA4D,CAC7D,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;YACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;IACnB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,IAAI,CAAC;KACb,WAAW,CAAC,oBAAoB,CAAC;KACjC,QAAQ,CAAC,cAAc,EAAE,aAAa,CAAC;KACvC,MAAM,CAAC,qBAAqB,EAAE,sBAAsB,EAAE,kBAAkB,CAAC;KACzE,MAAM,CACL,oBAAoB,EACpB,+DAA+D,CAChE;KACA,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,OAAsD,EAAE,EAAE;IACnF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,EAAE,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC;IAEnE,IAAI,CAAC;QACH,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;YAChE,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;YAC5C,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;gBAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YACD,MAAM,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAC5D,IAAI,EACJ,QAAQ,EACR,EAAE,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAChE,CAAC;YACF,KAAK,MAAM,OAAO,IAAI,cAAc,CAAC,kBAAkB,CAAC,EAAE,CAAC;gBACzD,OAAO,CAAC,IAAI,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC;YACtC,CAAC;YACD,IAAI,iBAAiB,KAAK,CAAC,IAAI,kBAAkB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC/D,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;YAC/C,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CACT,2BAA2B,iBAAiB,wBAAwB,CACrE,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;YACjC,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,CAAC;YAC/C,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,IAAI,kBAAkB,CAAC,CAAC;YACtE,MAAM,SAAS,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,CAAC,GAAG,CAAC,qBAAqB,UAAU,EAAE,CAAC,CAAC;QACjD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;IACnB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DestructiveChange, Manifest, ManifestColumn, ManifestDiff, TableDiff } from "../dialect/types.js";
|
|
2
|
+
export declare function columnSqlType(col: ManifestColumn, manifest?: Manifest): string;
|
|
3
|
+
export declare function columnsEqual(a: ManifestColumn, b: ManifestColumn, manifest?: Manifest): boolean;
|
|
4
|
+
export declare function buildMigrationSql(tableDiffs: TableDiff[], extensions?: string[], manifest?: Manifest): string[];
|
|
5
|
+
export declare function diffManifest(prev: Manifest | null, next: Manifest): ManifestDiff;
|
|
6
|
+
export declare function resolveMigrationSql(diff: ManifestDiff, prev: Manifest | null, next: Manifest, acceptDataLoss: boolean): {
|
|
7
|
+
sql: string[];
|
|
8
|
+
blocked: DestructiveChange[];
|
|
9
|
+
};
|
|
10
|
+
export declare function formatDestructiveWarnings(destructive: DestructiveChange[]): string[];
|
|
11
|
+
//# sourceMappingURL=diff-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-manifest.d.ts","sourceRoot":"","sources":["../../src/codegen/diff-manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,iBAAiB,EAEjB,QAAQ,EACR,cAAc,EAGd,YAAY,EACZ,SAAS,EACV,MAAM,qBAAqB,CAAC;AAgB7B,wBAAgB,aAAa,CAC3B,GAAG,EAAE,cAAc,EACnB,QAAQ,CAAC,EAAE,QAAQ,GAClB,MAAM,CAER;AAuCD,wBAAgB,YAAY,CAC1B,CAAC,EAAE,cAAc,EACjB,CAAC,EAAE,cAAc,EACjB,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAYT;AAqbD,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,SAAS,EAAE,EACvB,UAAU,GAAE,MAAM,EAAO,EACzB,QAAQ,CAAC,EAAE,QAAQ,GAClB,MAAM,EAAE,CAqCV;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,GAAG,IAAI,EACrB,IAAI,EAAE,QAAQ,GACb,YAAY,CA6Cd;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,QAAQ,GAAG,IAAI,EACrB,IAAI,EAAE,QAAQ,EACd,cAAc,EAAE,OAAO,GACtB;IAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAAE,CAgDjD;AAED,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,iBAAiB,EAAE,GAC/B,MAAM,EAAE,CAuBV"}
|