@jwerre/vellum 1.3.0-next.2 → 1.3.0-next.3
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 +205 -81
- package/dist/Collection.svelte.d.ts +16 -4
- package/dist/Collection.svelte.js +11 -4
- package/dist/Model.svelte.d.ts +34 -39
- package/dist/Model.svelte.js +39 -45
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
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 that is inspired by Backbone.js but 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
|
|
|
@@ -61,9 +61,7 @@ interface BookSchema {
|
|
|
61
61
|
|
|
62
62
|
export class Book extends Model<BookSchema> {
|
|
63
63
|
|
|
64
|
-
endpoint
|
|
65
|
-
return `/v1/books`;
|
|
66
|
-
}
|
|
64
|
+
endpoint = `/v1/books`;
|
|
67
65
|
|
|
68
66
|
defaults() {
|
|
69
67
|
return {
|
|
@@ -108,10 +106,7 @@ import { Book } from './Book.svelte.js';
|
|
|
108
106
|
|
|
109
107
|
export class Books extends Collection<Book, BookSchema> {
|
|
110
108
|
model = Book;
|
|
111
|
-
|
|
112
|
-
endpoint() {
|
|
113
|
-
return `/v1/books`;
|
|
114
|
-
}
|
|
109
|
+
endpoint = `/v1/books`;
|
|
115
110
|
|
|
116
111
|
// Derived state for the entire collection
|
|
117
112
|
classicCount = $derived(this.items.filter((u) => u.isClassic()).length);
|
|
@@ -124,40 +119,20 @@ Vellum works seamlessly with Svelte 5 components.
|
|
|
124
119
|
|
|
125
120
|
```svelte
|
|
126
121
|
<script lang="ts">
|
|
127
|
-
import {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
});
|
|
122
|
+
import { Book } from './Book.svelte.js';
|
|
123
|
+
import { Books } from './Books.svelte.js';
|
|
124
|
+
|
|
125
|
+
const books = new Books();
|
|
126
|
+
await books.fetch();
|
|
127
|
+
|
|
128
|
+
async function onSubmitBook(e: Event) {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
const form = e.target as HTMLFormElement;
|
|
131
|
+
const formData = new FormData(form);
|
|
132
|
+
const data = Object.fromEntries(formData) as BookSchema;
|
|
133
|
+
const book = new Book(data);
|
|
134
|
+
await book.save();
|
|
135
|
+
books.add(book);
|
|
161
136
|
}
|
|
162
137
|
</script>
|
|
163
138
|
|
|
@@ -170,8 +145,18 @@ Vellum works seamlessly with Svelte 5 components.
|
|
|
170
145
|
{/if}
|
|
171
146
|
{/each}
|
|
172
147
|
</ul>
|
|
173
|
-
|
|
174
|
-
<
|
|
148
|
+
<form onsubmit={onSubmitBook}>
|
|
149
|
+
<div>
|
|
150
|
+
<label for="title">Title</label>
|
|
151
|
+
<input type="text" id="title" name="title" />
|
|
152
|
+
</div>
|
|
153
|
+
<div>
|
|
154
|
+
<label for="author">Author</label>
|
|
155
|
+
<input type="text" id="author" name="author" />
|
|
156
|
+
</div>
|
|
157
|
+
...
|
|
158
|
+
<button type="submit">+ Add book</button>
|
|
159
|
+
</form>
|
|
175
160
|
```
|
|
176
161
|
|
|
177
162
|
### Working example
|
|
@@ -199,60 +184,72 @@ There is a working example of Vellum in the `routes` directory. To run it, clone
|
|
|
199
184
|
- [idAttribute](#idattribute)
|
|
200
185
|
- [Examples](#examples-2)
|
|
201
186
|
- [validationError](#validationerror)
|
|
187
|
+
- [changed](#changed)
|
|
188
|
+
- [previous](#previous)
|
|
202
189
|
- [defaults](#defaults)
|
|
203
190
|
- [Examples](#examples-3)
|
|
204
|
-
- [
|
|
191
|
+
- [hasChanged](#haschanged)
|
|
205
192
|
- [Parameters](#parameters-2)
|
|
206
193
|
- [Examples](#examples-4)
|
|
207
|
-
- [
|
|
194
|
+
- [get](#get)
|
|
208
195
|
- [Parameters](#parameters-3)
|
|
209
196
|
- [Examples](#examples-5)
|
|
210
|
-
- [
|
|
197
|
+
- [has](#has)
|
|
211
198
|
- [Parameters](#parameters-4)
|
|
212
199
|
- [Examples](#examples-6)
|
|
213
|
-
- [
|
|
214
|
-
- [Examples](#examples-7)
|
|
215
|
-
- [escape](#escape)
|
|
200
|
+
- [unset](#unset)
|
|
216
201
|
- [Parameters](#parameters-5)
|
|
202
|
+
- [Examples](#examples-7)
|
|
203
|
+
- [clear](#clear)
|
|
217
204
|
- [Examples](#examples-8)
|
|
218
|
-
- [
|
|
219
|
-
- [isValid](#isvalid)
|
|
205
|
+
- [escape](#escape)
|
|
220
206
|
- [Parameters](#parameters-6)
|
|
221
207
|
- [Examples](#examples-9)
|
|
222
|
-
- [
|
|
208
|
+
- [isNew](#isnew)
|
|
209
|
+
- [isValid](#isvalid)
|
|
223
210
|
- [Parameters](#parameters-7)
|
|
224
211
|
- [Examples](#examples-10)
|
|
225
|
-
- [
|
|
212
|
+
- [validate](#validate)
|
|
226
213
|
- [Parameters](#parameters-8)
|
|
227
214
|
- [Examples](#examples-11)
|
|
228
|
-
- [
|
|
229
|
-
- [Examples](#examples-12)
|
|
230
|
-
- [save](#save)
|
|
215
|
+
- [clone](#clone)
|
|
231
216
|
- [Parameters](#parameters-9)
|
|
217
|
+
- [Examples](#examples-12)
|
|
218
|
+
- [sync](#sync)
|
|
219
|
+
- [Parameters](#parameters-10)
|
|
232
220
|
- [Examples](#examples-13)
|
|
233
|
-
- [
|
|
221
|
+
- [fetch](#fetch)
|
|
234
222
|
- [Examples](#examples-14)
|
|
235
|
-
- [
|
|
223
|
+
- [save](#save)
|
|
224
|
+
- [Parameters](#parameters-11)
|
|
236
225
|
- [Examples](#examples-15)
|
|
226
|
+
- [destroy](#destroy)
|
|
227
|
+
- [Examples](#examples-16)
|
|
228
|
+
- [toJSON](#tojson)
|
|
229
|
+
- [Examples](#examples-17)
|
|
230
|
+
- [attributes](#attributes)
|
|
237
231
|
- [validationError](#validationerror-1)
|
|
232
|
+
- [changed](#changed-1)
|
|
233
|
+
- [previous](#previous-1)
|
|
238
234
|
- [Collection](#collection)
|
|
239
|
-
- [Parameters](#parameters-
|
|
240
|
-
- [Examples](#examples-
|
|
235
|
+
- [Parameters](#parameters-12)
|
|
236
|
+
- [Examples](#examples-18)
|
|
241
237
|
- [items](#items)
|
|
242
238
|
- [length](#length)
|
|
243
239
|
- [add](#add)
|
|
244
|
-
- [Parameters](#parameters-
|
|
245
|
-
- [Examples](#examples-
|
|
240
|
+
- [Parameters](#parameters-13)
|
|
241
|
+
- [Examples](#examples-19)
|
|
246
242
|
- [sort](#sort)
|
|
243
|
+
- [Examples](#examples-20)
|
|
247
244
|
- [reset](#reset)
|
|
248
|
-
- [Parameters](#parameters-
|
|
249
|
-
- [Examples](#examples-
|
|
245
|
+
- [Parameters](#parameters-14)
|
|
246
|
+
- [Examples](#examples-21)
|
|
250
247
|
- [find](#find)
|
|
251
|
-
- [Parameters](#parameters-
|
|
252
|
-
- [Examples](#examples-
|
|
248
|
+
- [Parameters](#parameters-15)
|
|
249
|
+
- [Examples](#examples-22)
|
|
253
250
|
- [fetch](#fetch-1)
|
|
254
|
-
- [Parameters](#parameters-
|
|
255
|
-
- [Examples](#examples-
|
|
251
|
+
- [Parameters](#parameters-16)
|
|
252
|
+
- [Examples](#examples-23)
|
|
256
253
|
|
|
257
254
|
### vellumConfig
|
|
258
255
|
|
|
@@ -329,9 +326,8 @@ interface UserAttributes {
|
|
|
329
326
|
}
|
|
330
327
|
|
|
331
328
|
class User extends Model<UserAttributes> {
|
|
332
|
-
endpoint
|
|
333
|
-
|
|
334
|
-
}
|
|
329
|
+
endpoint = '/users';
|
|
330
|
+
|
|
335
331
|
defaults() {
|
|
336
332
|
return { name: '', createdAt: new Date() };
|
|
337
333
|
}
|
|
@@ -365,9 +361,7 @@ const user = new User({ id: 1, name: 'John' });
|
|
|
365
361
|
// Custom ID attribute can be specified in constructor options
|
|
366
362
|
class User extends Model<UserSchema> {
|
|
367
363
|
idAttribute = '_id
|
|
368
|
-
endpoint
|
|
369
|
-
return '/users';
|
|
370
|
-
}
|
|
364
|
+
endpoint = '/users';
|
|
371
365
|
}
|
|
372
366
|
const user = new User({ _id: '507f1f77bcf86cd799439011', name: 'John' });
|
|
373
367
|
```
|
|
@@ -376,6 +370,20 @@ const user = new User({ _id: '507f1f77bcf86cd799439011', name: 'John' });
|
|
|
376
370
|
|
|
377
371
|
Gets the latest validation error.
|
|
378
372
|
|
|
373
|
+
#### changed
|
|
374
|
+
|
|
375
|
+
Gets the hash of attributes that have changed since the last set call.
|
|
376
|
+
This is a readonly accessor - the changed object cannot be modified directly.
|
|
377
|
+
|
|
378
|
+
Returns **Partial\<T>** An object containing all attributes that have changed
|
|
379
|
+
|
|
380
|
+
#### previous
|
|
381
|
+
|
|
382
|
+
Gets the hash of previous attribute values before they were changed.
|
|
383
|
+
This is a readonly accessor - the previous object cannot be modified directly.
|
|
384
|
+
|
|
385
|
+
Returns **Partial\<T>** An object containing the previous values of changed attributes
|
|
386
|
+
|
|
379
387
|
#### defaults
|
|
380
388
|
|
|
381
389
|
Provides default attribute values for new model instances.
|
|
@@ -410,6 +418,37 @@ const user = new User({ name: 'John' });
|
|
|
410
418
|
|
|
411
419
|
Returns **Partial\<T>** A partial object containing default attribute values
|
|
412
420
|
|
|
421
|
+
#### hasChanged
|
|
422
|
+
|
|
423
|
+
Determines if attributes have changed since the last set call.
|
|
424
|
+
|
|
425
|
+
This method checks whether any attributes were modified in the most recent
|
|
426
|
+
set operation. When called without arguments, it returns true if any attributes
|
|
427
|
+
have changed. When called with a specific attribute key, it returns true only
|
|
428
|
+
if that particular attribute was changed.
|
|
429
|
+
|
|
430
|
+
##### Parameters
|
|
431
|
+
|
|
432
|
+
- `attr`  
|
|
433
|
+
|
|
434
|
+
##### Examples
|
|
435
|
+
|
|
436
|
+
```javascript
|
|
437
|
+
// Check if any attributes changed
|
|
438
|
+
const user = new User({ name: 'John', email: 'john@example.com' });
|
|
439
|
+
user.set('name', 'Jane');
|
|
440
|
+
user.hasChanged(); // Returns true
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
```javascript
|
|
444
|
+
// Check if a specific attribute changed
|
|
445
|
+
user.set('name', 'Jane');
|
|
446
|
+
user.hasChanged('name'); // Returns true
|
|
447
|
+
user.hasChanged('email'); // Returns false
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if attributes have changed, false otherwise
|
|
451
|
+
|
|
413
452
|
#### get
|
|
414
453
|
|
|
415
454
|
Retrieves the value of a specific attribute from the model.
|
|
@@ -638,19 +677,62 @@ validate(attributes: Partial<UserAttributes>) {
|
|
|
638
677
|
|
|
639
678
|
Returns **any** Returns undefined if valid, or an error (string/object) if invalid
|
|
640
679
|
|
|
680
|
+
#### clone
|
|
681
|
+
|
|
682
|
+
Creates a new instance of the model with identical attributes.
|
|
683
|
+
|
|
684
|
+
This method returns a new model instance that is a clone of the current model,
|
|
685
|
+
with all attributes copied to the new instance. The clone is a separate object
|
|
686
|
+
with its own state, so modifications to the clone will not affect the original
|
|
687
|
+
model and vice versa. By default, the ID is removed so the cloned instance is
|
|
688
|
+
considered "new" (isNew() returns true).
|
|
689
|
+
|
|
690
|
+
##### Parameters
|
|
691
|
+
|
|
692
|
+
- `options` **CloneOptions?** Configuration options for cloning
|
|
693
|
+
- `options.keepId` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** If true, preserves the ID in the clone (optional, default `false`)
|
|
694
|
+
- `options.deep` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** If true, performs a deep clone of nested objects/arrays (optional, default `false`)
|
|
695
|
+
|
|
696
|
+
##### Examples
|
|
697
|
+
|
|
698
|
+
```javascript
|
|
699
|
+
// Clone a user model (default - no ID)
|
|
700
|
+
const user = new User({ id: 1, name: 'John', email: 'john@example.com' });
|
|
701
|
+
const userCopy = user.clone();
|
|
702
|
+
console.log(userCopy.isNew()); // true
|
|
703
|
+
console.log(userCopy.get('id')); // undefined
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
```javascript
|
|
707
|
+
// Clone with ID preserved
|
|
708
|
+
const userCopy = user.clone({ keepId: true });
|
|
709
|
+
console.log(userCopy.isNew()); // false
|
|
710
|
+
console.log(userCopy.get('id')); // 1
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
```javascript
|
|
714
|
+
// Deep clone for nested objects
|
|
715
|
+
const user = new User({ id: 1, name: 'John', settings: { theme: 'dark' } });
|
|
716
|
+
const userCopy = user.clone({ deep: true });
|
|
717
|
+
userCopy.get('settings').theme = 'light';
|
|
718
|
+
console.log(user.get('settings').theme); // 'dark' (unchanged)
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
Returns **this** A new instance of the same model class with cloned attributes
|
|
722
|
+
|
|
641
723
|
#### sync
|
|
642
724
|
|
|
643
725
|
Performs HTTP synchronization with the server for CRUD operations.
|
|
644
726
|
|
|
645
727
|
This method handles all HTTP communication between the model and the server,
|
|
646
728
|
automatically constructing the appropriate URL based on the model's ID and
|
|
647
|
-
endpoint
|
|
729
|
+
endpoint. It supports all standard REST operations and provides type-safe
|
|
648
730
|
response handling.
|
|
649
731
|
|
|
650
732
|
The URL construction follows REST conventions:
|
|
651
733
|
|
|
652
|
-
- For new models (no ID): uses collection endpoint `${baseUrl}${endpoint
|
|
653
|
-
- For existing models (with ID): uses resource endpoint `${baseUrl}${endpoint
|
|
734
|
+
- For new models (no ID): uses collection endpoint `${baseUrl}${endpoint}`
|
|
735
|
+
- For existing models (with ID): uses resource endpoint `${baseUrl}${endpoint}/${id}`
|
|
654
736
|
|
|
655
737
|
##### Parameters
|
|
656
738
|
|
|
@@ -759,7 +841,7 @@ on the server). If the model is new and has no ID, the method will return withou
|
|
|
759
841
|
performing any operation.
|
|
760
842
|
|
|
761
843
|
The DELETE request is sent to the model's specific resource endpoint using the
|
|
762
|
-
pattern `${baseUrl}${endpoint
|
|
844
|
+
pattern `${baseUrl}${endpoint}/${id}`. After successful deletion, the model
|
|
763
845
|
instance remains in memory but the corresponding server resource is removed.
|
|
764
846
|
|
|
765
847
|
##### Examples
|
|
@@ -805,11 +887,35 @@ const jsonString = JSON.stringify(user.toJSON());
|
|
|
805
887
|
|
|
806
888
|
Returns **T** A plain object containing all of the model's attributes
|
|
807
889
|
|
|
890
|
+
### attributes
|
|
891
|
+
|
|
892
|
+
Internal reactive storage for all model attributes.
|
|
893
|
+
|
|
894
|
+
This private field uses Svelte's $state rune to create a reactive object that
|
|
895
|
+
holds all the model's attribute data. When attributes are modified, this reactivity
|
|
896
|
+
ensures that any Svelte components using the model will automatically re-render
|
|
897
|
+
to reflect the changes.
|
|
898
|
+
|
|
899
|
+
The attributes are initialized as an empty object cast to type T, and are populated
|
|
900
|
+
during construction by merging default values with provided data. All attribute
|
|
901
|
+
access and modification should go through the public API methods (get, set, has, etc.)
|
|
902
|
+
rather than directly accessing this field.
|
|
903
|
+
|
|
808
904
|
### validationError
|
|
809
905
|
|
|
810
906
|
Validation error property that gets set when validation fails.
|
|
811
907
|
This property contains the error returned by the validate method.
|
|
812
908
|
|
|
909
|
+
### changed
|
|
910
|
+
|
|
911
|
+
Internal hash containing all attributes that have changed since the last set call.
|
|
912
|
+
This property tracks which attributes have been modified and their new values.
|
|
913
|
+
|
|
914
|
+
### previous
|
|
915
|
+
|
|
916
|
+
Internal hash containing the previous values of attributes before they were changed.
|
|
917
|
+
This property stores the original values of attributes from before the last set call.
|
|
918
|
+
|
|
813
919
|
### Collection
|
|
814
920
|
|
|
815
921
|
Abstract base class for managing collections of Model instances.
|
|
@@ -827,7 +933,7 @@ system for automatic UI updates.
|
|
|
827
933
|
```javascript
|
|
828
934
|
class UserCollection extends Collection<UserModel, User> {
|
|
829
935
|
model = UserModel;
|
|
830
|
-
endpoint =
|
|
936
|
+
endpoint = '/api/users';
|
|
831
937
|
}
|
|
832
938
|
|
|
833
939
|
const users = new UserCollection();
|
|
@@ -867,7 +973,25 @@ Returns **any** The model instance that was added to the collection
|
|
|
867
973
|
#### sort
|
|
868
974
|
|
|
869
975
|
Sorts the collection using the comparator if one is defined.
|
|
870
|
-
|
|
976
|
+
|
|
977
|
+
This method is called automatically when items are added to the collection via `add()` or `reset()`.
|
|
978
|
+
You can also call it manually to re-sort the collection after modifying model attributes.
|
|
979
|
+
|
|
980
|
+
The sorting behavior depends on the type of comparator defined:
|
|
981
|
+
|
|
982
|
+
- **String attribute**: Sorts by the specified model attribute in ascending order
|
|
983
|
+
- **sortBy function** (1 argument): Sorts by the return value of the function in ascending order
|
|
984
|
+
- **sort function** (2 arguments): Uses a custom comparator that returns -1, 0, or 1
|
|
985
|
+
|
|
986
|
+
If no comparator is defined or the comparator returns undefined, no sorting is performed.
|
|
987
|
+
|
|
988
|
+
##### Examples
|
|
989
|
+
|
|
990
|
+
```javascript
|
|
991
|
+
// Manual sorting after updating a model
|
|
992
|
+
user.set('priority', 5);
|
|
993
|
+
collection.sort();
|
|
994
|
+
```
|
|
871
995
|
|
|
872
996
|
#### reset
|
|
873
997
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Model } from './Model.svelte';
|
|
2
2
|
import { type VellumConfig } from './config.svelte';
|
|
3
|
+
export interface CollectionOptions extends Partial<VellumConfig> {
|
|
4
|
+
model?: unknown;
|
|
5
|
+
}
|
|
3
6
|
export interface FetchOptions extends Partial<VellumConfig> {
|
|
4
7
|
endpoint?: string;
|
|
5
8
|
search?: Record<string, string | number | boolean>;
|
|
@@ -17,7 +20,7 @@ export interface FetchOptions extends Partial<VellumConfig> {
|
|
|
17
20
|
* @example
|
|
18
21
|
* class UserCollection extends Collection<UserModel, User> {
|
|
19
22
|
* model = UserModel;
|
|
20
|
-
* endpoint =
|
|
23
|
+
* endpoint = '/api/users';
|
|
21
24
|
* }
|
|
22
25
|
*
|
|
23
26
|
* const users = new UserCollection();
|
|
@@ -31,8 +34,16 @@ export declare abstract class Collection<M extends Model<T>, T extends object> {
|
|
|
31
34
|
abstract model: {
|
|
32
35
|
new (data: Partial<T>): M;
|
|
33
36
|
};
|
|
34
|
-
/**
|
|
35
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The base URL path for API endpoints related to this model.
|
|
39
|
+
*
|
|
40
|
+
* Define this as a property in your subclass.
|
|
41
|
+
* @example
|
|
42
|
+
* class User extends Collection<UserSchema> {
|
|
43
|
+
* endpoint = '/users';
|
|
44
|
+
* }
|
|
45
|
+
*/
|
|
46
|
+
protected abstract endpoint: string;
|
|
36
47
|
/**
|
|
37
48
|
* Optional comparator for sorting the collection.
|
|
38
49
|
*
|
|
@@ -83,7 +94,8 @@ export declare abstract class Collection<M extends Model<T>, T extends object> {
|
|
|
83
94
|
* const collection = new UserCollection();
|
|
84
95
|
*
|
|
85
96
|
* // Create collection with initial data
|
|
86
|
-
* const collection = new UserCollection(
|
|
97
|
+
* const collection = new UserCollection();
|
|
98
|
+
* collection.reset([
|
|
87
99
|
* { id: 1, name: 'John' },
|
|
88
100
|
* { id: 2, name: 'Jane' }
|
|
89
101
|
* ]);
|
|
@@ -14,7 +14,7 @@ import { vellumConfig } from './config.svelte';
|
|
|
14
14
|
* @example
|
|
15
15
|
* class UserCollection extends Collection<UserModel, User> {
|
|
16
16
|
* model = UserModel;
|
|
17
|
-
* endpoint =
|
|
17
|
+
* endpoint = '/api/users';
|
|
18
18
|
* }
|
|
19
19
|
*
|
|
20
20
|
* const users = new UserCollection();
|
|
@@ -34,14 +34,21 @@ export class Collection {
|
|
|
34
34
|
* const collection = new UserCollection();
|
|
35
35
|
*
|
|
36
36
|
* // Create collection with initial data
|
|
37
|
-
* const collection = new UserCollection(
|
|
37
|
+
* const collection = new UserCollection();
|
|
38
|
+
* collection.reset([
|
|
38
39
|
* { id: 1, name: 'John' },
|
|
39
40
|
* { id: 2, name: 'Jane' }
|
|
40
41
|
* ]);
|
|
41
42
|
*/
|
|
42
43
|
constructor(models = []) {
|
|
43
44
|
if (models.length > 0) {
|
|
44
|
-
|
|
45
|
+
try {
|
|
46
|
+
this.reset(models);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('Failed to initialize collection with data:', error);
|
|
50
|
+
console.error('Initializing the collection with data requires model to be defined as getter since model is not available in constructor.');
|
|
51
|
+
}
|
|
45
52
|
}
|
|
46
53
|
}
|
|
47
54
|
/** Gets the number of items in the collection */
|
|
@@ -194,7 +201,7 @@ export class Collection {
|
|
|
194
201
|
}
|
|
195
202
|
query = `?${params.toString()}`;
|
|
196
203
|
}
|
|
197
|
-
const endpoint = options?.endpoint?.length ? options.endpoint : this.endpoint
|
|
204
|
+
const endpoint = options?.endpoint?.length ? options.endpoint : this.endpoint;
|
|
198
205
|
const fullUrl = `${vellumConfig.origin}${endpoint}${query}`;
|
|
199
206
|
const response = await fetch(fullUrl, {
|
|
200
207
|
headers: { ...vellumConfig.headers }
|
package/dist/Model.svelte.d.ts
CHANGED
|
@@ -41,9 +41,8 @@ export interface CloneOptions {
|
|
|
41
41
|
* }
|
|
42
42
|
*
|
|
43
43
|
* class User extends Model<UserAttributes> {
|
|
44
|
-
* endpoint
|
|
45
|
-
|
|
46
|
-
* }
|
|
44
|
+
* endpoint = '/users';
|
|
45
|
+
|
|
47
46
|
* defaults() {
|
|
48
47
|
* return { name: '', createdAt: new Date() };
|
|
49
48
|
* }
|
|
@@ -63,18 +62,16 @@ export declare abstract class Model<T extends object> {
|
|
|
63
62
|
* for API endpoints related to this model.
|
|
64
63
|
*
|
|
65
64
|
* This method returns the root URL segment that will be appended to the base API URL
|
|
66
|
-
* to form complete endpoints for CRUD operations. For example, if endpoint
|
|
65
|
+
* to form complete endpoints for CRUD operations. For example, if endpoint is set to
|
|
67
66
|
* '/users', the full URL for API calls would be `${baseUrl}/users` for collections
|
|
68
67
|
* or `${baseUrl}/users/{id}` for individual resources.
|
|
69
68
|
*
|
|
70
69
|
* @returns {string} The root URL path for this model's API endpoints (e.g., '/users', '/posts')
|
|
71
70
|
* @example
|
|
72
71
|
* // In a User model subclass:
|
|
73
|
-
* endpoint
|
|
74
|
-
* return '/users';
|
|
75
|
-
* }
|
|
72
|
+
* endpoint = '/users';
|
|
76
73
|
*/
|
|
77
|
-
protected abstract endpoint
|
|
74
|
+
protected abstract endpoint: string;
|
|
78
75
|
/**
|
|
79
76
|
* The name of the attribute that serves as the unique identifier for this model instance.
|
|
80
77
|
*
|
|
@@ -93,9 +90,7 @@ export declare abstract class Model<T extends object> {
|
|
|
93
90
|
* // Custom ID attribute can be specified in constructor options
|
|
94
91
|
* class User extends Model<UserSchema> {
|
|
95
92
|
* idAttribute = '_id
|
|
96
|
-
* endpoint
|
|
97
|
-
* return '/users';
|
|
98
|
-
* }
|
|
93
|
+
* endpoint = '/users';
|
|
99
94
|
* }
|
|
100
95
|
* const user = new User({ _id: '507f1f77bcf86cd799439011', name: 'John' });
|
|
101
96
|
*/
|
|
@@ -124,30 +119,6 @@ export declare abstract class Model<T extends object> {
|
|
|
124
119
|
* @returns {Partial<T>} An object containing the previous values of changed attributes
|
|
125
120
|
*/
|
|
126
121
|
get previous(): Partial<T>;
|
|
127
|
-
/**
|
|
128
|
-
* Determines if attributes have changed since the last set call.
|
|
129
|
-
*
|
|
130
|
-
* This method checks whether any attributes were modified in the most recent
|
|
131
|
-
* set operation. When called without arguments, it returns true if any attributes
|
|
132
|
-
* have changed. When called with a specific attribute key, it returns true only
|
|
133
|
-
* if that particular attribute was changed.
|
|
134
|
-
*
|
|
135
|
-
* @param {keyof T} [attr] - Optional attribute key to check for changes
|
|
136
|
-
* @returns {boolean} True if attributes have changed, false otherwise
|
|
137
|
-
*
|
|
138
|
-
* @example
|
|
139
|
-
* // Check if any attributes changed
|
|
140
|
-
* const user = new User({ name: 'John', email: 'john@example.com' });
|
|
141
|
-
* user.set('name', 'Jane');
|
|
142
|
-
* user.hasChanged(); // Returns true
|
|
143
|
-
*
|
|
144
|
-
* @example
|
|
145
|
-
* // Check if a specific attribute changed
|
|
146
|
-
* user.set('name', 'Jane');
|
|
147
|
-
* user.hasChanged('name'); // Returns true
|
|
148
|
-
* user.hasChanged('email'); // Returns false
|
|
149
|
-
*/
|
|
150
|
-
hasChanged(attr?: keyof T): boolean;
|
|
151
122
|
/**
|
|
152
123
|
* Provides default attribute values for new model instances.
|
|
153
124
|
*
|
|
@@ -179,6 +150,30 @@ export declare abstract class Model<T extends object> {
|
|
|
179
150
|
* // Resulting attributes: { role: 'user', isActive: true, createdAt: Date, name: 'John' }
|
|
180
151
|
*/
|
|
181
152
|
protected defaults(): Partial<T>;
|
|
153
|
+
/**
|
|
154
|
+
* Determines if attributes have changed since the last set call.
|
|
155
|
+
*
|
|
156
|
+
* This method checks whether any attributes were modified in the most recent
|
|
157
|
+
* set operation. When called without arguments, it returns true if any attributes
|
|
158
|
+
* have changed. When called with a specific attribute key, it returns true only
|
|
159
|
+
* if that particular attribute was changed.
|
|
160
|
+
*
|
|
161
|
+
* @param {keyof T} [attr] - Optional attribute key to check for changes
|
|
162
|
+
* @returns {boolean} True if attributes have changed, false otherwise
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* // Check if any attributes changed
|
|
166
|
+
* const user = new User({ name: 'John', email: 'john@example.com' });
|
|
167
|
+
* user.set('name', 'Jane');
|
|
168
|
+
* user.hasChanged(); // Returns true
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* // Check if a specific attribute changed
|
|
172
|
+
* user.set('name', 'Jane');
|
|
173
|
+
* user.hasChanged('name'); // Returns true
|
|
174
|
+
* user.hasChanged('email'); // Returns false
|
|
175
|
+
*/
|
|
176
|
+
hasChanged(attr?: keyof T): boolean;
|
|
182
177
|
/**
|
|
183
178
|
* Retrieves the value of a specific attribute from the model.
|
|
184
179
|
*
|
|
@@ -456,12 +451,12 @@ export declare abstract class Model<T extends object> {
|
|
|
456
451
|
*
|
|
457
452
|
* This method handles all HTTP communication between the model and the server,
|
|
458
453
|
* automatically constructing the appropriate URL based on the model's ID and
|
|
459
|
-
* endpoint
|
|
454
|
+
* endpoint. It supports all standard REST operations and provides type-safe
|
|
460
455
|
* response handling.
|
|
461
456
|
*
|
|
462
457
|
* The URL construction follows REST conventions:
|
|
463
|
-
* - For new models (no ID): uses collection endpoint `${baseUrl}${endpoint
|
|
464
|
-
* - For existing models (with ID): uses resource endpoint `${baseUrl}${endpoint
|
|
458
|
+
* - For new models (no ID): uses collection endpoint `${baseUrl}${endpoint}`
|
|
459
|
+
* - For existing models (with ID): uses resource endpoint `${baseUrl}${endpoint}/${id}`
|
|
465
460
|
*
|
|
466
461
|
* @template R - The expected response type, defaults to T (the model's attribute type)
|
|
467
462
|
* @param {('GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE')} [method='GET'] - The HTTP method to use (defaults to 'GET')
|
|
@@ -557,7 +552,7 @@ export declare abstract class Model<T extends object> {
|
|
|
557
552
|
* performing any operation.
|
|
558
553
|
*
|
|
559
554
|
* The DELETE request is sent to the model's specific resource endpoint using the
|
|
560
|
-
* pattern `${baseUrl}${endpoint
|
|
555
|
+
* pattern `${baseUrl}${endpoint}/${id}`. After successful deletion, the model
|
|
561
556
|
* instance remains in memory but the corresponding server resource is removed.
|
|
562
557
|
*
|
|
563
558
|
* @returns {Promise<void>} A promise that resolves when the delete operation completes
|
package/dist/Model.svelte.js
CHANGED
|
@@ -30,9 +30,8 @@ import { escapeHTML } from './utils.js';
|
|
|
30
30
|
* }
|
|
31
31
|
*
|
|
32
32
|
* class User extends Model<UserAttributes> {
|
|
33
|
-
* endpoint
|
|
34
|
-
|
|
35
|
-
* }
|
|
33
|
+
* endpoint = '/users';
|
|
34
|
+
|
|
36
35
|
* defaults() {
|
|
37
36
|
* return { name: '', createdAt: new Date() };
|
|
38
37
|
* }
|
|
@@ -58,9 +57,6 @@ export class Model {
|
|
|
58
57
|
* during construction by merging default values with provided data. All attribute
|
|
59
58
|
* access and modification should go through the public API methods (get, set, has, etc.)
|
|
60
59
|
* rather than directly accessing this field.
|
|
61
|
-
*
|
|
62
|
-
* @private
|
|
63
|
-
* @type {T}
|
|
64
60
|
*/
|
|
65
61
|
#attributes = $state({});
|
|
66
62
|
/**
|
|
@@ -72,12 +68,12 @@ export class Model {
|
|
|
72
68
|
* Internal hash containing all attributes that have changed since the last set call.
|
|
73
69
|
* This property tracks which attributes have been modified and their new values.
|
|
74
70
|
*/
|
|
75
|
-
#changed = {};
|
|
71
|
+
#changed = $state({});
|
|
76
72
|
/**
|
|
77
73
|
* Internal hash containing the previous values of attributes before they were changed.
|
|
78
74
|
* This property stores the original values of attributes from before the last set call.
|
|
79
75
|
*/
|
|
80
|
-
#previous = {};
|
|
76
|
+
#previous = $state({});
|
|
81
77
|
/**
|
|
82
78
|
* The name of the attribute that serves as the unique identifier for this model instance.
|
|
83
79
|
*
|
|
@@ -96,9 +92,7 @@ export class Model {
|
|
|
96
92
|
* // Custom ID attribute can be specified in constructor options
|
|
97
93
|
* class User extends Model<UserSchema> {
|
|
98
94
|
* idAttribute = '_id
|
|
99
|
-
* endpoint
|
|
100
|
-
* return '/users';
|
|
101
|
-
* }
|
|
95
|
+
* endpoint = '/users';
|
|
102
96
|
* }
|
|
103
97
|
* const user = new User({ _id: '507f1f77bcf86cd799439011', name: 'John' });
|
|
104
98
|
*/
|
|
@@ -136,35 +130,6 @@ export class Model {
|
|
|
136
130
|
get previous() {
|
|
137
131
|
return this.#previous;
|
|
138
132
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Determines if attributes have changed since the last set call.
|
|
141
|
-
*
|
|
142
|
-
* This method checks whether any attributes were modified in the most recent
|
|
143
|
-
* set operation. When called without arguments, it returns true if any attributes
|
|
144
|
-
* have changed. When called with a specific attribute key, it returns true only
|
|
145
|
-
* if that particular attribute was changed.
|
|
146
|
-
*
|
|
147
|
-
* @param {keyof T} [attr] - Optional attribute key to check for changes
|
|
148
|
-
* @returns {boolean} True if attributes have changed, false otherwise
|
|
149
|
-
*
|
|
150
|
-
* @example
|
|
151
|
-
* // Check if any attributes changed
|
|
152
|
-
* const user = new User({ name: 'John', email: 'john@example.com' });
|
|
153
|
-
* user.set('name', 'Jane');
|
|
154
|
-
* user.hasChanged(); // Returns true
|
|
155
|
-
*
|
|
156
|
-
* @example
|
|
157
|
-
* // Check if a specific attribute changed
|
|
158
|
-
* user.set('name', 'Jane');
|
|
159
|
-
* user.hasChanged('name'); // Returns true
|
|
160
|
-
* user.hasChanged('email'); // Returns false
|
|
161
|
-
*/
|
|
162
|
-
hasChanged(attr) {
|
|
163
|
-
if (attr !== undefined) {
|
|
164
|
-
return attr in this.#changed;
|
|
165
|
-
}
|
|
166
|
-
return Object.keys(this.#changed).length > 0;
|
|
167
|
-
}
|
|
168
133
|
/**
|
|
169
134
|
* Provides default attribute values for new model instances.
|
|
170
135
|
*
|
|
@@ -198,6 +163,35 @@ export class Model {
|
|
|
198
163
|
defaults() {
|
|
199
164
|
return {};
|
|
200
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Determines if attributes have changed since the last set call.
|
|
168
|
+
*
|
|
169
|
+
* This method checks whether any attributes were modified in the most recent
|
|
170
|
+
* set operation. When called without arguments, it returns true if any attributes
|
|
171
|
+
* have changed. When called with a specific attribute key, it returns true only
|
|
172
|
+
* if that particular attribute was changed.
|
|
173
|
+
*
|
|
174
|
+
* @param {keyof T} [attr] - Optional attribute key to check for changes
|
|
175
|
+
* @returns {boolean} True if attributes have changed, false otherwise
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* // Check if any attributes changed
|
|
179
|
+
* const user = new User({ name: 'John', email: 'john@example.com' });
|
|
180
|
+
* user.set('name', 'Jane');
|
|
181
|
+
* user.hasChanged(); // Returns true
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* // Check if a specific attribute changed
|
|
185
|
+
* user.set('name', 'Jane');
|
|
186
|
+
* user.hasChanged('name'); // Returns true
|
|
187
|
+
* user.hasChanged('email'); // Returns false
|
|
188
|
+
*/
|
|
189
|
+
hasChanged(attr) {
|
|
190
|
+
if (attr !== undefined) {
|
|
191
|
+
return attr in this.#changed;
|
|
192
|
+
}
|
|
193
|
+
return Object.keys(this.#changed).length > 0;
|
|
194
|
+
}
|
|
201
195
|
/**
|
|
202
196
|
* Retrieves the value of a specific attribute from the model.
|
|
203
197
|
*
|
|
@@ -490,12 +484,12 @@ export class Model {
|
|
|
490
484
|
*
|
|
491
485
|
* This method handles all HTTP communication between the model and the server,
|
|
492
486
|
* automatically constructing the appropriate URL based on the model's ID and
|
|
493
|
-
* endpoint
|
|
487
|
+
* endpoint. It supports all standard REST operations and provides type-safe
|
|
494
488
|
* response handling.
|
|
495
489
|
*
|
|
496
490
|
* The URL construction follows REST conventions:
|
|
497
|
-
* - For new models (no ID): uses collection endpoint `${baseUrl}${endpoint
|
|
498
|
-
* - For existing models (with ID): uses resource endpoint `${baseUrl}${endpoint
|
|
491
|
+
* - For new models (no ID): uses collection endpoint `${baseUrl}${endpoint}`
|
|
492
|
+
* - For existing models (with ID): uses resource endpoint `${baseUrl}${endpoint}/${id}`
|
|
499
493
|
*
|
|
500
494
|
* @template R - The expected response type, defaults to T (the model's attribute type)
|
|
501
495
|
* @param {('GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE')} [method='GET'] - The HTTP method to use (defaults to 'GET')
|
|
@@ -518,7 +512,7 @@ export class Model {
|
|
|
518
512
|
*/
|
|
519
513
|
async sync(method = 'GET', body, options = {}) {
|
|
520
514
|
const id = this.#getId();
|
|
521
|
-
const endpoint = options?.endpoint?.length ? options.endpoint : this.endpoint
|
|
515
|
+
const endpoint = options?.endpoint?.length ? options.endpoint : this.endpoint;
|
|
522
516
|
const fullUrl = `${vellumConfig.origin}${endpoint}`;
|
|
523
517
|
const url = id ? `${fullUrl}/${id}` : fullUrl;
|
|
524
518
|
const fetchOpts = {
|
|
@@ -632,7 +626,7 @@ export class Model {
|
|
|
632
626
|
* performing any operation.
|
|
633
627
|
*
|
|
634
628
|
* The DELETE request is sent to the model's specific resource endpoint using the
|
|
635
|
-
* pattern `${baseUrl}${endpoint
|
|
629
|
+
* pattern `${baseUrl}${endpoint}/${id}`. After successful deletion, the model
|
|
636
630
|
* instance remains in memory but the corresponding server resource is removed.
|
|
637
631
|
*
|
|
638
632
|
* @returns {Promise<void>} A promise that resolves when the delete operation completes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { vellumConfig, configureVellum } from './config.svelte.js';
|
|
2
2
|
export { type ValidationDetails, ValidationError } from './errors/validation_error.js';
|
|
3
3
|
export { type SyncOptions, type ValidationOptions, Model } from './Model.svelte.js';
|
|
4
|
-
export { type FetchOptions, Collection } from './Collection.svelte.js';
|
|
4
|
+
export { type CollectionOptions, type FetchOptions, Collection } from './Collection.svelte.js';
|
|
5
5
|
export * as utils from './utils.js';
|