@stacksjs/orm 0.70.87 → 0.70.88
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/package.json +4 -4
- package/dist/auto-crud.d.ts +0 -234
- package/dist/batch-loader.d.ts +0 -19
- package/dist/db.d.ts +0 -7
- package/dist/define-model.d.ts +0 -180
- package/dist/generate-database-schema.d.ts +0 -23
- package/dist/index.d.ts +0 -232
- package/dist/index.js +0 -31
- package/dist/model-types.d.ts +0 -87
- package/dist/paginator-request.d.ts +0 -68
- package/dist/paginator.d.ts +0 -99
- package/dist/subquery.d.ts +0 -22
- package/dist/traits/audit.d.ts +0 -62
- package/dist/traits/billable.d.ts +0 -37
- package/dist/traits/categorizable.d.ts +0 -5
- package/dist/traits/commentable.d.ts +0 -1
- package/dist/traits/likeable.d.ts +0 -1
- package/dist/traits/soft-deletes.d.ts +0 -68
- package/dist/traits/taggable.d.ts +0 -1
- package/dist/traits/two-factor.d.ts +0 -1
- package/dist/transaction.d.ts +0 -67
- package/dist/types.d.ts +0 -59
- package/dist/utils/encrypted.d.ts +0 -81
- package/dist/utils/prunable.d.ts +0 -17
- package/dist/utils.d.ts +0 -22
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/orm",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.88",
|
|
6
6
|
"description": "The Stacks ORM integration",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"bun-query-builder": "^0.1.47"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@stacksjs/config": "0.70.
|
|
65
|
+
"@stacksjs/config": "0.70.88",
|
|
66
66
|
"better-dx": "^0.2.16",
|
|
67
|
-
"@stacksjs/query-builder": "0.70.
|
|
68
|
-
"@stacksjs/types": "0.70.
|
|
67
|
+
"@stacksjs/query-builder": "0.70.88",
|
|
68
|
+
"@stacksjs/types": "0.70.88"
|
|
69
69
|
}
|
|
70
70
|
}
|
package/dist/auto-crud.d.ts
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* True when the error is a unique-constraint violation, across SQLite,
|
|
3
|
-
* MySQL, and Postgres:
|
|
4
|
-
*
|
|
5
|
-
* - SQLite: `SQLITE_CONSTRAINT_UNIQUE` / `SQLITE_CONSTRAINT`
|
|
6
|
-
* - MySQL: `errno: 1062` (ER_DUP_ENTRY)
|
|
7
|
-
* - Postgres: `code: '23505'` (unique_violation)
|
|
8
|
-
* - Generic fallback: message text match — covers wrapped errors from drivers
|
|
9
|
-
* that lose the structured code.
|
|
10
|
-
*
|
|
11
|
-
* Lives here (cycle-free `@stacksjs/orm`) rather than in `@stacksjs/auth`
|
|
12
|
-
* because every framework write path needs it: auto-CRUD routes, commerce/cms
|
|
13
|
-
* write functions, and `@stacksjs/auth`'s `register()` (which re-exports this
|
|
14
|
-
* via './rbac-store-bqb' for back-compat). `@stacksjs/database` is NOT a valid
|
|
15
|
-
* home — its drivers statically import `@stacksjs/orm`, so orm routes importing
|
|
16
|
-
* from database would create a package cycle.
|
|
17
|
-
*
|
|
18
|
-
* Exported for direct unit testing and for callers that map duplicates to
|
|
19
|
-
* their own error (e.g. `register()`'s 409) instead of swallowing them.
|
|
20
|
-
*/
|
|
21
|
-
export declare function isUniqueViolation(err: unknown): boolean;
|
|
22
|
-
/**
|
|
23
|
-
* Classify a write-path error into an HTTP status + JSON body for the
|
|
24
|
-
* auto-CRUD store/update handlers. Three branches, in priority order:
|
|
25
|
-
*
|
|
26
|
-
* 1. HttpError-like (an Error carrying an integer `status` in 400-599) —
|
|
27
|
-
* preserve its status, message and optional `details`. Duck-typed rather
|
|
28
|
-
* than `instanceof HttpError` so this helper stays inline-copyable into the
|
|
29
|
-
* canonical generated routes file without importing @stacksjs/error-handling.
|
|
30
|
-
* Covers the 400/413/422 throws from getRequestBody / validation.
|
|
31
|
-
* 2. Unique-constraint violation — 409 with a clean `${Model} already exists`
|
|
32
|
-
* message (NO raw driver text, which would leak column names in prod).
|
|
33
|
-
* 3. Anything else — the unchanged 500 contract, including `detail: String(err)`.
|
|
34
|
-
*/
|
|
35
|
-
export declare function mapWriteError(err: unknown, modelName: string, op: 'create' | 'update'): { status: number, body: Record<string, unknown> };
|
|
36
|
-
/**
|
|
37
|
-
* Attribute names in model definitions may be camelCase; the migration
|
|
38
|
-
* drivers (database/src/drivers/{sqlite,mysql,postgres}.ts) snake_case them
|
|
39
|
-
* into column names. Write payload keys must be mapped the same way, LAST on
|
|
40
|
-
* the write path — fillable filtering, validation, set-hooks and casts are
|
|
41
|
-
* all keyed by attribute name. Output-identical to @stacksjs/strings
|
|
42
|
-
* snakeCase for word-shaped attribute names (locked in by tests).
|
|
43
|
-
*/
|
|
44
|
-
export declare function toSnakeCase(s: string): string;
|
|
45
|
-
/** Map every key of a write payload to its snake_case column spelling. */
|
|
46
|
-
export declare function toSnakeCaseKeys(data: Record<string, any>): Record<string, any>;
|
|
47
|
-
/**
|
|
48
|
-
* Filter a request body down to fillable fields. Accepts BOTH the
|
|
49
|
-
* attribute-name spelling and its snake_case column spelling on input, so
|
|
50
|
-
* read-modify-write round-trips work (GET responses expose snake_case
|
|
51
|
-
* columns). The result stays keyed by attribute name — setters, casts and
|
|
52
|
-
* validation rules all look fields up by that spelling.
|
|
53
|
-
*/
|
|
54
|
-
export declare function filterFillable(body: any, fillableFields: string[]): Record<string, any>;
|
|
55
|
-
/**
|
|
56
|
-
* Drop attribute keys flagged `hidden: true` from an incoming write body.
|
|
57
|
-
* Must drop BOTH spellings — accepting the snake spelling in filterFillable
|
|
58
|
-
* without this would let `payment_intent_id` sneak past a camelCase hidden
|
|
59
|
-
* marker.
|
|
60
|
-
*/
|
|
61
|
-
export declare function dropHiddenInputs(data: Record<string, any>, hiddenFields: string[]): Record<string, any>;
|
|
62
|
-
/**
|
|
63
|
-
* Strip attribute keys flagged `hidden: true` from an outgoing response
|
|
64
|
-
* record. Must drop BOTH spellings — DB rows come back keyed by snake_case
|
|
65
|
-
* column names, so deleting only the attribute-name spelling lets a
|
|
66
|
-
* camelCase hidden attribute (Transaction's `paymentDetails`) leak as
|
|
67
|
-
* `payment_details` on public reads. Response-side mirror of
|
|
68
|
-
* `dropHiddenInputs`.
|
|
69
|
-
*/
|
|
70
|
-
export declare function stripHidden(record: any, hiddenFields: string[]): any;
|
|
71
|
-
/**
|
|
72
|
-
* Build the read-path column allowlist for a model: a map from BOTH the
|
|
73
|
-
* attribute-name spelling and its snake_case column spelling to the real
|
|
74
|
-
* snake_case column. One map serves `?sort=` and `?<column>=` filters.
|
|
75
|
-
*
|
|
76
|
-
* Why a map and not a set: attribute names may be camelCase
|
|
77
|
-
* (`discountType`) while DB columns are always snake_case (the migration
|
|
78
|
-
* drivers snake_case them — same contract as `toSnakeCaseKeys` on the
|
|
79
|
-
* write path). A set keyed by attribute spelling let `?sort=discountType`
|
|
80
|
-
* through to `orderBy('discountType')` (ghost column → 500) while
|
|
81
|
-
* REJECTING the real column spelling `discount_type`. The map accepts
|
|
82
|
-
* either spelling and always emits the column spelling.
|
|
83
|
-
*
|
|
84
|
-
* Hidden attributes are removed under BOTH spellings — sorting or
|
|
85
|
-
* equality-filtering on a hidden column (`?two_factor_secret=x`) is a
|
|
86
|
-
* blind-enumeration oracle even though the value never appears in the
|
|
87
|
-
* response body.
|
|
88
|
-
*/
|
|
89
|
-
export declare function buildReadColumnMap(attributes: Record<string, unknown> | null | undefined, hiddenFields: string[]): Map<string, string>;
|
|
90
|
-
/**
|
|
91
|
-
* Apply a `?sort=` parameter to a query builder chain. Comma-separated
|
|
92
|
-
* tokens, each optionally `-` prefixed for descending. Tokens are resolved
|
|
93
|
-
* through the `columns` allowlist map (see `buildReadColumnMap`) so either
|
|
94
|
-
* spelling of a declared, non-hidden attribute works and everything else —
|
|
95
|
-
* unknown names, hidden attributes, non-word tokens — is silently skipped
|
|
96
|
-
* (the existing contract, matching the filter loop).
|
|
97
|
-
*
|
|
98
|
-
* Examples:
|
|
99
|
-
* ?sort=name → ORDER BY name ASC
|
|
100
|
-
* ?sort=-rating → ORDER BY rating DESC
|
|
101
|
-
* ?sort=discountType,name → ORDER BY discount_type ASC, name ASC
|
|
102
|
-
*/
|
|
103
|
-
export declare function applySorting(query: any, sortParam: string | null, columns: ReadonlyMap<string, string>): any;
|
|
104
|
-
declare function safeJSON(s: string): unknown;
|
|
105
|
-
declare function safeJSONOrEmpty(_s: string): unknown;
|
|
106
|
-
/**
|
|
107
|
-
* Apply a model's `casts` to a record, in either direction:
|
|
108
|
-
* - `'get'` — DB shape → JS-typed values (read responses)
|
|
109
|
-
* - `'set'` — input → DB shape (write payloads)
|
|
110
|
-
*
|
|
111
|
-
* Casts are declared keyed by attribute name (possibly camelCase:
|
|
112
|
-
* `instantBook: 'boolean'`) but DB rows come back keyed by snake_case
|
|
113
|
-
* column names (`instant_book`) — so each cast is applied under BOTH
|
|
114
|
-
* spellings, whichever is present. A record keyed by attribute names
|
|
115
|
-
* (the write path) behaves exactly as before; a snake-keyed DB row (the
|
|
116
|
-
* read path) now gets its casts instead of leaking raw SQLite `"1"`s.
|
|
117
|
-
*/
|
|
118
|
-
export declare function applyCasts(record: Record<string, any> | null | undefined, casts: Record<string, string | { get: (v: unknown) => unknown, set: (v: unknown) => unknown }> | null | undefined, direction: 'get' | 'set'): any;
|
|
119
|
-
/**
|
|
120
|
-
* Resolve middleware lists for a model's `useApi` trait value (which may be
|
|
121
|
-
* `true` or `{ uri, routes, middleware }`).
|
|
122
|
-
*
|
|
123
|
-
* Secure-by-default: mutating routes (store/update/destroy) get `auth`
|
|
124
|
-
* unless the model explicitly declares `useApi.middleware` — an explicit
|
|
125
|
-
* `middleware: []` is a deliberate opt-out and is honored (with a startup
|
|
126
|
-
* warning at the call site). Read routes stay public unless declared.
|
|
127
|
-
*/
|
|
128
|
-
export declare function resolveApiMiddleware(useApi: unknown): { read: string[], write: string[], declared: boolean };
|
|
129
|
-
/**
|
|
130
|
-
* Resolve `?page=` / `?per_page=` for the index route into a clamped,
|
|
131
|
-
* NaN-safe `{ page, perPage, offset }`.
|
|
132
|
-
*
|
|
133
|
-
* - `page` is clamped to `>= 1` (a `?page=0` / negative would otherwise
|
|
134
|
-
* produce a negative OFFSET), defaulting to 1 on missing/NaN.
|
|
135
|
-
* - `perPage` defaults to {@link INDEX_DEFAULT_PER_PAGE}, is clamped to
|
|
136
|
-
* `>= 1`, and capped at {@link INDEX_MAX_PER_PAGE}.
|
|
137
|
-
*/
|
|
138
|
-
export declare function resolveIndexPageArgs(params: URLSearchParams): { page: number, perPage: number, offset: number };
|
|
139
|
-
/**
|
|
140
|
-
* Build the index pagination `meta`. `hasMore` is the source of truth for
|
|
141
|
-
* "is there a next page" (derived by the route from a `LIMIT perPage + 1`
|
|
142
|
-
* probe fetch), so `next_page_url` stays consistent whether or not a total
|
|
143
|
-
* was counted. When `total` is known, `last_page` uses the
|
|
144
|
-
* `Math.max(1, ceil(total / perPage))` floor from the Paginator interface.
|
|
145
|
-
*/
|
|
146
|
-
export declare function buildIndexMeta(url: URL, page: number, perPage: number, rowCount: number, hasMore: boolean, total?: number): IndexPageMeta;
|
|
147
|
-
/**
|
|
148
|
-
* Flat Laravel paginator shape for the index response top level. Same values
|
|
149
|
-
* as {@link buildIndexMeta} but keyed `current_page` (not `page`) so a
|
|
150
|
-
* generated-endpoint list response deep-equals a `Model.paginate()` envelope.
|
|
151
|
-
* The `page` -> `current_page` rename is the only delta; the value math lives
|
|
152
|
-
* solely in `buildIndexMeta`.
|
|
153
|
-
*/
|
|
154
|
-
export declare function buildIndexPaginator(url: URL, page: number, perPage: number, rowCount: number, hasMore: boolean, total?: number): IndexPaginator;
|
|
155
|
-
/**
|
|
156
|
-
* Columns every auto-CRUD table carries regardless of declared attributes.
|
|
157
|
-
* Members of the read allowlist (sort/filter) alongside the model's own
|
|
158
|
-
* attribute names.
|
|
159
|
-
* @defaultValue `['id', 'uuid', 'created_at', 'updated_at', 'deleted_at']`
|
|
160
|
-
*/
|
|
161
|
-
export declare const SYSTEM_COLUMNS: string[];
|
|
162
|
-
/**
|
|
163
|
-
* Built-in cast resolvers — kept in sync with @stacksjs/orm/define-model.
|
|
164
|
-
* A duplicate here is the simplest way to keep auto-CRUD parity with the
|
|
165
|
-
* model-driven path without introducing a circular import.
|
|
166
|
-
* @defaultValue
|
|
167
|
-
* ```ts
|
|
168
|
-
* {
|
|
169
|
-
* string: { get: (v) => unknown | null, set: (v) => unknown | null },
|
|
170
|
-
* number: { get: (v) => unknown | null, set: (v) => unknown | null },
|
|
171
|
-
* integer: { get: (v) => unknown | null, set: (v) => unknown | null },
|
|
172
|
-
* float: { get: (v) => unknown | null, set: (v) => unknown | null },
|
|
173
|
-
* boolean: { get: (v) => boolean, set: (v) => number },
|
|
174
|
-
* json: { get: (v) => null | unknown, set: (v) => null | unknown },
|
|
175
|
-
* datetime: { get: (v) => Date | null, set: (v) => unknown },
|
|
176
|
-
* date: { get: (v) => Date | null, set: (v) => unknown },
|
|
177
|
-
* array: {
|
|
178
|
-
* get: (v) => never[] | unknown | unknown | never[],
|
|
179
|
-
* set: (v) => null | unknown
|
|
180
|
-
* }
|
|
181
|
-
* }
|
|
182
|
-
* ```
|
|
183
|
-
*/
|
|
184
|
-
export declare const AUTO_CRUD_CASTERS: Record<string, { get: (v: unknown) => unknown, set: (v: unknown) => unknown }>;
|
|
185
|
-
// Default page size for the auto-CRUD index route. Matches the
|
|
186
|
-
// request-aware Model.paginate() / resolvePageArgs default (15) so the
|
|
187
|
-
// REST list endpoint and the in-process paginator agree out of the box.
|
|
188
|
-
export declare const INDEX_DEFAULT_PER_PAGE: 15;
|
|
189
|
-
// Upper bound on ?per_page= so a single request can't ask for an
|
|
190
|
-
// unbounded page and exhaust memory.
|
|
191
|
-
export declare const INDEX_MAX_PER_PAGE: 100;
|
|
192
|
-
/**
|
|
193
|
-
* Pagination `meta` for the auto-CRUD index envelope (`{ data, meta }`).
|
|
194
|
-
*
|
|
195
|
-
* Always carries `page` / `per_page` / `from` / `to` / `has_more_pages`
|
|
196
|
-
* plus `prev_page_url` / `next_page_url`. `total` / `last_page` and the
|
|
197
|
-
* `first_page_url` / `last_page_url` are added only when a total is known
|
|
198
|
-
* (`?with_count=true`).
|
|
199
|
-
*/
|
|
200
|
-
export declare interface IndexPageMeta {
|
|
201
|
-
page: number
|
|
202
|
-
per_page: number
|
|
203
|
-
from: number | null
|
|
204
|
-
to: number | null
|
|
205
|
-
has_more_pages: boolean
|
|
206
|
-
prev_page_url: string | null
|
|
207
|
-
next_page_url: string | null
|
|
208
|
-
total?: number
|
|
209
|
-
last_page?: number
|
|
210
|
-
first_page_url?: string
|
|
211
|
-
last_page_url?: string
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Flat Laravel paginator shape lifted to the index response top level.
|
|
215
|
-
* Mirrors {@link IndexPageMeta} minus `data`/`path` (the route spreads this
|
|
216
|
-
* alongside its own `data`), but keys the current page as `current_page`
|
|
217
|
-
* instead of `page` so a generated-endpoint list response deep-equals a
|
|
218
|
-
* `Model.paginate()` envelope. `total` / `last_page` / `first_page_url` /
|
|
219
|
-
* `last_page_url` stay gated on `total` (`?with_count=true`), matching
|
|
220
|
-
* {@link SimplePaginator} when absent.
|
|
221
|
-
*/
|
|
222
|
-
export declare interface IndexPaginator {
|
|
223
|
-
current_page: number
|
|
224
|
-
per_page: number
|
|
225
|
-
from: number | null
|
|
226
|
-
to: number | null
|
|
227
|
-
has_more_pages: boolean
|
|
228
|
-
prev_page_url: string | null
|
|
229
|
-
next_page_url: string | null
|
|
230
|
-
total?: number
|
|
231
|
-
last_page?: number
|
|
232
|
-
first_page_url?: string
|
|
233
|
-
last_page_url?: string
|
|
234
|
-
}
|
package/dist/batch-loader.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
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/db.d.ts
DELETED
package/dist/define-model.d.ts
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { createBillableMethods } from './traits/billable';
|
|
2
|
-
import { createCategorizableMethods } from './traits/categorizable';
|
|
3
|
-
import { createCommentableMethods } from './traits/commentable';
|
|
4
|
-
import { createLikeableMethods } from './traits/likeable';
|
|
5
|
-
import { createSoftDeleteMethods } from './traits/soft-deletes';
|
|
6
|
-
import { createTaggableMethods } from './traits/taggable';
|
|
7
|
-
import { createTwoFactorMethods } from './traits/two-factor';
|
|
8
|
-
import { type OrmModelDefinition as BQBModelDefinition, type OrmModelStatic } from '@stacksjs/query-builder';
|
|
9
|
-
import type { InferRelationNames } from '@stacksjs/query-builder';
|
|
10
|
-
// Re-export types from bun-query-builder for convenience
|
|
11
|
-
export type { ModelDefinition, InferRelationNames, ModelAttributes, InferModelAttributes, SystemFields, ColumnName, AttributeKeys, FillableKeys, HiddenKeys, ModelInstance, ModelQueryBuilder } from '@stacksjs/query-builder';
|
|
12
|
-
/**
|
|
13
|
-
* Run a callback with model lifecycle events suppressed for its entire
|
|
14
|
-
* (synchronous + async) duration. Any nested awaits inside the callback
|
|
15
|
-
* inherit the suppression via the AsyncLocalStorage propagation.
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```ts
|
|
19
|
-
* await User.withoutEvents(async () => {
|
|
20
|
-
* for (const row of importedRows) await User.create(row) // no events fire
|
|
21
|
-
* })
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
export declare function withoutEvents<T>(fn: () => T | Promise<T>): Promise<T>;
|
|
25
|
-
export declare function defineModel<const TDef extends ModelDefinition>(definition: TDef): StacksModelStatic<TDef>;
|
|
26
|
-
/**
|
|
27
|
-
* Normalize a ModelInstance (or array of them, or already-plain row) into
|
|
28
|
-
* a serialization-ready plain object.
|
|
29
|
-
*
|
|
30
|
-
* Resolves the three shapes a Stacks model query can return:
|
|
31
|
-
* - ModelInstance (find/first/get) → calls toJSON() → strips `hidden` attrs
|
|
32
|
-
* - Bare attribute bag with `_attributes` → returns _attributes as-is
|
|
33
|
-
* - Plain row (already normalized) → returns it unchanged
|
|
34
|
-
*
|
|
35
|
-
* Use `toAttrs(inst)` in actions instead of `inst._attributes ?? inst` —
|
|
36
|
-
* the latter pattern silently leaks `hidden: true` fields (e.g. license_plate,
|
|
37
|
-
* vin, password hashes) into responses.
|
|
38
|
-
*/
|
|
39
|
-
export declare function toAttrs<T = any>(value: any): T;
|
|
40
|
-
/**
|
|
41
|
-
* Custom caster interface for user-defined attribute transformations.
|
|
42
|
-
*/
|
|
43
|
-
export declare interface CasterInterface {
|
|
44
|
-
get(value: unknown): unknown
|
|
45
|
-
set(value: unknown): unknown
|
|
46
|
-
}
|
|
47
|
-
declare interface StacksModelDefinition extends Omit<BQBModelDefinition, 'attributes' | 'indexes' | 'traits'> {
|
|
48
|
-
name: string
|
|
49
|
-
table: string
|
|
50
|
-
primaryKey?: string
|
|
51
|
-
autoIncrement?: boolean
|
|
52
|
-
traits?: NonNullable<BQBModelDefinition['traits']> & Record<string, unknown>
|
|
53
|
-
indexes?: Array<{ name: string, columns: string[], unique?: boolean, where?: string }>
|
|
54
|
-
casts?: Record<string, CastType | CasterInterface>
|
|
55
|
-
attributes: {
|
|
56
|
-
[key: string]: {
|
|
57
|
-
factory?: (faker: any) => any
|
|
58
|
-
[key: string]: any
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
[key: string]: any
|
|
62
|
-
}
|
|
63
|
-
export declare interface TraitMethods {
|
|
64
|
-
_taggable?: ReturnType<typeof createTaggableMethods>
|
|
65
|
-
_categorizable?: ReturnType<typeof createCategorizableMethods>
|
|
66
|
-
_commentable?: ReturnType<typeof createCommentableMethods>
|
|
67
|
-
_billable?: ReturnType<typeof createBillableMethods>
|
|
68
|
-
_likeable?: ReturnType<typeof createLikeableMethods>
|
|
69
|
-
_twoFactor?: ReturnType<typeof createTwoFactorMethods>
|
|
70
|
-
_softDeletes?: ReturnType<typeof createSoftDeleteMethods>
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Built-in cast types for model attributes.
|
|
74
|
-
*
|
|
75
|
-
* ### Timezone contract (stacksjs/stacks#1876 O-5, D-5)
|
|
76
|
-
*
|
|
77
|
-
* `datetime` and `date` casts persist values in **UTC** regardless of
|
|
78
|
-
* which driver is connected. The `set` direction uses
|
|
79
|
-
* `Date.toISOString()`, which always emits `Z`-suffixed UTC. The
|
|
80
|
-
* `get` direction parses the stored string back into a JavaScript
|
|
81
|
-
* `Date`, which represents an instant on the universal timeline —
|
|
82
|
-
* timezone presentation is the caller's responsibility (typically via
|
|
83
|
-
* `Intl.DateTimeFormat` at the render layer, or a Temporal-API
|
|
84
|
-
* adapter).
|
|
85
|
-
*
|
|
86
|
-
* **Why UTC-only:** Per-driver behavior diverges sharply on
|
|
87
|
-
* timezone-aware columns. PostgreSQL has `timestamptz` (timezone-
|
|
88
|
-
* aware); MySQL stores `TIMESTAMP` as UTC but presents in the
|
|
89
|
-
* session timezone; SQLite has no timezone concept at all and stores
|
|
90
|
-
* ISO strings verbatim. The ORM normalizes them to a single
|
|
91
|
-
* convention (UTC on the wire) so multi-driver apps behave the same
|
|
92
|
-
* across environments. Apps that need original-timezone preservation
|
|
93
|
-
* should store the user's TZ as a separate column and convert at
|
|
94
|
-
* the render layer.
|
|
95
|
-
*/
|
|
96
|
-
export type CastType = 'string' | 'number' | 'boolean' | 'json' | 'datetime' | 'date' | 'array' | 'integer' | 'float';
|
|
97
|
-
declare type ModelDefinition = StacksModelDefinition;
|
|
98
|
-
/**
|
|
99
|
-
* Stacks-enhanced model definition.
|
|
100
|
-
*
|
|
101
|
-
* Wraps bun-query-builder's `createModel()` with:
|
|
102
|
-
* - Event dispatching via `traits.observe`
|
|
103
|
-
* - Trait methods (billable, taggable, categorizable, commentable, likeable, 2FA)
|
|
104
|
-
* - Full backward compatibility with generators (migration, routes, dashboard)
|
|
105
|
-
*
|
|
106
|
-
* ### Relationships
|
|
107
|
-
* Each entry in `belongsTo`, `hasMany`, `hasOne`, `belongsToMany`,
|
|
108
|
-
* `hasOneThrough`, and `hasManyThrough` declares a typed relation
|
|
109
|
-
* usable via `.with('relationName')`:
|
|
110
|
-
*
|
|
111
|
-
* ```ts
|
|
112
|
-
* defineModel({
|
|
113
|
-
* belongsTo: ['Author'], // ↪ post.author
|
|
114
|
-
* hasMany: ['Comment'], // ↪ post.comments (lowercase + pluralized)
|
|
115
|
-
* hasOne: ['Cover'], // ↪ post.cover
|
|
116
|
-
* })
|
|
117
|
-
* ```
|
|
118
|
-
*
|
|
119
|
-
* After eager loading the related row(s) are reachable as a property
|
|
120
|
-
* on the instance — `(await Post.with('author').first()).author`.
|
|
121
|
-
*
|
|
122
|
-
* @example
|
|
123
|
-
* ```ts
|
|
124
|
-
* import { defineModel } from '@stacksjs/orm'
|
|
125
|
-
* import { schema } from '@stacksjs/validation'
|
|
126
|
-
*
|
|
127
|
-
* export default defineModel({
|
|
128
|
-
* name: 'Post',
|
|
129
|
-
* table: 'posts',
|
|
130
|
-
* attributes: {
|
|
131
|
-
* title: { type: 'string', fillable: true, validation: { rule: schema.string() } },
|
|
132
|
-
* views: { type: 'number', fillable: true, validation: { rule: schema.number() } },
|
|
133
|
-
* },
|
|
134
|
-
* belongsTo: ['Author'],
|
|
135
|
-
* hasMany: ['Tag', 'Category', 'Comment'],
|
|
136
|
-
* traits: { useTimestamps: true, useUuid: true },
|
|
137
|
-
* } as const)
|
|
138
|
-
*
|
|
139
|
-
* // Result: Post.where('title', 'test') — 'title' narrowed to valid columns
|
|
140
|
-
* // Result: Post.with('author') — 'author' narrowed to valid relations
|
|
141
|
-
* ```
|
|
142
|
-
*/
|
|
143
|
-
export type StacksModelStatic<TDef extends ModelDefinition> = OrmModelStatic<TDef> & TDef & TraitMethods & {
|
|
144
|
-
update: (id: number | string, data: Record<string, unknown>) => ReturnType<OrmModelStatic<TDef>['find']>
|
|
145
|
-
forceUpdate: (id: number | string, data: Record<string, unknown>) => ReturnType<OrmModelStatic<TDef>['find']>
|
|
146
|
-
forceCreate: (data: Record<string, unknown>) => ReturnType<OrmModelStatic<TDef>['create']>
|
|
147
|
-
delete: (id: number | string) => Promise<boolean>
|
|
148
|
-
withoutEvents: <T>(fn: () => T | Promise<T>) => Promise<T>
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Thrown by `Model.findOrFail(id)` (and other strict lookups) when no row matches.
|
|
152
|
-
* Callers can `instanceof` against this to distinguish "missing" from other errors.
|
|
153
|
-
*/
|
|
154
|
-
export declare class ModelNotFoundError extends Error {
|
|
155
|
-
readonly model: string;
|
|
156
|
-
readonly id: number | string | undefined;
|
|
157
|
-
constructor(model: string, id?: number | string);
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Thrown when a write payload (`Model.create` / `Model.update` /
|
|
161
|
-
* `firstOrCreate` / `updateOrCreate`) contains an attribute the model
|
|
162
|
-
* forbids from mass assignment. There are two reasons this fires:
|
|
163
|
-
*
|
|
164
|
-
* • `guarded` — the attribute is explicitly marked `guarded: true`.
|
|
165
|
-
* • `not-fillable` — the model is in *allowlist* mode (at least one
|
|
166
|
-
* attribute has `fillable: true`) and the write payload contains a
|
|
167
|
-
* non-allowlisted field.
|
|
168
|
-
*
|
|
169
|
-
* The check exists to stop unfiltered request payloads from landing
|
|
170
|
-
* directly in the DB. If you genuinely need to write a normally-protected
|
|
171
|
-
* column, use the `force*` escape hatches (`Model.forceCreate(...)`,
|
|
172
|
-
* `Model.forceUpdate(id, ...)`) — those bypass the check by design and
|
|
173
|
-
* make the bypass auditable in code review.
|
|
174
|
-
*/
|
|
175
|
-
export declare class MassAssignmentException extends Error {
|
|
176
|
-
readonly model: string;
|
|
177
|
-
readonly attribute: string;
|
|
178
|
-
readonly reason: 'guarded' | 'not-fillable';
|
|
179
|
-
constructor(model: string, attribute: string, reason: 'guarded' | 'not-fillable');
|
|
180
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Walk userland + framework-default models, return the rendered
|
|
3
|
-
* `database/types.d.ts` content plus a structured per-table summary
|
|
4
|
-
* the CLI can render.
|
|
5
|
-
*/
|
|
6
|
-
export declare function buildDatabaseSchema(options?: GenerateSchemaOptions): Promise<GenerateSchemaResult>;
|
|
7
|
-
/**
|
|
8
|
-
* Pure renderer — for tests that don't want to round-trip through the
|
|
9
|
-
* model loader.
|
|
10
|
-
*/
|
|
11
|
-
export declare function renderDatabaseTypeFile(tables: Array<{ table: string, columns: Record<string, string> }>): string;
|
|
12
|
-
export declare interface GenerateSchemaOptions {
|
|
13
|
-
modelsDir?: string
|
|
14
|
-
defaultsDir?: string
|
|
15
|
-
outFile?: string
|
|
16
|
-
dryRun?: boolean
|
|
17
|
-
}
|
|
18
|
-
export declare interface GenerateSchemaResult {
|
|
19
|
-
outFile: string
|
|
20
|
-
tables: Array<{ table: string, model: string, columns: Record<string, string> }>
|
|
21
|
-
errors: Array<{ file: string, error: string }>
|
|
22
|
-
content: string
|
|
23
|
-
}
|