@jwerre/vellum 1.0.1 → 1.1.1

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,20 +1,129 @@
1
1
  import { vellumConfig } from './config.svelte';
2
+ import { ValidationError } from './errors/validation_error.js';
3
+ import { escapeHTML } from './utils.js';
4
+ /**
5
+ * Abstract base class for creating model instances that interact with RESTful APIs.
6
+ *
7
+ * The Model class provides a structured way to manage data objects with full CRUD
8
+ * (Create, Read, Update, Delete) capabilities. It includes built-in HTTP synchronization,
9
+ * attribute management, and data validation features. This class is designed to work
10
+ * with Svelte's reactivity system using the `$state` rune for automatic UI updates.
11
+ *
12
+ * Key features:
13
+ * - Type-safe attribute access and manipulation
14
+ * - Automatic HTTP synchronization with RESTful APIs
15
+ * - Built-in HTML escaping for XSS prevention
16
+ * - Configurable ID attributes for different database schemas
17
+ * - Reactive attributes that integrate with Svelte's state management
18
+ * - Support for both single attribute and bulk attribute operations
19
+ *
20
+ * @template T - The type definition for the model's attributes, must extend object
21
+ * @abstract This class must be extended by concrete model implementations
22
+ *
23
+ * @example
24
+ * // Define a User model
25
+ * interface UserAttributes {
26
+ * id?: number;
27
+ * name: string;
28
+ * email: string;
29
+ * createdAt?: Date;
30
+ * }
31
+ *
32
+ * class User extends Model<UserAttributes> {
33
+ * endpoint() {
34
+ * return '/users';
35
+ * }
36
+ * defaults() {
37
+ * return { name: '', createdAt: new Date() };
38
+ * }
39
+ * }
40
+ *
41
+ * // Create and use a model instance
42
+ * const user = new User({ name: 'John Doe', email: 'john@example.com' });
43
+ * await user.save(); // Creates new user on server
44
+ * user.set('name', 'Jane Doe');
45
+ * await user.save(); // Updates existing user
46
+ * await user.destroy(); // Deletes user
47
+ */
2
48
  export class Model {
3
49
  #attributes = $state({});
50
+ /**
51
+ * Validation error property that gets set when validation fails.
52
+ * This property contains the error returned by the validate method.
53
+ */
54
+ #validationError = $state();
55
+ /**
56
+ * The name of the attribute that serves as the unique identifier for this model instance.
57
+ *
58
+ * This private field stores the attribute name that will be used to identify the model's
59
+ * primary key when performing operations like determining if the model is new, constructing
60
+ * URLs for API requests, and managing model identity. The default value is 'id', but it
61
+ * can be customized through the ModelOptions parameter in the constructor.
62
+ *
63
+ * @protected
64
+ * @type {string}
65
+ * @default 'id'
66
+ * @example
67
+ * // Default behavior uses 'id' as the identifier
68
+ * const user = new User({ id: 1, name: 'John' });
69
+ *
70
+ * // Custom ID attribute can be specified in constructor options
71
+ * class User extends Model<UserSchema> {
72
+ * idAttribute = '_id
73
+ * endpoint(): string {
74
+ * return '/users';
75
+ * }
76
+ * }
77
+ * const user = new User({ _id: '507f1f77bcf86cd799439011', name: 'John' });
78
+ */
79
+ idAttribute = vellumConfig.idAttribute;
80
+ /**
81
+ * Creates a new instance of Model.
82
+ */
4
83
  constructor(data = {}) {
5
- this.#attributes = { ...data };
84
+ // Initialize model attributes with provided data, ensuring type safety
85
+ this.#attributes = { ...this.defaults(), ...data };
6
86
  }
7
87
  /**
8
- * Internal helper to find the ID
88
+ * Gets the latest validation error.
89
+ *
90
+ * @returns {ValidationError || undefined} An instance of ValidationError if validation failed, otherwise undefined
9
91
  */
10
- #getId() {
11
- // Cast to Record<string, unknown> to allow string indexing
12
- const attrs = this.#attributes;
13
- const id = attrs['id'] ?? attrs['_id'];
14
- if (typeof id === 'string' || typeof id === 'number') {
15
- return id;
16
- }
17
- return undefined;
92
+ get validationError() {
93
+ return this.#validationError;
94
+ }
95
+ /**
96
+ * Provides default attribute values for new model instances.
97
+ *
98
+ * This method is called during model construction to establish initial attribute
99
+ * values before applying any user-provided data. Subclasses can override this
100
+ * method to define default values for their specific attributes, ensuring that
101
+ * models always have sensible initial state.
102
+ *
103
+ * The defaults are applied first, then any data passed to the constructor will
104
+ * override these default values. This allows for flexible model initialization
105
+ * where some attributes have fallback values while others can be explicitly set.
106
+ *
107
+ * @protected
108
+ * @returns {Partial<T>} A partial object containing default attribute values
109
+ *
110
+ * @example
111
+ * // Override in a User model subclass
112
+ * protected defaults(): Partial<UserAttributes> {
113
+ * return {
114
+ * role: 'user',
115
+ * isActive: true,
116
+ * createdAt: new Date()
117
+ * };
118
+ * }
119
+ *
120
+ * @example
121
+ * // Creating a model with defaults
122
+ * const user = new User({ name: 'John' });
123
+ * // Resulting attributes: { role: 'user', isActive: true, createdAt: Date, name: 'John' }
124
+ */
125
+ defaults() {
126
+ return {};
18
127
  }
19
128
  /**
20
129
  * Retrieves the value of a specific attribute from the model.
@@ -35,30 +144,140 @@ export class Model {
35
144
  get(key) {
36
145
  return this.#attributes[key];
37
146
  }
147
+ set(keyOrAttrs, valueOrOptions, options) {
148
+ let attrs = {};
149
+ let opts;
150
+ if (typeof keyOrAttrs === 'object') {
151
+ attrs = keyOrAttrs;
152
+ opts = valueOrOptions;
153
+ }
154
+ else {
155
+ attrs[keyOrAttrs] = valueOrOptions;
156
+ opts = options;
157
+ }
158
+ if (opts?.validate && !this.#doValidation({ ...this.#attributes, ...attrs }, opts)) {
159
+ return false;
160
+ }
161
+ Object.assign(this.#attributes, attrs);
162
+ return true;
163
+ }
164
+ /**
165
+ * Checks whether a specific attribute has a non-null, non-undefined value.
166
+ *
167
+ * This method provides a way to determine if an attribute exists and has a
168
+ * meaningful value. It returns true if the attribute is set to any value
169
+ * other than null or undefined, including falsy values like false, 0, or
170
+ * empty strings, which are considered valid values.
171
+ *
172
+ * @template K - The key type, constrained to keys of T
173
+ * @param {K} key - The attribute key to check
174
+ * @returns {boolean} True if the attribute has a non-null, non-undefined value
175
+ * @example
176
+ * // Assuming a User model with attributes { id: number, name: string, email?: string }
177
+ * const user = new User({ id: 1, name: 'John', email: null });
178
+ *
179
+ * user.has('id'); // Returns true (value is 1)
180
+ * user.has('name'); // Returns true (value is 'John')
181
+ * user.has('email'); // Returns false (value is null)
182
+ *
183
+ * // Even falsy values are considered "set"
184
+ * user.set({ name: '' });
185
+ * user.has('name'); // Returns true (empty string is a valid value)
186
+ */
187
+ has(key) {
188
+ const value = this.#attributes[key];
189
+ return value !== null && value !== undefined;
190
+ }
38
191
  /**
39
- * Sets multiple attributes on the model instance.
192
+ * Removes a specific attribute from the model by deleting it from the internal attributes hash.
40
193
  *
41
- * This method allows for bulk updating of model attributes by merging the provided
42
- * partial attributes object with the existing attributes. The method performs a
43
- * shallow merge, meaning that only the top-level properties specified in the attrs
44
- * parameter will be updated, while other existing attributes remain unchanged.
194
+ * This method permanently removes an attribute from the model instance. Once unset,
195
+ * the attribute will no longer exist on the model and subsequent calls to get() for
196
+ * that key will return undefined. This is different from setting an attribute to
197
+ * null or undefined, as the property is completely removed from the attributes object.
45
198
  *
46
- * @param {Partial<T>} attrs - A partial object containing the attributes to update
199
+ * @template K - The key type, constrained to keys of T
200
+ * @param {K} key - The attribute key to remove from the model
47
201
  * @returns {void}
48
202
  * @example
49
203
  * // Assuming a User model with attributes { id: number, name: string, email: string }
50
204
  * const user = new User({ id: 1, name: 'John', email: 'john@example.com' });
51
205
  *
52
- * // Update multiple attributes at once
206
+ * user.has('email'); // Returns true
207
+ * user.unset('email'); // Remove the email attribute
208
+ * user.has('email'); // Returns false
209
+ * user.get('email'); // Returns undefined
210
+ *
211
+ * // The attribute is completely removed, not just set to undefined
212
+ * const userData = user.toJSON(); // { id: 1, name: 'John' }
213
+ */
214
+ unset(key) {
215
+ // Cast to Record to allow delete operation
216
+ const attrs = this.#attributes;
217
+ delete attrs[key];
218
+ }
219
+ /**
220
+ * Removes all attributes from the model, including the id attribute.
221
+ *
222
+ * This method completely clears the model instance by removing all stored attributes,
223
+ * effectively resetting it to an empty state. After calling clear(), the model will
224
+ * behave as if it were newly instantiated with no data. This includes removing the
225
+ * ID attribute, which means the model will be considered "new" after clearing.
226
+ *
227
+ * This is useful when you want to reuse a model instance for different data or
228
+ * reset a model to its initial state without creating a new instance.
229
+ *
230
+ * @returns {void}
231
+ * @example
232
+ * // Clear all data from a user model
233
+ * const user = new User({ id: 1, name: 'John', email: 'john@example.com' });
234
+ * user.clear();
235
+ *
236
+ * user.has('id'); // Returns false
237
+ * user.has('name'); // Returns false
238
+ * user.isNew(); // Returns true
239
+ * user.toJSON(); // Returns {}
240
+ *
241
+ * // Model can be reused with new data
53
242
  * user.set({ name: 'Jane', email: 'jane@example.com' });
54
- * // Now user has { id: 1, name: 'Jane', email: 'jane@example.com' }
243
+ */
244
+ clear() {
245
+ this.#attributes = {};
246
+ }
247
+ /**
248
+ * Retrieves and escapes the HTML content of a specific attribute from the model.
249
+ *
250
+ * This method provides a safe way to access model attributes that may contain
251
+ * user-generated content or data that will be rendered in HTML contexts. It
252
+ * automatically applies HTML escaping to prevent XSS attacks and ensure safe
253
+ * rendering of potentially dangerous content.
254
+ *
255
+ * The method uses the escapeHTML utility function to convert special HTML
256
+ * characters (such as <, >, &, ", and ') into their corresponding HTML entities,
257
+ * making the content safe for direct insertion into HTML templates.
55
258
  *
56
- * // Update a single attribute
57
- * user.set({ name: 'Bob' });
58
- * // Now user has { id: 1, name: 'Bob', email: 'jane@example.com' }
259
+ * @template K - The key type, constrained to keys of T
260
+ * @param {K} key - The attribute key to retrieve and escape the value for
261
+ * @returns {T[K]} The HTML-escaped value associated with the specified key
262
+ * @example
263
+ * // Assuming a Post model with attributes { id: number, title: string, content: string }
264
+ * const post = new Post({
265
+ * id: 1,
266
+ * title: 'Hello <script>alert("XSS")</script>',
267
+ * content: 'This is "safe" & secure content'
268
+ * });
269
+ *
270
+ * const safeTitle = post.escape('title');
271
+ * // Returns: 'Hello &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;'
272
+ *
273
+ * const safeContent = post.escape('content');
274
+ * // Returns: 'This is &quot;safe&quot; &amp; secure content'
59
275
  */
60
- set(attrs) {
61
- Object.assign(this.#attributes, attrs);
276
+ escape(key) {
277
+ const val = this.#attributes[key];
278
+ if (val === undefined || val === null)
279
+ return '';
280
+ return escapeHTML(val.toString());
62
281
  }
63
282
  /**
64
283
  * Determines whether this model instance is new (not yet persisted).
@@ -69,6 +288,75 @@ export class Model {
69
288
  isNew() {
70
289
  return !this.#getId();
71
290
  }
291
+ /**
292
+ * Validates the current model instance and returns whether it passes validation.
293
+ *
294
+ * This method performs validation on the model's current attributes using the
295
+ * validate() method defined by the subclass. It's a convenience method that
296
+ * allows you to check if a model is valid without having to manually call
297
+ * the validation logic.
298
+ *
299
+ * If validation fails, the validationError property will be set with details
300
+ * about what went wrong. If validation passes, validationError will be cleared.
301
+ *
302
+ * @param {ValidationOptions} [options] - Optional validation configuration
303
+ * @param {boolean} [options.silent] - If true, suppresses validation error setting
304
+ * @returns {boolean} True if the model passes validation, false otherwise
305
+ *
306
+ * @example
307
+ * // Check if a user model is valid
308
+ * const user = new User({ name: '', email: 'invalid-email' });
309
+ *
310
+ * if (!user.isValid()) {
311
+ * console.log('Validation failed:', user.validationError);
312
+ * // Handle validation errors
313
+ * } else {
314
+ * // Proceed with saving or other operations
315
+ * await user.save();
316
+ * }
317
+ *
318
+ * @example
319
+ * // Validate silently without setting validationError
320
+ * const isValid = user.isValid({ silent: true });
321
+ * // user.validationError remains unchanged
322
+ */
323
+ isValid(options) {
324
+ return this.#doValidation(this.#attributes, { ...options, validate: true });
325
+ }
326
+ /**
327
+ * Validates the model's attributes using custom validation logic.
328
+ *
329
+ * This method is intended to be overridden by subclasses to implement custom
330
+ * validation rules. By default, it returns undefined (no validation errors).
331
+ * If validation fails, this method should return an error - either a simple
332
+ * string message or a complete error object.
333
+ *
334
+ * The validate method is automatically called by save() before persisting data,
335
+ * and can also be explicitly called by set() when the {validate: true} option
336
+ * is passed. If validation fails, the save operation is aborted and model
337
+ * attributes are not modified.
338
+ *
339
+ * @param {Partial<T>} attributes - The attributes to validate
340
+ * @param {any} [options] - Additional options passed from set() or save()
341
+ * @returns {any} Returns undefined if valid, or an error (string/object) if invalid
342
+ *
343
+ * @example
344
+ * // Override in a User model subclass
345
+ * validate(attributes: Partial<UserAttributes>) {
346
+ * if (!attributes.email) {
347
+ * return 'Email is required';
348
+ * }
349
+ * if (!attributes.email.includes('@')) {
350
+ * return { email: 'Invalid email format' };
351
+ * }
352
+ * // Return undefined if validation passes
353
+ * }
354
+ */
355
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
356
+ validate(attributes, options) {
357
+ // Default implementation - no validation
358
+ return undefined;
359
+ }
72
360
  /**
73
361
  * Performs HTTP synchronization with the server for CRUD operations.
74
362
  *
@@ -167,7 +455,13 @@ export class Model {
167
455
  * generates additional fields (like timestamps, computed values, or normalized data)
168
456
  * during the save process.
169
457
  *
170
- * @returns {Promise<void>} A promise that resolves when the save operation completes
458
+ * @param {ValidationOptions & SyncOptions} [options] - Optional configuration for save behavior
459
+ * @param {boolean} [options.validate] - Whether to validate before saving (defaults to true)
460
+ * @param {boolean} [options.silent] - If true, suppresses validation error setting
461
+ * @param {string} [options.endpoint] - Override the default endpoint for this save operation
462
+ * @param {Record<string, string>} [options.headers] - Additional headers to include in the request
463
+ * @param {string} [options.origin] - Override the base URL origin for this request
464
+ * @returns {Promise<boolean>} A promise that resolves to true if save succeeds, false if validation fails
171
465
  * @throws {Error} Throws an error if the HTTP request fails or server returns an error
172
466
  *
173
467
  * @example
@@ -178,14 +472,28 @@ export class Model {
178
472
  * // Update an existing user
179
473
  * existingUser.set({ name: 'Jane' });
180
474
  * await existingUser.save(); // PUT request with updated data
475
+ *
476
+ * // Save without validation
477
+ * await user.save({ validate: false });
478
+ *
479
+ * // Save with custom endpoint and headers
480
+ * await user.save({
481
+ * endpoint: '/api/v2/users',
482
+ * headers: { 'Custom-Header': 'value' }
483
+ * });
181
484
  */
182
- async save() {
485
+ async save(options) {
486
+ // Always validates before saving unless {validate: false}
487
+ if (options?.validate !== false && !this.#doValidation(this.#attributes, options)) {
488
+ return false;
489
+ }
183
490
  const id = this.#getId();
184
491
  const method = id ? 'PUT' : 'POST';
185
- const data = await this.sync(method, this.toJSON());
492
+ const data = await this.sync(method, this.toJSON(), options);
186
493
  if (data && typeof data === 'object') {
187
- this.set(data);
494
+ this.set(data, { silent: true });
188
495
  }
496
+ return true;
189
497
  }
190
498
  /**
191
499
  * Deletes the model from the server.
@@ -243,4 +551,26 @@ export class Model {
243
551
  toJSON() {
244
552
  return { ...this.#attributes };
245
553
  }
554
+ // --------------- PRIVATE METHODS --------------- //
555
+ #getId() {
556
+ const id = this.#attributes[this.idAttribute];
557
+ if (typeof id === 'number' || (typeof id === 'string' && id.length > 0)) {
558
+ return id;
559
+ }
560
+ return undefined;
561
+ }
562
+ #doValidation(attrs, options) {
563
+ if (options?.silent || !this.validate || typeof this.validate !== 'function') {
564
+ this.#validationError = undefined;
565
+ return true;
566
+ }
567
+ const errMsg = this.validate(attrs, options);
568
+ // console.log(errMsg);
569
+ if (errMsg && errMsg.length > 0) {
570
+ this.#validationError = new ValidationError(errMsg);
571
+ return false;
572
+ }
573
+ this.#validationError = undefined;
574
+ return true;
575
+ }
246
576
  }
@@ -1,9 +1,45 @@
1
1
  export interface VellumConfig {
2
2
  origin: string;
3
3
  headers: Record<string, string>;
4
+ idAttribute: string;
4
5
  }
6
+ /**
7
+ * Global reactive state for Vellum configuration. Uses Svelte's $state rune to
8
+ * create a reactive configuration object that automatically triggers updates when modified.
9
+ *
10
+ * @default origin - Empty string (must be configured before use)
11
+ * @default headers - Contains 'Content-Type': 'application/json'
12
+ * @default idAttribute - The default unique identifier attribute for models
13
+ */
5
14
  export declare const vellumConfig: VellumConfig;
6
15
  /**
7
- * Helper to update global configuration
16
+ * Helper function to update global Vellum configuration
17
+ *
18
+ * Allows partial updates to the global configuration state. Only provided
19
+ * properties will be updated, leaving others unchanged. Headers are merged
20
+ * with existing headers rather than replaced entirely.
21
+ *
22
+ * @param {Partial<VellumConfig>} config - Partial configuration object with properties to update
23
+ * @param {string} [config.origin] - New origin URL to set
24
+ * @param {Record<string, string>} [config.headers] - Headers to merge with existing headers
25
+ * @param {string} [config.idAttribute="id"] - The default unique identifier attribute for models
26
+ *
27
+ * @example
28
+ * // Set the API origin
29
+ * configureVellum({ origin: 'https://api.vellum.ai' });
30
+ *
31
+ * // Add custom headers
32
+ * configureVellum({
33
+ * headers: {
34
+ * 'Authorization': 'Bearer token123',
35
+ * 'X-Custom-Header': 'value'
36
+ * }
37
+ * });
38
+ *
39
+ * // Update both origin and headers
40
+ * configureVellum({
41
+ * origin: 'https://api.vellum.ai',
42
+ * headers: { 'Authorization': 'Bearer token123' }
43
+ * });
8
44
  */
9
45
  export declare const configureVellum: (config: Partial<VellumConfig>) => void;
@@ -1,15 +1,55 @@
1
+ /**
2
+ * Global reactive state for Vellum configuration. Uses Svelte's $state rune to
3
+ * create a reactive configuration object that automatically triggers updates when modified.
4
+ *
5
+ * @default origin - Empty string (must be configured before use)
6
+ * @default headers - Contains 'Content-Type': 'application/json'
7
+ * @default idAttribute - The default unique identifier attribute for models
8
+ */
1
9
  export const vellumConfig = $state({
2
10
  origin: '',
3
11
  headers: {
4
12
  'Content-Type': 'application/json'
5
- }
13
+ },
14
+ idAttribute: 'id'
6
15
  });
7
16
  /**
8
- * Helper to update global configuration
17
+ * Helper function to update global Vellum configuration
18
+ *
19
+ * Allows partial updates to the global configuration state. Only provided
20
+ * properties will be updated, leaving others unchanged. Headers are merged
21
+ * with existing headers rather than replaced entirely.
22
+ *
23
+ * @param {Partial<VellumConfig>} config - Partial configuration object with properties to update
24
+ * @param {string} [config.origin] - New origin URL to set
25
+ * @param {Record<string, string>} [config.headers] - Headers to merge with existing headers
26
+ * @param {string} [config.idAttribute="id"] - The default unique identifier attribute for models
27
+ *
28
+ * @example
29
+ * // Set the API origin
30
+ * configureVellum({ origin: 'https://api.vellum.ai' });
31
+ *
32
+ * // Add custom headers
33
+ * configureVellum({
34
+ * headers: {
35
+ * 'Authorization': 'Bearer token123',
36
+ * 'X-Custom-Header': 'value'
37
+ * }
38
+ * });
39
+ *
40
+ * // Update both origin and headers
41
+ * configureVellum({
42
+ * origin: 'https://api.vellum.ai',
43
+ * headers: { 'Authorization': 'Bearer token123' }
44
+ * });
9
45
  */
10
46
  export const configureVellum = (config) => {
11
- if (config.origin)
47
+ if (config.origin?.length) {
12
48
  vellumConfig.origin = config.origin;
49
+ }
50
+ if (config.idAttribute?.length) {
51
+ vellumConfig.idAttribute = config.idAttribute;
52
+ }
13
53
  if (config.headers) {
14
54
  vellumConfig.headers = { ...vellumConfig.headers, ...config.headers };
15
55
  }
@@ -0,0 +1,8 @@
1
+ export interface ValidationDetails {
2
+ [key: string]: string | string[];
3
+ }
4
+ export declare class ValidationError extends Error {
5
+ details: ValidationDetails;
6
+ status: number;
7
+ constructor(message: string, details?: ValidationDetails);
8
+ }
@@ -0,0 +1,9 @@
1
+ export class ValidationError extends Error {
2
+ details;
3
+ status = 400;
4
+ constructor(message, details = {}) {
5
+ super(message);
6
+ this.name = 'ValidationError';
7
+ this.details = details;
8
+ }
9
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export { vellumConfig, configureVellum } from './config.svelte.js';
2
- export { Model } from './Model.svelte.js';
3
- export { Collection } from './Collection.svelte.js';
2
+ export { type ValidationDetails, ValidationError } from './errors/validation_error.js';
3
+ export { type SyncOptions, type ValidationOptions, Model } from './Model.svelte.js';
4
+ export { type FetchOptions, Collection } from './Collection.svelte.js';
5
+ export * as utils from './utils.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- // Reexport your entry components here
2
1
  export { vellumConfig, configureVellum } from './config.svelte.js';
2
+ export { ValidationError } from './errors/validation_error.js';
3
3
  export { Model } from './Model.svelte.js';
4
4
  export { Collection } from './Collection.svelte.js';
5
+ export * as utils from './utils.js';
@@ -0,0 +1 @@
1
+ export declare const escapeHTML: (text: string) => string;
package/dist/utils.js ADDED
@@ -0,0 +1,10 @@
1
+ export const escapeHTML = (text) => {
2
+ const lookup = {
3
+ '&': '&amp;',
4
+ '<': '&lt;',
5
+ '>': '&gt;',
6
+ '"': '&quot;',
7
+ "'": '&#39;'
8
+ };
9
+ return text.replace(/[&<>"']/g, (char) => lookup[char]);
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jwerre/vellum",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Structural state management library for Svelte 5",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,6 +32,7 @@
32
32
  "types:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
33
33
  "check": "npm audit --audit-level=moderate --omit=dev && npm run format && npm run lint && npm run spell && npm run types && npm test && npm run build && npm run release:dry",
34
34
  "dev": "vite dev",
35
+ "docs": "npm run build && documentation readme dist/index.js --section=\"API Documentation\" && prettier --write README.md",
35
36
  "format": "prettier --check .",
36
37
  "format:write": "prettier --write .",
37
38
  "lint": "prettier --check . && eslint .",
@@ -75,6 +76,7 @@
75
76
  "@sveltejs/vite-plugin-svelte": "^6.2.1",
76
77
  "@types/node": "^24",
77
78
  "cspell": "^9.4.0",
79
+ "documentation": "^14.0.3",
78
80
  "eslint": "^9.39.1",
79
81
  "eslint-config-prettier": "^10.1.8",
80
82
  "eslint-plugin-svelte": "^3.13.1",