peta-orm 0.4.0 → 0.4.1
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 +5 -6
- package/dist/index.d.mts +478 -2
- package/package.json +1 -9
- package/bin/peta +0 -3
- package/dist/index-BdJnSMYi.d.mts +0 -480
- package/dist/migrations/cli.d.mts +0 -4
- package/dist/migrations/cli.mjs +0 -74
- package/dist/migrations/index.d.mts +0 -53
- package/dist/migrations/index.mjs +0 -2
- package/dist/runner-DQ7uT6LC.mjs +0 -180
package/README.md
CHANGED
|
@@ -17,17 +17,16 @@ const page = await Post.query().with("author").orderBy("id", "asc").paginate(1,
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
bun add peta-orm arktype kysely
|
|
21
|
-
bun add -d kysely-bun-sqlite
|
|
20
|
+
bun add peta-orm arktype kysely @libsql/kysely-libsql @libsql/client
|
|
22
21
|
```
|
|
23
22
|
|
|
24
23
|
```ts
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
24
|
+
import { createClient } from "@libsql/client"
|
|
25
|
+
import { LibsqlDialect } from "@libsql/kysely-libsql"
|
|
27
26
|
import { createORM, defineModel, t } from "peta-orm"
|
|
28
27
|
|
|
29
28
|
const orm = createORM({
|
|
30
|
-
dialect: new
|
|
29
|
+
dialect: new LibsqlDialect({ url: "file:my-app.db" }),
|
|
31
30
|
})
|
|
32
31
|
|
|
33
32
|
const User = defineModel("users", {
|
|
@@ -343,7 +342,7 @@ bun run examples/07-soft-deletes.ts
|
|
|
343
342
|
|
|
344
343
|
| Database | Dialect package | Status |
|
|
345
344
|
|----------|----------------|--------|
|
|
346
|
-
| SQLite |
|
|
345
|
+
| SQLite | `@libsql/kysely-libsql` + `@libsql/client` | ✅ Tested |
|
|
347
346
|
| PostgreSQL | `pg` | ✅ Tested via Docker |
|
|
348
347
|
| MySQL | `mysql2` | ✅ Tested via Docker |
|
|
349
348
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,479 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Dialect } from "kysely";
|
|
1
|
+
import { Dialect, Kysely } from "kysely";
|
|
3
2
|
|
|
3
|
+
//#region src/lib/kysely.d.ts
|
|
4
|
+
type Database = Kysely<Record<string, never>>;
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/pagination/index.d.ts
|
|
7
|
+
interface Paginator {
|
|
8
|
+
readonly data: Collection;
|
|
9
|
+
readonly total: number;
|
|
10
|
+
readonly perPage: number;
|
|
11
|
+
readonly currentPage: number;
|
|
12
|
+
readonly lastPage: number;
|
|
13
|
+
readonly hasMorePages: boolean;
|
|
14
|
+
readonly hasPages: boolean;
|
|
15
|
+
readonly firstItem: number;
|
|
16
|
+
readonly lastItem: number;
|
|
17
|
+
readonly onFirstPage: boolean;
|
|
18
|
+
readonly onLastPage: boolean;
|
|
19
|
+
readonly count: number;
|
|
20
|
+
map<T>(fn: (item: ModelInstance) => T): T[];
|
|
21
|
+
toJSON(): PaginatorJson;
|
|
22
|
+
}
|
|
23
|
+
interface PaginatorJson {
|
|
24
|
+
data: Record<string, unknown>[];
|
|
25
|
+
total: number;
|
|
26
|
+
perPage: number;
|
|
27
|
+
currentPage: number;
|
|
28
|
+
lastPage: number;
|
|
29
|
+
hasMorePages: boolean;
|
|
30
|
+
hasPages: boolean;
|
|
31
|
+
firstItem: number | null;
|
|
32
|
+
lastItem: number | null;
|
|
33
|
+
onFirstPage: boolean;
|
|
34
|
+
onLastPage: boolean;
|
|
35
|
+
}
|
|
36
|
+
type PaginatedResult = PaginatorJson;
|
|
37
|
+
declare function createPaginator(items: ModelInstance[], total: number, perPage: number, currentPage: number): Paginator;
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/relations/base.d.ts
|
|
40
|
+
type RelationType = "hasMany" | "belongsTo" | "hasOne" | "manyToMany" | "hasManyThrough";
|
|
41
|
+
interface RelationOptions {
|
|
42
|
+
foreignKey?: string;
|
|
43
|
+
localKey?: string;
|
|
44
|
+
through?: string;
|
|
45
|
+
foreignPivotKey?: string;
|
|
46
|
+
relatedPivotKey?: string;
|
|
47
|
+
throughForeignKey?: string;
|
|
48
|
+
throughLocalKey?: string;
|
|
49
|
+
pivotExtras?: string[];
|
|
50
|
+
}
|
|
51
|
+
interface Relation {
|
|
52
|
+
readonly type: RelationType;
|
|
53
|
+
readonly relatedModelClass: ModelDefinition;
|
|
54
|
+
readonly foreignKey: string;
|
|
55
|
+
readonly localKey: string;
|
|
56
|
+
readonly throughTable?: string;
|
|
57
|
+
readonly foreignPivotKey?: string;
|
|
58
|
+
readonly relatedPivotKey?: string;
|
|
59
|
+
readonly throughForeignKey?: string;
|
|
60
|
+
readonly throughLocalKey?: string;
|
|
61
|
+
_morphMap?: Record<string, () => ModelDefinition>;
|
|
62
|
+
_morphType?: string;
|
|
63
|
+
_morphId?: string;
|
|
64
|
+
_morphTypeValue?: string;
|
|
65
|
+
query(parent: ModelInstance): QueryBuilder;
|
|
66
|
+
addEagerConstraints(query: QueryBuilder, models: ModelInstance[]): void;
|
|
67
|
+
match(models: ModelInstance[], results: ModelInstance[], relationName: string): void;
|
|
68
|
+
getResults(parent: ModelInstance): Promise<ModelInstance | ModelInstance[] | null>;
|
|
69
|
+
loadEager(models: ModelInstance[], relationName: string, constraints?: ((qb: QueryBuilder) => void) | null): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/relations/graph/types.d.ts
|
|
73
|
+
interface InsertGraphOptions {
|
|
74
|
+
/** Allow `#id` / `#ref` special properties in the graph */
|
|
75
|
+
allowRefs?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* If `true`, objects with an `id` property get related (pivot row / FK set)
|
|
78
|
+
* instead of inserted. Can be an array of relation names to scope.
|
|
79
|
+
*/
|
|
80
|
+
relate?: boolean | string[];
|
|
81
|
+
/**
|
|
82
|
+
* Whitelist of relation paths allowed for this graph operation.
|
|
83
|
+
* Accepts an array of dotted paths or a Set. If not set, all relations are allowed.
|
|
84
|
+
* When used via the query builder, the QB's `allowGraph()` set is forwarded automatically.
|
|
85
|
+
*/
|
|
86
|
+
allowGraph?: string[] | Set<string>;
|
|
87
|
+
}
|
|
88
|
+
interface UpsertGraphOptions extends InsertGraphOptions {
|
|
89
|
+
/** Unrelate (set FK null / remove pivot) instead of deleting missing items */
|
|
90
|
+
unrelate?: boolean | string[];
|
|
91
|
+
/** Prevent deletion for all or specific relation paths */
|
|
92
|
+
noDelete?: boolean | string[];
|
|
93
|
+
/** Prevent insertion for all or specific relation paths */
|
|
94
|
+
noInsert?: boolean | string[];
|
|
95
|
+
/** Prevent update for all or specific relation paths */
|
|
96
|
+
noUpdate?: boolean | string[];
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#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>;
|
|
108
|
+
toSQL(): {
|
|
109
|
+
sql: string;
|
|
110
|
+
parameters: readonly unknown[];
|
|
111
|
+
};
|
|
112
|
+
count(): Promise<number>;
|
|
113
|
+
sum(column: string): Promise<number>;
|
|
114
|
+
avg(column: string): Promise<number>;
|
|
115
|
+
min(column: string): Promise<number>;
|
|
116
|
+
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>;
|
|
125
|
+
insertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: InsertGraphOptions): Promise<any>;
|
|
126
|
+
upsertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: UpsertGraphOptions): Promise<any>;
|
|
127
|
+
with(...relations: (string | Record<string, (qb: QueryBuilder) => void>)[]): QueryBuilder;
|
|
128
|
+
/**
|
|
129
|
+
* Whitelist allowed relations (and nested paths) for eager loading.
|
|
130
|
+
* Throws if a relation path is not in the allow list.
|
|
131
|
+
*
|
|
132
|
+
* Supports dotted paths for granular control:
|
|
133
|
+
* - `allowGraph("posts")` allows `posts`, `posts.author`, `posts.author.profile`, etc.
|
|
134
|
+
* - `allowGraph("posts.author")` allows `posts.author` and `posts.author.profile`,
|
|
135
|
+
* but NOT bare `posts` or `posts.comments`.
|
|
136
|
+
*
|
|
137
|
+
* Multiple arguments are merged: `allowGraph("posts", "profile")`
|
|
138
|
+
*/
|
|
139
|
+
allowGraph(...expressions: string[]): QueryBuilder;
|
|
140
|
+
updateMany(data: Record<string, unknown>): Promise<number>;
|
|
141
|
+
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;
|
|
163
|
+
/** @internal Access underlying Kysely builder for raw SQL operations */
|
|
164
|
+
_getKyselyQb(): any;
|
|
165
|
+
/** @internal Replace the underlying Kysely builder */
|
|
166
|
+
_replaceKyselyQb(newQb: any): void;
|
|
167
|
+
when(condition: unknown, callback: (q: QueryBuilder) => QueryBuilder): QueryBuilder;
|
|
168
|
+
unless(condition: unknown, callback: (q: QueryBuilder) => QueryBuilder): QueryBuilder;
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/query/builder.d.ts
|
|
172
|
+
declare function createQueryBuilder(def: ModelDefinition, peta?: {
|
|
173
|
+
kysely: Database;
|
|
174
|
+
}): QueryBuilder;
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/relations/related-query.d.ts
|
|
177
|
+
interface RelationQuery extends QueryBuilder {
|
|
178
|
+
/** Attach related model(s) for many-to-many relations. */
|
|
179
|
+
attach(ids: number | number[] | string | string[], pivotData?: Record<string, unknown>): Promise<void>;
|
|
180
|
+
/** Detach related model(s) for many-to-many relations. */
|
|
181
|
+
detach(ids?: number | number[] | string | string[]): Promise<void>;
|
|
182
|
+
/** Sync related models: attaches new IDs, detaches missing ones. */
|
|
183
|
+
sync(ids: (number | string)[] | Record<number | string, Record<string, unknown>>): Promise<void>;
|
|
184
|
+
/** Sync without detaching existing IDs. */
|
|
185
|
+
syncWithoutDetaching(ids: (number | string)[]): Promise<void>;
|
|
186
|
+
/** Update pivot data for a specific related model. */
|
|
187
|
+
updateExistingPivot(id: number | string, data: Record<string, unknown>): Promise<void>;
|
|
188
|
+
}
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/plugins/index.d.ts
|
|
191
|
+
/**
|
|
192
|
+
* A plugin is a function that receives a model definition and can
|
|
193
|
+
* modify it by adding hooks, scopes, columns, or methods.
|
|
194
|
+
*
|
|
195
|
+
* ```ts
|
|
196
|
+
* const myPlugin = (options?: MyOptions): Plugin =>
|
|
197
|
+
* (def) => {
|
|
198
|
+
* def.addGlobalScope?.('active', (q) => q.where('active', true))
|
|
199
|
+
* return def
|
|
200
|
+
* }
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
type Plugin = (def: ModelDefinition) => undefined | ModelDefinition;
|
|
204
|
+
//#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
|
+
//#region src/types.d.ts
|
|
246
|
+
type ModelId = number & {
|
|
247
|
+
readonly __brand: "ModelId";
|
|
248
|
+
};
|
|
249
|
+
interface ModelLike {
|
|
250
|
+
get<T = unknown>(key: string): T;
|
|
251
|
+
set(key: string, value: unknown): void;
|
|
252
|
+
}
|
|
253
|
+
interface ORMLike {
|
|
254
|
+
readonly kysely: Database;
|
|
255
|
+
register(model: ModelDefinition$1<any>): void;
|
|
256
|
+
registerAll(...models: (ModelDefinition$1<any> | ModelDefinition$1<any>[])[]): void;
|
|
257
|
+
destroy(): Promise<void>;
|
|
258
|
+
transaction<T>(fn: (trx: import("kysely").Kysely<Record<string, never>>) => Promise<T>): Promise<T>;
|
|
259
|
+
readonly models: ReadonlyMap<string, ModelDefinition$1<any>>;
|
|
260
|
+
getModel<T extends ColumnShape = ColumnShape>(name: string): ModelDefinition$1<T> | undefined;
|
|
261
|
+
}
|
|
262
|
+
interface ModelDefinition$1<TColumns extends ColumnShape = ColumnShape> {
|
|
263
|
+
readonly table: string;
|
|
264
|
+
readonly columns: TColumns;
|
|
265
|
+
readonly relations: Record<string, Relation>;
|
|
266
|
+
readonly name: string;
|
|
267
|
+
_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>;
|
|
276
|
+
delete(id: number | string): Promise<void>;
|
|
277
|
+
hydrate(row: Record<string, unknown>): ModelInstance;
|
|
278
|
+
on(event: string, callback: (model: ModelInstance) => void | Promise<void>): () => void;
|
|
279
|
+
getHooks(): HookManager;
|
|
280
|
+
addGlobalScope(name: string, callback: (qb: QueryBuilder) => void): void;
|
|
281
|
+
removeGlobalScope(name: string): void;
|
|
282
|
+
getGlobalScopes(): Map<string, (qb: QueryBuilder) => void> | undefined;
|
|
283
|
+
_init(orm: ORMLike): void;
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/hooks/index.d.ts
|
|
287
|
+
type LifecycleEvent = "beforeCreate" | "afterCreate" | "beforeUpdate" | "afterUpdate" | "beforeSave" | "afterSave" | "beforeDelete" | "afterDelete" | "beforeRestore" | "afterRestore" | "beforeForceDelete" | "afterForceDelete";
|
|
288
|
+
type HookCallback = (model: ModelLike) => void | Promise<void>;
|
|
289
|
+
interface HookManager {
|
|
290
|
+
on(event: LifecycleEvent, callback: HookCallback): () => void;
|
|
291
|
+
off(event: LifecycleEvent, callback: HookCallback): void;
|
|
292
|
+
trigger(event: LifecycleEvent, model: ModelLike): Promise<void>;
|
|
293
|
+
clone(): HookManager;
|
|
294
|
+
}
|
|
295
|
+
declare function createHookManager(): HookManager;
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/hooks/static.d.ts
|
|
298
|
+
interface StaticHookArgs {
|
|
299
|
+
/** Transform the mutating query into a SELECT to preview affected rows */
|
|
300
|
+
asFindQuery(): QueryBuilder;
|
|
301
|
+
/** Cancel the mutation and return a custom result */
|
|
302
|
+
cancelQuery(result: unknown): void;
|
|
303
|
+
/** The column data being inserted/updated (for create/update hooks) */
|
|
304
|
+
inputItems?: Record<string, unknown>[];
|
|
305
|
+
}
|
|
306
|
+
type StaticHookCallback = (args: StaticHookArgs) => void | Promise<void>;
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/model/computed.d.ts
|
|
309
|
+
interface ComputedColumn<T = unknown> {
|
|
310
|
+
readonly type: "sql" | "runtime" | "batch";
|
|
311
|
+
readonly dependencies: string[];
|
|
312
|
+
/** Compute a value for a single record (runtime) */
|
|
313
|
+
compute?: (record: ModelInstance) => T;
|
|
314
|
+
/** Compute values for a batch of records (batch async) */
|
|
315
|
+
batchCompute?: (records: ModelInstance[]) => Promise<T[]>;
|
|
316
|
+
/** Raw SQL expression to inline in SELECT */
|
|
317
|
+
sql?: string;
|
|
318
|
+
}
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/model/attribute.d.ts
|
|
321
|
+
/**
|
|
322
|
+
* Defines an accessor (`get`) and/or mutator (`set`) for a model attribute.
|
|
323
|
+
*
|
|
324
|
+
* ### Accessor (get)
|
|
325
|
+
* Transforms the attribute value when read via `model.get()` or `model.$toJSON()`.
|
|
326
|
+
* Receives the casted value and the model instance.
|
|
327
|
+
*
|
|
328
|
+
* ### Mutator (set)
|
|
329
|
+
* Transforms the attribute value when written via `model.set()`, `model.fill()`,
|
|
330
|
+
* or during model creation (`Model.insert()` / `Model.create()`).
|
|
331
|
+
* Receives the raw input value and the model instance, returns the value to store.
|
|
332
|
+
* Applied **before** type casting.
|
|
333
|
+
*
|
|
334
|
+
* ### Usage
|
|
335
|
+
* ```ts
|
|
336
|
+
* defineModel('users', {
|
|
337
|
+
* columns: { id, name, password, ... },
|
|
338
|
+
* attributes: {
|
|
339
|
+
* password: Attribute.make({
|
|
340
|
+
* set: (value) => Bun.password.hashSync(value, { algorithm: 'bcrypt' }),
|
|
341
|
+
* get: () => '***',
|
|
342
|
+
* }),
|
|
343
|
+
* fullName: Attribute.make({
|
|
344
|
+
* get: (_, instance) => `${instance.get('firstName')} ${instance.get('lastName')}`,
|
|
345
|
+
* }),
|
|
346
|
+
* },
|
|
347
|
+
* })
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
declare class Attribute<T = any> {
|
|
351
|
+
readonly get?: ((value: T, instance: ModelInstance) => any) | undefined;
|
|
352
|
+
readonly set?: ((value: any, instance: ModelInstance) => T) | undefined;
|
|
353
|
+
private constructor();
|
|
354
|
+
static make<T = any>(config: {
|
|
355
|
+
/** Transform the attribute value when read. Receives (castedValue, instance). */get?: (value: T, instance: ModelInstance) => any; /** Transform the attribute value when written. Receives (rawValue, instance), returns value to store. */
|
|
356
|
+
set?: (value: any, instance: ModelInstance) => T;
|
|
357
|
+
}): Attribute<T>;
|
|
358
|
+
}
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/model/types.d.ts
|
|
361
|
+
interface ModelInstance {
|
|
362
|
+
readonly exists: boolean;
|
|
363
|
+
readonly attributes: Record<string, unknown>;
|
|
364
|
+
readonly dirtyAttributes: Record<string, unknown>;
|
|
365
|
+
isDirty(key?: string): boolean;
|
|
366
|
+
get<T = unknown>(key: string): T;
|
|
367
|
+
set(key: string, value: unknown): void;
|
|
368
|
+
fill(data: Record<string, unknown>): void;
|
|
369
|
+
reset(): void;
|
|
370
|
+
$getRelation<T = unknown>(name: string): T;
|
|
371
|
+
$setRelation(name: string, value: unknown): void;
|
|
372
|
+
$hasRelation(name: string): boolean;
|
|
373
|
+
$relationData(): Record<string, unknown>;
|
|
374
|
+
$load(...relations: string[]): Promise<void>;
|
|
375
|
+
$related(name: string): RelationQuery;
|
|
376
|
+
$save(): Promise<this>;
|
|
377
|
+
$delete(): Promise<void>;
|
|
378
|
+
$forceDelete(): Promise<void>;
|
|
379
|
+
$restore(): Promise<void>;
|
|
380
|
+
$trashed(): boolean;
|
|
381
|
+
$reload(): Promise<void>;
|
|
382
|
+
$toJSON(): Record<string, unknown>;
|
|
383
|
+
toJSON(): Record<string, unknown>;
|
|
384
|
+
}
|
|
385
|
+
interface ModelDefinition<TColumns extends ColumnShape = ColumnShape> {
|
|
386
|
+
readonly table: string;
|
|
387
|
+
readonly columns: TColumns;
|
|
388
|
+
readonly relations: Record<string, Relation>;
|
|
389
|
+
readonly name: string;
|
|
390
|
+
_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>;
|
|
399
|
+
delete(id: number | string): Promise<void>;
|
|
400
|
+
insertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: InsertGraphOptions): Promise<any>;
|
|
401
|
+
upsertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: UpsertGraphOptions): Promise<any>;
|
|
402
|
+
hydrate(row: Record<string, unknown>): ModelInstance;
|
|
403
|
+
use(plugin: Plugin): ModelDefinition;
|
|
404
|
+
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;
|
|
406
|
+
getHooks(): HookManager;
|
|
407
|
+
beforeDelete(callback: StaticHookCallback): () => void;
|
|
408
|
+
afterDelete(callback: StaticHookCallback): () => void;
|
|
409
|
+
beforeUpdate(callback: StaticHookCallback): () => void;
|
|
410
|
+
afterUpdate(callback: StaticHookCallback): () => void;
|
|
411
|
+
beforeCreate(callback: StaticHookCallback): () => void;
|
|
412
|
+
afterCreate(callback: StaticHookCallback): () => void;
|
|
413
|
+
beforeFind(callback: StaticHookCallback): () => void;
|
|
414
|
+
afterFind(callback: StaticHookCallback): () => void;
|
|
415
|
+
addGlobalScope(name: string, callback: (qb: QueryBuilder) => void): void;
|
|
416
|
+
removeGlobalScope(name: string): void;
|
|
417
|
+
getGlobalScopes(): Map<string, (qb: QueryBuilder) => void> | undefined;
|
|
418
|
+
registerTimestamps?(createdAtCol?: string, updatedAtCol?: string): void;
|
|
419
|
+
registerSoftDeletes?(deletedAtCol?: string): void;
|
|
420
|
+
discover?(): Promise<never>;
|
|
421
|
+
_init(orm: ORMLike): void;
|
|
422
|
+
}
|
|
423
|
+
interface ModelConfig<TColumns extends ColumnShape = ColumnShape> {
|
|
424
|
+
columns: TColumns;
|
|
425
|
+
relations?: Record<string, Relation>;
|
|
426
|
+
casts?: Record<string, string>;
|
|
427
|
+
/** Per-attribute accessors (`get`) and/or mutators (`set`). See {@link Attribute.make}. */
|
|
428
|
+
attributes?: Record<string, Attribute<any>>;
|
|
429
|
+
hidden?: string[];
|
|
430
|
+
visible?: string[];
|
|
431
|
+
appends?: string[];
|
|
432
|
+
computed?: Record<string, ComputedColumn>;
|
|
433
|
+
}
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/collection/index.d.ts
|
|
436
|
+
interface Collection {
|
|
437
|
+
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;
|
|
447
|
+
isEmpty(): boolean;
|
|
448
|
+
isNotEmpty(): boolean;
|
|
449
|
+
get(key: string): unknown[];
|
|
450
|
+
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[];
|
|
464
|
+
sum(key: string): number;
|
|
465
|
+
avg(key: string): number;
|
|
466
|
+
min(key: string): number;
|
|
467
|
+
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;
|
|
476
|
+
//#endregion
|
|
4
477
|
//#region src/columns/arktype.d.ts
|
|
5
478
|
declare function createArkTypeSchemaConfig(): SchemaConfig;
|
|
6
479
|
//#endregion
|
|
@@ -63,6 +536,9 @@ declare class DatabaseError extends Error {
|
|
|
63
536
|
//#region src/errors/normalizer.d.ts
|
|
64
537
|
declare function normalizeError(e: unknown, table?: string): DatabaseError;
|
|
65
538
|
//#endregion
|
|
539
|
+
//#region src/model/define.d.ts
|
|
540
|
+
declare function defineModel<TColumns extends ColumnShape>(table: string, config: ModelConfig<TColumns>): ModelDefinition<TColumns>;
|
|
541
|
+
//#endregion
|
|
66
542
|
//#region src/orm/index.d.ts
|
|
67
543
|
interface ORMConfig {
|
|
68
544
|
dialect: Dialect;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "peta-orm",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.1",
|
|
6
6
|
"description": "ORM for Bun, built on Kysely",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -16,17 +16,9 @@
|
|
|
16
16
|
".": {
|
|
17
17
|
"import": "./dist/index.mjs",
|
|
18
18
|
"types": "./dist/index.d.mts"
|
|
19
|
-
},
|
|
20
|
-
"./migrator": {
|
|
21
|
-
"import": "./dist/migrations/index.mjs",
|
|
22
|
-
"types": "./dist/migrations/index.d.mts"
|
|
23
19
|
}
|
|
24
20
|
},
|
|
25
|
-
"bin": {
|
|
26
|
-
"peta": "./bin/peta"
|
|
27
|
-
},
|
|
28
21
|
"files": [
|
|
29
|
-
"bin/",
|
|
30
22
|
"dist/",
|
|
31
23
|
"README.md",
|
|
32
24
|
"LICENSE"
|
package/bin/peta
DELETED
|
@@ -1,480 +0,0 @@
|
|
|
1
|
-
import { Kysely } from "kysely";
|
|
2
|
-
|
|
3
|
-
//#region src/lib/kysely.d.ts
|
|
4
|
-
type Database = Kysely<Record<string, never>>;
|
|
5
|
-
//#endregion
|
|
6
|
-
//#region src/pagination/index.d.ts
|
|
7
|
-
interface Paginator {
|
|
8
|
-
readonly data: Collection;
|
|
9
|
-
readonly total: number;
|
|
10
|
-
readonly perPage: number;
|
|
11
|
-
readonly currentPage: number;
|
|
12
|
-
readonly lastPage: number;
|
|
13
|
-
readonly hasMorePages: boolean;
|
|
14
|
-
readonly hasPages: boolean;
|
|
15
|
-
readonly firstItem: number;
|
|
16
|
-
readonly lastItem: number;
|
|
17
|
-
readonly onFirstPage: boolean;
|
|
18
|
-
readonly onLastPage: boolean;
|
|
19
|
-
readonly count: number;
|
|
20
|
-
map<T>(fn: (item: ModelInstance) => T): T[];
|
|
21
|
-
toJSON(): PaginatorJson;
|
|
22
|
-
}
|
|
23
|
-
interface PaginatorJson {
|
|
24
|
-
data: Record<string, unknown>[];
|
|
25
|
-
total: number;
|
|
26
|
-
perPage: number;
|
|
27
|
-
currentPage: number;
|
|
28
|
-
lastPage: number;
|
|
29
|
-
hasMorePages: boolean;
|
|
30
|
-
hasPages: boolean;
|
|
31
|
-
firstItem: number | null;
|
|
32
|
-
lastItem: number | null;
|
|
33
|
-
onFirstPage: boolean;
|
|
34
|
-
onLastPage: boolean;
|
|
35
|
-
}
|
|
36
|
-
type PaginatedResult = PaginatorJson;
|
|
37
|
-
declare function createPaginator(items: ModelInstance[], total: number, perPage: number, currentPage: number): Paginator;
|
|
38
|
-
//#endregion
|
|
39
|
-
//#region src/relations/base.d.ts
|
|
40
|
-
type RelationType = "hasMany" | "belongsTo" | "hasOne" | "manyToMany" | "hasManyThrough";
|
|
41
|
-
interface RelationOptions {
|
|
42
|
-
foreignKey?: string;
|
|
43
|
-
localKey?: string;
|
|
44
|
-
through?: string;
|
|
45
|
-
foreignPivotKey?: string;
|
|
46
|
-
relatedPivotKey?: string;
|
|
47
|
-
throughForeignKey?: string;
|
|
48
|
-
throughLocalKey?: string;
|
|
49
|
-
pivotExtras?: string[];
|
|
50
|
-
}
|
|
51
|
-
interface Relation {
|
|
52
|
-
readonly type: RelationType;
|
|
53
|
-
readonly relatedModelClass: ModelDefinition;
|
|
54
|
-
readonly foreignKey: string;
|
|
55
|
-
readonly localKey: string;
|
|
56
|
-
readonly throughTable?: string;
|
|
57
|
-
readonly foreignPivotKey?: string;
|
|
58
|
-
readonly relatedPivotKey?: string;
|
|
59
|
-
readonly throughForeignKey?: string;
|
|
60
|
-
readonly throughLocalKey?: string;
|
|
61
|
-
_morphMap?: Record<string, () => ModelDefinition>;
|
|
62
|
-
_morphType?: string;
|
|
63
|
-
_morphId?: string;
|
|
64
|
-
_morphTypeValue?: string;
|
|
65
|
-
query(parent: ModelInstance): QueryBuilder;
|
|
66
|
-
addEagerConstraints(query: QueryBuilder, models: ModelInstance[]): void;
|
|
67
|
-
match(models: ModelInstance[], results: ModelInstance[], relationName: string): void;
|
|
68
|
-
getResults(parent: ModelInstance): Promise<ModelInstance | ModelInstance[] | null>;
|
|
69
|
-
loadEager(models: ModelInstance[], relationName: string, constraints?: ((qb: QueryBuilder) => void) | null): Promise<void>;
|
|
70
|
-
}
|
|
71
|
-
//#endregion
|
|
72
|
-
//#region src/relations/graph/types.d.ts
|
|
73
|
-
interface InsertGraphOptions {
|
|
74
|
-
/** Allow `#id` / `#ref` special properties in the graph */
|
|
75
|
-
allowRefs?: boolean;
|
|
76
|
-
/**
|
|
77
|
-
* If `true`, objects with an `id` property get related (pivot row / FK set)
|
|
78
|
-
* instead of inserted. Can be an array of relation names to scope.
|
|
79
|
-
*/
|
|
80
|
-
relate?: boolean | string[];
|
|
81
|
-
/**
|
|
82
|
-
* Whitelist of relation paths allowed for this graph operation.
|
|
83
|
-
* Accepts an array of dotted paths or a Set. If not set, all relations are allowed.
|
|
84
|
-
* When used via the query builder, the QB's `allowGraph()` set is forwarded automatically.
|
|
85
|
-
*/
|
|
86
|
-
allowGraph?: string[] | Set<string>;
|
|
87
|
-
}
|
|
88
|
-
interface UpsertGraphOptions extends InsertGraphOptions {
|
|
89
|
-
/** Unrelate (set FK null / remove pivot) instead of deleting missing items */
|
|
90
|
-
unrelate?: boolean | string[];
|
|
91
|
-
/** Prevent deletion for all or specific relation paths */
|
|
92
|
-
noDelete?: boolean | string[];
|
|
93
|
-
/** Prevent insertion for all or specific relation paths */
|
|
94
|
-
noInsert?: boolean | string[];
|
|
95
|
-
/** Prevent update for all or specific relation paths */
|
|
96
|
-
noUpdate?: boolean | string[];
|
|
97
|
-
}
|
|
98
|
-
//#endregion
|
|
99
|
-
//#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>;
|
|
108
|
-
toSQL(): {
|
|
109
|
-
sql: string;
|
|
110
|
-
parameters: readonly unknown[];
|
|
111
|
-
};
|
|
112
|
-
count(): Promise<number>;
|
|
113
|
-
sum(column: string): Promise<number>;
|
|
114
|
-
avg(column: string): Promise<number>;
|
|
115
|
-
min(column: string): Promise<number>;
|
|
116
|
-
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>;
|
|
125
|
-
insertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: InsertGraphOptions): Promise<any>;
|
|
126
|
-
upsertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: UpsertGraphOptions): Promise<any>;
|
|
127
|
-
with(...relations: (string | Record<string, (qb: QueryBuilder) => void>)[]): QueryBuilder;
|
|
128
|
-
/**
|
|
129
|
-
* Whitelist allowed relations (and nested paths) for eager loading.
|
|
130
|
-
* Throws if a relation path is not in the allow list.
|
|
131
|
-
*
|
|
132
|
-
* Supports dotted paths for granular control:
|
|
133
|
-
* - `allowGraph("posts")` allows `posts`, `posts.author`, `posts.author.profile`, etc.
|
|
134
|
-
* - `allowGraph("posts.author")` allows `posts.author` and `posts.author.profile`,
|
|
135
|
-
* but NOT bare `posts` or `posts.comments`.
|
|
136
|
-
*
|
|
137
|
-
* Multiple arguments are merged: `allowGraph("posts", "profile")`
|
|
138
|
-
*/
|
|
139
|
-
allowGraph(...expressions: string[]): QueryBuilder;
|
|
140
|
-
updateMany(data: Record<string, unknown>): Promise<number>;
|
|
141
|
-
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;
|
|
163
|
-
/** @internal Access underlying Kysely builder for raw SQL operations */
|
|
164
|
-
_getKyselyQb(): any;
|
|
165
|
-
/** @internal Replace the underlying Kysely builder */
|
|
166
|
-
_replaceKyselyQb(newQb: any): void;
|
|
167
|
-
when(condition: unknown, callback: (q: QueryBuilder) => QueryBuilder): QueryBuilder;
|
|
168
|
-
unless(condition: unknown, callback: (q: QueryBuilder) => QueryBuilder): QueryBuilder;
|
|
169
|
-
}
|
|
170
|
-
//#endregion
|
|
171
|
-
//#region src/query/builder.d.ts
|
|
172
|
-
declare function createQueryBuilder(def: ModelDefinition, peta?: {
|
|
173
|
-
kysely: Database;
|
|
174
|
-
}): QueryBuilder;
|
|
175
|
-
//#endregion
|
|
176
|
-
//#region src/relations/related-query.d.ts
|
|
177
|
-
interface RelationQuery extends QueryBuilder {
|
|
178
|
-
/** Attach related model(s) for many-to-many relations. */
|
|
179
|
-
attach(ids: number | number[] | string | string[], pivotData?: Record<string, unknown>): Promise<void>;
|
|
180
|
-
/** Detach related model(s) for many-to-many relations. */
|
|
181
|
-
detach(ids?: number | number[] | string | string[]): Promise<void>;
|
|
182
|
-
/** Sync related models: attaches new IDs, detaches missing ones. */
|
|
183
|
-
sync(ids: (number | string)[] | Record<number | string, Record<string, unknown>>): Promise<void>;
|
|
184
|
-
/** Sync without detaching existing IDs. */
|
|
185
|
-
syncWithoutDetaching(ids: (number | string)[]): Promise<void>;
|
|
186
|
-
/** Update pivot data for a specific related model. */
|
|
187
|
-
updateExistingPivot(id: number | string, data: Record<string, unknown>): Promise<void>;
|
|
188
|
-
}
|
|
189
|
-
//#endregion
|
|
190
|
-
//#region src/plugins/index.d.ts
|
|
191
|
-
/**
|
|
192
|
-
* A plugin is a function that receives a model definition and can
|
|
193
|
-
* modify it by adding hooks, scopes, columns, or methods.
|
|
194
|
-
*
|
|
195
|
-
* ```ts
|
|
196
|
-
* const myPlugin = (options?: MyOptions): Plugin =>
|
|
197
|
-
* (def) => {
|
|
198
|
-
* def.addGlobalScope?.('active', (q) => q.where('active', true))
|
|
199
|
-
* return def
|
|
200
|
-
* }
|
|
201
|
-
* ```
|
|
202
|
-
*/
|
|
203
|
-
type Plugin = (def: ModelDefinition) => undefined | ModelDefinition;
|
|
204
|
-
//#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
|
-
//#region src/types.d.ts
|
|
246
|
-
type ModelId = number & {
|
|
247
|
-
readonly __brand: "ModelId";
|
|
248
|
-
};
|
|
249
|
-
interface ModelLike {
|
|
250
|
-
get<T = unknown>(key: string): T;
|
|
251
|
-
set(key: string, value: unknown): void;
|
|
252
|
-
}
|
|
253
|
-
interface ORMLike {
|
|
254
|
-
readonly kysely: Database;
|
|
255
|
-
register(model: ModelDefinition$1<any>): void;
|
|
256
|
-
registerAll(...models: (ModelDefinition$1<any> | ModelDefinition$1<any>[])[]): void;
|
|
257
|
-
destroy(): Promise<void>;
|
|
258
|
-
transaction<T>(fn: (trx: import("kysely").Kysely<Record<string, never>>) => Promise<T>): Promise<T>;
|
|
259
|
-
readonly models: ReadonlyMap<string, ModelDefinition$1<any>>;
|
|
260
|
-
getModel<T extends ColumnShape = ColumnShape>(name: string): ModelDefinition$1<T> | undefined;
|
|
261
|
-
}
|
|
262
|
-
interface ModelDefinition$1<TColumns extends ColumnShape = ColumnShape> {
|
|
263
|
-
readonly table: string;
|
|
264
|
-
readonly columns: TColumns;
|
|
265
|
-
readonly relations: Record<string, Relation>;
|
|
266
|
-
readonly name: string;
|
|
267
|
-
_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>;
|
|
276
|
-
delete(id: number | string): Promise<void>;
|
|
277
|
-
hydrate(row: Record<string, unknown>): ModelInstance;
|
|
278
|
-
on(event: string, callback: (model: ModelInstance) => void | Promise<void>): () => void;
|
|
279
|
-
getHooks(): HookManager;
|
|
280
|
-
addGlobalScope(name: string, callback: (qb: QueryBuilder) => void): void;
|
|
281
|
-
removeGlobalScope(name: string): void;
|
|
282
|
-
getGlobalScopes(): Map<string, (qb: QueryBuilder) => void> | undefined;
|
|
283
|
-
_init(orm: ORMLike): void;
|
|
284
|
-
}
|
|
285
|
-
//#endregion
|
|
286
|
-
//#region src/hooks/index.d.ts
|
|
287
|
-
type LifecycleEvent = "beforeCreate" | "afterCreate" | "beforeUpdate" | "afterUpdate" | "beforeSave" | "afterSave" | "beforeDelete" | "afterDelete" | "beforeRestore" | "afterRestore" | "beforeForceDelete" | "afterForceDelete";
|
|
288
|
-
type HookCallback = (model: ModelLike) => void | Promise<void>;
|
|
289
|
-
interface HookManager {
|
|
290
|
-
on(event: LifecycleEvent, callback: HookCallback): () => void;
|
|
291
|
-
off(event: LifecycleEvent, callback: HookCallback): void;
|
|
292
|
-
trigger(event: LifecycleEvent, model: ModelLike): Promise<void>;
|
|
293
|
-
clone(): HookManager;
|
|
294
|
-
}
|
|
295
|
-
declare function createHookManager(): HookManager;
|
|
296
|
-
//#endregion
|
|
297
|
-
//#region src/hooks/static.d.ts
|
|
298
|
-
interface StaticHookArgs {
|
|
299
|
-
/** Transform the mutating query into a SELECT to preview affected rows */
|
|
300
|
-
asFindQuery(): QueryBuilder;
|
|
301
|
-
/** Cancel the mutation and return a custom result */
|
|
302
|
-
cancelQuery(result: unknown): void;
|
|
303
|
-
/** The column data being inserted/updated (for create/update hooks) */
|
|
304
|
-
inputItems?: Record<string, unknown>[];
|
|
305
|
-
}
|
|
306
|
-
type StaticHookCallback = (args: StaticHookArgs) => void | Promise<void>;
|
|
307
|
-
//#endregion
|
|
308
|
-
//#region src/model/computed.d.ts
|
|
309
|
-
interface ComputedColumn<T = unknown> {
|
|
310
|
-
readonly type: "sql" | "runtime" | "batch";
|
|
311
|
-
readonly dependencies: string[];
|
|
312
|
-
/** Compute a value for a single record (runtime) */
|
|
313
|
-
compute?: (record: ModelInstance) => T;
|
|
314
|
-
/** Compute values for a batch of records (batch async) */
|
|
315
|
-
batchCompute?: (records: ModelInstance[]) => Promise<T[]>;
|
|
316
|
-
/** Raw SQL expression to inline in SELECT */
|
|
317
|
-
sql?: string;
|
|
318
|
-
}
|
|
319
|
-
//#endregion
|
|
320
|
-
//#region src/model/attribute.d.ts
|
|
321
|
-
/**
|
|
322
|
-
* Defines an accessor (`get`) and/or mutator (`set`) for a model attribute.
|
|
323
|
-
*
|
|
324
|
-
* ### Accessor (get)
|
|
325
|
-
* Transforms the attribute value when read via `model.get()` or `model.$toJSON()`.
|
|
326
|
-
* Receives the casted value and the model instance.
|
|
327
|
-
*
|
|
328
|
-
* ### Mutator (set)
|
|
329
|
-
* Transforms the attribute value when written via `model.set()`, `model.fill()`,
|
|
330
|
-
* or during model creation (`Model.insert()` / `Model.create()`).
|
|
331
|
-
* Receives the raw input value and the model instance, returns the value to store.
|
|
332
|
-
* Applied **before** type casting.
|
|
333
|
-
*
|
|
334
|
-
* ### Usage
|
|
335
|
-
* ```ts
|
|
336
|
-
* defineModel('users', {
|
|
337
|
-
* columns: { id, name, password, ... },
|
|
338
|
-
* attributes: {
|
|
339
|
-
* password: Attribute.make({
|
|
340
|
-
* set: (value) => Bun.password.hashSync(value, { algorithm: 'bcrypt' }),
|
|
341
|
-
* get: () => '***',
|
|
342
|
-
* }),
|
|
343
|
-
* fullName: Attribute.make({
|
|
344
|
-
* get: (_, instance) => `${instance.get('firstName')} ${instance.get('lastName')}`,
|
|
345
|
-
* }),
|
|
346
|
-
* },
|
|
347
|
-
* })
|
|
348
|
-
* ```
|
|
349
|
-
*/
|
|
350
|
-
declare class Attribute<T = any> {
|
|
351
|
-
readonly get?: ((value: T, instance: ModelInstance) => any) | undefined;
|
|
352
|
-
readonly set?: ((value: any, instance: ModelInstance) => T) | undefined;
|
|
353
|
-
private constructor();
|
|
354
|
-
static make<T = any>(config: {
|
|
355
|
-
/** Transform the attribute value when read. Receives (castedValue, instance). */get?: (value: T, instance: ModelInstance) => any; /** Transform the attribute value when written. Receives (rawValue, instance), returns value to store. */
|
|
356
|
-
set?: (value: any, instance: ModelInstance) => T;
|
|
357
|
-
}): Attribute<T>;
|
|
358
|
-
}
|
|
359
|
-
//#endregion
|
|
360
|
-
//#region src/model/types.d.ts
|
|
361
|
-
interface ModelInstance {
|
|
362
|
-
readonly exists: boolean;
|
|
363
|
-
readonly attributes: Record<string, unknown>;
|
|
364
|
-
readonly dirtyAttributes: Record<string, unknown>;
|
|
365
|
-
isDirty(key?: string): boolean;
|
|
366
|
-
get<T = unknown>(key: string): T;
|
|
367
|
-
set(key: string, value: unknown): void;
|
|
368
|
-
fill(data: Record<string, unknown>): void;
|
|
369
|
-
reset(): void;
|
|
370
|
-
$getRelation<T = unknown>(name: string): T;
|
|
371
|
-
$setRelation(name: string, value: unknown): void;
|
|
372
|
-
$hasRelation(name: string): boolean;
|
|
373
|
-
$relationData(): Record<string, unknown>;
|
|
374
|
-
$load(...relations: string[]): Promise<void>;
|
|
375
|
-
$related(name: string): RelationQuery;
|
|
376
|
-
$save(): Promise<this>;
|
|
377
|
-
$delete(): Promise<void>;
|
|
378
|
-
$forceDelete(): Promise<void>;
|
|
379
|
-
$restore(): Promise<void>;
|
|
380
|
-
$trashed(): boolean;
|
|
381
|
-
$reload(): Promise<void>;
|
|
382
|
-
$toJSON(): Record<string, unknown>;
|
|
383
|
-
toJSON(): Record<string, unknown>;
|
|
384
|
-
}
|
|
385
|
-
interface ModelDefinition<TColumns extends ColumnShape = ColumnShape> {
|
|
386
|
-
readonly table: string;
|
|
387
|
-
readonly columns: TColumns;
|
|
388
|
-
readonly relations: Record<string, Relation>;
|
|
389
|
-
readonly name: string;
|
|
390
|
-
_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>;
|
|
399
|
-
delete(id: number | string): Promise<void>;
|
|
400
|
-
insertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: InsertGraphOptions): Promise<any>;
|
|
401
|
-
upsertGraph(data: Record<string, unknown> | Record<string, unknown>[], options?: UpsertGraphOptions): Promise<any>;
|
|
402
|
-
hydrate(row: Record<string, unknown>): ModelInstance;
|
|
403
|
-
use(plugin: Plugin): ModelDefinition;
|
|
404
|
-
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;
|
|
406
|
-
getHooks(): HookManager;
|
|
407
|
-
beforeDelete(callback: StaticHookCallback): () => void;
|
|
408
|
-
afterDelete(callback: StaticHookCallback): () => void;
|
|
409
|
-
beforeUpdate(callback: StaticHookCallback): () => void;
|
|
410
|
-
afterUpdate(callback: StaticHookCallback): () => void;
|
|
411
|
-
beforeCreate(callback: StaticHookCallback): () => void;
|
|
412
|
-
afterCreate(callback: StaticHookCallback): () => void;
|
|
413
|
-
beforeFind(callback: StaticHookCallback): () => void;
|
|
414
|
-
afterFind(callback: StaticHookCallback): () => void;
|
|
415
|
-
addGlobalScope(name: string, callback: (qb: QueryBuilder) => void): void;
|
|
416
|
-
removeGlobalScope(name: string): void;
|
|
417
|
-
getGlobalScopes(): Map<string, (qb: QueryBuilder) => void> | undefined;
|
|
418
|
-
registerTimestamps?(createdAtCol?: string, updatedAtCol?: string): void;
|
|
419
|
-
registerSoftDeletes?(deletedAtCol?: string): void;
|
|
420
|
-
discover?(): Promise<never>;
|
|
421
|
-
_init(orm: ORMLike): void;
|
|
422
|
-
}
|
|
423
|
-
interface ModelConfig<TColumns extends ColumnShape = ColumnShape> {
|
|
424
|
-
columns: TColumns;
|
|
425
|
-
relations?: Record<string, Relation>;
|
|
426
|
-
casts?: Record<string, string>;
|
|
427
|
-
/** Per-attribute accessors (`get`) and/or mutators (`set`). See {@link Attribute.make}. */
|
|
428
|
-
attributes?: Record<string, Attribute<any>>;
|
|
429
|
-
hidden?: string[];
|
|
430
|
-
visible?: string[];
|
|
431
|
-
appends?: string[];
|
|
432
|
-
computed?: Record<string, ComputedColumn>;
|
|
433
|
-
}
|
|
434
|
-
//#endregion
|
|
435
|
-
//#region src/collection/index.d.ts
|
|
436
|
-
interface Collection {
|
|
437
|
-
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;
|
|
447
|
-
isEmpty(): boolean;
|
|
448
|
-
isNotEmpty(): boolean;
|
|
449
|
-
get(key: string): unknown[];
|
|
450
|
-
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[];
|
|
464
|
-
sum(key: string): number;
|
|
465
|
-
avg(key: string): number;
|
|
466
|
-
min(key: string): number;
|
|
467
|
-
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;
|
|
476
|
-
//#endregion
|
|
477
|
-
//#region src/model/define.d.ts
|
|
478
|
-
declare function defineModel<TColumns extends ColumnShape>(table: string, config: ModelConfig<TColumns>): ModelDefinition<TColumns>;
|
|
479
|
-
//#endregion
|
|
480
|
-
export { PaginatorJson as A, InsertGraphOptions as C, RelationType as D, RelationOptions as E, Database as M, PaginatedResult as O, QueryBuilder as S, Relation as T, createColumn as _, ModelDefinition as a, Plugin as b, HookCallback as c, createHookManager as d, ModelId as f, ColumnValue as g, ColumnShape as h, ModelConfig as i, createPaginator as j, Paginator as k, HookManager as l, Column as m, Collection as n, ModelInstance as o, ORMLike as p, createCollection as r, Attribute as s, defineModel as t, LifecycleEvent as u, Constraint as v, UpsertGraphOptions as w, createQueryBuilder as x, SchemaConfig as y };
|
package/dist/migrations/cli.mjs
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { a as loadMigrationFiles, i as loadConfig, n as createMigrationGenerator, t as createMigrationRunner } from "../runner-DQ7uT6LC.mjs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
-
import cac from "cac";
|
|
5
|
-
import ora from "ora";
|
|
6
|
-
//#region src/migrations/cli.ts
|
|
7
|
-
async function run() {
|
|
8
|
-
const cli = cac("peta");
|
|
9
|
-
cli.command("migrate:init", "Create migrations directory and tracking table").action(async () => {
|
|
10
|
-
const config = await loadConfig();
|
|
11
|
-
const spinner = ora("Setting up migrations...").start();
|
|
12
|
-
mkdirSync(config.migrationsDir, { recursive: true });
|
|
13
|
-
await createMigrationRunner(config.getKysely()).ensureTable();
|
|
14
|
-
spinner.succeed(`Migrations directory created at ${config.migrationsDir}`);
|
|
15
|
-
});
|
|
16
|
-
cli.command("migrate:generate [name]", "Generate initial migration from models").action(async (name) => {
|
|
17
|
-
const config = await loadConfig();
|
|
18
|
-
const spinner = ora("Generating migration...").start();
|
|
19
|
-
const code = createMigrationGenerator().generateInitialMigration(/* @__PURE__ */ new Map(), { name: name ?? "Initial" });
|
|
20
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T.Z]/g, "").slice(0, 14);
|
|
21
|
-
const safeName = (name ?? "initial").replace(/[^a-zA-Z0-9_]/g, "_");
|
|
22
|
-
const filename = resolve(config.migrationsDir, `${timestamp}_${safeName}.ts`);
|
|
23
|
-
mkdirSync(config.migrationsDir, { recursive: true });
|
|
24
|
-
writeFileSync(filename, code);
|
|
25
|
-
spinner.succeed(`Created ${filename}`);
|
|
26
|
-
});
|
|
27
|
-
cli.command("migrate:up", "Apply pending migrations").action(async () => {
|
|
28
|
-
const config = await loadConfig();
|
|
29
|
-
const migrations = await loadMigrationFiles(config.migrationsDir);
|
|
30
|
-
if (migrations.length === 0) {
|
|
31
|
-
console.log("No migration files found.");
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const runner = createMigrationRunner(config.getKysely());
|
|
35
|
-
const status = await runner.status(migrations);
|
|
36
|
-
if (status.pending.length === 0) {
|
|
37
|
-
console.log("All migrations have been applied.");
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const spinner = ora(`Running ${status.pending.length} migration(s)...`).start();
|
|
41
|
-
await runner.up(migrations);
|
|
42
|
-
spinner.succeed(`Applied ${(await runner.getCompleted()).length} migration(s)`);
|
|
43
|
-
});
|
|
44
|
-
cli.command("migrate:down", "Rollback last batch").action(async () => {
|
|
45
|
-
const config = await loadConfig();
|
|
46
|
-
const migrations = await loadMigrationFiles(config.migrationsDir);
|
|
47
|
-
if (migrations.length === 0) {
|
|
48
|
-
console.log("No migration files found.");
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const runner = createMigrationRunner(config.getKysely());
|
|
52
|
-
if ((await runner.getCompleted()).length === 0) {
|
|
53
|
-
console.log("Nothing to rollback.");
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
const spinner = ora("Rolling back...").start();
|
|
57
|
-
await runner.down(migrations);
|
|
58
|
-
spinner.succeed("Rolled back");
|
|
59
|
-
});
|
|
60
|
-
cli.command("migrate:status", "Show migration status").action(async () => {
|
|
61
|
-
const config = await loadConfig();
|
|
62
|
-
const migrations = await loadMigrationFiles(config.migrationsDir);
|
|
63
|
-
const { completed, pending } = await createMigrationRunner(config.getKysely()).status(migrations);
|
|
64
|
-
console.log(`\n Completed: ${completed.length}`);
|
|
65
|
-
for (const m of completed) console.log(` ✓ ${m.name}`);
|
|
66
|
-
console.log(`\n Pending: ${pending.length}`);
|
|
67
|
-
for (const m of pending) console.log(` · ${m.name}`);
|
|
68
|
-
console.log();
|
|
69
|
-
});
|
|
70
|
-
cli.help();
|
|
71
|
-
cli.parse();
|
|
72
|
-
}
|
|
73
|
-
//#endregion
|
|
74
|
-
export { run };
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { a as ModelDefinition } from "../index-BdJnSMYi.mjs";
|
|
2
|
-
import { Kysely } from "kysely";
|
|
3
|
-
|
|
4
|
-
//#region src/migrations/types.d.ts
|
|
5
|
-
interface MigrationFile {
|
|
6
|
-
name: string;
|
|
7
|
-
up: (db: Kysely<unknown>) => Promise<void>;
|
|
8
|
-
down: (db: Kysely<unknown>) => Promise<void>;
|
|
9
|
-
}
|
|
10
|
-
interface MigrationRecord {
|
|
11
|
-
name: string;
|
|
12
|
-
appliedAt: string;
|
|
13
|
-
}
|
|
14
|
-
interface MigrationStatus {
|
|
15
|
-
completed: MigrationRecord[];
|
|
16
|
-
pending: MigrationFile[];
|
|
17
|
-
}
|
|
18
|
-
interface PetaMigrateConfig {
|
|
19
|
-
migrationsDir: string;
|
|
20
|
-
models: string[] | string;
|
|
21
|
-
getKysely: () => Kysely<unknown>;
|
|
22
|
-
}
|
|
23
|
-
interface ResolvedConfig {
|
|
24
|
-
migrationsDir: string;
|
|
25
|
-
models: string[] | string;
|
|
26
|
-
getKysely: () => Kysely<unknown>;
|
|
27
|
-
}
|
|
28
|
-
//#endregion
|
|
29
|
-
//#region src/migrations/config.d.ts
|
|
30
|
-
declare function defineConfig(config: PetaMigrateConfig): PetaMigrateConfig;
|
|
31
|
-
declare function loadConfig(): Promise<ResolvedConfig>;
|
|
32
|
-
declare function loadMigrationFiles(dir: string): Promise<MigrationFile[]>;
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/migrations/generator.d.ts
|
|
35
|
-
interface GeneratorOptions {
|
|
36
|
-
name?: string;
|
|
37
|
-
}
|
|
38
|
-
interface MigrationGenerator {
|
|
39
|
-
generateInitialMigration(models: Map<string, ModelDefinition>, options?: GeneratorOptions): string;
|
|
40
|
-
}
|
|
41
|
-
declare function createMigrationGenerator(): MigrationGenerator;
|
|
42
|
-
//#endregion
|
|
43
|
-
//#region src/migrations/runner.d.ts
|
|
44
|
-
interface MigrationRunner {
|
|
45
|
-
ensureTable(): Promise<void>;
|
|
46
|
-
getCompleted(): Promise<MigrationRecord[]>;
|
|
47
|
-
up(migrations: MigrationFile[]): Promise<void>;
|
|
48
|
-
down(migrations: MigrationFile[]): Promise<void>;
|
|
49
|
-
status(migrations: MigrationFile[]): Promise<MigrationStatus>;
|
|
50
|
-
}
|
|
51
|
-
declare function createMigrationRunner(db: Kysely<Record<string, never>>, table?: string): MigrationRunner;
|
|
52
|
-
//#endregion
|
|
53
|
-
export { type GeneratorOptions, type MigrationFile, type MigrationGenerator, type MigrationRecord, type MigrationRunner, type MigrationStatus, type PetaMigrateConfig, type ResolvedConfig, createMigrationGenerator, createMigrationRunner, defineConfig, loadConfig, loadMigrationFiles };
|
package/dist/runner-DQ7uT6LC.mjs
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { sql } from "kysely";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
//#region src/migrations/config.ts
|
|
4
|
-
function defineConfig(config) {
|
|
5
|
-
return config;
|
|
6
|
-
}
|
|
7
|
-
async function loadConfig() {
|
|
8
|
-
const candidates = [
|
|
9
|
-
"peta.config.ts",
|
|
10
|
-
"peta.config.js",
|
|
11
|
-
"peta.config.mjs"
|
|
12
|
-
];
|
|
13
|
-
let mod = null;
|
|
14
|
-
for (const file of candidates) try {
|
|
15
|
-
mod = await import(resolve(process.cwd(), file));
|
|
16
|
-
break;
|
|
17
|
-
} catch {}
|
|
18
|
-
if (!mod) throw new Error("No peta.config.ts found. Create one in your project root.");
|
|
19
|
-
const config = mod.default ?? mod;
|
|
20
|
-
return {
|
|
21
|
-
migrationsDir: config.migrationsDir,
|
|
22
|
-
models: config.models,
|
|
23
|
-
getKysely: config.getKysely
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
async function loadMigrationFiles(dir) {
|
|
27
|
-
const { readdirSync } = await import("node:fs");
|
|
28
|
-
const files = readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".js")).sort();
|
|
29
|
-
const migrations = [];
|
|
30
|
-
for (const file of files) {
|
|
31
|
-
const mod = await import(resolve(dir, file));
|
|
32
|
-
if (mod.up) migrations.push({
|
|
33
|
-
name: file.replace(/\.(ts|js)$/, ""),
|
|
34
|
-
up: mod.up,
|
|
35
|
-
down: mod.down ?? (async () => {})
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
return migrations;
|
|
39
|
-
}
|
|
40
|
-
//#endregion
|
|
41
|
-
//#region src/migrations/generator.ts
|
|
42
|
-
function createMigrationGenerator() {
|
|
43
|
-
function generateInitialMigration(models, options = {}) {
|
|
44
|
-
options.name;
|
|
45
|
-
const parts = [];
|
|
46
|
-
const indexParts = [];
|
|
47
|
-
const warnings = [];
|
|
48
|
-
const registeredTables = new Set([...models.values()].map((m) => m.table).filter(Boolean));
|
|
49
|
-
for (const [, modelDef] of models) {
|
|
50
|
-
const table = modelDef.table;
|
|
51
|
-
if (!table) continue;
|
|
52
|
-
parts.push(generateCreateTable(table, modelDef.columns));
|
|
53
|
-
for (const [colName, col] of Object.entries(modelDef.columns)) if (col.hasConstraint("index") && !col.isPrimaryKey && !col.isUnique) indexParts.push(generateCreateIndex(table, colName));
|
|
54
|
-
for (const [, rel] of Object.entries(modelDef.relations ?? {})) if (rel.type === "manyToMany") {
|
|
55
|
-
const through = rel.throughTable;
|
|
56
|
-
if (through && !registeredTables.has(through)) warnings.push(`// ⚠ Detected ManyToMany "${modelDef.name}" references table "${through}" but no model is registered for it.\n// Add a model to include the pivot table.`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const upBody = [...parts, ...indexParts].join("\n\n");
|
|
60
|
-
const downTables = [...models.values()].filter((m) => m.table).map((m) => ` await db.schema.dropTable("${m.table}").ifExists().execute()`).join("\n");
|
|
61
|
-
return `import type { Kysely } from "kysely"\nimport { sql } from "kysely"\n\nexport async function up(db: Kysely<any>): Promise<void> {\n${warnings.length > 0 ? ` // Warnings:\n${warnings.join("\n")}\n\n` : ""}${upBody}\n}\n\nexport async function down(db: Kysely<any>): Promise<void> {\n${downTables}\n}\n`;
|
|
62
|
-
}
|
|
63
|
-
return { generateInitialMigration };
|
|
64
|
-
}
|
|
65
|
-
function generateCreateTable(table, columns) {
|
|
66
|
-
const lines = [` await db.schema.createTable("${table}").ifNotExists()`];
|
|
67
|
-
for (const [name, col] of Object.entries(columns)) lines.push(` .addColumn("${name}", "${mapType(col)}"${columnCallback(col)})`);
|
|
68
|
-
lines.push(" .execute()");
|
|
69
|
-
return lines.join("\n");
|
|
70
|
-
}
|
|
71
|
-
function generateCreateIndex(table, column) {
|
|
72
|
-
return [
|
|
73
|
-
` await db.schema.createIndex("${table}_${column}_index")`,
|
|
74
|
-
` .on("${table}")`,
|
|
75
|
-
` .column("${column}")`,
|
|
76
|
-
" .execute()"
|
|
77
|
-
].join("\n");
|
|
78
|
-
}
|
|
79
|
-
function columnCallback(col) {
|
|
80
|
-
const calls = [];
|
|
81
|
-
if (col.isPrimaryKey) {
|
|
82
|
-
if (col.dataType === "integer") calls.push("autoIncrement()");
|
|
83
|
-
calls.push("primaryKey()");
|
|
84
|
-
}
|
|
85
|
-
if (!col.isNullable && !col.isPrimaryKey) calls.push("notNull()");
|
|
86
|
-
const dv = col.defaultValue;
|
|
87
|
-
if (dv !== void 0 && typeof dv !== "function") calls.push(`defaultTo(${JSON.stringify(dv)})`);
|
|
88
|
-
if (col.isUnique && !col.isPrimaryKey) calls.push("unique()");
|
|
89
|
-
const refConstraint = col.constraints.find((c) => c.type === "references");
|
|
90
|
-
if (refConstraint?.args[0]) {
|
|
91
|
-
const targetTable = (typeof refConstraint.args[0] === "function" ? refConstraint.args[0]() : refConstraint.args[0])?.table;
|
|
92
|
-
const targetColumns = refConstraint.args[1];
|
|
93
|
-
if (typeof targetTable === "string" && targetTable && targetColumns?.length) calls.push(`references("${targetTable}.${targetColumns[0]}")`);
|
|
94
|
-
}
|
|
95
|
-
return calls.length === 0 ? "" : `, (c) => c.${calls.join(".")}`;
|
|
96
|
-
}
|
|
97
|
-
function mapType(col) {
|
|
98
|
-
switch (col.dataType) {
|
|
99
|
-
case "integer":
|
|
100
|
-
case "smallint":
|
|
101
|
-
case "bigint":
|
|
102
|
-
case "text":
|
|
103
|
-
case "boolean":
|
|
104
|
-
case "timestamp":
|
|
105
|
-
case "date":
|
|
106
|
-
case "float":
|
|
107
|
-
case "double":
|
|
108
|
-
case "uuid": return col.dataType;
|
|
109
|
-
case "string": {
|
|
110
|
-
const max = col.args[0];
|
|
111
|
-
return max != null ? `varchar(${max})` : "varchar";
|
|
112
|
-
}
|
|
113
|
-
case "json":
|
|
114
|
-
case "jsonb": return "json";
|
|
115
|
-
case "decimal": {
|
|
116
|
-
const p = col.args[0];
|
|
117
|
-
const s = col.args[1];
|
|
118
|
-
return p != null ? `decimal(${p}, ${s ?? 0})` : "decimal";
|
|
119
|
-
}
|
|
120
|
-
case "enum": return "text";
|
|
121
|
-
default: return col.dataType;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
//#endregion
|
|
125
|
-
//#region src/migrations/runner.ts
|
|
126
|
-
function createMigrationRunner(db, table = "_peta_migrations") {
|
|
127
|
-
async function ensureTable() {
|
|
128
|
-
await db.schema.createTable(table).ifNotExists().addColumn("name", "varchar", (c) => c.notNull().primaryKey()).addColumn("applied_at", "timestamp", (c) => c.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute();
|
|
129
|
-
}
|
|
130
|
-
async function getCompleted() {
|
|
131
|
-
try {
|
|
132
|
-
return (await db.selectFrom(table).select(["name", "applied_at"]).orderBy("name", "asc").execute()).map((r) => ({
|
|
133
|
-
name: String(r.name),
|
|
134
|
-
appliedAt: String(r.applied_at)
|
|
135
|
-
}));
|
|
136
|
-
} catch {
|
|
137
|
-
return [];
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
async function up(migrations) {
|
|
141
|
-
await ensureTable();
|
|
142
|
-
const completed = await getCompleted();
|
|
143
|
-
const completedNames = new Set(completed.map((r) => r.name));
|
|
144
|
-
for (const m of migrations.filter((m) => !completedNames.has(m.name)).sort(byName)) {
|
|
145
|
-
await m.up(db);
|
|
146
|
-
await db.insertInto(table).values({
|
|
147
|
-
name: m.name,
|
|
148
|
-
applied_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
149
|
-
}).execute();
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
async function down(migrations) {
|
|
153
|
-
const completed = await getCompleted();
|
|
154
|
-
if (completed.length === 0 || migrations.length === 0) return;
|
|
155
|
-
for (const m of migrations.filter((m) => completed.some((r) => r.name === m.name)).sort(byName).reverse()) {
|
|
156
|
-
await m.down(db);
|
|
157
|
-
await db.deleteFrom(table).where("name", "=", m.name).execute();
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
async function status(migrations) {
|
|
161
|
-
const completed = await getCompleted();
|
|
162
|
-
const completedNames = new Set(completed.map((r) => r.name));
|
|
163
|
-
return {
|
|
164
|
-
completed,
|
|
165
|
-
pending: migrations.filter((m) => !completedNames.has(m.name)).sort(byName)
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
ensureTable,
|
|
170
|
-
getCompleted,
|
|
171
|
-
up,
|
|
172
|
-
down,
|
|
173
|
-
status
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
function byName(a, b) {
|
|
177
|
-
return a.name.localeCompare(b.name);
|
|
178
|
-
}
|
|
179
|
-
//#endregion
|
|
180
|
-
export { loadMigrationFiles as a, loadConfig as i, createMigrationGenerator as n, defineConfig as r, createMigrationRunner as t };
|