neoorm 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +6 -4
  2. package/dist/bin/neoorm.js +28 -1
  3. package/dist/bin/neoorm.js.map +1 -1
  4. package/dist/codegen/generate.d.ts +7 -0
  5. package/dist/codegen/generate.d.ts.map +1 -1
  6. package/dist/codegen/generate.js +18 -2
  7. package/dist/codegen/generate.js.map +1 -1
  8. package/dist/codegen/schema-to-manifest.d.ts +15 -1
  9. package/dist/codegen/schema-to-manifest.d.ts.map +1 -1
  10. package/dist/codegen/schema-to-manifest.js +87 -25
  11. package/dist/codegen/schema-to-manifest.js.map +1 -1
  12. package/dist/config.d.ts +12 -0
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +6 -0
  15. package/dist/config.js.map +1 -1
  16. package/dist/docs/pages.d.ts +9 -0
  17. package/dist/docs/pages.d.ts.map +1 -0
  18. package/dist/docs/pages.js +64 -0
  19. package/dist/docs/pages.js.map +1 -0
  20. package/dist/docs/render.d.ts +4 -0
  21. package/dist/docs/render.d.ts.map +1 -0
  22. package/dist/docs/render.js +245 -0
  23. package/dist/docs/render.js.map +1 -0
  24. package/dist/docs/resolve-docs-dir.d.ts +2 -0
  25. package/dist/docs/resolve-docs-dir.d.ts.map +1 -0
  26. package/dist/docs/resolve-docs-dir.js +27 -0
  27. package/dist/docs/resolve-docs-dir.js.map +1 -0
  28. package/dist/docs/server.d.ts +11 -0
  29. package/dist/docs/server.d.ts.map +1 -0
  30. package/dist/docs/server.js +99 -0
  31. package/dist/docs/server.js.map +1 -0
  32. package/dist/index.d.ts +8 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +7 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/init/templates.js +2 -2
  37. package/dist/introspect/pull.d.ts.map +1 -1
  38. package/dist/introspect/pull.js +16 -2
  39. package/dist/introspect/pull.js.map +1 -1
  40. package/dist/migrate/runner.d.ts +4 -0
  41. package/dist/migrate/runner.d.ts.map +1 -1
  42. package/dist/migrate/runner.js +4 -0
  43. package/dist/migrate/runner.js.map +1 -1
  44. package/dist/plugins/builtin.d.ts +24 -0
  45. package/dist/plugins/builtin.d.ts.map +1 -1
  46. package/dist/plugins/builtin.js +21 -0
  47. package/dist/plugins/builtin.js.map +1 -1
  48. package/dist/plugins/index.d.ts +4 -0
  49. package/dist/plugins/index.d.ts.map +1 -1
  50. package/dist/plugins/index.js +4 -0
  51. package/dist/plugins/index.js.map +1 -1
  52. package/dist/plugins/postgis/columns.d.ts +7 -3
  53. package/dist/plugins/postgis/columns.d.ts.map +1 -1
  54. package/dist/plugins/postgis/columns.js +3 -0
  55. package/dist/plugins/postgis/columns.js.map +1 -1
  56. package/dist/plugins/registry.d.ts +1 -0
  57. package/dist/plugins/registry.d.ts.map +1 -1
  58. package/dist/plugins/registry.js +1 -0
  59. package/dist/plugins/registry.js.map +1 -1
  60. package/dist/plugins/types.d.ts +2 -0
  61. package/dist/plugins/types.d.ts.map +1 -1
  62. package/dist/runtime/client.d.ts +27 -1
  63. package/dist/runtime/client.d.ts.map +1 -1
  64. package/dist/runtime/client.js +15 -5
  65. package/dist/runtime/client.js.map +1 -1
  66. package/dist/runtime/error-builders.d.ts +14 -0
  67. package/dist/runtime/error-builders.d.ts.map +1 -0
  68. package/dist/runtime/error-builders.js +54 -0
  69. package/dist/runtime/error-builders.js.map +1 -0
  70. package/dist/runtime/error-hints.d.ts +19 -0
  71. package/dist/runtime/error-hints.d.ts.map +1 -0
  72. package/dist/runtime/error-hints.js +181 -0
  73. package/dist/runtime/error-hints.js.map +1 -0
  74. package/dist/runtime/errors.d.ts +12 -0
  75. package/dist/runtime/errors.d.ts.map +1 -1
  76. package/dist/runtime/errors.js +24 -2
  77. package/dist/runtime/errors.js.map +1 -1
  78. package/dist/runtime/pg-error.d.ts.map +1 -1
  79. package/dist/runtime/pg-error.js +86 -0
  80. package/dist/runtime/pg-error.js.map +1 -1
  81. package/dist/runtime/query/compile.d.ts.map +1 -1
  82. package/dist/runtime/query/compile.js +5 -12
  83. package/dist/runtime/query/compile.js.map +1 -1
  84. package/dist/runtime/query/cursor-codec.d.ts +11 -0
  85. package/dist/runtime/query/cursor-codec.d.ts.map +1 -1
  86. package/dist/runtime/query/cursor-codec.js +11 -0
  87. package/dist/runtime/query/cursor-codec.js.map +1 -1
  88. package/dist/runtime/query/cursor.d.ts.map +1 -1
  89. package/dist/runtime/query/cursor.js +2 -5
  90. package/dist/runtime/query/cursor.js.map +1 -1
  91. package/dist/runtime/query/execute.d.ts.map +1 -1
  92. package/dist/runtime/query/execute.js +28 -9
  93. package/dist/runtime/query/execute.js.map +1 -1
  94. package/dist/runtime/query/group-by.d.ts.map +1 -1
  95. package/dist/runtime/query/group-by.js +3 -7
  96. package/dist/runtime/query/group-by.js.map +1 -1
  97. package/dist/runtime/query/projection.d.ts.map +1 -1
  98. package/dist/runtime/query/projection.js +2 -4
  99. package/dist/runtime/query/projection.js.map +1 -1
  100. package/dist/runtime/query/relation-planner.d.ts.map +1 -1
  101. package/dist/runtime/query/relation-planner.js +6 -1
  102. package/dist/runtime/query/relation-planner.js.map +1 -1
  103. package/dist/runtime/query/strip.d.ts.map +1 -1
  104. package/dist/runtime/query/strip.js +2 -4
  105. package/dist/runtime/query/strip.js.map +1 -1
  106. package/dist/runtime/query/table-index.d.ts +4 -0
  107. package/dist/runtime/query/table-index.d.ts.map +1 -1
  108. package/dist/runtime/query/table-index.js +45 -0
  109. package/dist/runtime/query/table-index.js.map +1 -1
  110. package/dist/runtime/schema-error.d.ts +1 -1
  111. package/dist/runtime/schema-error.d.ts.map +1 -1
  112. package/dist/runtime/schema-error.js +7 -2
  113. package/dist/runtime/schema-error.js.map +1 -1
  114. package/dist/runtime/sqlite-error.d.ts +12 -0
  115. package/dist/runtime/sqlite-error.d.ts.map +1 -0
  116. package/dist/runtime/sqlite-error.js +169 -0
  117. package/dist/runtime/sqlite-error.js.map +1 -0
  118. package/dist/runtime/types.d.ts +8 -0
  119. package/dist/runtime/types.d.ts.map +1 -1
  120. package/dist/schema/column.d.ts +32 -5
  121. package/dist/schema/column.d.ts.map +1 -1
  122. package/dist/schema/column.js +5 -0
  123. package/dist/schema/column.js.map +1 -1
  124. package/dist/schema/define-schema.d.ts +19 -0
  125. package/dist/schema/define-schema.d.ts.map +1 -1
  126. package/dist/schema/define-schema.js +15 -0
  127. package/dist/schema/define-schema.js.map +1 -1
  128. package/dist/schema/index.d.ts +7 -1
  129. package/dist/schema/index.d.ts.map +1 -1
  130. package/dist/schema/index.js +1 -1
  131. package/dist/schema/index.js.map +1 -1
  132. package/dist/schema/many-to-many.d.ts +23 -1
  133. package/dist/schema/many-to-many.d.ts.map +1 -1
  134. package/dist/schema/many-to-many.js +16 -1
  135. package/dist/schema/many-to-many.js.map +1 -1
  136. package/dist/schema/nested-relation-types.d.ts +2 -1
  137. package/dist/schema/nested-relation-types.d.ts.map +1 -1
  138. package/dist/schema/relation-types.d.ts +14 -9
  139. package/dist/schema/relation-types.d.ts.map +1 -1
  140. package/dist/schema/relation.d.ts +34 -2
  141. package/dist/schema/relation.d.ts.map +1 -1
  142. package/dist/schema/relation.js.map +1 -1
  143. package/dist/schema/table.d.ts +20 -0
  144. package/dist/schema/table.d.ts.map +1 -1
  145. package/dist/schema/table.js +3 -0
  146. package/dist/schema/table.js.map +1 -1
  147. package/dist/schema/types.d.ts +7 -0
  148. package/dist/schema/types.d.ts.map +1 -1
  149. package/dist/sql/builder.d.ts +1 -0
  150. package/dist/sql/builder.d.ts.map +1 -1
  151. package/dist/sql/builder.js +1 -0
  152. package/dist/sql/builder.js.map +1 -1
  153. package/dist/sql/index.d.ts +5 -1
  154. package/dist/sql/index.d.ts.map +1 -1
  155. package/dist/sql/index.js +5 -1
  156. package/dist/sql/index.js.map +1 -1
  157. package/dist/sql/template.d.ts +3 -0
  158. package/dist/sql/template.d.ts.map +1 -1
  159. package/dist/sql/template.js +3 -0
  160. package/dist/sql/template.js.map +1 -1
  161. package/docs/api-docstrings.md +43 -0
  162. package/docs/cli.md +107 -0
  163. package/docs/configuration.md +51 -0
  164. package/docs/errors.md +72 -0
  165. package/docs/examples.md +498 -0
  166. package/docs/getting-started.md +122 -0
  167. package/docs/migrations.md +67 -0
  168. package/docs/plugins.md +47 -0
  169. package/docs/queries.md +465 -0
  170. package/docs/relations.md +75 -0
  171. package/docs/schema.md +265 -0
  172. package/docs/sqlite.md +105 -0
  173. package/docs/transactions.md +54 -0
  174. package/package.json +3 -1
@@ -0,0 +1,498 @@
1
+ # Examples
2
+
3
+ Copy-paste patterns for common NeoOrm tasks. The full runnable blog demo lives in [`examples/blog/`](https://github.com/xmreur/neoorm/tree/main/examples/blog) (`schema.ts` + `queries.example.ts`). PostGIS sample: [`examples/postgis/`](https://github.com/xmreur/neoorm/tree/main/examples/postgis).
4
+
5
+ Run `neoorm docs` to browse all documentation locally.
6
+
7
+ ## Blog schema
8
+
9
+ A small CMS-style schema used throughout these examples:
10
+
11
+ ```ts
12
+ import {
13
+ bool,
14
+ defineSchema,
15
+ enumType,
16
+ fk,
17
+ id,
18
+ int,
19
+ jsonb,
20
+ many,
21
+ table,
22
+ text,
23
+ timestamps,
24
+ uuid,
25
+ } from "neoorm/schema";
26
+
27
+ export const schema = defineSchema({
28
+ users: table({
29
+ id: uuid().primary(),
30
+ email: text().notNull().unique(),
31
+ name: text(),
32
+ password: text().notNull().hidden(),
33
+ ...timestamps(),
34
+ }),
35
+
36
+ profiles: table({
37
+ id: id(),
38
+ userId: fk("users").notNull().unique().onDelete("cascade"),
39
+ bio: text(),
40
+ }),
41
+
42
+ posts: table({
43
+ id: id(),
44
+ authorId: fk("users").notNull().index().onDelete("restrict"),
45
+ title: text().notNull(),
46
+ body: text().notNull(),
47
+ published: bool().notNull().default(false),
48
+ views: int().notNull().default(0),
49
+ status: enumType(["draft", "published", "archived"]).notNull().default("draft"),
50
+ metadata: jsonb<Record<string, unknown>>(),
51
+ ...timestamps(),
52
+ tags: many("tags"),
53
+ }),
54
+
55
+ comments: table({
56
+ id: id(),
57
+ postId: fk("posts").notNull().onDelete("cascade"),
58
+ authorId: fk("users").notNull(),
59
+ body: text().notNull(),
60
+ createdAt: timestamps().createdAt,
61
+ }),
62
+
63
+ tags: table({
64
+ id: id(),
65
+ slug: text().notNull().unique(),
66
+ name: text().notNull(),
67
+ }),
68
+ });
69
+ ```
70
+
71
+ Generate the client, then import it:
72
+
73
+ ```ts
74
+ import { db } from "./neoorm/client.js";
75
+ ```
76
+
77
+ ## CRUD basics
78
+
79
+ ```ts
80
+ // Create
81
+ const user = await db.users.create({
82
+ data: { email: "alice@example.com", name: "Alice" },
83
+ });
84
+
85
+ // Create and return full row
86
+ const post = await db.posts.create({
87
+ data: {
88
+ title: "Hello",
89
+ body: "World",
90
+ authorId: user.id,
91
+ },
92
+ returnCreated: true,
93
+ });
94
+
95
+ // Find by primary key
96
+ const found = await db.users.findById(user.id);
97
+
98
+ // Find first match
99
+ const draft = await db.posts.findFirst({
100
+ where: { published: false },
101
+ orderBy: { createdAt: "desc" },
102
+ });
103
+
104
+ // Update
105
+ await db.users.updateById(user.id, {
106
+ data: { name: "Alice Smith" },
107
+ });
108
+
109
+ // Update many
110
+ await db.posts.updateMany({
111
+ where: { published: false },
112
+ data: { views: 0 },
113
+ });
114
+
115
+ // Delete
116
+ await db.posts.delete({ where: { id: post.id } });
117
+ await db.users.deleteById(user.id);
118
+ ```
119
+
120
+ ## Nested reads (`with`)
121
+
122
+ ```ts
123
+ const user = await db.users.findById("user_1", {
124
+ with: {
125
+ profile: true,
126
+ posts: {
127
+ where: { published: true },
128
+ orderBy: { createdAt: "desc" },
129
+ take: 10,
130
+ with: {
131
+ comments: {
132
+ orderBy: { createdAt: "desc" },
133
+ take: 5,
134
+ with: { author: { select: { id: true, name: true } } },
135
+ },
136
+ tags: true,
137
+ },
138
+ },
139
+ },
140
+ });
141
+ ```
142
+
143
+ Select only specific columns:
144
+
145
+ ```ts
146
+ const users = await db.users.findMany({
147
+ select: { id: true, email: true },
148
+ with: {
149
+ posts: { select: { title: true, published: true } },
150
+ },
151
+ });
152
+ ```
153
+
154
+ Omit columns at query time (still fetched from DB):
155
+
156
+ ```ts
157
+ const user = await db.users.findById("user_1", {
158
+ omit: { createdAt: true, updatedAt: true },
159
+ });
160
+ ```
161
+
162
+ ## Where filters
163
+
164
+ ```ts
165
+ // Equality and operators
166
+ const posts = await db.posts.findMany({
167
+ where: {
168
+ published: true,
169
+ views: { gte: 100 },
170
+ title: { contains: "ORM" },
171
+ },
172
+ });
173
+
174
+ // Case-insensitive search
175
+ const hits = await db.posts.findMany({
176
+ where: { title: { contains: "orm", mode: "insensitive" } },
177
+ });
178
+
179
+ // Logical combinators
180
+ const filtered = await db.posts.findMany({
181
+ where: {
182
+ OR: [
183
+ { status: "published" },
184
+ { AND: [{ published: true }, { views: { gt: 50 } }] },
185
+ ],
186
+ },
187
+ });
188
+
189
+ // Filter through relations
190
+ const authors = await db.users.findMany({
191
+ where: {
192
+ posts: { some: { published: true, tags: { some: { slug: "typescript" } } } },
193
+ },
194
+ });
195
+
196
+ // JSONB containment
197
+ const featured = await db.posts.findMany({
198
+ where: {
199
+ metadata: {
200
+ jsonContains: { featured: true, category: "engineering" },
201
+ },
202
+ },
203
+ });
204
+
205
+ // Exists / count helpers
206
+ const taken = await db.users.exists({ where: { email: "alice@example.com" } });
207
+ const total = await db.posts.count({ where: { published: true } });
208
+ ```
209
+
210
+ ## Relation writes
211
+
212
+ Create with nested relations in one call:
213
+
214
+ ```ts
215
+ const post = await db.posts.create({
216
+ data: {
217
+ title: "NeoORM",
218
+ body: "Typed relations and JSONB.",
219
+ published: true,
220
+ status: "published",
221
+ author: { connect: { id: "user_1" } },
222
+ tags: {
223
+ connectOrCreate: [
224
+ {
225
+ where: { slug: "orm" },
226
+ create: { slug: "orm", name: "ORM" },
227
+ },
228
+ ],
229
+ },
230
+ },
231
+ with: { author: true, tags: true },
232
+ });
233
+ ```
234
+
235
+ Update relations on an existing row:
236
+
237
+ ```ts
238
+ await db.posts.update({
239
+ where: { id: post.id },
240
+ data: {
241
+ views: { increment: 1 },
242
+ comments: {
243
+ create: [
244
+ {
245
+ body: "Great post!",
246
+ author: { connect: { id: "user_1" } },
247
+ },
248
+ ],
249
+ },
250
+ tags: { set: [{ id: "tag_1" }, { id: "tag_2" }] },
251
+ },
252
+ });
253
+ ```
254
+
255
+ `connect`, `disconnect`, `set`, `create`, and `delete` work on to-one, one-to-many, and many-to-many relations. See [Relation writes](relations.md).
256
+
257
+ ## Pagination
258
+
259
+ Offset/limit:
260
+
261
+ ```ts
262
+ const page = await db.posts.findMany({
263
+ where: { published: true },
264
+ orderBy: { createdAt: "desc" },
265
+ take: 20,
266
+ skip: 40,
267
+ });
268
+ ```
269
+
270
+ Keyset (cursor) pagination for feeds:
271
+
272
+ ```ts
273
+ let cursor: { createdAt: Date; id: string } | null = null;
274
+
275
+ const page = await db.posts.paginate({
276
+ where: { published: true },
277
+ orderBy: { createdAt: "desc" },
278
+ take: 20,
279
+ ...(cursor ? { after: cursor } : {}),
280
+ });
281
+
282
+ cursor = page.nextCursor;
283
+ // page.items, page.hasMore, page.hasPrevious
284
+ ```
285
+
286
+ ## Aggregations
287
+
288
+ ```ts
289
+ const byStatus = await db.posts.groupBy({
290
+ by: ["status"],
291
+ _count: true,
292
+ orderBy: { _count: "desc" },
293
+ });
294
+
295
+ const stats = await db.posts.aggregate({
296
+ _count: true,
297
+ _avg: { views: true },
298
+ where: { published: true },
299
+ });
300
+ ```
301
+
302
+ ## Transactions
303
+
304
+ Batch steps (single transaction):
305
+
306
+ ```ts
307
+ const [author, post] = await db.$transaction([
308
+ (tx) =>
309
+ tx.users.create({
310
+ data: { email: "author@example.com", name: "Author" },
311
+ }),
312
+ (tx) =>
313
+ tx.posts.create({
314
+ data: {
315
+ title: "Transactional post",
316
+ body: "...",
317
+ author: { connect: { id: "user_1" } },
318
+ },
319
+ }),
320
+ ]);
321
+ ```
322
+
323
+ Interactive callback with rollback:
324
+
325
+ ```ts
326
+ await db.$transaction(async (tx) => {
327
+ const user = await tx.users.create({
328
+ data: { email: "x@y.z", name: "Temp" },
329
+ });
330
+ await tx.posts.create({
331
+ data: {
332
+ title: "Draft",
333
+ body: "...",
334
+ author: { connect: { id: user.id } },
335
+ },
336
+ });
337
+ // throw to roll back everything
338
+ });
339
+ ```
340
+
341
+ Nested savepoints:
342
+
343
+ ```ts
344
+ await db.$transaction(async (tx) => {
345
+ await tx.users.create({ data: { email: "outer@x.y", name: "Outer" } });
346
+
347
+ await tx.$transaction(async (nested) => {
348
+ await nested.users.create({ data: { email: "inner@x.y", name: "Inner" } });
349
+ throw new Error("roll back inner only");
350
+ }).catch(() => undefined);
351
+ });
352
+ ```
353
+
354
+ ## Raw SQL
355
+
356
+ Use `db.sql` when you need full SQL control. Table names in tagged templates use your schema accessors:
357
+
358
+ ```ts
359
+ const rows = await db.sql`
360
+ SELECT u.id, u.email, count(p.id) AS post_count
361
+ FROM users u
362
+ LEFT JOIN posts p ON p.author_id = u.id
363
+ GROUP BY u.id, u.email
364
+ ORDER BY post_count DESC
365
+ `;
366
+ ```
367
+
368
+ ## API responses (`strip`)
369
+
370
+ Fetch sensitive fields for auth, then strip before sending JSON:
371
+
372
+ ```ts
373
+ const user = await db.users.findFirst({
374
+ where: { email: input.email },
375
+ });
376
+
377
+ if (!user || !verifyPassword(input.password, user.password)) {
378
+ throw new Error("Invalid credentials");
379
+ }
380
+
381
+ return user.strip(); // removes .hidden() columns (e.g. password)
382
+ return user.strip({ email: true }); // hidden + extra fields
383
+ ```
384
+
385
+ `.strip()` is non-enumerable and returns a plain object. See [Queries → API responses](queries.md#api-responses-strip).
386
+
387
+ ## Explicit junction table (many-to-many)
388
+
389
+ Auto-junction works for most cases. For a custom junction table:
390
+
391
+ ```ts
392
+ posts: table({
393
+ id: id(),
394
+ tags: many("tags", {
395
+ through: "postTags",
396
+ leftKey: "postId",
397
+ rightKey: "tagId",
398
+ }),
399
+ }),
400
+ postTags: table("post_tags", {
401
+ postId: fk("posts").primary(),
402
+ tagId: fk("tags").primary(),
403
+ }),
404
+ ```
405
+
406
+ `through` is the **accessor** (`postTags`), not the SQL name (`post_tags`).
407
+
408
+ ## SQLite
409
+
410
+ Same schema and client API — point config at a file:
411
+
412
+ ```ts
413
+ // neoorm.config.ts
414
+ datasource: {
415
+ provider: "sqlite",
416
+ url: "./dev.db",
417
+ },
418
+ ```
419
+
420
+ ```ts
421
+ import { createNeoOrmClient } from "neoorm";
422
+ import { manifest } from "./neoorm/manifest.js";
423
+
424
+ const db = createNeoOrmClient(manifest, {
425
+ provider: "sqlite",
426
+ databasePath: "./dev.db",
427
+ });
428
+ ```
429
+
430
+ See [SQLite](sqlite.md) for type mapping and limitations.
431
+
432
+ ## PostGIS
433
+
434
+ ```ts
435
+ import "neoorm/plugins/postgis";
436
+ import { geometry, point } from "neoorm/plugins/postgis";
437
+
438
+ places: table({
439
+ id: id(),
440
+ name: text().notNull(),
441
+ location: geometry({ subtype: "Point", srid: 4326 }).notNull(),
442
+ }),
443
+ ```
444
+
445
+ ```ts
446
+ const nearby = await db.places.findMany({
447
+ where: {
448
+ location: {
449
+ dWithin: {
450
+ geometry: { type: "Point", coordinates: [-122.4, 37.8] },
451
+ distance: 1000,
452
+ },
453
+ },
454
+ },
455
+ });
456
+ ```
457
+
458
+ See [Plugins](plugins.md).
459
+
460
+ ## Schema extras
461
+
462
+ Composite unique constraint and partial index:
463
+
464
+ ```ts
465
+ import { fk, id, index, table, text, unique } from "neoorm/schema";
466
+
467
+ posts: table(
468
+ {
469
+ id: id(),
470
+ authorId: fk("users").notNull(),
471
+ title: text().notNull(),
472
+ published: bool().notNull().default(false),
473
+ },
474
+ (t) => [
475
+ unique(t.authorId, t.title),
476
+ index(t.title).where({ published: true }),
477
+ ],
478
+ ),
479
+ ```
480
+
481
+ SQL name override when accessor differs from table name:
482
+
483
+ ```ts
484
+ postTags: table("post_tags", {
485
+ postId: fk("posts").primary(),
486
+ tagId: fk("tags").primary(),
487
+ }),
488
+ ```
489
+
490
+ ## Next steps
491
+
492
+ | Topic | Doc |
493
+ |-------|-----|
494
+ | Schema DSL | [schema.md](schema.md) |
495
+ | Queries | [queries.md](queries.md) |
496
+ | Relation writes | [relations.md](relations.md) |
497
+ | Migrations | [migrations.md](migrations.md) |
498
+ | CLI | [cli.md](cli.md) |
@@ -0,0 +1,122 @@
1
+ # Getting started
2
+
3
+ ## Scaffold a new project
4
+
5
+ ```bash
6
+ bunx neoorm init
7
+ ```
8
+
9
+ This creates `neoorm.config.ts`, `schema.ts`, and `.env.example`. Run `bunx neoorm migrate dev` after setting your database URL.
10
+
11
+ ```bash
12
+ cp .env.example .env
13
+ # edit DATABASE_URL in .env
14
+ bunx neoorm migrate dev
15
+ ```
16
+
17
+ Query:
18
+
19
+ ```ts
20
+ import { db } from "./neoorm/client.js";
21
+
22
+ const user = await db.users.findById(userId, {
23
+ with: { posts: { where: { published: true }, orderBy: { createdAt: "desc" }, take: 10 } },
24
+ });
25
+ ```
26
+
27
+ ## Manual setup
28
+
29
+ ### 1. Define a schema
30
+
31
+ ```ts
32
+ // schema.ts
33
+ import {
34
+ defineSchema,
35
+ fk,
36
+ id,
37
+ table,
38
+ text,
39
+ timestamps,
40
+ uuid,
41
+ } from "neoorm/schema";
42
+
43
+ export const schema = defineSchema({
44
+ users: table({
45
+ id: uuid().primary(),
46
+ email: text().notNull().unique(),
47
+ ...timestamps(),
48
+ }),
49
+
50
+ posts: table({
51
+ id: id(),
52
+ authorId: fk("users").notNull().index(),
53
+ title: text().notNull(),
54
+ }),
55
+ });
56
+ ```
57
+
58
+ - `table({ ... })` uses the accessor as the SQL table name.
59
+ - `fk("users")` references the **users accessor** (not a SQL string).
60
+ - Relation names are inferred (`authorId` → `author` on posts, `posts` on users).
61
+
62
+ See [Schema DSL](schema.md).
63
+
64
+ Browse [Examples](examples.md) for copy-paste patterns (CRUD, relations, pagination, transactions, `strip`, and more).
65
+
66
+ ### 2. Configure NeoOrm
67
+
68
+ ```ts
69
+ // neoorm.config.ts
70
+ import { defineConfig } from "neoorm";
71
+
72
+ export default defineConfig({
73
+ schema: "./schema.ts",
74
+ out: "./neoorm",
75
+ datasource: {
76
+ provider: "postgresql",
77
+ url: process.env.DATABASE_URL!,
78
+ schema: "public",
79
+ enum: "check",
80
+ },
81
+ });
82
+ ```
83
+
84
+ ### 3. Generate the client
85
+
86
+ ```bash
87
+ bunx neoorm generate
88
+ ```
89
+
90
+ ## SQLite
91
+
92
+ Set `provider: "sqlite"` and `url` to a file path or `:memory:`:
93
+
94
+ ```ts
95
+ datasource: {
96
+ provider: "sqlite",
97
+ url: "./dev.db",
98
+ },
99
+ ```
100
+
101
+ ```ts
102
+ import { createNeoOrmClient } from "neoorm";
103
+ import { manifest } from "./neoorm/manifest.js";
104
+
105
+ const db = createNeoOrmClient(manifest, {
106
+ provider: "sqlite",
107
+ databasePath: "./dev.db",
108
+ });
109
+ ```
110
+
111
+ See [SQLite](sqlite.md).
112
+
113
+ ## Tenant-specific schemas
114
+
115
+ ```ts
116
+ const tenantDb = createNeoOrmClient(manifest, {
117
+ connectionString: process.env.DATABASE_URL!,
118
+ schema: "tenant_acme",
119
+ });
120
+ ```
121
+
122
+ Raw `db.sql` / `db.execute` are not rewritten — qualify tenant schema yourself in raw SQL.
@@ -0,0 +1,67 @@
1
+ # Migrations
2
+
3
+ ## Commands
4
+
5
+ | Command | Description |
6
+ |---------|-------------|
7
+ | `neoorm init` | Scaffold `neoorm.config.ts`, `schema.ts`, `.env.example` only (no codegen or migrations; run `neoorm migrate dev` after) |
8
+ | `neoorm generate` | Emit manifest, typed client, models, includes, and migrations |
9
+ | `neoorm migrate dev` | Apply pending migrations, then generate a new one if the schema changed |
10
+ | `neoorm migrate deploy` | Apply pending migrations |
11
+ | `neoorm migrate status` | List applied vs pending migrations |
12
+ | `neoorm migrate down [--steps N]` | Roll back the last N applied migrations (default 1) |
13
+ | `neoorm migrate reset --force` | Drop the `public` schema (PostgreSQL) or all tables (SQLite) and re-apply migrations (local dev) |
14
+ | `neoorm db push` | Push the current snapshot schema to the database |
15
+ | `neoorm db pull` | Introspect the database into a schema file |
16
+
17
+ ## Generate outcomes
18
+
19
+ `neoorm generate` always refreshes generated TypeScript files. It prints one of four outcomes:
20
+
21
+ | Outcome | Meaning |
22
+ |---------|---------|
23
+ | Schema unchanged | Snapshot hash matches — no manifest or migration changes |
24
+ | Client regenerated | Manifest changed but no database DDL needed |
25
+ | Migration created | New `migrations/<timestamp>/migration.sql` written |
26
+ | Migration blocked | Destructive or manual changes prevented writing SQL |
27
+
28
+ When migration is blocked, the CLI explains why — for example unsupported type casts (`alter_column_type_manual`), enum value changes, or destructive drops. Re-run with `--accept-data-loss` to include destructive DDL:
29
+
30
+ ```bash
31
+ neoorm generate --accept-data-loss
32
+ ```
33
+
34
+ ## Status
35
+
36
+ ```bash
37
+ neoorm migrate status
38
+ ```
39
+
40
+ Shows applied migrations (with timestamps), pending folders on disk, and warnings for drift (applied in DB but missing on disk).
41
+
42
+ ## Reset
43
+
44
+ ```bash
45
+ neoorm migrate reset --force
46
+ ```
47
+
48
+ Drops the `public` schema (PostgreSQL) or all tables (SQLite) and re-applies all migrations from disk. Requires `--force`. Use `--skip-apply` to only drop without re-applying.
49
+
50
+ ## Rollback
51
+
52
+ `down.sql` and `snapshot.before.json` are written automatically when `neoorm generate` or `neoorm migrate dev` creates a migration. The down SQL is the reverse schema diff (`next → prev`), with destructive changes accepted so rollbacks can drop columns or tables added in the forward migration.
53
+
54
+ ```bash
55
+ neoorm migrate down
56
+ neoorm migrate down --steps 2
57
+ ```
58
+
59
+ Rolls back the most recently applied migration(s) by running each migration folder's `down.sql`, removing the ledger entry from `_neoorm_migrations`, and restoring `snapshot.json` from `snapshot.before.json`.
60
+
61
+ Legacy migrations without `down.sql` cannot be rolled back — re-generate the migration or add `down.sql` manually.
62
+
63
+ Migration folders are not deleted on rollback (same as Prisma). Re-run `neoorm migrate deploy` to re-apply rolled-back migrations.
64
+
65
+ After rollback, `schema.ts` may still describe a newer schema than the restored snapshot; `neoorm migrate dev` may generate a new forward migration. `db push` and `migrate down` are independent — push ignores the migration ledger.
66
+
67
+ For a full wipe during local development, use `neoorm migrate reset --force` instead.