@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 CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/orm",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.87",
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.87",
65
+ "@stacksjs/config": "0.70.88",
66
66
  "better-dx": "^0.2.16",
67
- "@stacksjs/query-builder": "0.70.87",
68
- "@stacksjs/types": "0.70.87"
67
+ "@stacksjs/query-builder": "0.70.88",
68
+ "@stacksjs/types": "0.70.88"
69
69
  }
70
70
  }
@@ -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
- }
@@ -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
@@ -1,7 +0,0 @@
1
- /**
2
- * Database access re-export
3
- *
4
- * This module re-exports the db instance from @stacksjs/database
5
- * for convenience within the ORM package.
6
- */
7
- export { db } from '@stacksjs/database';
@@ -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
- }