@stacksjs/orm 0.70.88 → 0.70.90
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/auto-crud.d.ts +234 -0
- package/dist/batch-loader.d.ts +19 -0
- package/dist/db.d.ts +7 -0
- package/dist/define-model.d.ts +180 -0
- package/dist/generate-database-schema.d.ts +23 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.js +31 -0
- package/dist/model-types.d.ts +87 -0
- package/dist/paginator-request.d.ts +68 -0
- package/dist/paginator.d.ts +99 -0
- package/dist/subquery.d.ts +22 -0
- package/dist/traits/audit.d.ts +62 -0
- package/dist/traits/billable.d.ts +37 -0
- package/dist/traits/categorizable.d.ts +5 -0
- package/dist/traits/commentable.d.ts +1 -0
- package/dist/traits/likeable.d.ts +1 -0
- package/dist/traits/soft-deletes.d.ts +68 -0
- package/dist/traits/taggable.d.ts +1 -0
- package/dist/traits/two-factor.d.ts +1 -0
- package/dist/transaction.d.ts +67 -0
- package/dist/types.d.ts +59 -0
- package/dist/utils/encrypted.d.ts +81 -0
- package/dist/utils/prunable.d.ts +17 -0
- package/dist/utils.d.ts +22 -0
- package/package.json +5 -5
|
@@ -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;
|
package/dist/types.d.ts
ADDED
|
@@ -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 }
|