@jwerre/vellum 0.0.1 → 1.1.0

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.
@@ -1,4 +1,9 @@
1
1
  import { Model } from './Model.svelte';
2
+ import { type VellumConfig } from './config.svelte';
3
+ export interface FetchOptions extends Partial<VellumConfig> {
4
+ endpoint?: string;
5
+ search?: Record<string, string | number | boolean>;
6
+ }
2
7
  /**
3
8
  * Abstract base class for managing collections of Model instances.
4
9
  *
@@ -10,7 +15,6 @@ import { Model } from './Model.svelte';
10
15
  * @template T - The data object type that the models represent
11
16
  *
12
17
  * @example
13
- * ```typescript
14
18
  * class UserCollection extends Collection<UserModel, User> {
15
19
  * model = UserModel;
16
20
  * endpoint = () => '/api/users';
@@ -19,7 +23,6 @@ import { Model } from './Model.svelte';
19
23
  * const users = new UserCollection();
20
24
  * await users.fetch(); // Loads users from API
21
25
  * users.add({ name: 'John', email: 'john@example.com' }); // Adds new user
22
- * ```
23
26
  */
24
27
  export declare abstract class Collection<M extends Model<T>, T extends object> {
25
28
  /** Reactive array of model instances in the collection */
@@ -36,7 +39,6 @@ export declare abstract class Collection<M extends Model<T>, T extends object> {
36
39
  * @param models - Optional array of data objects to initialize the collection with
37
40
  *
38
41
  * @example
39
- * ```typescript
40
42
  * // Create empty collection
41
43
  * const collection = new UserCollection();
42
44
  *
@@ -45,7 +47,6 @@ export declare abstract class Collection<M extends Model<T>, T extends object> {
45
47
  * { id: 1, name: 'John' },
46
48
  * { id: 2, name: 'Jane' }
47
49
  * ]);
48
- * ```
49
50
  */
50
51
  constructor(models?: T[]);
51
52
  /** Gets the number of items in the collection */
@@ -57,14 +58,12 @@ export declare abstract class Collection<M extends Model<T>, T extends object> {
57
58
  * @returns The model instance that was added to the collection
58
59
  *
59
60
  * @example
60
- * ```typescript
61
61
  * // Add raw data
62
62
  * const user = collection.add({ name: 'John', email: 'john@example.com' });
63
63
  *
64
64
  * // Add existing model instance
65
65
  * const existingUser = new UserModel({ name: 'Jane' });
66
66
  * collection.add(existingUser);
67
- * ```
68
67
  */
69
68
  add(data: T | M): M;
70
69
  /**
@@ -73,13 +72,11 @@ export declare abstract class Collection<M extends Model<T>, T extends object> {
73
72
  * @param data - An array of raw data objects to populate the collection with
74
73
  *
75
74
  * @example
76
- * ```typescript
77
75
  * // Reset collection with new user data
78
76
  * collection.reset([
79
77
  * { id: 1, name: 'John', email: 'john@example.com' },
80
78
  * { id: 2, name: 'Jane', email: 'jane@example.com' }
81
79
  * ]);
82
- * ```
83
80
  */
84
81
  reset(data: T[]): void;
85
82
  /**
@@ -90,26 +87,24 @@ export declare abstract class Collection<M extends Model<T>, T extends object> {
90
87
  * @returns The first matching item, or undefined if no match is found.
91
88
  *
92
89
  * @example
93
- * ```typescript
94
90
  * // Find a user by ID
95
91
  * const user = collection.find({ id: 123 });
96
92
  *
97
93
  * // Find by multiple properties
98
94
  * const activeAdmin = collection.find({ role: 'admin', status: 'active' });
99
- * ```
100
95
  */
101
96
  find(query: Partial<T>): M | undefined;
102
97
  /**
103
98
  * Fetches data from the server and populates the collection.
104
99
  *
105
100
  * @param options - Configuration options for the fetch request
106
- * @param options.search - Optional search parameters to include in the query string.
101
+ * @param [options.endpoint] - Optional endpoint to use if different than this.endpoint()
102
+ * @param [options.search] - Optional search parameters to include in the query string.
107
103
  * Keys and values will be converted to strings and URL-encoded.
108
104
  *
109
105
  * @throws {Error} Throws an error if the HTTP request fails or returns a non-ok status
110
106
  *
111
107
  * @example
112
- * ```typescript
113
108
  * // Fetch all items
114
109
  * await collection.fetch();
115
110
  *
@@ -117,9 +112,6 @@ export declare abstract class Collection<M extends Model<T>, T extends object> {
117
112
  * await collection.fetch({
118
113
  * search: { limit: 30, after: 29 }
119
114
  * });
120
- * ```
121
115
  */
122
- fetch(options?: {
123
- search?: Record<string, string | number | boolean>;
124
- }): Promise<void>;
116
+ fetch(options?: FetchOptions): Promise<void>;
125
117
  }
@@ -12,7 +12,6 @@ import { vellumConfig } from './config.svelte';
12
12
  * @template T - The data object type that the models represent
13
13
  *
14
14
  * @example
15
- * ```typescript
16
15
  * class UserCollection extends Collection<UserModel, User> {
17
16
  * model = UserModel;
18
17
  * endpoint = () => '/api/users';
@@ -21,7 +20,6 @@ import { vellumConfig } from './config.svelte';
21
20
  * const users = new UserCollection();
22
21
  * await users.fetch(); // Loads users from API
23
22
  * users.add({ name: 'John', email: 'john@example.com' }); // Adds new user
24
- * ```
25
23
  */
26
24
  export class Collection {
27
25
  /** Reactive array of model instances in the collection */
@@ -32,7 +30,6 @@ export class Collection {
32
30
  * @param models - Optional array of data objects to initialize the collection with
33
31
  *
34
32
  * @example
35
- * ```typescript
36
33
  * // Create empty collection
37
34
  * const collection = new UserCollection();
38
35
  *
@@ -41,7 +38,6 @@ export class Collection {
41
38
  * { id: 1, name: 'John' },
42
39
  * { id: 2, name: 'Jane' }
43
40
  * ]);
44
- * ```
45
41
  */
46
42
  constructor(models = []) {
47
43
  if (models.length > 0) {
@@ -59,14 +55,12 @@ export class Collection {
59
55
  * @returns The model instance that was added to the collection
60
56
  *
61
57
  * @example
62
- * ```typescript
63
58
  * // Add raw data
64
59
  * const user = collection.add({ name: 'John', email: 'john@example.com' });
65
60
  *
66
61
  * // Add existing model instance
67
62
  * const existingUser = new UserModel({ name: 'Jane' });
68
63
  * collection.add(existingUser);
69
- * ```
70
64
  */
71
65
  add(data) {
72
66
  const instance = data instanceof Model ? data : new this.model(data);
@@ -79,13 +73,11 @@ export class Collection {
79
73
  * @param data - An array of raw data objects to populate the collection with
80
74
  *
81
75
  * @example
82
- * ```typescript
83
76
  * // Reset collection with new user data
84
77
  * collection.reset([
85
78
  * { id: 1, name: 'John', email: 'john@example.com' },
86
79
  * { id: 2, name: 'Jane', email: 'jane@example.com' }
87
80
  * ]);
88
- * ```
89
81
  */
90
82
  reset(data) {
91
83
  this.items = data.map((attrs) => new this.model(attrs));
@@ -98,13 +90,11 @@ export class Collection {
98
90
  * @returns The first matching item, or undefined if no match is found.
99
91
  *
100
92
  * @example
101
- * ```typescript
102
93
  * // Find a user by ID
103
94
  * const user = collection.find({ id: 123 });
104
95
  *
105
96
  * // Find by multiple properties
106
97
  * const activeAdmin = collection.find({ role: 'admin', status: 'active' });
107
- * ```
108
98
  */
109
99
  find(query) {
110
100
  return this.items.find((item) => {
@@ -117,13 +107,13 @@ export class Collection {
117
107
  * Fetches data from the server and populates the collection.
118
108
  *
119
109
  * @param options - Configuration options for the fetch request
120
- * @param options.search - Optional search parameters to include in the query string.
110
+ * @param [options.endpoint] - Optional endpoint to use if different than this.endpoint()
111
+ * @param [options.search] - Optional search parameters to include in the query string.
121
112
  * Keys and values will be converted to strings and URL-encoded.
122
113
  *
123
114
  * @throws {Error} Throws an error if the HTTP request fails or returns a non-ok status
124
115
  *
125
116
  * @example
126
- * ```typescript
127
117
  * // Fetch all items
128
118
  * await collection.fetch();
129
119
  *
@@ -131,7 +121,6 @@ export class Collection {
131
121
  * await collection.fetch({
132
122
  * search: { limit: 30, after: 29 }
133
123
  * });
134
- * ```
135
124
  */
136
125
  async fetch(options = {}) {
137
126
  let query = '';
@@ -142,7 +131,8 @@ export class Collection {
142
131
  }
143
132
  query = `?${params.toString()}`;
144
133
  }
145
- const fullUrl = `${vellumConfig.origin}${this.endpoint()}${query}`;
134
+ const endpoint = options?.endpoint?.length ? options.endpoint : this.endpoint();
135
+ const fullUrl = `${vellumConfig.origin}${endpoint}${query}`;
146
136
  const response = await fetch(fullUrl, {
147
137
  headers: { ...vellumConfig.headers }
148
138
  });
@@ -1,4 +1,57 @@
1
1
  import { type VellumConfig } from './config.svelte';
2
+ import { ValidationError } from './errors/validation_error.js';
3
+ export interface SyncOptions extends Partial<VellumConfig> {
4
+ endpoint?: string;
5
+ }
6
+ export interface ValidationOptions {
7
+ validate?: boolean;
8
+ silent?: boolean;
9
+ [key: string]: unknown;
10
+ }
11
+ /**
12
+ * Abstract base class for creating model instances that interact with RESTful APIs.
13
+ *
14
+ * The Model class provides a structured way to manage data objects with full CRUD
15
+ * (Create, Read, Update, Delete) capabilities. It includes built-in HTTP synchronization,
16
+ * attribute management, and data validation features. This class is designed to work
17
+ * with Svelte's reactivity system using the `$state` rune for automatic UI updates.
18
+ *
19
+ * Key features:
20
+ * - Type-safe attribute access and manipulation
21
+ * - Automatic HTTP synchronization with RESTful APIs
22
+ * - Built-in HTML escaping for XSS prevention
23
+ * - Configurable ID attributes for different database schemas
24
+ * - Reactive attributes that integrate with Svelte's state management
25
+ * - Support for both single attribute and bulk attribute operations
26
+ *
27
+ * @template T - The type definition for the model's attributes, must extend object
28
+ * @abstract This class must be extended by concrete model implementations
29
+ *
30
+ * @example
31
+ * // Define a User model
32
+ * interface UserAttributes {
33
+ * id?: number;
34
+ * name: string;
35
+ * email: string;
36
+ * createdAt?: Date;
37
+ * }
38
+ *
39
+ * class User extends Model<UserAttributes> {
40
+ * endpoint() {
41
+ * return '/users';
42
+ * }
43
+ * defaults() {
44
+ * return { name: '', createdAt: new Date() };
45
+ * }
46
+ * }
47
+ *
48
+ * // Create and use a model instance
49
+ * const user = new User({ name: 'John Doe', email: 'john@example.com' });
50
+ * await user.save(); // Creates new user on server
51
+ * user.set('name', 'Jane Doe');
52
+ * await user.save(); // Updates existing user
53
+ * await user.destroy(); // Deletes user
54
+ */
2
55
  export declare abstract class Model<T extends object> {
3
56
  #private;
4
57
  /**
@@ -17,8 +70,73 @@ export declare abstract class Model<T extends object> {
17
70
  * return '/users';
18
71
  * }
19
72
  */
20
- abstract endpoint(): string;
73
+ protected abstract endpoint(): string;
74
+ /**
75
+ * The name of the attribute that serves as the unique identifier for this model instance.
76
+ *
77
+ * This private field stores the attribute name that will be used to identify the model's
78
+ * primary key when performing operations like determining if the model is new, constructing
79
+ * URLs for API requests, and managing model identity. The default value is 'id', but it
80
+ * can be customized through the ModelOptions parameter in the constructor.
81
+ *
82
+ * @protected
83
+ * @type {string}
84
+ * @default 'id'
85
+ * @example
86
+ * // Default behavior uses 'id' as the identifier
87
+ * const user = new User({ id: 1, name: 'John' });
88
+ *
89
+ * // Custom ID attribute can be specified in constructor options
90
+ * class User extends Model<UserSchema> {
91
+ * idAttribute = '_id
92
+ * endpoint(): string {
93
+ * return '/users';
94
+ * }
95
+ * }
96
+ * const user = new User({ _id: '507f1f77bcf86cd799439011', name: 'John' });
97
+ */
98
+ protected idAttribute: string;
99
+ /**
100
+ * Creates a new instance of Model.
101
+ */
21
102
  constructor(data?: Partial<T>);
103
+ /**
104
+ * Gets the latest validation error.
105
+ *
106
+ * @returns {ValidationError || undefined} An instance of ValidationError if validation failed, otherwise undefined
107
+ */
108
+ get validationError(): ValidationError | undefined;
109
+ /**
110
+ * Provides default attribute values for new model instances.
111
+ *
112
+ * This method is called during model construction to establish initial attribute
113
+ * values before applying any user-provided data. Subclasses can override this
114
+ * method to define default values for their specific attributes, ensuring that
115
+ * models always have sensible initial state.
116
+ *
117
+ * The defaults are applied first, then any data passed to the constructor will
118
+ * override these default values. This allows for flexible model initialization
119
+ * where some attributes have fallback values while others can be explicitly set.
120
+ *
121
+ * @protected
122
+ * @returns {Partial<T>} A partial object containing default attribute values
123
+ *
124
+ * @example
125
+ * // Override in a User model subclass
126
+ * protected defaults(): Partial<UserAttributes> {
127
+ * return {
128
+ * role: 'user',
129
+ * isActive: true,
130
+ * createdAt: new Date()
131
+ * };
132
+ * }
133
+ *
134
+ * @example
135
+ * // Creating a model with defaults
136
+ * const user = new User({ name: 'John' });
137
+ * // Resulting attributes: { role: 'user', isActive: true, createdAt: Date, name: 'John' }
138
+ */
139
+ protected defaults(): Partial<T>;
22
140
  /**
23
141
  * Retrieves the value of a specific attribute from the model.
24
142
  *
@@ -44,21 +162,148 @@ export declare abstract class Model<T extends object> {
44
162
  * shallow merge, meaning that only the top-level properties specified in the attrs
45
163
  * parameter will be updated, while other existing attributes remain unchanged.
46
164
  *
165
+ * The method supports optional validation through the ValidationOptions parameter.
166
+ * If validation is enabled and fails, the attributes will not be updated and the
167
+ * method will return false. The validationError property will be set with details
168
+ * about the validation failure.
169
+ *
170
+ * @overload
171
+ * @param {K} key - The attribute key to set
172
+ * @param {T[K]} value - The value to set for the specified key
173
+ * @param {ValidationOptions} [options] - Optional validation and behavior settings
174
+ * @param {boolean} [options.validate] - If true, validates attributes before setting them
175
+ * @param {boolean} [options.silent] - If true, suppresses validation error setting
176
+ * @returns {boolean} True if attributes were set successfully, false if validation failed
177
+ *
178
+ * @overload
47
179
  * @param {Partial<T>} attrs - A partial object containing the attributes to update
180
+ * @param {ValidationOptions} [options] - Optional validation and behavior settings
181
+ * @param {boolean} [options.validate] - If true, validates attributes before setting them
182
+ * @param {boolean} [options.silent] - If true, suppresses validation error setting
183
+ * @returns {boolean} True if attributes were set successfully, false if validation failed
184
+ *
185
+ * @example
186
+ * // Set a single attribute
187
+ * user.set('name', 'Jane');
188
+ *
189
+ * // Set multiple attributes
190
+ * user.set({ name: 'Jane', email: 'jane@example.com' });
191
+ *
192
+ * // Set with validation enabled
193
+ * const success = user.set({ email: 'invalid-email' }, { validate: true });
194
+ * if (!success) {
195
+ * console.log('Validation failed:', user.validationError);
196
+ * }
197
+ *
198
+ * // Set attributes silently without validation errors
199
+ * user.set({ name: '' }, { validate: true, silent: true });
200
+ */
201
+ set<K extends keyof T>(key: K, value: T[K], options?: ValidationOptions): boolean;
202
+ set(attrs: Partial<T>, options?: ValidationOptions): boolean;
203
+ /**
204
+ * Checks whether a specific attribute has a non-null, non-undefined value.
205
+ *
206
+ * This method provides a way to determine if an attribute exists and has a
207
+ * meaningful value. It returns true if the attribute is set to any value
208
+ * other than null or undefined, including falsy values like false, 0, or
209
+ * empty strings, which are considered valid values.
210
+ *
211
+ * @template K - The key type, constrained to keys of T
212
+ * @param {K} key - The attribute key to check
213
+ * @returns {boolean} True if the attribute has a non-null, non-undefined value
214
+ * @example
215
+ * // Assuming a User model with attributes { id: number, name: string, email?: string }
216
+ * const user = new User({ id: 1, name: 'John', email: null });
217
+ *
218
+ * user.has('id'); // Returns true (value is 1)
219
+ * user.has('name'); // Returns true (value is 'John')
220
+ * user.has('email'); // Returns false (value is null)
221
+ *
222
+ * // Even falsy values are considered "set"
223
+ * user.set({ name: '' });
224
+ * user.has('name'); // Returns true (empty string is a valid value)
225
+ */
226
+ has<K extends keyof T>(key: K): boolean;
227
+ /**
228
+ * Removes a specific attribute from the model by deleting it from the internal attributes hash.
229
+ *
230
+ * This method permanently removes an attribute from the model instance. Once unset,
231
+ * the attribute will no longer exist on the model and subsequent calls to get() for
232
+ * that key will return undefined. This is different from setting an attribute to
233
+ * null or undefined, as the property is completely removed from the attributes object.
234
+ *
235
+ * @template K - The key type, constrained to keys of T
236
+ * @param {K} key - The attribute key to remove from the model
48
237
  * @returns {void}
49
238
  * @example
50
239
  * // Assuming a User model with attributes { id: number, name: string, email: string }
51
240
  * const user = new User({ id: 1, name: 'John', email: 'john@example.com' });
52
241
  *
53
- * // Update multiple attributes at once
242
+ * user.has('email'); // Returns true
243
+ * user.unset('email'); // Remove the email attribute
244
+ * user.has('email'); // Returns false
245
+ * user.get('email'); // Returns undefined
246
+ *
247
+ * // The attribute is completely removed, not just set to undefined
248
+ * const userData = user.toJSON(); // { id: 1, name: 'John' }
249
+ */
250
+ unset<K extends keyof T>(key: K): void;
251
+ /**
252
+ * Removes all attributes from the model, including the id attribute.
253
+ *
254
+ * This method completely clears the model instance by removing all stored attributes,
255
+ * effectively resetting it to an empty state. After calling clear(), the model will
256
+ * behave as if it were newly instantiated with no data. This includes removing the
257
+ * ID attribute, which means the model will be considered "new" after clearing.
258
+ *
259
+ * This is useful when you want to reuse a model instance for different data or
260
+ * reset a model to its initial state without creating a new instance.
261
+ *
262
+ * @returns {void}
263
+ * @example
264
+ * // Clear all data from a user model
265
+ * const user = new User({ id: 1, name: 'John', email: 'john@example.com' });
266
+ * user.clear();
267
+ *
268
+ * user.has('id'); // Returns false
269
+ * user.has('name'); // Returns false
270
+ * user.isNew(); // Returns true
271
+ * user.toJSON(); // Returns {}
272
+ *
273
+ * // Model can be reused with new data
54
274
  * user.set({ name: 'Jane', email: 'jane@example.com' });
55
- * // Now user has { id: 1, name: 'Jane', email: 'jane@example.com' }
275
+ */
276
+ clear(): void;
277
+ /**
278
+ * Retrieves and escapes the HTML content of a specific attribute from the model.
279
+ *
280
+ * This method provides a safe way to access model attributes that may contain
281
+ * user-generated content or data that will be rendered in HTML contexts. It
282
+ * automatically applies HTML escaping to prevent XSS attacks and ensure safe
283
+ * rendering of potentially dangerous content.
284
+ *
285
+ * The method uses the escapeHTML utility function to convert special HTML
286
+ * characters (such as <, >, &, ", and ') into their corresponding HTML entities,
287
+ * making the content safe for direct insertion into HTML templates.
56
288
  *
57
- * // Update a single attribute
58
- * user.set({ name: 'Bob' });
59
- * // Now user has { id: 1, name: 'Bob', email: 'jane@example.com' }
289
+ * @template K - The key type, constrained to keys of T
290
+ * @param {K} key - The attribute key to retrieve and escape the value for
291
+ * @returns {T[K]} The HTML-escaped value associated with the specified key
292
+ * @example
293
+ * // Assuming a Post model with attributes { id: number, title: string, content: string }
294
+ * const post = new Post({
295
+ * id: 1,
296
+ * title: 'Hello <script>alert("XSS")</script>',
297
+ * content: 'This is "safe" & secure content'
298
+ * });
299
+ *
300
+ * const safeTitle = post.escape('title');
301
+ * // Returns: 'Hello &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;'
302
+ *
303
+ * const safeContent = post.escape('content');
304
+ * // Returns: 'This is &quot;safe&quot; &amp; secure content'
60
305
  */
61
- set(attrs: Partial<T>): void;
306
+ escape<K extends keyof T>(key: K): string;
62
307
  /**
63
308
  * Determines whether this model instance is new (not yet persisted).
64
309
  * A model is considered new if it doesn't have an 'id' or '_id' attribute.
@@ -66,6 +311,69 @@ export declare abstract class Model<T extends object> {
66
311
  * @returns {boolean} true if the model is new, false if it has been persisted
67
312
  */
68
313
  isNew(): boolean;
314
+ /**
315
+ * Validates the current model instance and returns whether it passes validation.
316
+ *
317
+ * This method performs validation on the model's current attributes using the
318
+ * validate() method defined by the subclass. It's a convenience method that
319
+ * allows you to check if a model is valid without having to manually call
320
+ * the validation logic.
321
+ *
322
+ * If validation fails, the validationError property will be set with details
323
+ * about what went wrong. If validation passes, validationError will be cleared.
324
+ *
325
+ * @param {ValidationOptions} [options] - Optional validation configuration
326
+ * @param {boolean} [options.silent] - If true, suppresses validation error setting
327
+ * @returns {boolean} True if the model passes validation, false otherwise
328
+ *
329
+ * @example
330
+ * // Check if a user model is valid
331
+ * const user = new User({ name: '', email: 'invalid-email' });
332
+ *
333
+ * if (!user.isValid()) {
334
+ * console.log('Validation failed:', user.validationError);
335
+ * // Handle validation errors
336
+ * } else {
337
+ * // Proceed with saving or other operations
338
+ * await user.save();
339
+ * }
340
+ *
341
+ * @example
342
+ * // Validate silently without setting validationError
343
+ * const isValid = user.isValid({ silent: true });
344
+ * // user.validationError remains unchanged
345
+ */
346
+ isValid(options?: ValidationOptions): boolean;
347
+ /**
348
+ * Validates the model's attributes using custom validation logic.
349
+ *
350
+ * This method is intended to be overridden by subclasses to implement custom
351
+ * validation rules. By default, it returns undefined (no validation errors).
352
+ * If validation fails, this method should return an error - either a simple
353
+ * string message or a complete error object.
354
+ *
355
+ * The validate method is automatically called by save() before persisting data,
356
+ * and can also be explicitly called by set() when the {validate: true} option
357
+ * is passed. If validation fails, the save operation is aborted and model
358
+ * attributes are not modified.
359
+ *
360
+ * @param {Partial<T>} attributes - The attributes to validate
361
+ * @param {any} [options] - Additional options passed from set() or save()
362
+ * @returns {any} Returns undefined if valid, or an error (string/object) if invalid
363
+ *
364
+ * @example
365
+ * // Override in a User model subclass
366
+ * validate(attributes: Partial<UserAttributes>) {
367
+ * if (!attributes.email) {
368
+ * return 'Email is required';
369
+ * }
370
+ * if (!attributes.email.includes('@')) {
371
+ * return { email: 'Invalid email format' };
372
+ * }
373
+ * // Return undefined if validation passes
374
+ * }
375
+ */
376
+ validate(attributes: Partial<T>, options?: ValidationOptions): string | undefined;
69
377
  /**
70
378
  * Performs HTTP synchronization with the server for CRUD operations.
71
379
  *
@@ -97,7 +405,7 @@ export declare abstract class Model<T extends object> {
97
405
  * // Delete a user (DELETE request)
98
406
  * await user.sync('DELETE'); // Returns null for 204 responses
99
407
  */
100
- sync<R = T>(method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', body?: Record<string, unknown> | T, options?: VellumConfig): Promise<R | null>;
408
+ sync<R = T>(method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', body?: Record<string, unknown> | T, options?: SyncOptions): Promise<R | null>;
101
409
  /**
102
410
  * Fetches data from the server and updates the model's attributes.
103
411
  *
@@ -135,7 +443,13 @@ export declare abstract class Model<T extends object> {
135
443
  * generates additional fields (like timestamps, computed values, or normalized data)
136
444
  * during the save process.
137
445
  *
138
- * @returns {Promise<void>} A promise that resolves when the save operation completes
446
+ * @param {ValidationOptions & SyncOptions} [options] - Optional configuration for save behavior
447
+ * @param {boolean} [options.validate] - Whether to validate before saving (defaults to true)
448
+ * @param {boolean} [options.silent] - If true, suppresses validation error setting
449
+ * @param {string} [options.endpoint] - Override the default endpoint for this save operation
450
+ * @param {Record<string, string>} [options.headers] - Additional headers to include in the request
451
+ * @param {string} [options.origin] - Override the base URL origin for this request
452
+ * @returns {Promise<boolean>} A promise that resolves to true if save succeeds, false if validation fails
139
453
  * @throws {Error} Throws an error if the HTTP request fails or server returns an error
140
454
  *
141
455
  * @example
@@ -146,8 +460,17 @@ export declare abstract class Model<T extends object> {
146
460
  * // Update an existing user
147
461
  * existingUser.set({ name: 'Jane' });
148
462
  * await existingUser.save(); // PUT request with updated data
463
+ *
464
+ * // Save without validation
465
+ * await user.save({ validate: false });
466
+ *
467
+ * // Save with custom endpoint and headers
468
+ * await user.save({
469
+ * endpoint: '/api/v2/users',
470
+ * headers: { 'Custom-Header': 'value' }
471
+ * });
149
472
  */
150
- save(): Promise<void>;
473
+ save(options?: ValidationOptions & SyncOptions): Promise<boolean>;
151
474
  /**
152
475
  * Deletes the model from the server.
153
476
  *