@stacksjs/orm 0.70.23 → 0.70.32
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/dist/index.js +132 -3495
- package/dist/src/batch-loader.d.ts +19 -0
- package/dist/src/db.d.ts +7 -0
- package/dist/src/define-model.d.ts +116 -0
- package/dist/src/index.d.ts +157 -0
- package/dist/src/model-types.d.ts +40 -0
- package/dist/src/subquery.d.ts +22 -0
- package/dist/src/traits/audit.d.ts +45 -0
- package/dist/src/traits/soft-deletes.d.ts +68 -0
- package/dist/src/transaction.d.ts +67 -0
- package/dist/src/types.d.ts +55 -0
- package/dist/src/utils/encrypted.d.ts +81 -0
- package/dist/src/utils/prunable.d.ts +17 -0
- package/dist/src/utils.d.ts +24 -0
- package/package.json +26 -10
- package/dist/builder.d.ts +0 -94
- package/dist/db.d.ts +0 -15
- package/dist/generate.d.ts +0 -7
- package/dist/index.d.ts +0 -7
- package/dist/requests.d.ts +0 -1
- package/dist/subquery.d.ts +0 -93
- package/dist/transaction.d.ts +0 -1
- package/dist/types.d.ts +0 -8
- package/dist/utils.d.ts +0 -23
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-tick batched load against a Stacks model. The model object is the
|
|
3
|
+
* batch identity — one queue per `model`. Ids posted in the same
|
|
4
|
+
* microtask are merged into a single `findMany([...])` call.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { batchLoad } from '@stacksjs/orm'
|
|
9
|
+
* import User from '~/app/Models/User'
|
|
10
|
+
*
|
|
11
|
+
* // Without batching: 100 SELECTs
|
|
12
|
+
* for (const id of orderUserIds) await User.find(id)
|
|
13
|
+
*
|
|
14
|
+
* // With batching: 1 SELECT (`WHERE id IN (1, 2, … 100)`)
|
|
15
|
+
* await Promise.all(orderUserIds.map(id => batchLoad(User, id)))
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
// eslint-disable-next-line pickier/no-unused-vars
|
|
19
|
+
export declare function batchLoad<T extends { findMany?: (ids: any[]) => Promise<any[]>, find?: (id: any) => Promise<any> }, K = number | string>(model: T, key: K): Promise<unknown>;
|
package/dist/src/db.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { InferRelationNames } from 'bun-query-builder';
|
|
2
|
+
import { createBillableMethods } from './traits/billable';
|
|
3
|
+
import { createCategorizableMethods } from './traits/categorizable';
|
|
4
|
+
import { createCommentableMethods } from './traits/commentable';
|
|
5
|
+
import { createLikeableMethods } from './traits/likeable';
|
|
6
|
+
import { createSoftDeleteMethods } from './traits/soft-deletes';
|
|
7
|
+
import { createTaggableMethods } from './traits/taggable';
|
|
8
|
+
import { createTwoFactorMethods } from './traits/two-factor';
|
|
9
|
+
// Re-export types from bun-query-builder for convenience
|
|
10
|
+
export type { ModelDefinition, InferRelationNames, ModelAttributes, InferModelAttributes, SystemFields, ColumnName, AttributeKeys, FillableKeys, HiddenKeys, ModelInstance, ModelQueryBuilder } from 'bun-query-builder';
|
|
11
|
+
/**
|
|
12
|
+
* Stacks-enhanced model definition.
|
|
13
|
+
*
|
|
14
|
+
* Wraps bun-query-builder's `createModel()` with:
|
|
15
|
+
* - Event dispatching via `traits.observe`
|
|
16
|
+
* - Trait methods (billable, taggable, categorizable, commentable, likeable, 2FA)
|
|
17
|
+
* - Full backward compatibility with generators (migration, routes, dashboard)
|
|
18
|
+
*
|
|
19
|
+
* ### Relationships
|
|
20
|
+
* Each entry in `belongsTo`, `hasMany`, `hasOne`, `belongsToMany`,
|
|
21
|
+
* `hasOneThrough`, and `hasManyThrough` declares a typed relation
|
|
22
|
+
* usable via `.with('relationName')`:
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* defineModel({
|
|
26
|
+
* belongsTo: ['Author'], // ↪ post.author
|
|
27
|
+
* hasMany: ['Comment'], // ↪ post.comments (lowercase + pluralized)
|
|
28
|
+
* hasOne: ['Cover'], // ↪ post.cover
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* After eager loading the related row(s) are reachable as a property
|
|
33
|
+
* on the instance — `(await Post.with('author').first()).author`.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { defineModel } from '@stacksjs/orm'
|
|
38
|
+
* import { schema } from '@stacksjs/validation'
|
|
39
|
+
*
|
|
40
|
+
* export default defineModel({
|
|
41
|
+
* name: 'Post',
|
|
42
|
+
* table: 'posts',
|
|
43
|
+
* attributes: {
|
|
44
|
+
* title: { type: 'string', fillable: true, validation: { rule: schema.string() } },
|
|
45
|
+
* views: { type: 'number', fillable: true, validation: { rule: schema.number() } },
|
|
46
|
+
* },
|
|
47
|
+
* belongsTo: ['Author'],
|
|
48
|
+
* hasMany: ['Tag', 'Category', 'Comment'],
|
|
49
|
+
* traits: { useTimestamps: true, useUuid: true },
|
|
50
|
+
* } as const)
|
|
51
|
+
*
|
|
52
|
+
* // Result: Post.where('title', 'test') — 'title' narrowed to valid columns
|
|
53
|
+
* // Result: Post.with('author') — 'author' narrowed to valid relations
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function defineModel<const TDef extends ModelDefinition>(definition: TDef): void;
|
|
57
|
+
/**
|
|
58
|
+
* Normalize a ModelInstance (or array of them, or already-plain row) into
|
|
59
|
+
* a serialization-ready plain object.
|
|
60
|
+
*
|
|
61
|
+
* Resolves the three shapes a Stacks model query can return:
|
|
62
|
+
* - ModelInstance (find/first/get) → calls toJSON() → strips `hidden` attrs
|
|
63
|
+
* - Bare attribute bag with `_attributes` → returns _attributes as-is
|
|
64
|
+
* - Plain row (already normalized) → returns it unchanged
|
|
65
|
+
*
|
|
66
|
+
* Use `toAttrs(inst)` in actions instead of `inst._attributes ?? inst` —
|
|
67
|
+
* the latter pattern silently leaks `hidden: true` fields (e.g. license_plate,
|
|
68
|
+
* vin, password hashes) into responses.
|
|
69
|
+
*/
|
|
70
|
+
export declare function toAttrs<T = any>(value: any): T;
|
|
71
|
+
/**
|
|
72
|
+
* Custom caster interface for user-defined attribute transformations.
|
|
73
|
+
*/
|
|
74
|
+
export declare interface CasterInterface {
|
|
75
|
+
get(value: unknown): unknown
|
|
76
|
+
set(value: unknown): unknown
|
|
77
|
+
}
|
|
78
|
+
declare interface StacksModelDefinition {
|
|
79
|
+
name: string
|
|
80
|
+
table: string
|
|
81
|
+
primaryKey?: string
|
|
82
|
+
autoIncrement?: boolean
|
|
83
|
+
traits?: Record<string, unknown>
|
|
84
|
+
indexes?: Array<{ name: string, columns: string[] }>
|
|
85
|
+
casts?: Record<string, CastType | CasterInterface>
|
|
86
|
+
attributes: {
|
|
87
|
+
[key: string]: {
|
|
88
|
+
factory?: (faker: any) => any
|
|
89
|
+
[key: string]: any
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
[key: string]: any
|
|
93
|
+
}
|
|
94
|
+
export declare interface TraitMethods {
|
|
95
|
+
_taggable?: ReturnType<typeof createTaggableMethods>
|
|
96
|
+
_categorizable?: ReturnType<typeof createCategorizableMethods>
|
|
97
|
+
_commentable?: ReturnType<typeof createCommentableMethods>
|
|
98
|
+
_billable?: ReturnType<typeof createBillableMethods>
|
|
99
|
+
_likeable?: ReturnType<typeof createLikeableMethods>
|
|
100
|
+
_twoFactor?: ReturnType<typeof createTwoFactorMethods>
|
|
101
|
+
_softDeletes?: ReturnType<typeof createSoftDeleteMethods>
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Built-in cast types for model attributes.
|
|
105
|
+
*/
|
|
106
|
+
export type CastType = 'string' | 'number' | 'boolean' | 'json' | 'datetime' | 'date' | 'array' | 'integer' | 'float';
|
|
107
|
+
declare type ModelDefinition = StacksModelDefinition;
|
|
108
|
+
/**
|
|
109
|
+
* Thrown by `Model.findOrFail(id)` (and other strict lookups) when no row matches.
|
|
110
|
+
* Callers can `instanceof` against this to distinguish "missing" from other errors.
|
|
111
|
+
*/
|
|
112
|
+
export declare class ModelNotFoundError extends Error {
|
|
113
|
+
readonly model: string;
|
|
114
|
+
readonly id: number | string | undefined;
|
|
115
|
+
constructor(model: string, id?: number | string);
|
|
116
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import '@stacksjs/validation';
|
|
2
|
+
export type { PrunableOptions } from './utils/prunable';
|
|
3
|
+
export type { AuditHelpers } from './traits/audit';
|
|
4
|
+
// Re-export soft-delete option types so user code can `satisfies SoftDeleteOptions`.
|
|
5
|
+
export type { SoftDeleteOptions, SoftDeleteHelpers } from './traits/soft-deletes';
|
|
6
|
+
// Re-export type utilities from bun-query-builder so consumers can infer
|
|
7
|
+
// model types directly from defineModel() definitions
|
|
8
|
+
export type {
|
|
9
|
+
InferAttributes,
|
|
10
|
+
InferPrimaryKey,
|
|
11
|
+
InferRelationNames,
|
|
12
|
+
InferTableName,
|
|
13
|
+
ModelDefinition,
|
|
14
|
+
} from 'bun-query-builder';
|
|
15
|
+
export declare const User: unknown;
|
|
16
|
+
// Same lazy-export pattern for the two queue framework models. The CLI
|
|
17
|
+
// commands `buddy queue:status`, `queue:failed`, `queue:flush`,
|
|
18
|
+
// `queue:inspect`, `queue:monitor`, `queue:clear` import them as
|
|
19
|
+
// `import { Job, FailedJob } from '@stacksjs/orm'`. Prefer the userland
|
|
20
|
+
// publication (`app/Models/Job.ts`, dropped in by `buddy publish:model
|
|
21
|
+
// Job`) so projects that customize the queue model — renamed columns,
|
|
22
|
+
// extra observers, custom traits — see their version.
|
|
23
|
+
export declare const Job: unknown;
|
|
24
|
+
export declare const FailedJob: unknown;
|
|
25
|
+
export declare const Activity: unknown;
|
|
26
|
+
export declare const Author: unknown;
|
|
27
|
+
export declare const Campaign: unknown;
|
|
28
|
+
export declare const Cart: unknown;
|
|
29
|
+
export declare const CartItem: unknown;
|
|
30
|
+
export declare const Category: unknown;
|
|
31
|
+
export declare const Comment: unknown;
|
|
32
|
+
export declare const Coupon: unknown;
|
|
33
|
+
export declare const Customer: unknown;
|
|
34
|
+
export declare const DeliveryRoute: unknown;
|
|
35
|
+
export declare const Deployment: unknown;
|
|
36
|
+
export declare const DigitalDelivery: unknown;
|
|
37
|
+
export declare const Driver: unknown;
|
|
38
|
+
export declare const CampaignSend: unknown;
|
|
39
|
+
export declare const EmailList: unknown;
|
|
40
|
+
export declare const EmailListSubscriber: unknown;
|
|
41
|
+
export declare const ErrorModel: unknown;
|
|
42
|
+
export declare const GiftCard: unknown;
|
|
43
|
+
export declare const LicenseKey: unknown;
|
|
44
|
+
export declare const Log: unknown;
|
|
45
|
+
export declare const LoyaltyPoint: unknown;
|
|
46
|
+
export declare const LoyaltyReward: unknown;
|
|
47
|
+
export declare const Manufacturer: unknown;
|
|
48
|
+
export declare const Notification: unknown;
|
|
49
|
+
export declare const Order: unknown;
|
|
50
|
+
export declare const OrderItem: unknown;
|
|
51
|
+
export declare const Page: unknown;
|
|
52
|
+
export declare const Payment: unknown;
|
|
53
|
+
export declare const PaymentMethod: unknown;
|
|
54
|
+
export declare const PaymentProduct: unknown;
|
|
55
|
+
export declare const PaymentTransaction: unknown;
|
|
56
|
+
export declare const Post: unknown;
|
|
57
|
+
export declare const PrintDevice: unknown;
|
|
58
|
+
export declare const Product: unknown;
|
|
59
|
+
export declare const ProductUnit: unknown;
|
|
60
|
+
export declare const ProductVariant: unknown;
|
|
61
|
+
export declare const Receipt: unknown;
|
|
62
|
+
export declare const Release: unknown;
|
|
63
|
+
export declare const Request: unknown;
|
|
64
|
+
export declare const Review: unknown;
|
|
65
|
+
export declare const ShippingMethod: unknown;
|
|
66
|
+
export declare const ShippingRate: unknown;
|
|
67
|
+
export declare const ShippingZone: unknown;
|
|
68
|
+
export declare const SocialPost: unknown;
|
|
69
|
+
export declare const Subscriber: unknown;
|
|
70
|
+
export declare const SubscriberEmail: unknown;
|
|
71
|
+
export declare const Subscription: unknown;
|
|
72
|
+
export declare const Tag: unknown;
|
|
73
|
+
export declare const TaxRate: unknown;
|
|
74
|
+
export declare const Team: unknown;
|
|
75
|
+
export declare const Transaction: unknown;
|
|
76
|
+
export declare const WaitlistProduct: unknown;
|
|
77
|
+
export declare const WaitlistRestaurant: unknown;
|
|
78
|
+
export declare const Websocket: unknown;
|
|
79
|
+
/** Row type for the polymorphic categories table (categorizable trait). */
|
|
80
|
+
export declare interface CategorizableTable {
|
|
81
|
+
id?: number
|
|
82
|
+
name: string
|
|
83
|
+
slug: string
|
|
84
|
+
description?: string
|
|
85
|
+
is_active: boolean
|
|
86
|
+
categorizable_type: string
|
|
87
|
+
created_at?: string
|
|
88
|
+
updated_at?: string
|
|
89
|
+
}
|
|
90
|
+
/** Row type for the category-model pivot table (categorizable trait). */
|
|
91
|
+
export declare interface CategorizableModelsTable {
|
|
92
|
+
id?: number
|
|
93
|
+
category_id: number
|
|
94
|
+
categorizable_type: string
|
|
95
|
+
categorizable_id: number
|
|
96
|
+
created_at?: string
|
|
97
|
+
updated_at?: string
|
|
98
|
+
}
|
|
99
|
+
/** Row type for the polymorphic comments table (commentable trait). */
|
|
100
|
+
export declare interface CommentablesTable {
|
|
101
|
+
id?: number
|
|
102
|
+
title: string
|
|
103
|
+
body: string
|
|
104
|
+
status: string
|
|
105
|
+
approved_at: number | null
|
|
106
|
+
rejected_at: number | null
|
|
107
|
+
commentables_id: number
|
|
108
|
+
commentables_type: string
|
|
109
|
+
user_id: number | null
|
|
110
|
+
created_at?: string
|
|
111
|
+
updated_at?: string | null
|
|
112
|
+
}
|
|
113
|
+
/** Row type for the polymorphic tags table (taggable trait). */
|
|
114
|
+
export declare interface TaggableTable {
|
|
115
|
+
id?: number
|
|
116
|
+
name: string
|
|
117
|
+
slug: string
|
|
118
|
+
description?: string
|
|
119
|
+
is_active: boolean
|
|
120
|
+
taggable_type: string
|
|
121
|
+
created_at?: string
|
|
122
|
+
updated_at?: string
|
|
123
|
+
}
|
|
124
|
+
// The following type utilities are referenced by the framework but are not
|
|
125
|
+
// yet exported by the installed `bun-query-builder` version. Until upstream
|
|
126
|
+
// catches up we ship structural stubs so consumer code still type-checks.
|
|
127
|
+
// These intentionally fall back to `any` to avoid spurious narrowing errors;
|
|
128
|
+
// once `bun-query-builder` exports the real shapes, remove these stubs.
|
|
129
|
+
export type InferFillableAttributes<_M> = any;
|
|
130
|
+
export type InferNumericColumns<_M> = string;
|
|
131
|
+
export type InferColumnNames<_M> = string;
|
|
132
|
+
export type ModelRow<_M> = any;
|
|
133
|
+
export type ModelRowLoose<_M> = any;
|
|
134
|
+
export type ModelCreateData<_M> = any;
|
|
135
|
+
export type ModelCreateDataLoose<_M> = any;
|
|
136
|
+
/** User model row type — inferred from the User model definition. */
|
|
137
|
+
export type UserModel = ModelRowLoose<unknown>;
|
|
138
|
+
/** Data required to create a new User — inferred fillable attributes. */
|
|
139
|
+
export type NewUser = ModelCreateDataLoose<unknown>;
|
|
140
|
+
export * from './utils/prunable';
|
|
141
|
+
export {
|
|
142
|
+
collectEncryptedAttributes,
|
|
143
|
+
decryptValue,
|
|
144
|
+
encryptValue,
|
|
145
|
+
isEncrypted,
|
|
146
|
+
} from './utils/encrypted';
|
|
147
|
+
// Audit trait public API: setAuditUser is the queue/cron escape hatch for
|
|
148
|
+
// attributing audit rows to a user when there's no current HTTP request.
|
|
149
|
+
export { setAuditUser, createAuditMethods } from './traits/audit';
|
|
150
|
+
export * from './batch-loader';
|
|
151
|
+
export * from './db';
|
|
152
|
+
export * from './subquery';
|
|
153
|
+
export * from './transaction';
|
|
154
|
+
export * from './model-types';
|
|
155
|
+
export * from './types';
|
|
156
|
+
export * from './utils';
|
|
157
|
+
export * from './define-model';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { InferModelAttributes, ModelAttributes, ModelDefinition } from 'bun-query-builder';
|
|
2
|
+
/**
|
|
3
|
+
* Extract the raw ModelDefinition from a defineModel() return value.
|
|
4
|
+
* Uses the getDefinition() accessor that defineModel() provides.
|
|
5
|
+
*/
|
|
6
|
+
export type Def<T> = T extends { getDefinition: () => infer D extends ModelDefinition } ? D : never;
|
|
7
|
+
/**
|
|
8
|
+
* Extract foreign key columns from belongsTo relations.
|
|
9
|
+
* e.g., belongsTo: ['Customer', 'Coupon'] → { customer_id: number, coupon_id: number }
|
|
10
|
+
*/
|
|
11
|
+
export type BelongsToForeignKeys<TDef> = TDef extends { readonly belongsTo: readonly (infer R extends string)[] }
|
|
12
|
+
? { [K in R as `${Lowercase<K>}_id`]: number }
|
|
13
|
+
: {}
|
|
14
|
+
/**
|
|
15
|
+
* Full database row type: model attributes + system fields (id, uuid, timestamps) + FK columns.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* import type { ModelRow } from '@stacksjs/orm'
|
|
19
|
+
* import type Post from '../models/Post'
|
|
20
|
+
* type PostJsonResponse = ModelRow<Post>
|
|
21
|
+
*/
|
|
22
|
+
export type ModelRow<T> = ModelAttributes<Def<T>> & BelongsToForeignKeys<Def<T>>;
|
|
23
|
+
/**
|
|
24
|
+
* Insertable data type: model attributes + FK columns, all optional.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* import type { NewModelData } from '@stacksjs/orm'
|
|
28
|
+
* import type Post from '../models/Post'
|
|
29
|
+
* type NewPost = NewModelData<Post>
|
|
30
|
+
*/
|
|
31
|
+
export type NewModelData<T> = Partial<InferModelAttributes<Def<T>> & BelongsToForeignKeys<Def<T>>>;
|
|
32
|
+
/**
|
|
33
|
+
* Updateable data type: model attributes + FK columns, all optional.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* import type { UpdateModelData } from '@stacksjs/orm'
|
|
37
|
+
* import type Post from '../models/Post'
|
|
38
|
+
* type PostUpdate = UpdateModelData<Post>
|
|
39
|
+
*/
|
|
40
|
+
export type UpdateModelData<T> = Partial<InferModelAttributes<Def<T>> & BelongsToForeignKeys<Def<T>>>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare interface WhereCondition<T, V = any> {
|
|
2
|
+
type: 'and' | 'or'
|
|
3
|
+
method: 'where' | 'whereIn' | 'whereNull' | 'whereNotNull' | 'whereBetween' | 'whereExists'
|
|
4
|
+
column: keyof T
|
|
5
|
+
operator?: Operator
|
|
6
|
+
value?: V
|
|
7
|
+
values?: V[] | [V, V]
|
|
8
|
+
range?: [V, V]
|
|
9
|
+
callback?: (query: SubqueryBuilder<T>) => void
|
|
10
|
+
}
|
|
11
|
+
export type Operator = '=' | '<' | '>' | '<=' | '>=' | '<>' | '!=' | 'like' | 'not like' | 'in' | 'not in' | 'between' | 'not between' | 'is' | 'is not';
|
|
12
|
+
export declare class SubqueryBuilder<T> {
|
|
13
|
+
where<V>(column: keyof T, ...args: [V] | [Operator, V]): void;
|
|
14
|
+
orWhere<V>(column: keyof T, ...args: [V] | [Operator, V]): void;
|
|
15
|
+
whereIn<V>(column: keyof T, values: V[]): void;
|
|
16
|
+
whereNotIn<V>(column: keyof T, values: V[]): void;
|
|
17
|
+
whereNull(column: keyof T): void;
|
|
18
|
+
whereNotNull(column: keyof T): void;
|
|
19
|
+
whereBetween<V>(column: keyof T, range: [V, V]): void;
|
|
20
|
+
whereExists(callback: (query: SubqueryBuilder<T>) => void): void;
|
|
21
|
+
getConditions(): WhereCondition<T>[];
|
|
22
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Override the user id attached to subsequent audit rows. Useful in queue
|
|
3
|
+
* workers, scheduled jobs, and CLI commands where there is no current HTTP
|
|
4
|
+
* request to extract the user from. Pass `null` to clear the override and
|
|
5
|
+
* fall back to the request-derived id.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { setAuditUser } from '@stacksjs/orm'
|
|
10
|
+
*
|
|
11
|
+
* // In a queue job that's running on behalf of user 42:
|
|
12
|
+
* setAuditUser(42)
|
|
13
|
+
* try { await processOrder(orderId) } finally { setAuditUser(null) }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function setAuditUser(id: number | string | null): void;
|
|
17
|
+
/**
|
|
18
|
+
* Build the public-facing audit helper(s). Right now that's just
|
|
19
|
+
* `audits(id)` — the rest is wired up via `applyAudit()` which intercepts
|
|
20
|
+
* the model's write methods.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createAuditMethods(modelName: string): AuditHelpers;
|
|
23
|
+
/**
|
|
24
|
+
* Wire the audit trait into a model's static surface. Wraps `create`,
|
|
25
|
+
* `update`, and `delete` so each one writes a `model_audits` row after a
|
|
26
|
+
* successful operation. Idempotent against the proxy machinery — relies on
|
|
27
|
+
* the same wrapping pattern used by `applySoftDeletes`.
|
|
28
|
+
*
|
|
29
|
+
* Must run AFTER the static-helpers / cast / soft-delete wrappers have
|
|
30
|
+
* installed their own versions of `create` / `update` / `delete`, so that
|
|
31
|
+
* we wrap the final composed function rather than something that gets
|
|
32
|
+
* shadowed later.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* // Inside defineModel():
|
|
37
|
+
* if (definition.traits?.useAudit) {
|
|
38
|
+
* applyAudit(baseModel, definition)
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function applyAudit(baseModel: Record<string, unknown>, modelName: string, primaryKey?: string): void;
|
|
43
|
+
export declare interface AuditHelpers {
|
|
44
|
+
audits: (id: number | string) => Promise<Array<Record<string, unknown>>>
|
|
45
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the runtime methods backing the soft-delete trait. The trait is
|
|
3
|
+
* applied via `applySoftDeletes()` in `define-model.ts`.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* const helpers = createSoftDeleteMethods(model, 'id')
|
|
8
|
+
* await helpers.softDelete(42)
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare function createSoftDeleteMethods(model: SoftDeleteCapableModel, primaryKey?: string): SoftDeleteHelpers;
|
|
12
|
+
/**
|
|
13
|
+
* Convert `traits.useSoftDeletes` into a normalized options object. The
|
|
14
|
+
* trait accepts either `true` (legacy) or `{ cascade: [...] }` (new), so
|
|
15
|
+
* downstream code should always go through this resolver.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* resolveSoftDeleteOptions(true) // → {}
|
|
20
|
+
* resolveSoftDeleteOptions({ cascade: ['posts'] }) // → { cascade: ['posts'] }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveSoftDeleteOptions(value: unknown): SoftDeleteOptions;
|
|
24
|
+
/**
|
|
25
|
+
* Run cascade soft-delete (or restore) for every relation listed in
|
|
26
|
+
* `options.cascade`. Called by `define-model.ts` immediately after the
|
|
27
|
+
* parent's own soft-delete or restore succeeds.
|
|
28
|
+
*
|
|
29
|
+
* IMPORTANT: cascade is fire-and-forget on the audit/observer side — this
|
|
30
|
+
* function awaits each child to ensure ordering (parent before children
|
|
31
|
+
* for delete, vice versa for restore) but does not propagate child
|
|
32
|
+
* failures up to the caller. The parent operation has already committed.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* await cascadeSoftDelete(parentDefinition, options, parentId, 'softDelete')
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function cascadeSoftDelete(parentDefinition: { name: string, hasMany?: ReadonlyArray<string>, hasOne?: ReadonlyArray<string> }, options: SoftDeleteOptions, parentId: number | string, action: 'softDelete' | 'restore'): Promise<void>;
|
|
40
|
+
declare interface SoftDeleteCapableModel {
|
|
41
|
+
where: (...args: unknown[]) => any
|
|
42
|
+
query?: () => any
|
|
43
|
+
delete?: (...args: unknown[]) => unknown
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Object-form options for `traits.useSoftDeletes`.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* traits: {
|
|
51
|
+
* useSoftDeletes: {
|
|
52
|
+
* // Names of relations declared on this model (hasMany / hasOne)
|
|
53
|
+
* // that should be soft-deleted alongside the parent.
|
|
54
|
+
* cascade: ['posts', 'comments'],
|
|
55
|
+
* },
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare interface SoftDeleteOptions {
|
|
60
|
+
cascade?: ReadonlyArray<string>
|
|
61
|
+
}
|
|
62
|
+
export declare interface SoftDeleteHelpers {
|
|
63
|
+
softDelete: (id: number | string) => Promise<boolean>
|
|
64
|
+
restore: (id: number | string) => Promise<boolean>
|
|
65
|
+
forceDelete: (id: number | string) => Promise<boolean>
|
|
66
|
+
withTrashed: () => any
|
|
67
|
+
onlyTrashed: () => any
|
|
68
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { db } from '@stacksjs/database';
|
|
2
|
+
/**
|
|
3
|
+
* Execute a callback within a database transaction.
|
|
4
|
+
*
|
|
5
|
+
* The transaction will automatically commit if the callback succeeds,
|
|
6
|
+
* or rollback if an error is thrown.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* await transaction(async (tx) => {
|
|
11
|
+
* await tx.insertInto('users').values({ name: 'Alice' }).execute()
|
|
12
|
+
* await tx.insertInto('profiles').values({ user_id: 1 }).execute()
|
|
13
|
+
* })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function transaction<T>(callback: (tx: TransactionHandle) => Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
17
|
+
/**
|
|
18
|
+
* Legacy alias for transaction()
|
|
19
|
+
* @deprecated Use transaction() instead
|
|
20
|
+
*/
|
|
21
|
+
export declare function transactionBuilder(callback: () => Promise<void>): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Create a savepoint within a transaction for nested rollback support.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* await transaction(async (tx) => {
|
|
28
|
+
* await tx.insertInto('users').values({ name: 'Bob' }).execute()
|
|
29
|
+
*
|
|
30
|
+
* await savepoint(async (sp) => {
|
|
31
|
+
* await sp.insertInto('logs').values({ action: 'created' }).execute()
|
|
32
|
+
* // If this fails, only this savepoint rolls back
|
|
33
|
+
* })
|
|
34
|
+
* })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function savepoint<T>(callback: (sp: TransactionHandle) => Promise<T>): Promise<T>;
|
|
38
|
+
/**
|
|
39
|
+
* Wrap a function to automatically run within a transaction when called.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const createUserWithProfile = transactional(async (tx, name: string, bio: string) => {
|
|
44
|
+
* const user = await tx.insertInto('users').values({ name }).returningAll().executeTakeFirst()
|
|
45
|
+
* await tx.insertInto('profiles').values({ user_id: user.id, bio }).execute()
|
|
46
|
+
* return user
|
|
47
|
+
* })
|
|
48
|
+
*
|
|
49
|
+
* // Usage - automatically wrapped in transaction
|
|
50
|
+
* const user = await createUserWithProfile('Alice', 'Hello world')
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function transactional<TArgs extends any[], R>(fn: (tx: TransactionHandle, ...args: TArgs) => Promise<R>, options?: TransactionOptions): (...args: TArgs) => Promise<R>;
|
|
54
|
+
export declare interface TransactionOptions {
|
|
55
|
+
retries?: number
|
|
56
|
+
isolation?: 'read committed' | 'repeatable read' | 'serializable'
|
|
57
|
+
readOnly?: boolean
|
|
58
|
+
onRollback?: (error: any) => void
|
|
59
|
+
afterRollback?: () => void
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Transaction handle. Aliases the project's `db` type so callers get
|
|
63
|
+
* the same fluent query API inside the callback as outside, without
|
|
64
|
+
* the previous untyped `(tx: any)` signature that erased intellisense
|
|
65
|
+
* and let typo'd column names slip through to runtime.
|
|
66
|
+
*/
|
|
67
|
+
export type TransactionHandle = typeof db;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Operator } from './subquery';
|
|
2
|
+
export declare interface OrmDriver<T = unknown> {
|
|
3
|
+
find: (id: number) => Promise<T | undefined>
|
|
4
|
+
create: (data: Partial<T>) => Promise<T>
|
|
5
|
+
update: (id: number, data: Partial<T>) => Promise<T | undefined>
|
|
6
|
+
delete: (id: number) => Promise<boolean>
|
|
7
|
+
all: () => Promise<T[]>
|
|
8
|
+
where: (column: string, value: unknown) => Promise<T[]>
|
|
9
|
+
}
|
|
10
|
+
export declare interface SelectedQuery<TTable, TJson, K extends string> {
|
|
11
|
+
where<V = string>(column: keyof TTable, ...args: [V] | [Operator, V]): SelectedQuery<TTable, TJson, K>
|
|
12
|
+
orWhere(...conditions: [keyof TTable, any][]): SelectedQuery<TTable, TJson, K>
|
|
13
|
+
whereIn<V = number>(column: keyof TTable, values: V[]): SelectedQuery<TTable, TJson, K>
|
|
14
|
+
whereNotIn<V = number>(column: keyof TTable, values: V[]): SelectedQuery<TTable, TJson, K>
|
|
15
|
+
whereBetween<V = number>(column: keyof TTable, range: [V, V]): SelectedQuery<TTable, TJson, K>
|
|
16
|
+
whereRef(column: keyof TTable, ...args: string[]): SelectedQuery<TTable, TJson, K>
|
|
17
|
+
when(condition: boolean, callback: (query: SelectedQuery<TTable, TJson, K>) => SelectedQuery<TTable, TJson, K>): SelectedQuery<TTable, TJson, K>
|
|
18
|
+
whereNull(column: keyof TTable): SelectedQuery<TTable, TJson, K>
|
|
19
|
+
whereNotNull(column: keyof TTable): SelectedQuery<TTable, TJson, K>
|
|
20
|
+
whereLike(column: keyof TTable, value: string): SelectedQuery<TTable, TJson, K>
|
|
21
|
+
orderBy(column: keyof TTable, order: 'asc' | 'desc'): SelectedQuery<TTable, TJson, K>
|
|
22
|
+
orderByAsc(column: keyof TTable): SelectedQuery<TTable, TJson, K>
|
|
23
|
+
orderByDesc(column: keyof TTable): SelectedQuery<TTable, TJson, K>
|
|
24
|
+
groupBy(column: keyof TTable): SelectedQuery<TTable, TJson, K>
|
|
25
|
+
having<V = string>(column: keyof TTable, operator: Operator, value: V): SelectedQuery<TTable, TJson, K>
|
|
26
|
+
inRandomOrder(): SelectedQuery<TTable, TJson, K>
|
|
27
|
+
whereColumn(first: keyof TTable, operator: Operator, second: keyof TTable): SelectedQuery<TTable, TJson, K>
|
|
28
|
+
skip(count: number): SelectedQuery<TTable, TJson, K>
|
|
29
|
+
take(count: number): SelectedQuery<TTable, TJson, K>
|
|
30
|
+
distinct(column: keyof TJson): SelectedQuery<TTable, TJson, K>
|
|
31
|
+
join(table: string, firstCol: string, secondCol: string): SelectedQuery<TTable, TJson, K>
|
|
32
|
+
first(): Promise<SelectedResult<TJson, K> | undefined>
|
|
33
|
+
last(): Promise<SelectedResult<TJson, K> | undefined>
|
|
34
|
+
firstOrFail(): Promise<SelectedResult<TJson, K>>
|
|
35
|
+
find(id: number): Promise<SelectedResult<TJson, K> | undefined>
|
|
36
|
+
findOrFail(id: number): Promise<SelectedResult<TJson, K>>
|
|
37
|
+
findMany(ids: number[]): Promise<SelectedResult<TJson, K>[]>
|
|
38
|
+
all(): Promise<SelectedResult<TJson, K>[]>
|
|
39
|
+
get(): Promise<SelectedResult<TJson, K>[]>
|
|
40
|
+
latest(column?: keyof TTable): Promise<SelectedResult<TJson, K> | undefined>
|
|
41
|
+
oldest(column?: keyof TTable): Promise<SelectedResult<TJson, K> | undefined>
|
|
42
|
+
paginate(options?: { limit?: number, offset?: number, page?: number }): Promise<{
|
|
43
|
+
data: SelectedResult<TJson, K>[]
|
|
44
|
+
paging: { total_records: number, page: number, total_pages: number }
|
|
45
|
+
next_cursor: number | null
|
|
46
|
+
}>
|
|
47
|
+
chunk(size: number, callback: (models: SelectedResult<TJson, K>[]) => Promise<void>): Promise<void>
|
|
48
|
+
pluck<PK extends Extract<K | 'id', keyof TJson>>(field: PK): Promise<TJson[PK][]>
|
|
49
|
+
max(field: keyof TTable): Promise<number>
|
|
50
|
+
min(field: keyof TTable): Promise<number>
|
|
51
|
+
avg(field: keyof TTable): Promise<number>
|
|
52
|
+
sum(field: keyof TTable): Promise<number>
|
|
53
|
+
count(): Promise<number>
|
|
54
|
+
}
|
|
55
|
+
export type SelectedResult<TJson, K extends string> = Pick<TJson, Extract<K | 'id', keyof TJson>> & { id: number }
|