@converse/skeletor 3.1.5 → 4.0.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/CHANGES.md +62 -0
- package/dist/skeletor.d.ts +50 -15
- package/dist/skeletor.esm.js +304 -175
- package/dist/skeletor.esm.js.map +1 -1
- package/dist/skeletor.js +304 -174
- package/dist/skeletor.js.map +1 -1
- package/dist/skeletor.min.js +4 -4
- package/dist/skeletor.node.d.ts +978 -0
- package/dist/skeletor.node.js +305 -174
- package/dist/skeletor.node.mjs +305 -175
- package/package.json +6 -4
- package/src/collection.ts +68 -78
- package/src/drivers/nodeSQLiteStorage.ts +1 -0
- package/src/helpers.ts +135 -25
- package/src/index.ts +5 -2
- package/src/model.ts +43 -48
- package/src/storage.ts +22 -2
package/CHANGES.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.0.0 (2026-10-24)
|
|
4
|
+
|
|
5
|
+
- Attribute-object queries on a collection (`findWhere`, `where`, and `find`, `filter`, `some`,
|
|
6
|
+
`every`, `map`, `sortBy`, `countBy`, `groupBy`, `findIndex` and `findLastIndex` when given an
|
|
7
|
+
object) now build their lodash matcher once per scan instead of once per model visited. Each
|
|
8
|
+
per-model build deep-cloned the attributes object and walked its keys, which made `findWhere`
|
|
9
|
+
roughly 8 to 13 times slower than `find` with a hand-written predicate on a few thousand models.
|
|
10
|
+
Matching semantics are unchanged: still lodash's partial, deep comparison, so nested lookups
|
|
11
|
+
such as `where({ FORM_TYPE: { type: 'hidden' } })` keep working. One alignment: `find` and
|
|
12
|
+
`filter` given a string now read that attribute through `Model.get`, like the other query
|
|
13
|
+
methods already did, instead of resolving it as a lodash property path over the raw attributes.
|
|
14
|
+
|
|
15
|
+
- `Model.matches` no longer deep-clones its argument on every call (it uses lodash's `isMatch`)
|
|
16
|
+
and its signature now admits the predicate function it has always accepted at runtime. The
|
|
17
|
+
undocumented lodash string and `[key, value]` shorthand forms are no longer supported.
|
|
18
|
+
|
|
19
|
+
- Fix `Model.save({ promise: true })` hanging forever, and leaking an unhandled rejection, when
|
|
20
|
+
its `sync` settles the returned promise instead of invoking the `success`/`error` callbacks
|
|
21
|
+
(a rejecting custom `sync`, or a throwing `'sync'`/`'error'` listener). `save` now bridges that
|
|
22
|
+
promise to the one it hands back, the way `fetch` already did. `Collection.create({ promise: true })`,
|
|
23
|
+
which delegates to `save`, is fixed the same way.
|
|
24
|
+
|
|
25
|
+
- Fix `fetch({ promise: true })` on a Model or Collection settling early when a callback-style `sync`
|
|
26
|
+
returns a non-thenable (nothing, or an XHR) and invokes `success`/`error` later. The bridge from the
|
|
27
|
+
`sync` return value to the returned promise now only engages for a thenable, so such a `sync` settles
|
|
28
|
+
the promise through its callbacks. The same guard applies to `save({ promise: true })`.
|
|
29
|
+
|
|
30
|
+
- The `{ promise: true }` paths of `fetch` and `save` now share one bridge from the `sync` outcome
|
|
31
|
+
to the returned promise, with two behaviour changes: a `sync` that rejects its promise without
|
|
32
|
+
calling the callbacks now also runs the `error` callback and fires the `'error'` event, and a
|
|
33
|
+
`'sync'`/`'error'` listener (or `success`/`error` callback) that throws now rejects the promise
|
|
34
|
+
instead of leaving it pending with an unhandled rejection. `Collection.fetch` fires `'sync'` before
|
|
35
|
+
resolving, as `Model.fetch`/`save` already did, so a throwing listener rejects there too.
|
|
36
|
+
|
|
37
|
+
- Fix `save({ promise: true })` and `fetch({ promise: true, validate: true })` leaving the promise
|
|
38
|
+
pending when the attributes returned by `sync` fail validation. The promise now rejects with
|
|
39
|
+
`validationError`; the `'invalid'` event fires as before.
|
|
40
|
+
|
|
41
|
+
- `save({ promise: true })` now returns a promise rejected with `validationError`, instead of `false`,
|
|
42
|
+
when the attributes passed to it fail validation; `create({ promise: true })` likewise returns a
|
|
43
|
+
rejected promise instead of `null`. The callback forms still return `false`/`null`.
|
|
44
|
+
|
|
45
|
+
- The default HTTP `sync` now honours both the callback and the promise contracts. It never invoked
|
|
46
|
+
`success`/`error` (it passed them to `fetch()` as init options, which ignores them), sent an empty
|
|
47
|
+
body on GET (which the Fetch API rejects outright), and resolved with the raw `Response`, which the
|
|
48
|
+
promise bridge then merged into the model as attributes. It now sends `data` as query parameters on
|
|
49
|
+
GET and as a JSON body otherwise, passes a parsed 2xx JSON body (`null` when empty) to `success` and
|
|
50
|
+
resolves with it, and on a non-2xx response or a network failure calls `error` with, and rejects
|
|
51
|
+
with, the error: an `HTTPError` (new export, carrying `status` and `response`) for the former.
|
|
52
|
+
|
|
53
|
+
## 3.1.6 (2026-08-06)
|
|
54
|
+
|
|
55
|
+
- The `'node'` store type can now be told where to put its database files, via the new
|
|
56
|
+
`PersistentStorage.nodeStorageDir`. The driver has always taken a `storageDir` argument,
|
|
57
|
+
but nothing could reach it through the string store type, so `new PersistentStorage(id, 'node')`
|
|
58
|
+
was stuck with the driver's default. That default is relative, so it resolves against
|
|
59
|
+
`process.cwd()` and an app started from two directories keeps two separate sets of state.
|
|
60
|
+
Set an absolute path (`$XDG_STATE_HOME/<app>`, say) to pin it. The default is unchanged.
|
|
61
|
+
|
|
62
|
+
- Ship a separate `dist/skeletor.node.d.ts` for the `@converse/skeletor/node` entry point.
|
|
63
|
+
Both entry points pointed their `types` at the browser rollup.
|
|
64
|
+
|
|
3
65
|
## 3.1.5 (2026-07-22)
|
|
4
66
|
|
|
5
67
|
- Fix every write through the Node.js SQLite driver failing with `Record Not Found` while
|
package/dist/skeletor.d.ts
CHANGED
|
@@ -136,7 +136,7 @@ export declare class Collection<T extends Model = Model> extends EventEmitterObj
|
|
|
136
136
|
min(): number;
|
|
137
137
|
drop(n?: number): T[];
|
|
138
138
|
some(pred: ((attrs: ModelAttributes) => boolean) | Options): boolean;
|
|
139
|
-
sortBy(iteratee: string | ((model: T) => any)): T[];
|
|
139
|
+
sortBy(iteratee: string | ((model: T) => any) | Partial<ModelAttributes>): T[];
|
|
140
140
|
isEmpty(): boolean;
|
|
141
141
|
keyBy(iteratee: string | ((model: T) => string)): Record<string, T>;
|
|
142
142
|
each(callback: (model: T, index: number, array: T[]) => void, thisArg?: any): void;
|
|
@@ -144,7 +144,7 @@ export declare class Collection<T extends Model = Model> extends EventEmitterObj
|
|
|
144
144
|
includes(item: T): boolean;
|
|
145
145
|
size(): number;
|
|
146
146
|
countBy(f: string | ((model: T) => string) | Partial<ModelAttributes>): Record<string, number>;
|
|
147
|
-
groupBy(pred: string | ((model: T) => string | number)): Record<string, T[]>;
|
|
147
|
+
groupBy(pred: string | ((model: T) => string | number) | Partial<ModelAttributes>): Record<string, T[]>;
|
|
148
148
|
indexOf(model: T, fromIndex?: number): number;
|
|
149
149
|
findLastIndex(pred: ((model: T) => boolean) | string | Partial<ModelAttributes>, fromIndex?: number): number;
|
|
150
150
|
lastIndexOf(model: T, fromIndex?: number): number;
|
|
@@ -487,6 +487,17 @@ export declare type FetchOrCreateOptions = Options & {
|
|
|
487
487
|
*/
|
|
488
488
|
export declare function getStorage(obj: Model<any> | Collection<any> | null | undefined): PersistentStorage | undefined;
|
|
489
489
|
|
|
490
|
+
/**
|
|
491
|
+
* The error the default `sync` hands to `options.error`, and rejects with, for
|
|
492
|
+
* a response outside the 2xx range.
|
|
493
|
+
* @public
|
|
494
|
+
*/
|
|
495
|
+
export declare class HTTPError extends Error {
|
|
496
|
+
readonly status: number;
|
|
497
|
+
readonly response: Response;
|
|
498
|
+
constructor(response: Response, method: string, url: string);
|
|
499
|
+
}
|
|
500
|
+
|
|
490
501
|
/**
|
|
491
502
|
* @public
|
|
492
503
|
*/
|
|
@@ -634,9 +645,17 @@ export declare class Model<T extends ModelAttributes = ModelAttributes> extends
|
|
|
634
645
|
*/
|
|
635
646
|
has(attr: keyof T): boolean;
|
|
636
647
|
/**
|
|
637
|
-
*
|
|
648
|
+
* Returns `true` if the model's attributes satisfy `attrs`.
|
|
649
|
+
*
|
|
650
|
+
* An attributes object is compared with lodash's `isMatch`: a partial, deep
|
|
651
|
+
* comparison, so `{ form: { type: 'hidden' } }` matches a `form` attribute
|
|
652
|
+
* that has `type: 'hidden'` alongside other keys. A function is called with
|
|
653
|
+
* the attributes and its result coerced to a boolean.
|
|
654
|
+
*
|
|
655
|
+
* To scan a collection, prefer `Collection#find`, `filter` or `where`. They
|
|
656
|
+
* build their matcher once per scan instead of once per model.
|
|
638
657
|
*/
|
|
639
|
-
matches(attrs: Partial<T>): boolean;
|
|
658
|
+
matches(attrs: Partial<T> | ((attrs: T) => any)): boolean;
|
|
640
659
|
/**
|
|
641
660
|
* Set a hash of model attributes on the object, firing `"change"`. This is
|
|
642
661
|
* the core primitive operation of a model, updating the data and notifying
|
|
@@ -762,8 +781,9 @@ export declare function noConflict(): any;
|
|
|
762
781
|
/**
|
|
763
782
|
* Constructor for a Node.js-only storage driver. Registered by the
|
|
764
783
|
* `@converse/skeletor/node` entry point via {@link PersistentStorage.nodeStorage}.
|
|
784
|
+
* @public
|
|
765
785
|
*/
|
|
766
|
-
declare type NodeStorageConstructor = new (name: string) => StorageDriver;
|
|
786
|
+
export declare type NodeStorageConstructor = new (name: string, storageDir?: string, inMemory?: boolean) => StorageDriver;
|
|
767
787
|
|
|
768
788
|
/**
|
|
769
789
|
* @public
|
|
@@ -795,6 +815,18 @@ export declare class PersistentStorage {
|
|
|
795
815
|
static sessionStorageInitialized: Promise<void>;
|
|
796
816
|
static localForage: typeof localForage;
|
|
797
817
|
static nodeStorage: NodeStorageConstructor | null;
|
|
818
|
+
/**
|
|
819
|
+
* Directory the `'node'` store type writes its database files to. The driver's
|
|
820
|
+
* own default is relative, so it resolves against `process.cwd()` and a process
|
|
821
|
+
* started from two directories ends up with two separate sets of state. Set an
|
|
822
|
+
* absolute path (`$XDG_STATE_HOME/<app>`, say) to pin it. Left `null`, the
|
|
823
|
+
* driver's default applies.
|
|
824
|
+
*
|
|
825
|
+
* Only the `'node'` string store type reads this; passing a driver instance to
|
|
826
|
+
* the constructor means you've already chosen a directory yourself.
|
|
827
|
+
* @public
|
|
828
|
+
*/
|
|
829
|
+
static nodeStorageDir: string | null;
|
|
798
830
|
/** Register a storage instance with a weak reference. @public */
|
|
799
831
|
static register(instance: PersistentStorage): void;
|
|
800
832
|
/**
|
|
@@ -843,6 +875,7 @@ export declare interface SkeletorType {
|
|
|
843
875
|
EventEmitter: typeof EventEmitter;
|
|
844
876
|
Model: typeof Model;
|
|
845
877
|
sync: typeof sync;
|
|
878
|
+
HTTPError: typeof HTTPError;
|
|
846
879
|
VERSION?: string;
|
|
847
880
|
noConflict?: () => SkeletorType;
|
|
848
881
|
}
|
|
@@ -880,18 +913,20 @@ export declare interface StorageDriver {
|
|
|
880
913
|
export declare type StoreType = 'local' | 'session' | 'indexed' | 'in_memory' | 'node';
|
|
881
914
|
|
|
882
915
|
/**
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
* to
|
|
916
|
+
* The default `sync`: a JSON request over `fetch()` to the model's `url()`.
|
|
917
|
+
* Override it (or `Model.sync`/`Collection.sync`) to change how models are
|
|
918
|
+
* persisted, for instance to batch rapid-fire updates with `setTimeout`, to
|
|
919
|
+
* talk over WebSockets, or to write to browser storage.
|
|
887
920
|
*
|
|
888
|
-
*
|
|
921
|
+
* It honours both contracts a `sync` may use. On a 2xx response the JSON body
|
|
922
|
+
* (`null` when the body is empty) is passed to `options.success` and the
|
|
923
|
+
* returned promise resolves with it. On a non-2xx response, a network failure
|
|
924
|
+
* or an unparsable body, `options.error` receives the error (an `HTTPError`
|
|
925
|
+
* for the first) and the promise rejects with it.
|
|
889
926
|
*
|
|
890
|
-
*
|
|
891
|
-
*
|
|
892
|
-
*
|
|
893
|
-
*/
|
|
894
|
-
/**
|
|
927
|
+
* `options.data` is sent as query parameters on GET, which may not carry a
|
|
928
|
+
* body, and as a JSON body otherwise. For create, update and patch the body
|
|
929
|
+
* defaults to `options.attrs` or the model's `toJSON()`.
|
|
895
930
|
* @public
|
|
896
931
|
*/
|
|
897
932
|
export declare function sync(method: SyncOperation, model: Model | Collection<any>, options?: SyncOptions): Promise<any>;
|