@stacksjs/orm 0.70.294 → 0.70.297

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.
@@ -137,6 +137,7 @@ declare function safeJSONOrEmpty(_s: string): unknown;
137
137
  * read path) now gets its casts instead of leaking raw SQLite `"1"`s.
138
138
  */
139
139
  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;
140
+ export declare function validateWriteBody(data: Record<string, any>, model: any, hook: 'creating' | 'updating'): WriteValidationResult;
140
141
  /**
141
142
  * A route path with every parameter name flattened to `{}`.
142
143
  *
@@ -306,3 +307,22 @@ export declare interface IndexPaginator {
306
307
  first_page_url?: string
307
308
  last_page_url?: string
308
309
  }
310
+ /**
311
+ * Run each declared `validation.rule` against a write payload.
312
+ *
313
+ * Returns `{ valid: true }` or `{ valid: false, errors }`. Per-attribute custom
314
+ * messages from `validation.message` override the rule's default text.
315
+ *
316
+ * Fields the caller never sent are skipped on the `updating` hook, so a partial
317
+ * update does not trip a `required` rule on a sibling field it never touched.
318
+ *
319
+ * Lives here rather than in `../routes.ts` so BOTH write paths can reach it.
320
+ * It used to be a local function in that module, which meant the declared rules
321
+ * ran on the generated REST routes and nowhere else: `Model.create()`,
322
+ * `.update()` and `.save()` went straight to the driver, and an over-length
323
+ * value first got noticed by Postgres as a 22001, surfacing as a 500 on
324
+ * whichever endpoint performed the write (stacksjs/stacks#2233). Importing it
325
+ * from `routes.ts` was not an option — that module registers routes on import.
326
+ */
327
+ export type WriteValidationResult = | { valid: true }
328
+ | { valid: false, errors: Record<string, string[]> }
@@ -22,6 +22,17 @@ export type { ModelDefinition, InferRelationNames, ModelAttributes, InferModelAt
22
22
  * ```
23
23
  */
24
24
  export declare function withoutEvents<T>(fn: () => T | Promise<T>): Promise<T>;
25
+ /**
26
+ * Run a callback with model validation suppressed for its entire duration.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * await User.withoutValidation(async () => {
31
+ * for (const row of legacyRows) await User.create(row) // rules do not run
32
+ * })
33
+ * ```
34
+ */
35
+ export declare function withoutValidation<T>(fn: () => T | Promise<T>): Promise<T>;
25
36
  export declare function defineModel<const TDef extends ModelDefinition>(definition: TDef): StacksModelStatic<TDef>;
26
37
  /**
27
38
  * Normalize a ModelInstance (or array of them, or already-plain row) into
@@ -152,6 +163,23 @@ export type StacksModelStatic<TDef extends ModelDefinition> = OrmModelStatic<TDe
152
163
  forceCreate: (data: Record<string, unknown>) => ReturnType<OrmModelStatic<TDef>['create']>
153
164
  delete: (id: number | string) => Promise<boolean>
154
165
  withoutEvents: <T>(fn: () => T | Promise<T>) => Promise<T>
166
+ /** Run `fn` with declared `validation.rule`s suppressed (bulk imports, backfills). */
167
+ withoutValidation: <T>(fn: () => T | Promise<T>) => Promise<T>
168
+ }
169
+ /**
170
+ * Thrown when a direct write fails a declared `validation.rule`.
171
+ *
172
+ * Carries `status = 422` and a per-field `errors` map, matching the shape the
173
+ * generated REST routes already return, so a handler that catches this can
174
+ * respond with the same body it would have produced through auto-CRUD. It is
175
+ * duck-typed by `mapWriteError`, which preserves any integer `status` in
176
+ * 400-599 — so an over-length value now surfaces as a 422 instead of the
177
+ * driver's raw 22001 becoming a 500 (stacksjs/stacks#2233).
178
+ */
179
+ export declare class ModelValidationError extends Error {
180
+ readonly status: number;
181
+ readonly errors: Record<string, string[]>;
182
+ constructor(modelName: string, errors: Record<string, string[]>);
155
183
  }
156
184
  /**
157
185
  * Thrown by `Model.findOrFail(id)` (and other strict lookups) when no row matches.