peta-orm 0.4.1 → 0.5.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/README.md CHANGED
@@ -20,24 +20,67 @@ const page = await Post.query().with("author").orderBy("id", "asc").paginate(1,
20
20
  bun add peta-orm arktype kysely @libsql/kysely-libsql @libsql/client
21
21
  ```
22
22
 
23
+ ### Simple setup (examples, scripts)
24
+
23
25
  ```ts
24
26
  import { createClient } from "@libsql/client"
25
27
  import { LibsqlDialect } from "@libsql/kysely-libsql"
26
28
  import { createORM, defineModel, t } from "peta-orm"
27
29
 
28
- const orm = createORM({
29
- dialect: new LibsqlDialect({ url: "file:my-app.db" }),
30
+ const User = defineModel("users", {
31
+ columns: { id: t.integer().primaryKey(), name: t.string(255), email: t.text().unique() },
30
32
  })
31
33
 
34
+ // Eager init — fine for scripts, one-off tasks
35
+ const client = createClient({ url: "file::memory:?cache=shared" })
36
+ await client.execute(
37
+ "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE)",
38
+ )
39
+
40
+ const orm = createORM({ dialect: new LibsqlDialect({ client }), models: { User } })
41
+
42
+ const user = await User.insert({ name: "Alice", email: "alice@test.com" })
43
+ ```
44
+
45
+ ### Production setup (apps, servers) — no module-level side effects
46
+
47
+ Module-level side effects (database connections, schema init, ORM setup at import time) cause problems with testing, HMR, and error recovery. Use `createDb()` for lazy, safe initialization:
48
+
49
+ ```ts
50
+ import { createClient } from "@libsql/client"
51
+ import { LibsqlDialect } from "@libsql/kysely-libsql"
52
+ import { createDb, createORM, defineModel, t } from "peta-orm"
53
+
32
54
  const User = defineModel("users", {
33
55
  columns: { id: t.integer().primaryKey(), name: t.string(255), email: t.text().unique() },
34
56
  })
35
57
 
36
- orm.registerAll(User)
58
+ async function setup() {
59
+ const client = createClient({ url: "file:my-app.db" })
60
+ await client.execute(
61
+ "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE)",
62
+ )
63
+ const orm = createORM({ dialect: new LibsqlDialect({ client }) })
64
+ orm.registerAll(User)
65
+ return orm
66
+ }
67
+
68
+ /** Lazy singleton — first call creates the connection, subsequent calls reuse it. */
69
+ export const db = createDb(setup)
37
70
 
38
- const user = await User.insert({ name: "Alice", email: "alice@test.com" })
71
+ // In route handlers:
72
+ // const orm = await db()
73
+ // const users = await User.query().execute()
39
74
  ```
40
75
 
76
+ The factory function runs **once** on the first `await db()` call. Importing models has zero side effects — no connection, no schema init, no unhandled promises.
77
+
78
+ > [!TIP]
79
+ > For an existing Kysely instance (e.g. from a migration runner), pass it via the `kysely` config option:
80
+ > ```ts
81
+ > const orm = createORM({ kysely: existingKysely })
82
+ > ```
83
+
41
84
  > [!TIP]
42
85
  > See the 32 [runnable examples](./examples) for every feature. Run them with `bun run examples/XX-*.ts`.
43
86
 
@@ -70,7 +113,6 @@ const user = await User.insert({ name: "Alice", email: "alice@test.com" })
70
113
  Column definitions double as validation schemas — no separate validation step needed.
71
114
 
72
115
  ```ts
73
- const t = columnTypes({ schema: createArkTypeSchemaConfig() })
74
116
 
75
117
  const User = defineModel("users", {
76
118
  columns: {
package/dist/index.d.mts CHANGED
@@ -1,11 +1,51 @@
1
1
  import { Dialect, Kysely } from "kysely";
2
2
 
3
+ //#region src/columns/schema.d.ts
4
+ interface Constraint {
5
+ type: string;
6
+ args: unknown[];
7
+ }
8
+ interface SchemaConfig {
9
+ compile(dataType: string, args: unknown[], constraints: Constraint[]): unknown;
10
+ parse<T>(schema: unknown, value: unknown): T;
11
+ assert<T>(schema: unknown, value: unknown): T;
12
+ }
13
+ //#endregion
14
+ //#region src/columns/column.d.ts
15
+ interface Column<out T = unknown> {
16
+ readonly arkType: unknown;
17
+ readonly dataType: string;
18
+ readonly args: readonly unknown[];
19
+ readonly constraints: readonly Constraint[];
20
+ readonly isNullable: boolean;
21
+ readonly isPrimaryKey: boolean;
22
+ readonly isUnique: boolean;
23
+ readonly defaultValue: unknown;
24
+ hasConstraint(type: string): boolean;
25
+ parse(value: unknown): T;
26
+ assert(value: unknown): T;
27
+ primaryKey(): Column<T>;
28
+ nullable(): Column<T | null>;
29
+ default<V>(value: V): Column<T>;
30
+ unique(): Column<T>;
31
+ index(): Column<T>;
32
+ min(n: number): Column<T>;
33
+ max(n: number): Column<T>;
34
+ email(): Column<T>;
35
+ url(): Column<T>;
36
+ pattern(regex: RegExp | string): Column<T>;
37
+ references(table: () => unknown, columns: string[]): Column<T>;
38
+ }
39
+ declare function createColumn<T>(schema: SchemaConfig, dataType: string, args?: unknown[], constraints?: Constraint[]): Column<T>;
40
+ type ColumnShape = Record<string, Column>;
41
+ type ColumnValue<C> = C extends Column<infer T> ? T : never;
42
+ //#endregion
3
43
  //#region src/lib/kysely.d.ts
4
44
  type Database = Kysely<Record<string, never>>;
5
45
  //#endregion
6
46
  //#region src/pagination/index.d.ts
7
- interface Paginator {
8
- readonly data: Collection;
47
+ interface Paginator<TColumns extends ColumnShape = ColumnShape> {
48
+ readonly data: Collection<TColumns>;
9
49
  readonly total: number;
10
50
  readonly perPage: number;
11
51
  readonly currentPage: number;
@@ -17,11 +57,11 @@ interface Paginator {
17
57
  readonly onFirstPage: boolean;
18
58
  readonly onLastPage: boolean;
19
59
  readonly count: number;
20
- map<T>(fn: (item: ModelInstance) => T): T[];
21
- toJSON(): PaginatorJson;
60
+ map<T>(fn: (item: ModelInstance<TColumns>) => T): T[];
61
+ toJSON(): PaginatorJson<TColumns>;
22
62
  }
23
- interface PaginatorJson {
24
- data: Record<string, unknown>[];
63
+ interface PaginatorJson<TColumns extends ColumnShape = ColumnShape> {
64
+ data: SerializedShape<TColumns>[];
25
65
  total: number;
26
66
  perPage: number;
27
67
  currentPage: number;
@@ -34,7 +74,7 @@ interface PaginatorJson {
34
74
  onLastPage: boolean;
35
75
  }
36
76
  type PaginatedResult = PaginatorJson;
37
- declare function createPaginator(items: ModelInstance[], total: number, perPage: number, currentPage: number): Paginator;
77
+ declare function createPaginator<TColumns extends ColumnShape = ColumnShape>(items: ModelInstance<TColumns>[], total: number, perPage: number, currentPage: number): Paginator<TColumns>;
38
78
  //#endregion
39
79
  //#region src/relations/base.d.ts
40
80
  type RelationType = "hasMany" | "belongsTo" | "hasOne" | "manyToMany" | "hasManyThrough";
@@ -97,14 +137,14 @@ interface UpsertGraphOptions extends InsertGraphOptions {
97
137
  }
98
138
  //#endregion
99
139
  //#region src/query/types.d.ts
100
- interface QueryBuilder extends PromiseLike<ModelInstance[]> {
101
- execute(): Promise<ModelInstance[]>;
102
- collect(): Promise<Collection>;
103
- executeTakeFirst(): Promise<ModelInstance | undefined>;
104
- executeTakeFirstOrThrow(): Promise<ModelInstance>;
105
- find(id: number | string): Promise<ModelInstance | undefined>;
106
- findOrFail(id: number | string): Promise<ModelInstance>;
107
- first(): Promise<ModelInstance | undefined>;
140
+ interface QueryBuilder<TColumns extends ColumnShape = ColumnShape> extends PromiseLike<ModelInstance<TColumns>[]> {
141
+ execute(): Promise<ModelInstance<TColumns>[]>;
142
+ collect(): Promise<Collection<TColumns>>;
143
+ executeTakeFirst(): Promise<ModelInstance<TColumns> | undefined>;
144
+ executeTakeFirstOrThrow(): Promise<ModelInstance<TColumns>>;
145
+ find(id: number | string): Promise<ModelInstance<TColumns> | undefined>;
146
+ findOrFail(id: number | string): Promise<ModelInstance<TColumns>>;
147
+ first(): Promise<ModelInstance<TColumns> | undefined>;
108
148
  toSQL(): {
109
149
  sql: string;
110
150
  parameters: readonly unknown[];
@@ -114,17 +154,17 @@ interface QueryBuilder extends PromiseLike<ModelInstance[]> {
114
154
  avg(column: string): Promise<number>;
115
155
  min(column: string): Promise<number>;
116
156
  max(column: string): Promise<number>;
117
- withCount(relation: string): QueryBuilder;
118
- withSum(relation: string, column: string): QueryBuilder;
119
- withAvg(relation: string, column: string): QueryBuilder;
120
- withMin(relation: string, column: string): QueryBuilder;
121
- withMax(relation: string, column: string): QueryBuilder;
122
- withExists(relation: string): QueryBuilder;
123
- chunk(size: number, callback: (chunk: ModelInstance[]) => Promise<void>): Promise<void>;
124
- paginate(page: number, perPage?: number): Promise<Paginator>;
157
+ withCount(relation: string): QueryBuilder<TColumns>;
158
+ withSum(relation: string, column: string): QueryBuilder<TColumns>;
159
+ withAvg(relation: string, column: string): QueryBuilder<TColumns>;
160
+ withMin(relation: string, column: string): QueryBuilder<TColumns>;
161
+ withMax(relation: string, column: string): QueryBuilder<TColumns>;
162
+ withExists(relation: string): QueryBuilder<TColumns>;
163
+ chunk(size: number, callback: (chunk: ModelInstance<TColumns>[]) => Promise<void>): Promise<void>;
164
+ paginate(page: number, perPage?: number): Promise<Paginator<TColumns>>;
125
165
  insertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: InsertGraphOptions): Promise<any>;
126
166
  upsertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: UpsertGraphOptions): Promise<any>;
127
- with(...relations: (string | Record<string, (qb: QueryBuilder) => void>)[]): QueryBuilder;
167
+ with(...relations: (string | Record<string, (qb: QueryBuilder<TColumns>) => void>)[]): QueryBuilder<TColumns>;
128
168
  /**
129
169
  * Whitelist allowed relations (and nested paths) for eager loading.
130
170
  * Throws if a relation path is not in the allow list.
@@ -136,42 +176,42 @@ interface QueryBuilder extends PromiseLike<ModelInstance[]> {
136
176
  *
137
177
  * Multiple arguments are merged: `allowGraph("posts", "profile")`
138
178
  */
139
- allowGraph(...expressions: string[]): QueryBuilder;
179
+ allowGraph(...expressions: string[]): QueryBuilder<TColumns>;
140
180
  updateMany(data: Record<string, unknown>): Promise<number>;
141
181
  deleteMany(): Promise<number>;
142
- withTrashed(): QueryBuilder;
143
- onlyTrashed(): QueryBuilder;
144
- whereIn(column: string, values: unknown[]): QueryBuilder;
145
- whereInPivot(column: string, values: unknown[]): QueryBuilder;
146
- has(relationName: string): QueryBuilder;
147
- whereHas(relationName: string, callback?: (qb: QueryBuilder) => void): QueryBuilder;
148
- whereDoesntHave(relationName: string, callback?: (qb: QueryBuilder) => void): QueryBuilder;
149
- where(column: string, operator: unknown, value?: unknown): QueryBuilder;
150
- whereRef(col1: string, operator: string, col2: string): QueryBuilder;
151
- orWhere(column: string, operator: unknown, value?: unknown): QueryBuilder;
152
- orderBy(column: string, direction?: "asc" | "desc"): QueryBuilder;
153
- limit(n: number): QueryBuilder;
154
- offset(n: number): QueryBuilder;
155
- select(...columns: string[]): QueryBuilder;
156
- selectAll(table?: string): QueryBuilder;
157
- innerJoin(table: string, lhs: string, rhs: string): QueryBuilder;
158
- leftJoin(table: string, lhs: string, rhs: string): QueryBuilder;
159
- groupBy(...columns: string[]): QueryBuilder;
160
- having(column: string, operator: string, value: unknown): QueryBuilder;
161
- withoutGlobalScope(name: string): QueryBuilder;
162
- all(): QueryBuilder;
182
+ withTrashed(): QueryBuilder<TColumns>;
183
+ onlyTrashed(): QueryBuilder<TColumns>;
184
+ whereIn(column: string, values: unknown[]): QueryBuilder<TColumns>;
185
+ whereInPivot(column: string, values: unknown[]): QueryBuilder<TColumns>;
186
+ has(relationName: string): QueryBuilder<TColumns>;
187
+ whereHas(relationName: string, callback?: (qb: QueryBuilder<TColumns>) => void): QueryBuilder<TColumns>;
188
+ whereDoesntHave(relationName: string, callback?: (qb: QueryBuilder<TColumns>) => void): QueryBuilder<TColumns>;
189
+ where(column: string, operator: unknown, value?: unknown): QueryBuilder<TColumns>;
190
+ whereRef(col1: string, operator: string, col2: string): QueryBuilder<TColumns>;
191
+ orWhere(column: string, operator: unknown, value?: unknown): QueryBuilder<TColumns>;
192
+ orderBy(column: string, direction?: "asc" | "desc"): QueryBuilder<TColumns>;
193
+ limit(n: number): QueryBuilder<TColumns>;
194
+ offset(n: number): QueryBuilder<TColumns>;
195
+ select(...columns: string[]): QueryBuilder<TColumns>;
196
+ selectAll(table?: string): QueryBuilder<TColumns>;
197
+ innerJoin(table: string, lhs: string, rhs: string): QueryBuilder<TColumns>;
198
+ leftJoin(table: string, lhs: string, rhs: string): QueryBuilder<TColumns>;
199
+ groupBy(...columns: string[]): QueryBuilder<TColumns>;
200
+ having(column: string, operator: string, value: unknown): QueryBuilder<TColumns>;
201
+ withoutGlobalScope(name: string): QueryBuilder<TColumns>;
202
+ all(): QueryBuilder<TColumns>;
163
203
  /** @internal Access underlying Kysely builder for raw SQL operations */
164
204
  _getKyselyQb(): any;
165
205
  /** @internal Replace the underlying Kysely builder */
166
206
  _replaceKyselyQb(newQb: any): void;
167
- when(condition: unknown, callback: (q: QueryBuilder) => QueryBuilder): QueryBuilder;
168
- unless(condition: unknown, callback: (q: QueryBuilder) => QueryBuilder): QueryBuilder;
207
+ when(condition: unknown, callback: (q: QueryBuilder<TColumns>) => QueryBuilder<TColumns>): QueryBuilder<TColumns>;
208
+ unless(condition: unknown, callback: (q: QueryBuilder<TColumns>) => QueryBuilder<TColumns>): QueryBuilder<TColumns>;
169
209
  }
170
210
  //#endregion
171
211
  //#region src/query/builder.d.ts
172
- declare function createQueryBuilder(def: ModelDefinition, peta?: {
212
+ declare function createQueryBuilder<TColumns extends ColumnShape = ColumnShape>(def: ModelDefinition<TColumns>, peta?: {
173
213
  kysely: Database;
174
- }): QueryBuilder;
214
+ }): QueryBuilder<TColumns>;
175
215
  //#endregion
176
216
  //#region src/relations/related-query.d.ts
177
217
  interface RelationQuery extends QueryBuilder {
@@ -202,52 +242,13 @@ interface RelationQuery extends QueryBuilder {
202
242
  */
203
243
  type Plugin = (def: ModelDefinition) => undefined | ModelDefinition;
204
244
  //#endregion
205
- //#region src/columns/schema.d.ts
206
- interface Constraint {
207
- type: string;
208
- args: unknown[];
209
- }
210
- interface SchemaConfig {
211
- compile(dataType: string, args: unknown[], constraints: Constraint[]): unknown;
212
- parse<T>(schema: unknown, value: unknown): T;
213
- assert<T>(schema: unknown, value: unknown): T;
214
- }
215
- //#endregion
216
- //#region src/columns/column.d.ts
217
- interface Column<out T = unknown> {
218
- readonly arkType: unknown;
219
- readonly dataType: string;
220
- readonly args: readonly unknown[];
221
- readonly constraints: readonly Constraint[];
222
- readonly isNullable: boolean;
223
- readonly isPrimaryKey: boolean;
224
- readonly isUnique: boolean;
225
- readonly defaultValue: unknown;
226
- hasConstraint(type: string): boolean;
227
- parse(value: unknown): T;
228
- assert(value: unknown): T;
229
- primaryKey(): Column<T>;
230
- nullable(): Column<T | null>;
231
- default<V>(value: V): Column<T>;
232
- unique(): Column<T>;
233
- index(): Column<T>;
234
- min(n: number): Column<T>;
235
- max(n: number): Column<T>;
236
- email(): Column<T>;
237
- url(): Column<T>;
238
- pattern(regex: RegExp | string): Column<T>;
239
- references(table: () => unknown, columns: string[]): Column<T>;
240
- }
241
- declare function createColumn<T>(schema: SchemaConfig, dataType: string, args?: unknown[], constraints?: Constraint[]): Column<T>;
242
- type ColumnShape = Record<string, Column>;
243
- type ColumnValue<C> = C extends Column<infer T> ? T : never;
244
- //#endregion
245
245
  //#region src/types.d.ts
246
246
  type ModelId = number & {
247
247
  readonly __brand: "ModelId";
248
248
  };
249
- interface ModelLike {
250
- get<T = unknown>(key: string): T;
249
+ interface ModelLike<TColumns extends ColumnShape = ColumnShape> {
250
+ get<K extends keyof TColumns>(key: K): ColumnValue<TColumns[K]>;
251
+ get(key: string): unknown;
251
252
  set(key: string, value: unknown): void;
252
253
  }
253
254
  interface ORMLike {
@@ -258,6 +259,17 @@ interface ORMLike {
258
259
  transaction<T>(fn: (trx: import("kysely").Kysely<Record<string, never>>) => Promise<T>): Promise<T>;
259
260
  readonly models: ReadonlyMap<string, ModelDefinition$1<any>>;
260
261
  getModel<T extends ColumnShape = ColumnShape>(name: string): ModelDefinition$1<T> | undefined;
262
+ /**
263
+ * Discover model definitions by scanning files matching a glob pattern.
264
+ *
265
+ * Uses `fast-glob` to resolve the pattern relative to `cwd`, then dynamically
266
+ * imports each matching file and collects exported `ModelDefinition` values.
267
+ * Does **not** auto-register — use `registerAll(...result)` to register them.
268
+ *
269
+ * @param pattern Glob pattern (e.g. `"./src/models/**\/*.ts"`)
270
+ * @returns Array of discovered model definitions
271
+ */
272
+ discover(pattern: string): Promise<ModelDefinition$1<any>[]>;
261
273
  }
262
274
  interface ModelDefinition$1<TColumns extends ColumnShape = ColumnShape> {
263
275
  readonly table: string;
@@ -265,17 +277,17 @@ interface ModelDefinition$1<TColumns extends ColumnShape = ColumnShape> {
265
277
  readonly relations: Record<string, Relation>;
266
278
  readonly name: string;
267
279
  _orm: ORMLike | null;
268
- query(): QueryBuilder;
269
- find(id: number | string): Promise<ModelInstance | undefined>;
270
- findOrFail(id: number | string): Promise<ModelInstance>;
271
- first(): Promise<ModelInstance | undefined>;
272
- create(data: Record<string, unknown>): Promise<ModelInstance>;
273
- insert(data: Record<string, unknown>): Promise<ModelInstance>;
274
- insertMany(dataArray: Record<string, unknown>[]): Promise<ModelInstance[]>;
275
- update(id: number | string, data: Record<string, unknown>): Promise<ModelInstance>;
280
+ query(): QueryBuilder<TColumns>;
281
+ find(id: number | string): Promise<ModelInstance<TColumns> | undefined>;
282
+ findOrFail(id: number | string): Promise<ModelInstance<TColumns>>;
283
+ first(): Promise<ModelInstance<TColumns> | undefined>;
284
+ create(data: Record<string, unknown>): Promise<ModelInstance<TColumns>>;
285
+ insert(data: Record<string, unknown>): Promise<ModelInstance<TColumns>>;
286
+ insertMany(dataArray: Record<string, unknown>[]): Promise<ModelInstance<TColumns>[]>;
287
+ update(id: number | string, data: Record<string, unknown>): Promise<ModelInstance<TColumns>>;
276
288
  delete(id: number | string): Promise<void>;
277
- hydrate(row: Record<string, unknown>): ModelInstance;
278
- on(event: string, callback: (model: ModelInstance) => void | Promise<void>): () => void;
289
+ hydrate(row: Record<string, unknown>): ModelInstance<TColumns>;
290
+ on(event: string, callback: (model: ModelInstance<TColumns>) => void | Promise<void>): () => void;
279
291
  getHooks(): HookManager;
280
292
  addGlobalScope(name: string, callback: (qb: QueryBuilder) => void): void;
281
293
  removeGlobalScope(name: string): void;
@@ -358,12 +370,15 @@ declare class Attribute<T = any> {
358
370
  }
359
371
  //#endregion
360
372
  //#region src/model/types.d.ts
361
- interface ModelInstance {
373
+ /** Mapped column shape — resolves each column to its JS value type for $toJSON(). */
374
+ type SerializedShape<TColumns extends ColumnShape> = { [K in keyof TColumns]: TColumns[K] extends Column<infer T> ? T : unknown } & Record<string, unknown>;
375
+ interface ModelInstance<TColumns extends ColumnShape = ColumnShape> {
362
376
  readonly exists: boolean;
363
377
  readonly attributes: Record<string, unknown>;
364
378
  readonly dirtyAttributes: Record<string, unknown>;
365
379
  isDirty(key?: string): boolean;
366
- get<T = unknown>(key: string): T;
380
+ get<K extends keyof TColumns>(key: K): ColumnValue<TColumns[K]>;
381
+ get(key: string): unknown;
367
382
  set(key: string, value: unknown): void;
368
383
  fill(data: Record<string, unknown>): void;
369
384
  reset(): void;
@@ -379,8 +394,8 @@ interface ModelInstance {
379
394
  $restore(): Promise<void>;
380
395
  $trashed(): boolean;
381
396
  $reload(): Promise<void>;
382
- $toJSON(): Record<string, unknown>;
383
- toJSON(): Record<string, unknown>;
397
+ $toJSON(): SerializedShape<TColumns>;
398
+ toJSON(): SerializedShape<TColumns>;
384
399
  }
385
400
  interface ModelDefinition<TColumns extends ColumnShape = ColumnShape> {
386
401
  readonly table: string;
@@ -388,21 +403,21 @@ interface ModelDefinition<TColumns extends ColumnShape = ColumnShape> {
388
403
  readonly relations: Record<string, Relation>;
389
404
  readonly name: string;
390
405
  _orm: ORMLike | null;
391
- query(): QueryBuilder;
392
- find(id: number | string): Promise<ModelInstance | undefined>;
393
- findOrFail(id: number | string): Promise<ModelInstance>;
394
- first(): Promise<ModelInstance | undefined>;
395
- create(data: Record<string, unknown>): Promise<ModelInstance>;
396
- insert(data: Record<string, unknown>): Promise<ModelInstance>;
397
- insertMany(dataArray: Record<string, unknown>[]): Promise<ModelInstance[]>;
398
- update(id: number | string, data: Record<string, unknown>): Promise<ModelInstance>;
406
+ query(): QueryBuilder<TColumns>;
407
+ find(id: number | string): Promise<ModelInstance<TColumns> | undefined>;
408
+ findOrFail(id: number | string): Promise<ModelInstance<TColumns>>;
409
+ first(): Promise<ModelInstance<TColumns> | undefined>;
410
+ create(data: Record<string, unknown>): Promise<ModelInstance<TColumns>>;
411
+ insert(data: Record<string, unknown>): Promise<ModelInstance<TColumns>>;
412
+ insertMany(dataArray: Record<string, unknown>[]): Promise<ModelInstance<TColumns>[]>;
413
+ update(id: number | string, data: Record<string, unknown>): Promise<ModelInstance<TColumns>>;
399
414
  delete(id: number | string): Promise<void>;
400
415
  insertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: InsertGraphOptions): Promise<any>;
401
416
  upsertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: UpsertGraphOptions): Promise<any>;
402
- hydrate(row: Record<string, unknown>): ModelInstance;
403
- use(plugin: Plugin): ModelDefinition;
417
+ hydrate(row: Record<string, unknown>): ModelInstance<TColumns>;
418
+ use(plugin: Plugin): ModelDefinition<TColumns>;
404
419
  makeHelper<A extends any[], R>(fn: (qb: QueryBuilder, ...args: A) => R): (...args: A) => R;
405
- on(event: string, callback: (model: ModelInstance) => void | Promise<void>): () => void;
420
+ on(event: string, callback: (model: ModelInstance<TColumns>) => void | Promise<void>): () => void;
406
421
  getHooks(): HookManager;
407
422
  beforeDelete(callback: StaticHookCallback): () => void;
408
423
  afterDelete(callback: StaticHookCallback): () => void;
@@ -417,7 +432,6 @@ interface ModelDefinition<TColumns extends ColumnShape = ColumnShape> {
417
432
  getGlobalScopes(): Map<string, (qb: QueryBuilder) => void> | undefined;
418
433
  registerTimestamps?(createdAtCol?: string, updatedAtCol?: string): void;
419
434
  registerSoftDeletes?(deletedAtCol?: string): void;
420
- discover?(): Promise<never>;
421
435
  _init(orm: ORMLike): void;
422
436
  }
423
437
  interface ModelConfig<TColumns extends ColumnShape = ColumnShape> {
@@ -433,46 +447,46 @@ interface ModelConfig<TColumns extends ColumnShape = ColumnShape> {
433
447
  }
434
448
  //#endregion
435
449
  //#region src/collection/index.d.ts
436
- interface Collection {
450
+ interface Collection<TColumns extends ColumnShape = ColumnShape> {
437
451
  readonly length: number;
438
- [Symbol.iterator](): Iterator<ModelInstance>;
439
- at(index: number): ModelInstance | undefined;
440
- first(): ModelInstance | undefined;
441
- last(): ModelInstance | undefined;
442
- all(): ModelInstance[];
443
- findBy(id: number | string): ModelInstance | undefined;
444
- find(callback: (item: ModelInstance, index: number) => boolean): ModelInstance | undefined;
445
- some(callback: (item: ModelInstance, index: number) => boolean): boolean;
446
- includes(item: ModelInstance): boolean;
452
+ [Symbol.iterator](): Iterator<ModelInstance<TColumns>>;
453
+ at(index: number): ModelInstance<TColumns> | undefined;
454
+ first(): ModelInstance<TColumns> | undefined;
455
+ last(): ModelInstance<TColumns> | undefined;
456
+ all(): ModelInstance<TColumns>[];
457
+ findBy(id: number | string): ModelInstance<TColumns> | undefined;
458
+ find(callback: (item: ModelInstance<TColumns>, index: number) => boolean): ModelInstance<TColumns> | undefined;
459
+ some(callback: (item: ModelInstance<TColumns>, index: number) => boolean): boolean;
460
+ includes(item: ModelInstance<TColumns>): boolean;
447
461
  isEmpty(): boolean;
448
462
  isNotEmpty(): boolean;
449
463
  get(key: string): unknown[];
450
464
  pluck(key: string): unknown[];
451
- groupBy(key: string): Record<string, ModelInstance[]>;
452
- keyBy(key: string): Record<string, ModelInstance>;
453
- map<T>(fn: (item: ModelInstance, index: number) => T): T[];
454
- filter(fn: (item: ModelInstance, index: number) => boolean): Collection;
455
- reduce<T>(fn: (acc: T, item: ModelInstance, index: number) => T, initial: T): T;
456
- forEach(fn: (item: ModelInstance, index: number) => void): void;
457
- each(fn: (item: ModelInstance, index: number) => void): Collection;
458
- unique(key?: string): Collection;
459
- sortBy(key: string, direction?: "asc" | "desc"): Collection;
460
- shuffle(): Collection;
461
- take(n: number): Collection;
462
- skip(n: number): Collection;
463
- chunk(size: number): Collection[];
465
+ groupBy(key: string): Record<string, ModelInstance<TColumns>[]>;
466
+ keyBy(key: string): Record<string, ModelInstance<TColumns>>;
467
+ map<T>(fn: (item: ModelInstance<TColumns>, index: number) => T): T[];
468
+ filter(fn: (item: ModelInstance<TColumns>, index: number) => boolean): Collection<TColumns>;
469
+ reduce<T>(fn: (acc: T, item: ModelInstance<TColumns>, index: number) => T, initial: T): T;
470
+ forEach(fn: (item: ModelInstance<TColumns>, index: number) => void): void;
471
+ each(fn: (item: ModelInstance<TColumns>, index: number) => void): Collection<TColumns>;
472
+ unique(key?: string): Collection<TColumns>;
473
+ sortBy(key: string, direction?: "asc" | "desc"): Collection<TColumns>;
474
+ shuffle(): Collection<TColumns>;
475
+ take(n: number): Collection<TColumns>;
476
+ skip(n: number): Collection<TColumns>;
477
+ chunk(size: number): Collection<TColumns>[];
464
478
  sum(key: string): number;
465
479
  avg(key: string): number;
466
480
  min(key: string): number;
467
481
  max(key: string): number;
468
- diff(other: Collection): Collection;
469
- intersect(other: Collection): Collection;
470
- concat(other: Collection): Collection;
471
- push(...items: ModelInstance[]): void;
472
- load(...relations: string[]): Promise<Collection>;
473
- toJSON(): Record<string, unknown>[];
474
- }
475
- declare function createCollection(items?: ModelInstance[]): Collection;
482
+ diff(other: Collection<TColumns>): Collection<TColumns>;
483
+ intersect(other: Collection<TColumns>): Collection<TColumns>;
484
+ concat(other: Collection<TColumns>): Collection<TColumns>;
485
+ push(...items: ModelInstance<TColumns>[]): void;
486
+ load(...relations: string[]): Promise<Collection<TColumns>>;
487
+ toJSON(): SerializedShape<TColumns>[];
488
+ }
489
+ declare function createCollection<TColumns extends ColumnShape = ColumnShape>(items?: ModelInstance<TColumns>[]): Collection<TColumns>;
476
490
  //#endregion
477
491
  //#region src/columns/arktype.d.ts
478
492
  declare function createArkTypeSchemaConfig(): SchemaConfig;
@@ -499,7 +513,27 @@ interface ColumnTypes {
499
513
  updatedAt: Column<string>;
500
514
  };
501
515
  }
502
- declare function t(config: {
516
+ /**
517
+ * Pre-configured column type factory backed by ArkType validation.
518
+ *
519
+ * The most common usage — just import and use:
520
+ * ```ts
521
+ * import { t } from "peta-orm"
522
+ * const id = t.integer().primaryKey()
523
+ * ```
524
+ *
525
+ * For a custom validation backend, use `createColumnTypes({ schema })` instead.
526
+ */
527
+ declare const t: ColumnTypes;
528
+ /**
529
+ * Create a column type factory with a custom validation schema backend.
530
+ *
531
+ * @example
532
+ * ```ts
533
+ * const t = createColumnTypes({ schema: myCustomSchemaConfig })
534
+ * ```
535
+ */
536
+ declare function createColumnTypes(config: {
503
537
  schema: SchemaConfig;
504
538
  }): ColumnTypes;
505
539
  //#endregion
@@ -536,17 +570,56 @@ declare class DatabaseError extends Error {
536
570
  //#region src/errors/normalizer.d.ts
537
571
  declare function normalizeError(e: unknown, table?: string): DatabaseError;
538
572
  //#endregion
573
+ //#region src/init.d.ts
574
+ /**
575
+ * Create a lazy-initialized singleton factory.
576
+ *
577
+ * The factory function is called only once — on the first call to the returned
578
+ * function. Subsequent calls return the same resolved promise. This avoids
579
+ * module-level side effects: importing a model file won't trigger database
580
+ * connection or schema initialization until the first explicit `await db()`.
581
+ *
582
+ * @example
583
+ * ```ts
584
+ * import { createClient } from "@libsql/client"
585
+ * import { LibsqlDialect } from "@libsql/kysely-libsql"
586
+ * import { createDb, createORM, defineModel, t } from "peta-orm"
587
+ *
588
+ * const User = defineModel("users", { columns: { ... } })
589
+ *
590
+ * async function setup() {
591
+ * const client = createClient({ url: "file:my-app.db" })
592
+ * await client.execute("CREATE TABLE IF NOT EXISTS users (...)") // schema init
593
+ * const orm = createORM({ dialect: new LibsqlDialect({ client }) })
594
+ * orm.registerAll(User)
595
+ * return orm
596
+ * }
597
+ *
598
+ * export const db = createDb(setup)
599
+ * // Usage: const orm = await db()
600
+ * ```
601
+ */
602
+ declare function createDb<T>(factory: () => Promise<T>): () => Promise<T>;
603
+ //#endregion
539
604
  //#region src/model/define.d.ts
540
605
  declare function defineModel<TColumns extends ColumnShape>(table: string, config: ModelConfig<TColumns>): ModelDefinition<TColumns>;
541
606
  //#endregion
542
607
  //#region src/orm/index.d.ts
543
608
  interface ORMConfig {
544
- dialect: Dialect;
609
+ /** Kysely dialect to create an internal Kysely instance. Required unless `kysely` is provided. */
610
+ dialect?: Dialect;
611
+ /** A pre-existing Kysely instance to reuse. Required unless `dialect` is provided. */
612
+ kysely?: Kysely<any>;
613
+ /** Optional map of model definitions to register immediately. */
545
614
  models?: Record<string, ModelDefinition>;
546
615
  }
547
616
  /**
548
617
  * Create an ORM instance — the central registry that wires Kysely to model definitions.
549
618
  * Replaces createPeta() from v0.x.
619
+ *
620
+ * Pass either `dialect` (to auto-create a Kysely instance) or `kysely` (to reuse one).
621
+ * Passing a pre-existing Kysely instance avoids creating a second connection for
622
+ * migration runners or other tools that already have their own Kysely.
550
623
  */
551
624
  declare function createORM(config: ORMConfig): ORMLike & {
552
625
  kysely: Database;
@@ -688,4 +761,26 @@ declare function defineMorphMany(options: MorphManyOptions): Relation;
688
761
  */
689
762
  declare function defineMorphOne(options: MorphOneOptions): Relation;
690
763
  //#endregion
691
- export { Attribute, type Collection, type Column, type ColumnShape, type ColumnTypes, type ColumnValue, type Constraint, DatabaseError, type DatabaseErrorCode, type HookCallback, type HookManager, type InsertGraphOptions, type LifecycleEvent, type ModelConfig, type ModelDefinition, type ModelId, type ModelInstance, ModelNotFoundError, ModelNotRegisteredError, type MorphManyOptions, type MorphOneOptions, type MorphToOptions, type ORMConfig, type ORMLike, type PaginatedResult, type Paginator, type PaginatorJson, type Plugin, type QueryBuilder, type Relation, RelationNotAllowedError, RelationNotFoundError, type RelationOptions, type RelationType, type SchemaConfig, type UpsertGraphOptions, ValidationError, belongsTo, createArkTypeSchemaConfig, createCollection, createColumn, createHookManager, createORM, createORM as createPeta, createPaginator, createQueryBuilder, defineModel, defineMorphMany, defineMorphOne, defineMorphTo, hasMany, hasManyThrough, hasOne, manyToMany, normalizeError, resolveMorphRelation, softDeletes, t, timestamps, ulid };
764
+ //#region src/repo/index.d.ts
765
+ type QueryMethod = (qb: QueryBuilder, ...args: any[]) => QueryBuilder;
766
+ interface RepoMethods {
767
+ queryMethods?: Record<string, QueryMethod>;
768
+ methods?: Record<string, (...args: any[]) => any>;
769
+ }
770
+ /**
771
+ * Create a repository — a composable set of chainable query methods.
772
+ *
773
+ * ```ts
774
+ * const userRepo = createRepo(User, {
775
+ * queryMethods: {
776
+ * search(q, query: string) {
777
+ * return q.where('name', 'like', `%${query}%`)
778
+ * },
779
+ * },
780
+ * })
781
+ * const users = await userRepo.search('john').paginate(1, 20)
782
+ * ```
783
+ */
784
+ declare function createRepo<TMethods extends RepoMethods>(model: ModelDefinition, methods: TMethods): Record<string, never>;
785
+ //#endregion
786
+ export { Attribute, type Collection, type Column, type ColumnShape, type ColumnTypes, type ColumnValue, type Constraint, DatabaseError, type DatabaseErrorCode, type HookCallback, type HookManager, type InsertGraphOptions, type LifecycleEvent, type ModelConfig, type ModelDefinition, type ModelId, type ModelInstance, ModelNotFoundError, ModelNotRegisteredError, type MorphManyOptions, type MorphOneOptions, type MorphToOptions, type ORMConfig, type ORMLike, type PaginatedResult, type Paginator, type PaginatorJson, type Plugin, type QueryBuilder, type QueryMethod, type Relation, RelationNotAllowedError, RelationNotFoundError, type RelationOptions, type RelationType, type RepoMethods, type SchemaConfig, type SerializedShape, type UpsertGraphOptions, ValidationError, belongsTo, createArkTypeSchemaConfig, createCollection, createColumn, createColumnTypes, createDb, createHookManager, createORM, createORM as createPeta, createPaginator, createQueryBuilder, createRepo, defineModel, defineMorphMany, defineMorphOne, defineMorphTo, hasMany, hasManyThrough, hasOne, manyToMany, normalizeError, resolveMorphRelation, softDeletes, t, timestamps, ulid };
package/dist/index.mjs CHANGED
@@ -7,6 +7,7 @@ import { a as getRawRelations, d as setExists, o as getState } from "./state-Ltl
7
7
  import { a as setConfig$1, n as reloadModel, r as saveModel, t as getConfig$1 } from "./save-D5UKXvqC.mjs";
8
8
  import { type } from "arktype";
9
9
  import { Kysely, sql } from "kysely";
10
+ import { pathToFileURL } from "url";
10
11
  import { ulid as ulid$1 } from "ulid";
11
12
  //#region src/columns/arktype.ts
12
13
  function createArkTypeSchemaConfig() {
@@ -189,7 +190,27 @@ function createColumn(schema, dataType, args = [], constraints = []) {
189
190
  }
190
191
  //#endregion
191
192
  //#region src/columns/types.ts
192
- function t(config) {
193
+ /**
194
+ * Pre-configured column type factory backed by ArkType validation.
195
+ *
196
+ * The most common usage — just import and use:
197
+ * ```ts
198
+ * import { t } from "peta-orm"
199
+ * const id = t.integer().primaryKey()
200
+ * ```
201
+ *
202
+ * For a custom validation backend, use `createColumnTypes({ schema })` instead.
203
+ */
204
+ const t = createColumnTypes({ schema: createArkTypeSchemaConfig() });
205
+ /**
206
+ * Create a column type factory with a custom validation schema backend.
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * const t = createColumnTypes({ schema: myCustomSchemaConfig })
211
+ * ```
212
+ */
213
+ function createColumnTypes(config) {
193
214
  const schema = config.schema;
194
215
  function col(dataType, args) {
195
216
  return createColumn(schema, dataType, args);
@@ -217,6 +238,43 @@ function t(config) {
217
238
  };
218
239
  }
219
240
  //#endregion
241
+ //#region src/init.ts
242
+ /**
243
+ * Create a lazy-initialized singleton factory.
244
+ *
245
+ * The factory function is called only once — on the first call to the returned
246
+ * function. Subsequent calls return the same resolved promise. This avoids
247
+ * module-level side effects: importing a model file won't trigger database
248
+ * connection or schema initialization until the first explicit `await db()`.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * import { createClient } from "@libsql/client"
253
+ * import { LibsqlDialect } from "@libsql/kysely-libsql"
254
+ * import { createDb, createORM, defineModel, t } from "peta-orm"
255
+ *
256
+ * const User = defineModel("users", { columns: { ... } })
257
+ *
258
+ * async function setup() {
259
+ * const client = createClient({ url: "file:my-app.db" })
260
+ * await client.execute("CREATE TABLE IF NOT EXISTS users (...)") // schema init
261
+ * const orm = createORM({ dialect: new LibsqlDialect({ client }) })
262
+ * orm.registerAll(User)
263
+ * return orm
264
+ * }
265
+ *
266
+ * export const db = createDb(setup)
267
+ * // Usage: const orm = await db()
268
+ * ```
269
+ */
270
+ function createDb(factory) {
271
+ let promise = null;
272
+ return () => {
273
+ if (!promise) promise = factory();
274
+ return promise;
275
+ };
276
+ }
277
+ //#endregion
220
278
  //#region src/hooks/static.ts
221
279
  var static_exports = /* @__PURE__ */ __exportAll({
222
280
  addStaticHook: () => addStaticHook,
@@ -1483,9 +1541,6 @@ function defineModel(table, config) {
1483
1541
  def.registerSoftDeletes = (deletedAtCol) => {
1484
1542
  registerSoftDeletesFor(def, deletedAtCol);
1485
1543
  };
1486
- def.discover = async () => {
1487
- throw new Error("discover() not yet implemented in v2");
1488
- };
1489
1544
  return def;
1490
1545
  }
1491
1546
  //#endregion
@@ -1895,9 +1950,14 @@ initRuntime({
1895
1950
  /**
1896
1951
  * Create an ORM instance — the central registry that wires Kysely to model definitions.
1897
1952
  * Replaces createPeta() from v0.x.
1953
+ *
1954
+ * Pass either `dialect` (to auto-create a Kysely instance) or `kysely` (to reuse one).
1955
+ * Passing a pre-existing Kysely instance avoids creating a second connection for
1956
+ * migration runners or other tools that already have their own Kysely.
1898
1957
  */
1899
1958
  function createORM(config) {
1900
- const kysely = new Kysely({ dialect: config.dialect });
1959
+ if (!config.dialect && !config.kysely) throw new Error("createORM: provide either `dialect` (to create a Kysely instance) or `kysely` (to reuse one)");
1960
+ const kysely = config.kysely ?? new Kysely({ dialect: config.dialect });
1901
1961
  const modelMap = /* @__PURE__ */ new Map();
1902
1962
  const orm = {
1903
1963
  kysely,
@@ -1923,6 +1983,26 @@ function createORM(config) {
1923
1983
  },
1924
1984
  getModel(name) {
1925
1985
  return modelMap.get(name);
1986
+ },
1987
+ async discover(pattern) {
1988
+ const entries = await (await import("fast-glob")).glob(pattern, {
1989
+ absolute: true,
1990
+ onlyFiles: true
1991
+ });
1992
+ if (entries.length === 0) throw new Error(`discover: no files matched pattern "${pattern}"`);
1993
+ const models = [];
1994
+ const seen = /* @__PURE__ */ new Set();
1995
+ for (const fp of entries) {
1996
+ const mod = await import(pathToFileURL(fp).href);
1997
+ for (const val of Object.values(mod)) if (val && typeof val === "object" && "columns" in val && "table" in val && typeof val.table === "string" && val.table.length > 0) {
1998
+ const def = val;
1999
+ if (!seen.has(def.table)) {
2000
+ seen.add(def.table);
2001
+ models.push(def);
2002
+ }
2003
+ }
2004
+ }
2005
+ return models;
1926
2006
  }
1927
2007
  };
1928
2008
  if (config.models) for (const [_name, model] of Object.entries(config.models)) orm.register(model);
@@ -2601,4 +2681,65 @@ function defName(instance) {
2601
2681
  return instance.constructor?.name ?? "model";
2602
2682
  }
2603
2683
  //#endregion
2604
- export { Attribute, DatabaseError, ModelNotFoundError, ModelNotRegisteredError, RelationNotAllowedError, RelationNotFoundError, ValidationError, belongsTo, createArkTypeSchemaConfig, createCollection, createColumn, createHookManager, createORM, createORM as createPeta, createPaginator, createQueryBuilder, defineModel, defineMorphMany, defineMorphOne, defineMorphTo, hasMany, hasManyThrough, hasOne, eager_exports as i, manyToMany, relation_exports as n, normalizeError, delete_exports as r, resolveMorphRelation, softDeletes, t, timestamps, ulid };
2684
+ //#region src/repo/index.ts
2685
+ /**
2686
+ * Create a repository — a composable set of chainable query methods.
2687
+ *
2688
+ * ```ts
2689
+ * const userRepo = createRepo(User, {
2690
+ * queryMethods: {
2691
+ * search(q, query: string) {
2692
+ * return q.where('name', 'like', `%${query}%`)
2693
+ * },
2694
+ * },
2695
+ * })
2696
+ * const users = await userRepo.search('john').paginate(1, 20)
2697
+ * ```
2698
+ */
2699
+ function createRepo(model, methods) {
2700
+ const customMethods = /* @__PURE__ */ new Map();
2701
+ if (methods.queryMethods) for (const [name, fn] of Object.entries(methods.queryMethods)) customMethods.set(name, fn);
2702
+ /**
2703
+ * Wrap a QueryBuilder so custom methods are available for chaining.
2704
+ * Custom methods operate on the CURRENT QB (not a fresh one), so
2705
+ * chaining carries forward previous conditions.
2706
+ */
2707
+ function wrapQB(qb) {
2708
+ return new Proxy(qb, { get(target, prop) {
2709
+ if (typeof prop === "string") {
2710
+ if (customMethods.has(prop)) {
2711
+ const fn = customMethods.get(prop);
2712
+ return (...args) => {
2713
+ return wrapQB(fn(target, ...args));
2714
+ };
2715
+ }
2716
+ if (methods.methods?.[prop]) return (...args) => methods.methods[prop](...args);
2717
+ }
2718
+ const val = target[prop];
2719
+ if (typeof val === "function") return function(...args) {
2720
+ const result = val.apply(target, args);
2721
+ return result === target ? wrapQB(result) : result;
2722
+ };
2723
+ return val;
2724
+ } });
2725
+ }
2726
+ return new Proxy({}, { get(_target, prop) {
2727
+ if (typeof prop === "string") {
2728
+ if (customMethods.has(prop)) {
2729
+ const fn = customMethods.get(prop);
2730
+ return (...args) => {
2731
+ return wrapQB(fn(model.query(), ...args));
2732
+ };
2733
+ }
2734
+ if (methods.methods?.[prop]) return (...args) => methods.methods[prop](...args);
2735
+ }
2736
+ const qb = model.query();
2737
+ const val = qb[prop];
2738
+ if (typeof val === "function") return (...args) => {
2739
+ return wrapQB(val.apply(qb, args));
2740
+ };
2741
+ return val;
2742
+ } });
2743
+ }
2744
+ //#endregion
2745
+ export { Attribute, DatabaseError, ModelNotFoundError, ModelNotRegisteredError, RelationNotAllowedError, RelationNotFoundError, ValidationError, belongsTo, createArkTypeSchemaConfig, createCollection, createColumn, createColumnTypes, createDb, createHookManager, createORM, createORM as createPeta, createPaginator, createQueryBuilder, createRepo, defineModel, defineMorphMany, defineMorphOne, defineMorphTo, hasMany, hasManyThrough, hasOne, eager_exports as i, manyToMany, relation_exports as n, normalizeError, delete_exports as r, resolveMorphRelation, softDeletes, t, timestamps, ulid };
package/package.json CHANGED
@@ -2,14 +2,14 @@
2
2
  "name": "peta-orm",
3
3
  "type": "module",
4
4
  "private": false,
5
- "version": "0.4.1",
5
+ "version": "0.5.0",
6
6
  "description": "ORM for Bun, built on Kysely",
7
7
  "license": "MIT",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/zfadhli/peta-stack.git"
11
11
  },
12
- "module": "src/index.ts",
12
+ "module": "./dist/index.mjs",
13
13
  "main": "./dist/index.mjs",
14
14
  "types": "./dist/index.d.mts",
15
15
  "exports": {
@@ -26,6 +26,7 @@
26
26
  "dependencies": {
27
27
  "arktype": "^2.2.0",
28
28
  "cac": "^7.0.0",
29
+ "fast-glob": "^3.3.3",
29
30
  "ora": "^9.4.0",
30
31
  "ulid": "^3.0.2"
31
32
  },
@@ -55,6 +56,6 @@
55
56
  "build": "tsdown",
56
57
  "lint": "bunx biome check src test examples",
57
58
  "lint:fix": "bunx biome check --write --unsafe src test examples",
58
- "prepublish": "bun run build"
59
+ "prepublishOnly": "bun run build"
59
60
  }
60
61
  }