@stacksjs/orm 0.70.88 → 0.70.91

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.
@@ -0,0 +1,87 @@
1
+ import type { InferAttributes as QueryInferAttributes, InferColumnNames as QueryInferColumnNames, InferFillableAttributes as QueryInferFillableAttributes, InferNumericColumns as QueryInferNumericColumns, ModelRow as QueryModelRow } from '@stacksjs/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 } ? 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
+ // eslint-disable-next-line ts/no-empty-object-type
12
+ export type BelongsToForeignKeys<TDef> = TDef extends { readonly belongsTo: readonly (infer R extends string)[] }
13
+ ? { [K in R as `${Lowercase<K>}_id`]: number }
14
+ : {}
15
+ /**
16
+ * Full database row type: model attributes + system fields (id, uuid, timestamps) + FK columns.
17
+ *
18
+ * @example
19
+ * import type { ModelRow } from '@stacksjs/orm'
20
+ * import type Post from '../models/Post'
21
+ * type PostJsonResponse = ModelRow<typeof Post>
22
+ */
23
+ export type ModelRow<T> = QueryModelRow<T> & BelongsToForeignKeys<Def<T>>;
24
+ /**
25
+ * Same as {@link ModelRow} but with every field optional. Useful for
26
+ * partial-projection reads (`select('id', 'name')`) and test fixtures
27
+ * that don't bother populating every column.
28
+ */
29
+ export type ModelRowLoose<T> = Partial<ModelRow<T>>;
30
+ /**
31
+ * Insertable data type: model attributes + FK columns, all optional.
32
+ *
33
+ * @example
34
+ * import type { NewModelData } from '@stacksjs/orm'
35
+ * import type Post from '../models/Post'
36
+ * type NewPost = NewModelData<typeof Post>
37
+ */
38
+ export type NewModelData<T> = Partial<QueryInferAttributes<T> & BelongsToForeignKeys<Def<T>>>;
39
+ /**
40
+ * Strict insertable shape: only attributes marked `fillable: true` in
41
+ * the model definition (plus belongsTo foreign keys), partial because
42
+ * many fillable columns have factory defaults at the DB layer.
43
+ *
44
+ * Use this when you want compile-time enforcement that consumers
45
+ * can't pass non-fillable fields to `create()` / `insert()`.
46
+ * {@link NewModelData} is the looser sibling that allows any attribute.
47
+ */
48
+ export type ModelCreateData<T> = Partial<QueryInferFillableAttributes<T>> & BelongsToForeignKeys<Def<T>>;
49
+ /** Loose variant of {@link ModelCreateData} — same shape as {@link NewModelData}, aliased for naming-parity with the row types. */
50
+ export type ModelCreateDataLoose<T> = NewModelData<T>;
51
+ /**
52
+ * Updateable data type: model attributes + FK columns, all optional.
53
+ *
54
+ * @example
55
+ * import type { UpdateModelData } from '@stacksjs/orm'
56
+ * import type Post from '../models/Post'
57
+ * type PostUpdate = UpdateModelData<typeof Post>
58
+ */
59
+ export type UpdateModelData<T> = Partial<QueryInferAttributes<T> & BelongsToForeignKeys<Def<T>>>;
60
+ /**
61
+ * Just the attribute records flagged `fillable: true` — useful for
62
+ * code-generators and any helper that needs the typed shape of a
63
+ * model's fillable-attribute config (e.g., admin form schemas).
64
+ */
65
+ export type InferFillableAttributes<T> = {
66
+ [K in keyof QueryInferFillableAttributes<T>]: Def<T> extends { attributes: infer A }
67
+ ? K extends keyof A ? A[K] : never
68
+ : never
69
+ }
70
+ /**
71
+ * Every valid column name for the model (attributes + system fields
72
+ * added by traits like `id`, `uuid`, `created_at`). Useful for
73
+ * constraining query builders that accept a `column` parameter.
74
+ */
75
+ export type InferColumnNames<T> = QueryInferColumnNames<T>;
76
+ /**
77
+ * Attribute keys whose `type` is declared as `'number'` in the model
78
+ * definition. Used to constrain aggregate methods (`sum`, `avg`,
79
+ * `min`, `max`) so they can't be called against string columns.
80
+ *
81
+ * Models that don't declare an explicit `type` per attribute (the
82
+ * common case — most validation rules are inferred from
83
+ * `schema.number()` chains, not declared on `type`) fall back to
84
+ * `AttributeKeys<Def<T>>` here. Tighten by declaring `type: 'number'`
85
+ * on the attribute spec when narrowing matters.
86
+ */
87
+ export type InferNumericColumns<T> = QueryInferNumericColumns<T>;
@@ -0,0 +1,68 @@
1
+ import type { CursorPaginator, Paginator, SimplePaginator } from './paginator';
2
+ /**
3
+ * Resolve `[perPage, page]` for `Model.paginate(...)`. Precedence:
4
+ *
5
+ * 1. Explicit positional arg from the caller (`Model.paginate(20, 3)`).
6
+ * 2. `?per_page=` / `?page=` from the active request (auto-magic).
7
+ * 3. Defaults: `perPage = 15`, `page = 1`.
8
+ *
9
+ * `perPage` from the query string is clamped to `[1, DEFAULT_MAX_PER_PAGE]`.
10
+ * Explicit positional `perPage` is trusted as-is (the caller is the app,
11
+ * not an untrusted client).
12
+ *
13
+ * `page` is clamped to `>= 1` from both sources to avoid `OFFSET -X`-style
14
+ * negatives reaching the driver.
15
+ */
16
+ export declare function resolvePageArgs(perPageArg?: number, pageArg?: number): ResolvedPageArgs;
17
+ /**
18
+ * Resolve cursor args for `Model.cursorPaginate(...)`. Precedence
19
+ * mirrors {@link resolvePageArgs} — explicit > `?cursor=` > null.
20
+ *
21
+ * Cursor values that arrive via the query string (always strings) are
22
+ * fed through {@link parseCursor} so a composite cursor encoded as
23
+ * `JSON.stringify([val1, val2])` reaches bqb as a real array — bqb's
24
+ * `sql(cursor)` interpolation expects an array when the `column` arg
25
+ * is an array (composite-key pagination). Without this parse, the
26
+ * cursor round-trip breaks: serialize → string → wire → string →
27
+ * bqb thinks it's a primitive → wrong query.
28
+ */
29
+ export declare function resolveCursorArgs(perPageArg?: number, cursorArg?: string | number | unknown[] | null): { perPage: number, cursor: unknown };
30
+ /**
31
+ * Parse a cursor value into the shape bqb's `cursorPaginate` expects.
32
+ *
33
+ * - `null` / `undefined` → `null` (first page, no WHERE clause)
34
+ * - Already an array → returned as-is (composite cursor, native form)
35
+ * - String starting with `[` → JSON-parsed back into an array
36
+ * (composite cursor that was serialized for the wire format)
37
+ * - All other strings / primitives → returned as-is (single-column
38
+ * cursor)
39
+ *
40
+ * This is the missing piece in the wire-format round-trip:
41
+ * {@link toCursorPaginator} encodes composite cursors with
42
+ * `JSON.stringify` so they survive a URL query param round-trip, and
43
+ * this function decodes them on the way back in.
44
+ */
45
+ export declare function parseCursor(value: string | number | unknown[] | null | undefined): unknown;
46
+ /**
47
+ * Fill in `path`, `prev_page_url`, `next_page_url` (and `first_page_url`
48
+ * / `last_page_url` for the full {@link Paginator}) from the active
49
+ * request URL. Mutates the paginator in place and returns it for chaining.
50
+ *
51
+ * No-op when no request is in scope — keeps CLI / queue / cron callers
52
+ * unaffected. Preserves all OTHER query params on the request so search
53
+ * filters survive across page navigations (`?status=active&page=2` →
54
+ * `next_page_url` includes `status=active`).
55
+ */
56
+ export declare function enrichPaginatorUrls<T>(paginator: Paginator<T>): Paginator<T>;
57
+ export declare function enrichPaginatorUrls<T>(paginator: SimplePaginator<T>): SimplePaginator<T>;
58
+ export declare function enrichPaginatorUrls<T>(paginator: CursorPaginator<T>): CursorPaginator<T>;
59
+ /** Test helper — reset the lazy-import cache (for tests that mock the
60
+ * router module after first access). */
61
+ export declare function __resetRequestContextCache(): void;
62
+ /**
63
+ * Resolved pagination args ready to pass to bqb's `paginate(perPage, page)`.
64
+ */
65
+ export declare interface ResolvedPageArgs {
66
+ perPage: number
67
+ page: number
68
+ }
@@ -0,0 +1,99 @@
1
+ /** Duck-typed shape check — used by the response serializer (P4) to
2
+ * detect a paginator-shaped return value from an action. */
3
+ export declare function isPaginator<T = unknown>(value: unknown): value is Paginator<T>;
4
+ /** Duck-typed shape check for the simple paginator. */
5
+ export declare function isSimplePaginator<T = unknown>(value: unknown): value is SimplePaginator<T>;
6
+ /** Duck-typed shape check for the cursor paginator. */
7
+ export declare function isCursorPaginator<T = unknown>(value: unknown): value is CursorPaginator<T>;
8
+ /**
9
+ * Convert bun-query-builder's `paginate()` output (`{ data, meta:
10
+ * { perPage, page, total, lastPage } }`) into the canonical Stacks
11
+ * shape. P1 of the migration — the URL fields stay undefined until
12
+ * P2 wires the request-context resolver.
13
+ */
14
+ export declare function toPaginator<T>(result: { data: T[], meta: { perPage: number, page: number, total: number, lastPage: number } }): Paginator<T>;
15
+ /** Convert bqb's `simplePaginate()` output to the canonical
16
+ * {@link SimplePaginator} shape. */
17
+ export declare function toSimplePaginator<T>(result: { data: T[], meta: { perPage: number, page: number, hasMore: boolean } }): SimplePaginator<T>;
18
+ /** Convert bqb's `cursorPaginate()` output to the canonical
19
+ * {@link CursorPaginator} shape. Cursors are serialized to strings
20
+ * via JSON — composite cursors (arrays) survive the round-trip. */
21
+ export declare function toCursorPaginator<T>(result: { data: T[], meta: { perPage: number, nextCursor: unknown, prevCursor: unknown } }): CursorPaginator<T>;
22
+ /**
23
+ * Canonical paginator types + adapters (stacksjs/stacks#1905, P1 from #1910).
24
+ *
25
+ * Four pagination shapes used to ship across the framework — bqb's SQL
26
+ * client `{ data, meta }`, bqb's sync ORM `{ data, total, page, ... }`,
27
+ * the search-engine's `{ hits, total, page, perPage }`, and a fifth
28
+ * declared-but-not-implemented Stacks shape (`{ data, paging, next_cursor }`)
29
+ * that the runtime never actually produced. This module unifies them
30
+ * to the Laravel-flavored, snake_case shapes below.
31
+ *
32
+ * P1 ships only `data` + counts; the URL fields are declared but left
33
+ * `undefined` by default. P2 (stacksjs/stacks#1906) wires them in when
34
+ * a request is in scope.
35
+ *
36
+ * Shape choices:
37
+ * - snake_case for JSON friendliness (matches Laravel's serialized
38
+ * paginator + the existing REST convention across the Stacks API)
39
+ * - `from` / `to` are 1-indexed offsets of the first/last row on the
40
+ * current page; `null` when the page is empty
41
+ * - `has_more_pages` is the cheap boolean apps actually want — saves
42
+ * `current_page < last_page` checks everywhere
43
+ */
44
+ /**
45
+ * Full paginator — knows the total row count so it can compute
46
+ * `last_page`, supports "jump to page N" UI patterns. Costs an extra
47
+ * `COUNT(*)` query; use {@link SimplePaginator} or {@link CursorPaginator}
48
+ * for very large tables.
49
+ */
50
+ export declare interface Paginator<T> {
51
+ data: T[]
52
+ current_page: number
53
+ per_page: number
54
+ total: number
55
+ last_page: number
56
+ from: number | null
57
+ to: number | null
58
+ has_more_pages: boolean
59
+ prev_page_url?: string | null
60
+ next_page_url?: string | null
61
+ first_page_url?: string
62
+ last_page_url?: string
63
+ path?: string
64
+ }
65
+ /**
66
+ * Simple paginator — drops the `COUNT(*)` query. Faster on large tables
67
+ * but can't render "jump to page 5" UIs since `last_page` / `total` are
68
+ * unknown. Suitable for infinite-scroll feeds.
69
+ */
70
+ export declare interface SimplePaginator<T> {
71
+ data: T[]
72
+ current_page: number
73
+ per_page: number
74
+ has_more_pages: boolean
75
+ prev_page_url?: string | null
76
+ next_page_url?: string | null
77
+ path?: string
78
+ }
79
+ /**
80
+ * Cursor (keyset) paginator — uses `WHERE id > :cursor` ordering rather
81
+ * than `LIMIT/OFFSET`, so query cost stays constant regardless of how
82
+ * deep the user has paged. The trade-off is no random-access ("jump to
83
+ * page N"); only prev/next.
84
+ *
85
+ * Cursor values are opaque to the caller — serialize as JSON, send to
86
+ * the client, accept back unchanged. Implementations (cursorPaginate)
87
+ * decide the cursor shape (single column value, base64 tuple for
88
+ * composite keys, etc.).
89
+ */
90
+ export declare interface CursorPaginator<T> {
91
+ data: T[]
92
+ per_page: number
93
+ next_cursor: string | null
94
+ prev_cursor: string | null
95
+ has_more_pages: boolean
96
+ prev_page_url?: string | null
97
+ next_page_url?: string | null
98
+ path?: string
99
+ }
@@ -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,62 @@
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
+ * Resolve the transactional opt-in from a model's `traits.useAudit`
25
+ * declaration. Accepts both `true` (default, best-effort) and
26
+ * `{ transactional: true }` (audit failures roll back the user's
27
+ * write). Centralized so the wrapper functions below have a single
28
+ * boolean to check.
29
+ */
30
+ export declare function resolveAuditOptions(useAudit: unknown): { transactional: boolean };
31
+ /**
32
+ * Wire the audit trait into a model's static surface. Wraps `create`,
33
+ * `update`, and `delete` so each one writes a `model_audits` row after a
34
+ * successful operation. Idempotent against the proxy machinery — relies on
35
+ * the same wrapping pattern used by `applySoftDeletes`.
36
+ *
37
+ * Must run AFTER the static-helpers / cast / soft-delete wrappers have
38
+ * installed their own versions of `create` / `update` / `delete`, so that
39
+ * we wrap the final composed function rather than something that gets
40
+ * shadowed later.
41
+ *
42
+ * **Transactional opt-in (stacksjs/stacks#1876 X-2):** pass
43
+ * `{ transactional: true }` to wrap each create/update/delete in a
44
+ * `db.transaction(...)` so audit-row write failures roll back the
45
+ * underlying user write. Default is best-effort (audit failures are
46
+ * logged but don't abort the operation) — appropriate for a debug /
47
+ * change-log use case but NOT for compliance scenarios where a missing
48
+ * audit entry is itself a failure mode.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * // Best-effort (default): audit failures don't abort writes.
53
+ * defineModel({ traits: { useAudit: true } })
54
+ *
55
+ * // Transactional (compliance): audit failures roll back the write.
56
+ * defineModel({ traits: { useAudit: { transactional: true } } })
57
+ * ```
58
+ */
59
+ export declare function applyAudit(baseModel: Record<string, unknown>, modelName: string, primaryKey?: string, opts?: { transactional?: boolean }): void;
60
+ export declare interface AuditHelpers {
61
+ audits: (id: number | string) => Promise<Array<Record<string, unknown>>>
62
+ }
@@ -0,0 +1,37 @@
1
+ import type Stripe from 'stripe';
2
+ export declare function createBillableMethods(_tableName: string): void;
3
+ /**
4
+ * Stored subscription row shape — narrower than `Record<string, unknown>`
5
+ * so callers get autocompletion on the common fields without us
6
+ * importing the full `subscriptions` model type (which would pull in
7
+ * a circular @stacksjs/orm dependency). Keep in sync with the schema
8
+ * in `database/src/custom/subscriptions.ts`.
9
+ */
10
+ export declare interface StoredSubscriptionRow {
11
+ id: number
12
+ user_id: number
13
+ type: string
14
+ provider_id: string
15
+ provider_status: string
16
+ provider_price_id?: string
17
+ unit_price?: number
18
+ quantity?: number
19
+ trial_ends_at?: string
20
+ ends_at?: string
21
+ provider_type?: string
22
+ last_used_at?: string
23
+ }
24
+ /**
25
+ * Return shape for `newSubscription` / `updateSubscription` — pairs
26
+ * the Stripe-side subscription with the most-recent payment intent so
27
+ * callers can immediately handle 3DS / SCA flows without a second
28
+ * round-trip to Stripe.
29
+ */
30
+ export declare interface NewSubscriptionResult {
31
+ subscription: Stripe.Response<Stripe.Subscription>
32
+ paymentIntent?: Stripe.PaymentIntent
33
+ }
34
+ export declare interface ActiveSubscriptionResult {
35
+ subscription: StoredSubscriptionRow
36
+ providerSubscription: Stripe.Response<Stripe.Subscription>
37
+ }
@@ -0,0 +1,5 @@
1
+ // `db` is a Proxy whose methods are typed via bun-query-builder's generics —
2
+ // resolution to the concrete invocation here can leave methods marked
3
+ // `T | undefined` under strict null checks. Cast through `any` so the trait
4
+ // helpers can call the runtime-defined methods without a guard at every site.
5
+ export declare function createCategorizableMethods(tableName: string): void;
@@ -0,0 +1 @@
1
+ export declare function createCommentableMethods(tableName: string): void;
@@ -0,0 +1 @@
1
+ export declare function createLikeableMethods(tableName: string, options?: { table?: string, foreignKey?: string }): void;
@@ -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 @@
1
+ export declare function createTaggableMethods(tableName: string): void;
@@ -0,0 +1 @@
1
+ export declare function createTwoFactorMethods(): void;
@@ -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,59 @@
1
+ import type { CursorPaginator, Paginator, SimplePaginator } from './paginator';
2
+ import type { Operator } from './subquery';
3
+ export declare interface OrmDriver<T = unknown> {
4
+ find: (id: number) => Promise<T | undefined>
5
+ create: (data: Partial<T>) => Promise<T>
6
+ update: (id: number, data: Partial<T>) => Promise<T | undefined>
7
+ delete: (id: number) => Promise<boolean>
8
+ all: () => Promise<T[]>
9
+ where: (column: string, value: unknown) => Promise<T[]>
10
+ }
11
+ export declare interface SelectedQuery<TTable, TJson, K extends string> {
12
+ where<V = string>(column: keyof TTable, ...args: [V] | [Operator, V]): SelectedQuery<TTable, TJson, K>
13
+ orWhere(...conditions: [keyof TTable, any][]): SelectedQuery<TTable, TJson, K>
14
+ whereIn<V = number>(column: keyof TTable, values: V[]): SelectedQuery<TTable, TJson, K>
15
+ whereNotIn<V = number>(column: keyof TTable, values: V[]): SelectedQuery<TTable, TJson, K>
16
+ whereBetween<V = number>(column: keyof TTable, range: [V, V]): SelectedQuery<TTable, TJson, K>
17
+ whereRef(column: keyof TTable, ...args: string[]): SelectedQuery<TTable, TJson, K>
18
+ when(condition: boolean, callback: (query: SelectedQuery<TTable, TJson, K>) => SelectedQuery<TTable, TJson, K>): SelectedQuery<TTable, TJson, K>
19
+ whereNull(column: keyof TTable): SelectedQuery<TTable, TJson, K>
20
+ whereNotNull(column: keyof TTable): SelectedQuery<TTable, TJson, K>
21
+ whereLike(column: keyof TTable, value: string): SelectedQuery<TTable, TJson, K>
22
+ orderBy(column: keyof TTable, order: 'asc' | 'desc'): SelectedQuery<TTable, TJson, K>
23
+ orderByAsc(column: keyof TTable): SelectedQuery<TTable, TJson, K>
24
+ orderByDesc(column: keyof TTable): SelectedQuery<TTable, TJson, K>
25
+ groupBy(column: keyof TTable): SelectedQuery<TTable, TJson, K>
26
+ having<V = string>(column: keyof TTable, operator: Operator, value: V): SelectedQuery<TTable, TJson, K>
27
+ inRandomOrder(): SelectedQuery<TTable, TJson, K>
28
+ whereColumn(first: keyof TTable, operator: Operator, second: keyof TTable): SelectedQuery<TTable, TJson, K>
29
+ skip(count: number): SelectedQuery<TTable, TJson, K>
30
+ take(count: number): SelectedQuery<TTable, TJson, K>
31
+ distinct(column: keyof TJson): SelectedQuery<TTable, TJson, K>
32
+ join(table: string, firstCol: string, secondCol: string): SelectedQuery<TTable, TJson, K>
33
+ first(): Promise<SelectedResult<TJson, K> | undefined>
34
+ last(): Promise<SelectedResult<TJson, K> | undefined>
35
+ firstOrFail(): Promise<SelectedResult<TJson, K>>
36
+ find(id: number): Promise<SelectedResult<TJson, K> | undefined>
37
+ findOrFail(id: number): Promise<SelectedResult<TJson, K>>
38
+ findMany(ids: number[]): Promise<SelectedResult<TJson, K>[]>
39
+ all(): Promise<SelectedResult<TJson, K>[]>
40
+ get(): Promise<SelectedResult<TJson, K>[]>
41
+ latest(column?: keyof TTable): Promise<SelectedResult<TJson, K> | undefined>
42
+ oldest(column?: keyof TTable): Promise<SelectedResult<TJson, K> | undefined>
43
+ paginate(perPage?: number, page?: number): Promise<Paginator<SelectedResult<TJson, K>>>
44
+ simplePaginate(perPage?: number, page?: number): Promise<SimplePaginator<SelectedResult<TJson, K>>>
45
+ cursorPaginate(
46
+ perPage?: number,
47
+ cursor?: string | number | unknown[] | null,
48
+ column?: keyof TTable | (keyof TTable)[],
49
+ direction?: 'asc' | 'desc',
50
+ ): Promise<CursorPaginator<SelectedResult<TJson, K>>>
51
+ chunk(size: number, callback: (models: SelectedResult<TJson, K>[]) => Promise<void>): Promise<void>
52
+ pluck<PK extends Extract<K | 'id', keyof TJson>>(field: PK): Promise<TJson[PK][]>
53
+ max(field: keyof TTable): Promise<number>
54
+ min(field: keyof TTable): Promise<number>
55
+ avg(field: keyof TTable): Promise<number>
56
+ sum(field: keyof TTable): Promise<number>
57
+ count(): Promise<number>
58
+ }
59
+ export type SelectedResult<TJson, K extends string> = Pick<TJson, Extract<K | 'id', keyof TJson>> & { id: number }