@jwerre/vellum 1.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vellum
2
2
 
3
- Vellum is a lightweight, structural state management library for Svelte 5. Vellum provides a robust Model and Collection base powered by Svelte Runes.It bridges the gap between raw objects and complex state logic, offering a typed, class-based approach to managing data-heavy applications.
3
+ Vellum is a lightweight, structural state management library for Svelte 5. Vellum provides a robust Model and Collection base powered by Svelte Runes. It bridges the gap between raw objects and complex state logic, offering a typed, class-based approach to managing data-heavy applications.
4
4
 
5
5
  ## Features
6
6
 
@@ -27,7 +27,7 @@ Modern Svelte development often moves away from stores and toward raw $state obj
27
27
  npm install @jwerre/vellum
28
28
  ```
29
29
 
30
- ## Quick Start
30
+ ## Quick start
31
31
 
32
32
  ### Configuration
33
33
 
@@ -50,30 +50,52 @@ Extend the Model class to define your data structure and any derived state or bu
50
50
 
51
51
  ```ts
52
52
  import { Model } from '@jwerre/vellum';
53
- interface UserSchema {
53
+ interface BookSchema {
54
54
  id: number;
55
- firstName: string;
56
- lastName: string;
57
- role: 'admin' | 'user';
55
+ author: string;
56
+ genre: 'literature', 'romance' | 'adventure' | 'fantasy' | 'sci-fi' | 'mystery';
57
+ summary: string;
58
+ title: string;
59
+ year: number;
58
60
  }
59
61
 
60
- export class UserModel extends Model<UserSchema> {
62
+ export class Book extends Model<BookSchema> {
63
+
61
64
  endpoint() {
62
- return `/v1/user`;
65
+ return `/v1/books`;
66
+ }
67
+
68
+ defaults() {
69
+ return {
70
+ title: '',
71
+ author: '',
72
+ genre: 'literature',
73
+ summary: '',
74
+ year: new Date().getFullYear()
75
+ };
63
76
  }
64
77
 
65
- // Computed property using Svelte $derived
66
- fullName = $derived(`${this.get('firstName')} ${this.get('lastName')}`);
78
+ authorFirstName = $derived(`${this.get('author')?.split(' ')[0]}`);
67
79
 
68
- isAdmin() {
69
- return this.get('role') === 'admin';
80
+ authorLastName = $derived(`${this.get('author')?.split(' ')[1]}`);
81
+
82
+ isClassic() {
83
+ return this.get('year') < 1900;
70
84
  }
71
85
  }
72
86
 
73
- const user = new UserModel({ firstName: 'John', lastName: 'Doe', role: 'user' });
74
- await user.sync();
75
- console.log(user.id); // 1
76
- console.log(user.fullName); // John Doe
87
+ const book = new Book({
88
+ title: 'Crime and Punishment',
89
+ author: 'Fyodor Dostoevsky',
90
+ year: 1866,
91
+ genre: 'literature',
92
+ summary: 'An exploration of the mental anguish and moral dilemmas...',
93
+ });
94
+
95
+ await book.sync(); // issues POST request to endpoint
96
+ console.log(book.id); // unique ID created by POST request
97
+ console.log(book.authorFirstName); // Fyodor
98
+ console.log(book.isClassic()); // true
77
99
  ```
78
100
 
79
101
  ### Define a Collection
@@ -82,292 +104,773 @@ Manage groups of models with built-in reactivity.
82
104
 
83
105
  ```ts
84
106
  import { Collection } from '@jwerre/vellum';
85
- import { UserModel } from './UserModel.svelte.js';
107
+ import { Book } from './Book.svelte.js';
86
108
 
87
- export class UserCollection extends Collection<UserModel, UserSchema> {
88
- model = UserModel;
109
+ export class Books extends Collection<Book, BookSchema> {
110
+ model = Book;
89
111
 
90
112
  endpoint() {
91
- return `/v1/users`;
113
+ return `/v1/books`;
92
114
  }
93
115
 
94
116
  // Derived state for the entire collection
95
- adminCount = $derived(this.items.filter((u) => u.isAdmin()).length);
117
+ classicCount = $derived(this.items.filter((u) => u.isClassic()).length);
96
118
  }
97
119
  ```
98
120
 
99
- ### Use in Svelte Components
121
+ ### Use in Svelte components
100
122
 
101
123
  Vellum works seamlessly with Svelte 5 components.
102
124
 
103
125
  ```svelte
104
126
  <script lang="ts">
105
- import { UserCollection } from './UserCollection';
106
-
107
- const users = new UserCollection([{ id: 1, firstName: 'Jane', lastName: 'Doe', role: 'admin' }]);
108
-
109
- function addUser() {
110
- users.add({ id: 2, firstName: 'John', lastName: 'Smith', role: 'user' });
127
+ import { Books } from './Books';
128
+
129
+ const books = new Books([
130
+ {
131
+ id: 1,
132
+ title: 'The Great Gatsby',
133
+ author: 'F. Scott Fitzgerald',
134
+ year: 1925,
135
+ genre: 'literature'
136
+ },
137
+ {
138
+ id: 2,
139
+ title: 'To Kill a Mockingbird',
140
+ author: 'Harper Lee',
141
+ year: 1960,
142
+ genre: 'literature'
143
+ },
144
+ {
145
+ id: 3,
146
+ title: 'The Pillars of the Earth',
147
+ author: 'Ken Follett',
148
+ year: 1989,
149
+ genre: 'literature'
150
+ }
151
+ ]);
152
+
153
+ function addBook() {
154
+ books.add({
155
+ title: 'Moby Dick',
156
+ author: 'Herman Melville',
157
+ year: 1851,
158
+ genre: 'Adventure',
159
+ summary: 'The obsessive quest of Captain Ahab to seek revenge on the white whale.'
160
+ });
111
161
  }
112
162
  </script>
113
163
 
114
- <h1>Admins: {users.adminCount}</h1>
164
+ <h1>Classic Books: {books.classicCount}</h1>
115
165
 
116
166
  <ul>
117
- {#each users.items as user}
118
- {#if user.isAdmin()}
119
- <li>{user.fullName} ({user.get('email')})</li>
167
+ {#each books.items as book}
168
+ {#if book.isClassic()}
169
+ <li>{book.get('title')} ({book.get('year')}) - ({book.get('author')})</li>
120
170
  {/if}
121
171
  {/each}
122
172
  </ul>
123
173
 
124
- <button onclick={addUser}>Add User</button>
174
+ <button onclick={addBook}>Add Moby Dick</button>
175
+ ```
176
+
177
+ ### Working example
178
+
179
+ There is a working example of Vellum in the `routes` directory. To run it, clone the repository, install dependencies, and run the development server:
180
+
181
+ ```
182
+ git clone https://github.com/jwerre/vellum.git
183
+ cd vellum
184
+ npm install
185
+ npm run dev
186
+ ```
187
+
188
+ ## API Documentation
189
+
190
+ <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
191
+
192
+ #### Table of Contents
193
+
194
+ - [vellumConfig](#vellumconfig)
195
+ - [configureVellum](#configurevellum)
196
+ - [Parameters](#parameters)
197
+ - [Examples](#examples)
198
+ - [Model](#model)
199
+ - [Parameters](#parameters-1)
200
+ - [Examples](#examples-1)
201
+ - [validationError](#validationerror)
202
+ - [idAttribute](#idattribute)
203
+ - [get](#get)
204
+ - [Parameters](#parameters-2)
205
+ - [Examples](#examples-2)
206
+ - [has](#has)
207
+ - [Parameters](#parameters-3)
208
+ - [Examples](#examples-3)
209
+ - [unset](#unset)
210
+ - [Parameters](#parameters-4)
211
+ - [Examples](#examples-4)
212
+ - [clear](#clear)
213
+ - [Examples](#examples-5)
214
+ - [escape](#escape)
215
+ - [Parameters](#parameters-5)
216
+ - [Examples](#examples-6)
217
+ - [isNew](#isnew)
218
+ - [isValid](#isvalid)
219
+ - [Parameters](#parameters-6)
220
+ - [Examples](#examples-7)
221
+ - [validate](#validate)
222
+ - [Parameters](#parameters-7)
223
+ - [Examples](#examples-8)
224
+ - [sync](#sync)
225
+ - [Parameters](#parameters-8)
226
+ - [Examples](#examples-9)
227
+ - [fetch](#fetch)
228
+ - [Examples](#examples-10)
229
+ - [save](#save)
230
+ - [Parameters](#parameters-9)
231
+ - [Examples](#examples-11)
232
+ - [destroy](#destroy)
233
+ - [Examples](#examples-12)
234
+ - [toJSON](#tojson)
235
+ - [Examples](#examples-13)
236
+ - [Collection](#collection)
237
+ - [Parameters](#parameters-10)
238
+ - [Examples](#examples-14)
239
+ - [items](#items)
240
+ - [length](#length)
241
+ - [add](#add)
242
+ - [Parameters](#parameters-11)
243
+ - [Examples](#examples-15)
244
+ - [reset](#reset)
245
+ - [Parameters](#parameters-12)
246
+ - [Examples](#examples-16)
247
+ - [find](#find)
248
+ - [Parameters](#parameters-13)
249
+ - [Examples](#examples-17)
250
+ - [fetch](#fetch-1)
251
+ - [Parameters](#parameters-14)
252
+ - [Examples](#examples-18)
253
+
254
+ ### vellumConfig
255
+
256
+ Global reactive state for Vellum configuration. Uses Svelte's $state rune to
257
+ create a reactive configuration object that automatically triggers updates when modified.
258
+
259
+ ### configureVellum
260
+
261
+ Helper function to update global Vellum configuration
262
+
263
+ Allows partial updates to the global configuration state. Only provided
264
+ properties will be updated, leaving others unchanged. Headers are merged
265
+ with existing headers rather than replaced entirely.
266
+
267
+ #### Parameters
268
+
269
+ - `config` **Partial\<VellumConfig>** Partial configuration object with properties to update
270
+ - `config.origin` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** New origin URL to set
271
+ - `config.headers` **Record<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>?** Headers to merge with existing headers
272
+
273
+ #### Examples
274
+
275
+ ```javascript
276
+ // Set the API origin
277
+ configureVellum({ origin: 'https://api.vellum.ai' });
278
+
279
+ // Add custom headers
280
+ configureVellum({
281
+ headers: {
282
+ Authorization: 'Bearer token123',
283
+ 'X-Custom-Header': 'value'
284
+ }
285
+ });
286
+
287
+ // Update both origin and headers
288
+ configureVellum({
289
+ origin: 'https://api.vellum.ai',
290
+ headers: { Authorization: 'Bearer token123' }
291
+ });
125
292
  ```
126
293
 
127
- ## API Reference
294
+ ### Model
295
+
296
+ Abstract base class for creating model instances that interact with RESTful APIs.
297
+
298
+ The Model class provides a structured way to manage data objects with full CRUD
299
+ (Create, Read, Update, Delete) capabilities. It includes built-in HTTP synchronization,
300
+ attribute management, and data validation features. This class is designed to work
301
+ with Svelte's reactivity system using the `$state` rune for automatic UI updates.
302
+
303
+ Key features:
304
+
305
+ - Type-safe attribute access and manipulation
306
+ - Automatic HTTP synchronization with RESTful APIs
307
+ - Built-in HTML escaping for XSS prevention
308
+ - Configurable ID attributes for different database schemas
309
+ - Reactive attributes that integrate with Svelte's state management
310
+ - Support for both single attribute and bulk attribute operations
128
311
 
129
- ### `Model<T>`
312
+ #### Parameters
130
313
 
131
- The `Model` class provides a base class for creating data models with built-in CRUD operations and server synchronization.
314
+ - `data` (optional, default `{}`)
315
+ - `options` (optional, default `{}`)
132
316
 
133
- #### Constructor
317
+ #### Examples
134
318
 
135
319
  ```javascript
136
- new Model((data = {}));
320
+ // Define a User model
321
+ interface UserAttributes {
322
+ id?: number;
323
+ name: string;
324
+ email: string;
325
+ createdAt?: Date;
326
+ }
327
+
328
+ class User extends Model<UserAttributes> {
329
+ endpoint(): string {
330
+ return '/users';
331
+ }
332
+ }
333
+
334
+ // Create and use a model instance
335
+ const user = new User({ name: 'John Doe', email: 'john@example.com' });
336
+ await user.save(); // Creates new user on server
337
+ user.set('name', 'Jane Doe');
338
+ await user.save(); // Updates existing user
137
339
  ```
138
340
 
139
- Creates a new Model instance with optional initial attributes.
341
+ ```javascript
342
+ // Using custom ID attribute (e.g., MongoDB _id)
343
+ interface MongoUserAttributes {
344
+ _id?: string;
345
+ username: string;
346
+ profile: {
347
+ firstName: string;
348
+ lastName: string;
349
+ };
350
+ }
351
+
352
+ class MongoUser extends Model<MongoUserAttributes> {
353
+ constructor(data?: Partial<MongoUserAttributes>) {
354
+ super(data, { idAttribute: '_id' });
355
+ }
140
356
 
141
- **Parameters:**
357
+ endpoint(): string {
358
+ return '/api/users';
359
+ }
360
+ }
361
+ ```
142
362
 
143
- - `data` (Object) - Optional partial object of attributes to initialize the model
363
+ #### validationError
144
364
 
145
- #### Abstract Properties
365
+ Validation error property that gets set when validation fails.
366
+ This property contains the error returned by the validate method.
146
367
 
147
- Must be implemented by subclasses:
368
+ #### idAttribute
148
369
 
149
- - `endpoint()` - Function that returns the base URL path for API endpoints (e.g., '/users')
370
+ Gets the current ID attribute name used by this model instance.
150
371
 
151
- #### Methods
372
+ Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the attribute used as the ID field
152
373
 
153
- ##### get(key)
374
+ #### get
154
375
 
155
376
  Retrieves the value of a specific attribute from the model.
156
377
 
157
- **Parameters:**
378
+ This method provides type-safe access to model attributes, ensuring that the
379
+ returned value matches the expected type for the given key. It acts as a
380
+ getter for the internal attributes stored in the model instance.
158
381
 
159
- - `key` - The attribute key to retrieve
382
+ ##### Parameters
160
383
 
161
- **Returns:** The value associated with the specified key
384
+ - `key` **K** The attribute key to retrieve the value for
385
+
386
+ ##### Examples
162
387
 
163
388
  ```javascript
389
+ // Assuming a User model with attributes { id: number, name: string }
164
390
  const user = new User({ id: 1, name: 'John Doe' });
165
- const name = user.get('name'); // Returns 'John Doe'
391
+ const name = user.get('name'); // Returns 'John Doe' (string)
392
+ const id = user.get('id'); // Returns 1 (number)
393
+ ```
394
+
395
+ #### has
396
+
397
+ Checks whether a specific attribute has a non-null, non-undefined value.
398
+
399
+ This method provides a way to determine if an attribute exists and has a
400
+ meaningful value. It returns true if the attribute is set to any value
401
+ other than null or undefined, including falsy values like false, 0, or
402
+ empty strings, which are considered valid values.
403
+
404
+ ##### Parameters
405
+
406
+ - `key` **K** The attribute key to check
407
+
408
+ ##### Examples
409
+
410
+ ```javascript
411
+ // Assuming a User model with attributes { id: number, name: string, email?: string }
412
+ const user = new User({ id: 1, name: 'John', email: null });
413
+
414
+ user.has('id'); // Returns true (value is 1)
415
+ user.has('name'); // Returns true (value is 'John')
416
+ user.has('email'); // Returns false (value is null)
417
+
418
+ // Even falsy values are considered "set"
419
+ user.set({ name: '' });
420
+ user.has('name'); // Returns true (empty string is a valid value)
421
+ ```
422
+
423
+ Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if the attribute has a non-null, non-undefined value
424
+
425
+ #### unset
426
+
427
+ Removes a specific attribute from the model by deleting it from the internal attributes hash.
428
+
429
+ This method permanently removes an attribute from the model instance. Once unset,
430
+ the attribute will no longer exist on the model and subsequent calls to get() for
431
+ that key will return undefined. This is different from setting an attribute to
432
+ null or undefined, as the property is completely removed from the attributes object.
433
+
434
+ ##### Parameters
435
+
436
+ - `key` **K** The attribute key to remove from the model
437
+
438
+ ##### Examples
439
+
440
+ ```javascript
441
+ // Assuming a User model with attributes { id: number, name: string, email: string }
442
+ const user = new User({ id: 1, name: 'John', email: 'john@example.com' });
443
+
444
+ user.has('email'); // Returns true
445
+ user.unset('email'); // Remove the email attribute
446
+ user.has('email'); // Returns false
447
+ user.get('email'); // Returns undefined
448
+
449
+ // The attribute is completely removed, not just set to undefined
450
+ const userData = user.toJSON(); // { id: 1, name: 'John' }
166
451
  ```
167
452
 
168
- ##### set(attrs)
453
+ Returns **void**&#x20;
169
454
 
170
- Updates multiple attributes on the model instance.
455
+ #### clear
171
456
 
172
- **Parameters:**
457
+ Removes all attributes from the model, including the id attribute.
173
458
 
174
- - `attrs` - Partial object containing attributes to update
459
+ This method completely clears the model instance by removing all stored attributes,
460
+ effectively resetting it to an empty state. After calling clear(), the model will
461
+ behave as if it were newly instantiated with no data. This includes removing the
462
+ ID attribute, which means the model will be considered "new" after clearing.
463
+
464
+ This is useful when you want to reuse a model instance for different data or
465
+ reset a model to its initial state without creating a new instance.
466
+
467
+ ##### Examples
175
468
 
176
469
  ```javascript
470
+ // Clear all data from a user model
471
+ const user = new User({ id: 1, name: 'John', email: 'john@example.com' });
472
+ user.clear();
473
+
474
+ user.has('id'); // Returns false
475
+ user.has('name'); // Returns false
476
+ user.isNew(); // Returns true
477
+ user.toJSON(); // Returns {}
478
+
479
+ // Model can be reused with new data
177
480
  user.set({ name: 'Jane', email: 'jane@example.com' });
178
481
  ```
179
482
 
180
- ##### isNew()
483
+ Returns **void**&#x20;
484
+
485
+ #### escape
486
+
487
+ Retrieves and escapes the HTML content of a specific attribute from the model.
488
+
489
+ This method provides a safe way to access model attributes that may contain
490
+ user-generated content or data that will be rendered in HTML contexts. It
491
+ automatically applies HTML escaping to prevent XSS attacks and ensure safe
492
+ rendering of potentially dangerous content.
493
+
494
+ The method uses the escapeHTML utility function to convert special HTML
495
+ characters (such as <, >, &, ", and ') into their corresponding HTML entities,
496
+ making the content safe for direct insertion into HTML templates.
497
+
498
+ ##### Parameters
499
+
500
+ - `key` **K** The attribute key to retrieve and escape the value for
501
+
502
+ ##### Examples
503
+
504
+ ```javascript
505
+ // Assuming a Post model with attributes { id: number, title: string, content: string }
506
+ const post = new Post({
507
+ id: 1,
508
+ title: 'Hello <script>alert("XSS")</script>',
509
+ content: 'This is "safe" & secure content'
510
+ });
511
+
512
+ const safeTitle = post.escape('title');
513
+ // Returns: 'Hello &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;'
514
+
515
+ const safeContent = post.escape('content');
516
+ // Returns: 'This is &quot;safe&quot; &amp; secure content'
517
+ ```
518
+
519
+ #### isNew
181
520
 
182
521
  Determines whether this model instance is new (not yet persisted).
522
+ A model is considered new if it doesn't have an 'id' or '\_id' attribute.
523
+
524
+ Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** true if the model is new, false if it has been persisted
525
+
526
+ #### isValid
527
+
528
+ Validates the current model instance and returns whether it passes validation.
529
+
530
+ This method performs validation on the model's current attributes using the
531
+ validate() method defined by the subclass. It's a convenience method that
532
+ allows you to check if a model is valid without having to manually call
533
+ the validation logic.
534
+
535
+ If validation fails, the validationError property will be set with details
536
+ about what went wrong. If validation passes, validationError will be cleared.
537
+
538
+ ##### Parameters
539
+
540
+ - `options` **ValidationOptions?** Optional validation configuration
541
+ - `options.silent` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** If true, suppresses validation error setting
542
+
543
+ ##### Examples
544
+
545
+ ```javascript
546
+ // Check if a user model is valid
547
+ const user = new User({ name: '', email: 'invalid-email' });
548
+
549
+ if (!user.isValid()) {
550
+ console.log('Validation failed:', user.validationError);
551
+ // Handle validation errors
552
+ } else {
553
+ // Proceed with saving or other operations
554
+ await user.save();
555
+ }
556
+ ```
557
+
558
+ ```javascript
559
+ // Validate silently without setting validationError
560
+ const isValid = user.isValid({ silent: true });
561
+ // user.validationError remains unchanged
562
+ ```
183
563
 
184
- **Returns:** `true` if the model has no ID, `false` otherwise
564
+ Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if the model passes validation, false otherwise
565
+
566
+ #### validate
567
+
568
+ Validates the model's attributes using custom validation logic.
569
+
570
+ This method is intended to be overridden by subclasses to implement custom
571
+ validation rules. By default, it returns undefined (no validation errors).
572
+ If validation fails, this method should return an error - either a simple
573
+ string message or a complete error object.
574
+
575
+ The validate method is automatically called by save() before persisting data,
576
+ and can also be explicitly called by set() when the {validate: true} option
577
+ is passed. If validation fails, the save operation is aborted and model
578
+ attributes are not modified.
579
+
580
+ ##### Parameters
581
+
582
+ - `attributes` **Partial\<T>** The attributes to validate
583
+ - `options` **any?** Additional options passed from set() or save()
584
+
585
+ ##### Examples
185
586
 
186
587
  ```javascript
187
- const newUser = new User({ name: 'John' });
188
- console.log(newUser.isNew()); // true
588
+ // Override in a User model subclass
589
+ validate(attributes: Partial<UserAttributes>) {
590
+ if (!attributes.email) {
591
+ return 'Email is required';
592
+ }
593
+ if (!attributes.email.includes('@')) {
594
+ return { email: 'Invalid email format' };
595
+ }
596
+ // Return undefined if validation passes
597
+ }
189
598
  ```
190
599
 
191
- ##### sync(method, body, options)
600
+ Returns **any** Returns undefined if valid, or an error (string/object) if invalid
601
+
602
+ #### sync
192
603
 
193
604
  Performs HTTP synchronization with the server for CRUD operations.
194
605
 
195
- **Parameters:**
606
+ This method handles all HTTP communication between the model and the server,
607
+ automatically constructing the appropriate URL based on the model's ID and
608
+ endpoint(). It supports all standard REST operations and provides type-safe
609
+ response handling.
610
+
611
+ The URL construction follows REST conventions:
196
612
 
197
- - `method` - HTTP method ('GET', 'POST', 'PUT', 'PATCH', 'DELETE'), defaults to 'GET'
198
- - `body` - Optional request body data
199
- - `options` - Optional configuration overrides
613
+ - For new models (no ID): uses collection endpoint `${baseUrl}${endpoint()}`
614
+ - For existing models (with ID): uses resource endpoint `${baseUrl}${endpoint()}/${id}`
200
615
 
201
- **Returns:** Promise resolving to server response data or null
616
+ ##### Parameters
617
+
618
+ - `method` **(`"GET"` | `"POST"` | `"PUT"` | `"PATCH"` | `"DELETE"`)** The HTTP method to use (defaults to 'GET') (optional, default `'GET'`)
619
+ - `body` **(Record<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), unknown> | T)?** Optional request body data to send
620
+ - `options` (optional, default `{}`)
621
+
622
+ ##### Examples
202
623
 
203
624
  ```javascript
204
- // Fetch user data
625
+ // Fetch a user by ID (default 'GET' request)
205
626
  const userData = await user.sync();
206
627
 
207
- // Create new user
208
- const newUser = await user.sync('POST', { name: 'John' });
628
+ // Create a new user (POST request)
629
+ const newUser = await user.sync('POST', { name: 'John', email: 'john@example.com' });
630
+
631
+ // Update an existing user (PUT request)
632
+ const updatedUser = await user.sync('PUT', user.toJSON());
209
633
 
210
- // Update user
211
- const updated = await user.sync('PUT', user.toJSON());
634
+ // Delete a user (DELETE request)
635
+ await user.sync('DELETE'); // Returns null for 204 responses
212
636
  ```
213
637
 
214
- ##### fetch()
638
+ - Throws **[Error](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)** Throws an error if the HTTP response is not successful
639
+
640
+ Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<(R | null)>** The server response data, or null for 204 No Content responses
641
+
642
+ #### fetch
215
643
 
216
644
  Fetches data from the server and updates the model's attributes.
217
645
 
218
- **Returns:** Promise
646
+ This method performs a GET request to retrieve the latest data for this model
647
+ instance from the server. If the model has an ID, it will fetch the specific
648
+ resource; if it's a new model without an ID, it will make a request to the
649
+ collection endpoint.
650
+
651
+ Upon successful retrieval, the model's attributes are automatically updated
652
+ with the server response data. This method is useful for refreshing a model's
653
+ state or loading data after creating a model instance with just an ID.
654
+
655
+ ##### Examples
219
656
 
220
657
  ```javascript
658
+ // Fetch data for an existing user
221
659
  const user = new User({ id: 1 });
222
- await user.fetch(); // Model now contains full user data
660
+ await user.fetch(); // Model now contains full user data from server
661
+
662
+ // Refresh a model's data
663
+ await existingUser.fetch(); // Updates with latest server data
223
664
  ```
224
665
 
225
- ##### save()
666
+ - Throws **[Error](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)** Throws an error if the HTTP request fails or server returns an error
226
667
 
227
- Saves the model by creating (POST) or updating (PUT) the server resource.
668
+ Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<void>** A promise that resolves when the fetch operation completes
228
669
 
229
- **Returns:** Promise
670
+ #### save
230
671
 
231
- ```javascript
232
- // Create new user
233
- const newUser = new User({ name: 'John' });
234
- await newUser.save(); // POST request
672
+ Saves the model to the server by creating a new resource or updating an existing one.
235
673
 
236
- // Update existing user
237
- user.set({ name: 'Jane' });
238
- await user.save(); // PUT request
239
- ```
674
+ This method automatically determines whether to create or update based on the model's
675
+ state. If the model is new (has no ID), it performs a POST request to create a new
676
+ resource. If the model already exists (has an ID), it performs a PUT request to
677
+ update the existing resource.
240
678
 
241
- ##### destroy()
679
+ After a successful save operation, the model's attributes are updated with any
680
+ data returned from the server. This is particularly useful when the server
681
+ generates additional fields (like timestamps, computed values, or normalized data)
682
+ during the save process.
242
683
 
243
- Deletes the model from the server.
684
+ ##### Parameters
685
+
686
+ - `options` &#x20;
244
687
 
245
- **Returns:** Promise
688
+ ##### Examples
246
689
 
247
690
  ```javascript
248
- await user.destroy(); // DELETE request to /users/1
691
+ // Create a new user
692
+ const newUser = new User({ name: 'John', email: 'john@example.com' });
693
+ await newUser.save(); // POST request, user now has ID from server
694
+
695
+ // Update an existing user
696
+ existingUser.set({ name: 'Jane' });
697
+ await existingUser.save(); // PUT request with updated data
698
+
699
+ // Save without validation
700
+ await user.save({ validate: false });
701
+
702
+ // Save with custom endpoint and headers
703
+ await user.save({
704
+ endpoint: '/api/v2/users',
705
+ headers: { 'Custom-Header': 'value' }
706
+ });
249
707
  ```
250
708
 
251
- ##### toJSON()
709
+ - Throws **[Error](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)** Throws an error if the HTTP request fails or server returns an error
252
710
 
253
- Returns a plain object representation of the model's attributes.
711
+ Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)>** A promise that resolves to true if save succeeds, false if validation fails
254
712
 
255
- **Returns:** Plain object containing all attributes
713
+ #### destroy
714
+
715
+ Deletes the model from the server.
716
+
717
+ This method performs a DELETE request to remove the model's corresponding resource
718
+ from the server. The method only executes if the model has an ID (i.e., it exists
719
+ on the server). If the model is new and has no ID, the method will return without
720
+ performing any operation.
721
+
722
+ The DELETE request is sent to the model's specific resource endpoint using the
723
+ pattern `${baseUrl}${endpoint()}/${id}`. After successful deletion, the model
724
+ instance remains in memory but the corresponding server resource is removed.
725
+
726
+ ##### Examples
256
727
 
257
728
  ```javascript
729
+ // Delete an existing user
258
730
  const user = new User({ id: 1, name: 'John' });
259
- const userData = user.toJSON();
260
- // Returns: { id: 1, name: 'John' }
731
+ await user.destroy(); // DELETE request to /users/1
732
+
733
+ // Attempting to destroy a new model (no operation performed)
734
+ const newUser = new User({ name: 'Jane' }); // No ID
735
+ await newUser.destroy(); // Returns immediately, no HTTP request
261
736
  ```
262
737
 
263
- #### Example Usage
738
+ - Throws **[Error](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)** Throws an error if the HTTP request fails or server returns an error
264
739
 
265
- ```javascript
266
- class User extends Model {
267
- endpoint() {
268
- return '/users';
269
- }
270
- }
740
+ Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<void>** A promise that resolves when the delete operation completes
271
741
 
272
- // Create and save new user
273
- const user = new User({ name: 'John', email: 'john@example.com' });
274
- await user.save();
742
+ #### toJSON
275
743
 
276
- // Fetch existing user
277
- const existingUser = new User({ id: 1 });
278
- await existingUser.fetch();
744
+ Returns a plain JavaScript object representation of the model's attributes.
279
745
 
280
- // Update user
281
- existingUser.set({ name: 'Jane' });
282
- await existingUser.save();
746
+ This method creates a shallow copy of the model's internal attributes, returning
747
+ them as a plain object. This is useful for serialization, debugging, or when you
748
+ need to pass the model's data to functions that expect plain objects rather than
749
+ model instances.
750
+
751
+ The returned object is a copy, so modifications to it will not affect the original
752
+ model's attributes. This method is commonly used internally by other model methods
753
+ (like save()) when preparing data for HTTP requests.
754
+
755
+ ##### Examples
283
756
 
284
- // Delete user
285
- await existingUser.destroy();
757
+ ```javascript
758
+ // Get plain object representation
759
+ const user = new User({ id: 1, name: 'John', email: 'john@example.com' });
760
+ const userData = user.toJSON();
761
+ // Returns: { id: 1, name: 'John', email: 'john@example.com' }
762
+
763
+ // Useful for serialization
764
+ const jsonString = JSON.stringify(user.toJSON());
286
765
  ```
287
766
 
288
- ### `Collection<M, T>`
767
+ Returns **T** A plain object containing all of the model's attributes
289
768
 
290
- The `Collection` class provides a reactive container for managing groups of Model instances with automatic UI updates.
769
+ ### Collection
291
770
 
292
- #### Constructor
771
+ Abstract base class for managing collections of Model instances.
293
772
 
294
- ```javascript
295
- new Collection((models = []));
296
- ```
773
+ Provides a reactive collection that can be populated with data, fetched from a server,
774
+ and manipulated with type-safe operations. The collection is backed by Svelte's reactivity
775
+ system for automatic UI updates.
297
776
 
298
- Creates a new Collection instance with optional initial data.
777
+ #### Parameters
299
778
 
300
- **Parameters:**
779
+ - `models` Optional array of data objects to initialize the collection with (optional, default `[]`)
301
780
 
302
- - `models` (Array) - Optional array of data objects to initialize the collection
781
+ #### Examples
303
782
 
304
- #### Properties
783
+ ```javascript
784
+ class UserCollection extends Collection<UserModel, User> {
785
+ model = UserModel;
786
+ endpoint = () => '/api/users';
787
+ }
305
788
 
306
- - `items` - Reactive array of model instances in the collection
307
- - `length` - Number of items in the collection (read-only)
789
+ const users = new UserCollection();
790
+ await users.fetch(); // Loads users from API
791
+ users.add({ name: 'John', email: 'john@example.com' }); // Adds new user
792
+ ```
308
793
 
309
- #### Abstract Properties
794
+ #### items
310
795
 
311
- These must be implemented by subclasses:
796
+ Reactive array of model instances in the collection
312
797
 
313
- - `model` - The Model class constructor for creating instances
314
- - `endpoint()` - Function that returns the API endpoint URL
798
+ #### length
315
799
 
316
- #### Methods
800
+ Gets the number of items in the collection
317
801
 
318
- ##### add(data)
802
+ #### add
319
803
 
320
804
  Adds a new item to the collection.
321
805
 
322
- **Parameters:**
806
+ ##### Parameters
323
807
 
324
- - `data` - Raw data object or existing model instance
808
+ - `data` Either raw data of type T or an existing model instance of type M
325
809
 
326
- **Returns:** The model instance that was added
810
+ ##### Examples
327
811
 
328
812
  ```javascript
813
+ // Add raw data
329
814
  const user = collection.add({ name: 'John', email: 'john@example.com' });
815
+
816
+ // Add existing model instance
817
+ const existingUser = new UserModel({ name: 'Jane' });
818
+ collection.add(existingUser);
330
819
  ```
331
820
 
332
- ##### reset(data)
821
+ Returns **any** The model instance that was added to the collection
822
+
823
+ #### reset
333
824
 
334
- Replaces all items in the collection with new data.
825
+ Resets the collection with new data, replacing all existing items.
335
826
 
336
- **Parameters:**
827
+ ##### Parameters
337
828
 
338
- - `data` - Array of raw data objects
829
+ - `data` An array of raw data objects to populate the collection with
830
+
831
+ ##### Examples
339
832
 
340
833
  ```javascript
834
+ // Reset collection with new user data
341
835
  collection.reset([
342
- { id: 1, name: 'John' },
343
- { id: 2, name: 'Jane' }
836
+ { id: 1, name: 'John', email: 'john@example.com' },
837
+ { id: 2, name: 'Jane', email: 'jane@example.com' }
344
838
  ]);
345
839
  ```
346
840
 
347
- ##### find(query)
841
+ #### find
348
842
 
349
- Finds the first item matching the query object.
843
+ Finds the first item in the collection that matches the given query.
350
844
 
351
- **Parameters:**
845
+ ##### Parameters
352
846
 
353
- - `query` - Object with key-value pairs to match
847
+ - `query` An object containing key-value pairs to match against items in the collection.
848
+ Only items that match all specified properties will be returned.
354
849
 
355
- **Returns:** The first matching item or `undefined`
850
+ ##### Examples
356
851
 
357
852
  ```javascript
853
+ // Find a user by ID
358
854
  const user = collection.find({ id: 123 });
855
+
856
+ // Find by multiple properties
359
857
  const activeAdmin = collection.find({ role: 'admin', status: 'active' });
360
858
  ```
361
859
 
362
- ##### fetch(options)
860
+ Returns **any** The first matching item, or undefined if no match is found.
861
+
862
+ #### fetch
363
863
 
364
864
  Fetches data from the server and populates the collection.
365
865
 
366
- **Parameters:**
866
+ ##### Parameters
367
867
 
368
- - `options.search` - Optional search parameters for the query string
868
+ - `options` Configuration options for the fetch request (optional, default `{}`)
869
+ - `options.endpoint` Optional endpoint to use if different than this.endpoint()
870
+ - `options.search` Optional search parameters to include in the query string.
871
+ Keys and values will be converted to strings and URL-encoded.
369
872
 
370
- **Returns:** Promise
873
+ ##### Examples
371
874
 
372
875
  ```javascript
373
876
  // Fetch all items
@@ -378,3 +881,5 @@ await collection.fetch({
378
881
  search: { limit: 30, after: 29 }
379
882
  });
380
883
  ```
884
+
885
+ - Throws **[Error](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)** Throws an error if the HTTP request fails or returns a non-ok status