@converse/skeletor 4.0.0 → 4.0.2
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 +19 -0
- package/dist/skeletor.d.ts +925 -953
- package/dist/skeletor.esm.js +79 -91
- package/dist/skeletor.esm.js.map +1 -1
- package/dist/skeletor.js +79 -91
- package/dist/skeletor.js.map +1 -1
- package/dist/skeletor.min.js +5 -5
- package/dist/skeletor.node.d.ts +950 -978
- package/dist/skeletor.node.js +79 -91
- package/dist/skeletor.node.mjs +79 -91
- package/package.json +9 -19
- package/src/collection.ts +35 -34
- package/src/model.ts +85 -52
- package/src/types.ts +5 -6
package/package.json
CHANGED
|
@@ -39,25 +39,23 @@
|
|
|
39
39
|
"contributors": [
|
|
40
40
|
"JC Brand"
|
|
41
41
|
],
|
|
42
|
-
"version": "4.0.
|
|
42
|
+
"version": "4.0.2",
|
|
43
43
|
"engines": {
|
|
44
44
|
"node": ">=22.0.0"
|
|
45
45
|
},
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"repository": {
|
|
48
48
|
"type": "git",
|
|
49
|
-
"url": "https://github.com/conversejs/skeletor.git"
|
|
49
|
+
"url": "git+https://github.com/conversejs/skeletor.git"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
|
-
"build": "npm run
|
|
53
|
-
"build:declarations": "npm run build:types && api-extractor run --local && api-extractor run --local -c api-extractor.node.json && rm -rf dist/defs",
|
|
54
|
-
"build:types": "tsc -p tsconfig.declarations.json",
|
|
52
|
+
"build": "npm run clean && rollup -c",
|
|
55
53
|
"check": "npm run check:types && npm run test && npm run test:node && npm run lint",
|
|
56
54
|
"check:types": "tsc --noEmit",
|
|
55
|
+
"check:dts": "tsc -p tsconfig.dts.json",
|
|
57
56
|
"clean": "rm -rf dist",
|
|
58
57
|
"dev": "karma start --single-run=false",
|
|
59
58
|
"lint": "eslint src/*.ts test/*.ts",
|
|
60
|
-
"prebuild": "npm run clean && mkdir -p dist/defs",
|
|
61
59
|
"prepack": "npm run build",
|
|
62
60
|
"prettier": "prettier --write src/**/*.js test/**/*.js src/**/*.ts test/**/*.ts",
|
|
63
61
|
"test": "karma start && karma start --mocha-only",
|
|
@@ -74,7 +72,6 @@
|
|
|
74
72
|
"dist/skeletor.node.js",
|
|
75
73
|
"dist/skeletor.node.mjs",
|
|
76
74
|
"dist/skeletor.d.ts",
|
|
77
|
-
"dist/skeletor.d.ts.map",
|
|
78
75
|
"dist/skeletor.node.d.ts",
|
|
79
76
|
"src/**/*.ts"
|
|
80
77
|
],
|
|
@@ -90,25 +87,17 @@
|
|
|
90
87
|
"tslib": "^2.8.1"
|
|
91
88
|
},
|
|
92
89
|
"devDependencies": {
|
|
93
|
-
"@babel/core": "^7.10.2",
|
|
94
|
-
"@babel/plugin-external-helpers": "^7.10.1",
|
|
95
|
-
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
|
|
96
|
-
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
|
|
97
|
-
"@babel/preset-env": "^7.9.5",
|
|
98
|
-
"@microsoft/api-extractor": "^7.53.2",
|
|
99
|
-
"@rollup/plugin-babel": "^6.0.4",
|
|
100
90
|
"@rollup/plugin-commonjs": "^28.0.3",
|
|
101
91
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
102
|
-
"@rollup/plugin-terser": "^0.
|
|
92
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
103
93
|
"@rollup/plugin-typescript": "^12.1.4",
|
|
104
94
|
"@types/chai": "^5.2.3",
|
|
105
95
|
"@types/mocha": "^10.0.10",
|
|
96
|
+
"@types/node": "^26.6.5",
|
|
106
97
|
"@types/qunit": "^2.19.13",
|
|
107
98
|
"@types/sinon": "^17.0.4",
|
|
108
99
|
"@typescript-eslint/eslint-plugin": "^8.29.0",
|
|
109
100
|
"@typescript-eslint/parser": "^8.29.0",
|
|
110
|
-
"babel-eslint": "^10.1.0",
|
|
111
|
-
"babel-loader": "^9.2.1",
|
|
112
101
|
"chai": "^4.2.0",
|
|
113
102
|
"eslint": "^8.57.0",
|
|
114
103
|
"karma": "^6.3.16",
|
|
@@ -117,15 +106,16 @@
|
|
|
117
106
|
"karma-mocha": "^2.0.1",
|
|
118
107
|
"karma-qunit": "^4.1.1",
|
|
119
108
|
"karma-webpack": "^5.0.1",
|
|
120
|
-
"mocha": "^
|
|
109
|
+
"mocha": "^12.0.0",
|
|
121
110
|
"prettier": "^3.0.0",
|
|
122
111
|
"qunit": "^2.10.0",
|
|
123
112
|
"rollup": "^4.39.0",
|
|
113
|
+
"rollup-plugin-dts": "^6.5.1",
|
|
124
114
|
"rollup-plugin-summary": "^3.0.0",
|
|
125
115
|
"sinon": "^9.0.2",
|
|
126
116
|
"ts-loader": "^9.5.4",
|
|
127
117
|
"tsx": "^4.21.0",
|
|
128
|
-
"typescript": "^
|
|
118
|
+
"typescript": "^6.0.3",
|
|
129
119
|
"webpack": "^5.98.0",
|
|
130
120
|
"webpack-cli": "^6.0.1",
|
|
131
121
|
"window-or-global": "^1.0.1"
|
package/src/collection.ts
CHANGED
|
@@ -52,6 +52,36 @@ function toModelPredicate<T extends Model>(
|
|
|
52
52
|
return (m: T) => matcher(m.attributes);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Load the collection's members from storage. A module-level function rather
|
|
57
|
+
* than a `#private` method so the published declarations carry no private
|
|
58
|
+
* members, which TypeScript 6 rejects in anonymously extended classes (TS4094).
|
|
59
|
+
*/
|
|
60
|
+
async function hydrate(collection: Collection<any>): Promise<void> {
|
|
61
|
+
ensureUnloadListener(PersistentStorage);
|
|
62
|
+
// An empty store is a normal first-run state: findAll() returns [] and a
|
|
63
|
+
// missing model record resolves to null, so hydration resolves rather
|
|
64
|
+
// than rejecting.
|
|
65
|
+
await collection.fetch({ fromStorage: true, promise: true });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* `model` and `comparator` are declared on an interface merged with the class
|
|
70
|
+
* rather than on the class itself, so that subclasses may override them either
|
|
71
|
+
* as instance properties (`model = Contact`) or as accessors (`get model()`).
|
|
72
|
+
* TypeScript rejects the one form overriding the other (TS2610, TS2611) only
|
|
73
|
+
* when the base member is declared on a class.
|
|
74
|
+
*/
|
|
75
|
+
export interface Collection<T extends Model = Model> {
|
|
76
|
+
/**
|
|
77
|
+
* The model class to instantiate when raw attributes are added. Defaults to
|
|
78
|
+
* `Model` when unset. Typed loosely so that model classes whose constructors
|
|
79
|
+
* take required or narrower attributes and options fit.
|
|
80
|
+
*/
|
|
81
|
+
model: new (attributes?: any, options?: any) => T;
|
|
82
|
+
comparator: Comparator<T>;
|
|
83
|
+
}
|
|
84
|
+
|
|
55
85
|
/**
|
|
56
86
|
* @public
|
|
57
87
|
* If models tend to represent a single row of data, a Collection is
|
|
@@ -61,15 +91,14 @@ function toModelPredicate<T extends Model>(
|
|
|
61
91
|
* belonging to this particular author, and so on. Collections maintain
|
|
62
92
|
* indexes of their models, both in order, and for lookup by `id`.
|
|
63
93
|
*/
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging -- see the interface above
|
|
64
95
|
export class Collection<T extends Model = Model> extends EventEmitterObject {
|
|
65
96
|
[key: symbol]: () => CollectionIterator<T>;
|
|
66
97
|
_storage?: PersistentStorage;
|
|
67
|
-
_comparator?: Comparator<T>;
|
|
68
98
|
_url: string = '';
|
|
69
99
|
hydrated?: Promise<void>;
|
|
70
100
|
models: T[];
|
|
71
101
|
protected _byId: Record<string, T>;
|
|
72
|
-
protected _model?: new (attributes?: Partial<ModelAttributes>, options?: ModelOptions) => T;
|
|
73
102
|
|
|
74
103
|
/**
|
|
75
104
|
* Create a new **Collection**, perhaps to contain a specific type of `model`.
|
|
@@ -80,7 +109,7 @@ export class Collection<T extends Model = Model> extends EventEmitterObject {
|
|
|
80
109
|
super();
|
|
81
110
|
options = options || {};
|
|
82
111
|
this.preinitialize.apply(this, arguments as any);
|
|
83
|
-
if (options.model) this.
|
|
112
|
+
if (options.model) this.model = options.model;
|
|
84
113
|
if (options.comparator !== undefined) this.comparator = options.comparator;
|
|
85
114
|
this._reset();
|
|
86
115
|
this.initialize.apply(this, arguments as any);
|
|
@@ -91,18 +120,10 @@ export class Collection<T extends Model = Model> extends EventEmitterObject {
|
|
|
91
120
|
// uniformly `await collection.hydrated`; it resolves immediately when
|
|
92
121
|
// there's no storage to hydrate from.
|
|
93
122
|
if (this.autoSync) {
|
|
94
|
-
this.hydrated = getStorage(this) ?
|
|
123
|
+
this.hydrated = getStorage(this) ? hydrate(this) : Promise.resolve();
|
|
95
124
|
}
|
|
96
125
|
}
|
|
97
126
|
|
|
98
|
-
get comparator(): Comparator<T> {
|
|
99
|
-
return this._comparator;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
set comparator(c: Comparator<T>) {
|
|
103
|
-
this._comparator = c;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
127
|
/**
|
|
107
128
|
* The canonical storage accessor. Set to a `PersistentStorage` instance
|
|
108
129
|
* to enable local persistence for this collection.
|
|
@@ -143,26 +164,6 @@ export class Collection<T extends Model = Model> extends EventEmitterObject {
|
|
|
143
164
|
return false;
|
|
144
165
|
}
|
|
145
166
|
|
|
146
|
-
async #hydrate(): Promise<void> {
|
|
147
|
-
ensureUnloadListener(PersistentStorage);
|
|
148
|
-
// An empty store is a normal first-run state: findAll() returns [] and a
|
|
149
|
-
// missing model record resolves to null, so hydration resolves rather
|
|
150
|
-
// than rejecting.
|
|
151
|
-
await this.fetch({ fromStorage: true, promise: true });
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* The default model for a collection is just a **Model**.
|
|
156
|
-
* This should be overridden in most cases.
|
|
157
|
-
*/
|
|
158
|
-
get model(): new (attributes?: Partial<ModelAttributes>, options?: ModelOptions) => T | Model {
|
|
159
|
-
return this._model ?? Model;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
set model(model: new (attributes?: Partial<ModelAttributes>, options?: ModelOptions) => T) {
|
|
163
|
-
this._model = model;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
167
|
get length(): number {
|
|
167
168
|
return this.models.length;
|
|
168
169
|
}
|
|
@@ -740,7 +741,7 @@ export class Collection<T extends Model = Model> extends EventEmitterObject {
|
|
|
740
741
|
* Define how to uniquely identify models in the collection.
|
|
741
742
|
*/
|
|
742
743
|
modelId(attrs: ModelAttributes): string | number | undefined {
|
|
743
|
-
return attrs[this.model.prototype?.idAttribute || 'id'];
|
|
744
|
+
return attrs[(this.model ?? Model).prototype?.idAttribute || 'id'];
|
|
744
745
|
}
|
|
745
746
|
|
|
746
747
|
/** Get an iterator of all models in this collection. */
|
|
@@ -774,7 +775,7 @@ export class Collection<T extends Model = Model> extends EventEmitterObject {
|
|
|
774
775
|
}
|
|
775
776
|
|
|
776
777
|
createModel(attrs: ModelAttributes, options?: Options): T {
|
|
777
|
-
const Klass = this.model;
|
|
778
|
+
const Klass = this.model ?? Model;
|
|
778
779
|
return new Klass(attrs, options) as T;
|
|
779
780
|
}
|
|
780
781
|
|
package/src/model.ts
CHANGED
|
@@ -36,6 +36,72 @@ import {
|
|
|
36
36
|
Options,
|
|
37
37
|
} from './types';
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Per-instance state that is private to this module.
|
|
41
|
+
*
|
|
42
|
+
* It lives in a WeakMap rather than in ES `#private` fields so that the
|
|
43
|
+
* published declarations carry no private members. TypeScript 6 refuses to
|
|
44
|
+
* emit an anonymous class type whose base has private members (TS4094), and
|
|
45
|
+
* that is exactly what the usual mixin pattern (`class extends Base`) over
|
|
46
|
+
* `Model` produces in consumers such as Converse.
|
|
47
|
+
*/
|
|
48
|
+
type ModelInternals = {
|
|
49
|
+
state: 'constructing' | 'hydrating' | 'ready';
|
|
50
|
+
computedCache?: Record<string, any>;
|
|
51
|
+
computedDefs?: ComputedProperties<any>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const INTERNALS = new WeakMap<Model<any>, ModelInternals>();
|
|
55
|
+
|
|
56
|
+
function internals(model: Model<any>): ModelInternals {
|
|
57
|
+
let priv = INTERNALS.get(model);
|
|
58
|
+
if (!priv) {
|
|
59
|
+
priv = { state: 'constructing' };
|
|
60
|
+
INTERNALS.set(model, priv);
|
|
61
|
+
}
|
|
62
|
+
return priv;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Load the model from storage, marking it ready whether or not the read succeeds. */
|
|
66
|
+
async function hydrate(model: Model<any>, priv: ModelInternals): Promise<void> {
|
|
67
|
+
ensureUnloadListener(PersistentStorage);
|
|
68
|
+
try {
|
|
69
|
+
await model.fetch({ fromStorage: true, promise: true });
|
|
70
|
+
} finally {
|
|
71
|
+
priv.state = 'ready';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Evaluate the model's declared computed properties once, seeding the cache. */
|
|
76
|
+
function initComputed(model: Model<any>, priv: ModelInternals): void {
|
|
77
|
+
const defs = result(model, 'computed') as ComputedProperties<any>;
|
|
78
|
+
if (!defs || isEmpty(defs)) return;
|
|
79
|
+
priv.computedDefs = defs;
|
|
80
|
+
priv.computedCache = {};
|
|
81
|
+
for (const [key, def] of Object.entries(defs)) {
|
|
82
|
+
priv.computedCache[key] = def.fn(model);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Recompute one computed property, firing `change:key` when its value changed. */
|
|
87
|
+
function updateComputed(
|
|
88
|
+
model: Model<any>,
|
|
89
|
+
priv: ModelInternals,
|
|
90
|
+
key: string,
|
|
91
|
+
def: ComputedProperty<any>,
|
|
92
|
+
silent: boolean,
|
|
93
|
+
options: ModelOptions,
|
|
94
|
+
): void {
|
|
95
|
+
const newVal = def.fn(model);
|
|
96
|
+
if (!isEqual(priv.computedCache[key], newVal)) {
|
|
97
|
+
priv.computedCache[key] = newVal;
|
|
98
|
+
if (!silent) {
|
|
99
|
+
(model.changed as any)[key] = newVal;
|
|
100
|
+
model.trigger('change:' + key, model, newVal, options);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
39
105
|
/**
|
|
40
106
|
* @public
|
|
41
107
|
* **Models** are the basic data object in the framework --
|
|
@@ -44,9 +110,6 @@ import {
|
|
|
44
110
|
* performing computations and transformations on that data.
|
|
45
111
|
*/
|
|
46
112
|
export class Model<T extends ModelAttributes = ModelAttributes> extends EventEmitterObject {
|
|
47
|
-
#computedCache?: Record<string, any>;
|
|
48
|
-
#computedDefs?: Record<string, ComputedProperty<this>>;
|
|
49
|
-
#state: 'constructing' | 'hydrating' | 'ready' = 'constructing';
|
|
50
113
|
_storage?: PersistentStorage;
|
|
51
114
|
_changing = false;
|
|
52
115
|
_pending: boolean | ModelOptions = false;
|
|
@@ -68,6 +131,7 @@ export class Model<T extends ModelAttributes = ModelAttributes> extends EventEmi
|
|
|
68
131
|
*/
|
|
69
132
|
constructor(attributes?: Partial<T>, options?: ModelOptions) {
|
|
70
133
|
super();
|
|
134
|
+
const priv = internals(this);
|
|
71
135
|
let attrs: Partial<T> = attributes || ({} as Partial<T>);
|
|
72
136
|
options = options || {};
|
|
73
137
|
|
|
@@ -83,12 +147,13 @@ export class Model<T extends ModelAttributes = ModelAttributes> extends EventEmi
|
|
|
83
147
|
|
|
84
148
|
this.set(attrs, options);
|
|
85
149
|
|
|
86
|
-
|
|
150
|
+
initComputed(this, priv);
|
|
87
151
|
|
|
88
152
|
this.attrs = new Proxy(this.attributes as T, {
|
|
89
153
|
get: (_target, key) => {
|
|
90
|
-
|
|
91
|
-
|
|
154
|
+
const cache = priv.computedCache;
|
|
155
|
+
if (typeof key !== 'symbol' && cache && (key as string) in cache) {
|
|
156
|
+
return cache[key as string];
|
|
92
157
|
}
|
|
93
158
|
return (this.attributes as any)[key];
|
|
94
159
|
},
|
|
@@ -109,14 +174,14 @@ export class Model<T extends ModelAttributes = ModelAttributes> extends EventEmi
|
|
|
109
174
|
// otherwise it's already ready and resolves immediately.
|
|
110
175
|
if (this.autoSync) {
|
|
111
176
|
if (getStorage(this) && !this.isNew()) {
|
|
112
|
-
|
|
113
|
-
this.hydrated =
|
|
177
|
+
priv.state = 'hydrating';
|
|
178
|
+
this.hydrated = hydrate(this, priv);
|
|
114
179
|
} else {
|
|
115
|
-
|
|
180
|
+
priv.state = 'ready';
|
|
116
181
|
this.hydrated = Promise.resolve();
|
|
117
182
|
}
|
|
118
183
|
} else {
|
|
119
|
-
|
|
184
|
+
priv.state = 'ready';
|
|
120
185
|
}
|
|
121
186
|
}
|
|
122
187
|
|
|
@@ -161,19 +226,6 @@ export class Model<T extends ModelAttributes = ModelAttributes> extends EventEmi
|
|
|
161
226
|
return 100;
|
|
162
227
|
}
|
|
163
228
|
|
|
164
|
-
async #hydrate(): Promise<void> {
|
|
165
|
-
ensureUnloadListener(PersistentStorage);
|
|
166
|
-
try {
|
|
167
|
-
await this.fetch({ fromStorage: true, promise: true });
|
|
168
|
-
} finally {
|
|
169
|
-
this.#state = 'ready';
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
#doAutoSave(): unknown {
|
|
174
|
-
return this.save(null, { noAutoSave: true });
|
|
175
|
-
}
|
|
176
|
-
|
|
177
229
|
/**
|
|
178
230
|
* The default name for the JSON `id` attribute is `"id"`. MongoDB and
|
|
179
231
|
* CouchDB users may want to set this to `"_id"` (by overriding this getter
|
|
@@ -226,27 +278,6 @@ export class Model<T extends ModelAttributes = ModelAttributes> extends EventEmi
|
|
|
226
278
|
return {};
|
|
227
279
|
}
|
|
228
280
|
|
|
229
|
-
#updateComputed(key: string, def: ComputedProperty<this>, silent: boolean, options: ModelOptions): void {
|
|
230
|
-
const newVal = def.fn(this);
|
|
231
|
-
if (!isEqual(this.#computedCache[key], newVal)) {
|
|
232
|
-
this.#computedCache[key] = newVal;
|
|
233
|
-
if (!silent) {
|
|
234
|
-
(this.changed as any)[key] = newVal;
|
|
235
|
-
this.trigger('change:' + key, this, newVal, options);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
#initComputed(): void {
|
|
241
|
-
const defs = result(this, 'computed') as ComputedProperties<this>;
|
|
242
|
-
if (!defs || isEmpty(defs)) return;
|
|
243
|
-
this.#computedDefs = defs;
|
|
244
|
-
this.#computedCache = {};
|
|
245
|
-
for (const [key, def] of Object.entries(defs)) {
|
|
246
|
-
this.#computedCache[key] = def.fn(this);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
281
|
/**
|
|
251
282
|
* Return a copy of the model's `attributes` object.
|
|
252
283
|
*/
|
|
@@ -265,8 +296,9 @@ export class Model<T extends ModelAttributes = ModelAttributes> extends EventEmi
|
|
|
265
296
|
* Get the value of an attribute, or a computed property.
|
|
266
297
|
*/
|
|
267
298
|
get<K extends keyof T>(attr: K): T[K] {
|
|
268
|
-
|
|
269
|
-
|
|
299
|
+
const cache = internals(this).computedCache;
|
|
300
|
+
if (cache && (attr as string) in cache) {
|
|
301
|
+
return cache[attr as string];
|
|
270
302
|
}
|
|
271
303
|
return this.attributes[attr];
|
|
272
304
|
}
|
|
@@ -352,10 +384,11 @@ export class Model<T extends ModelAttributes = ModelAttributes> extends EventEmi
|
|
|
352
384
|
}
|
|
353
385
|
|
|
354
386
|
options = options || {};
|
|
387
|
+
const priv = internals(this);
|
|
355
388
|
|
|
356
389
|
// Guard must come after arg normalization: when called as set(obj, opts),
|
|
357
390
|
// fromStorage lives in `val` (second arg), not `options` (third arg).
|
|
358
|
-
if (this.autoSync &&
|
|
391
|
+
if (this.autoSync && priv.state === 'hydrating' && !options.fromStorage) {
|
|
359
392
|
throw new Error(
|
|
360
393
|
`Skeletor: set() called on an autoSync model before hydration resolved. Await model.hydrated first.`,
|
|
361
394
|
);
|
|
@@ -404,10 +437,10 @@ export class Model<T extends ModelAttributes = ModelAttributes> extends EventEmi
|
|
|
404
437
|
}
|
|
405
438
|
|
|
406
439
|
// Recalculate computed properties whose deps changed.
|
|
407
|
-
if (changes.length &&
|
|
408
|
-
for (const [key, def] of Object.entries(
|
|
440
|
+
if (changes.length && priv.computedDefs) {
|
|
441
|
+
for (const [key, def] of Object.entries(priv.computedDefs)) {
|
|
409
442
|
if (def.deps.some((dep) => changes.includes(dep))) {
|
|
410
|
-
|
|
443
|
+
updateComputed(this, priv, key, def, silent, options);
|
|
411
444
|
}
|
|
412
445
|
}
|
|
413
446
|
}
|
|
@@ -430,14 +463,14 @@ export class Model<T extends ModelAttributes = ModelAttributes> extends EventEmi
|
|
|
430
463
|
// were actual changes that originated from application code (not a
|
|
431
464
|
// storage read or an explicit noAutoSave call).
|
|
432
465
|
if (
|
|
433
|
-
|
|
466
|
+
priv.state === 'ready' &&
|
|
434
467
|
this.autoSync &&
|
|
435
468
|
getStorage(this) &&
|
|
436
469
|
changes.length &&
|
|
437
470
|
!options.fromStorage &&
|
|
438
471
|
!options.noAutoSave
|
|
439
472
|
) {
|
|
440
|
-
scheduleAutoSave(this, () => this
|
|
473
|
+
scheduleAutoSave(this, () => this.save(null, { noAutoSave: true }), this.autoSyncDelay);
|
|
441
474
|
}
|
|
442
475
|
|
|
443
476
|
return this;
|
package/src/types.ts
CHANGED
|
@@ -62,10 +62,7 @@ export type EventListenerMap = Record<string, ListeningType>;
|
|
|
62
62
|
/**
|
|
63
63
|
* @public
|
|
64
64
|
*/
|
|
65
|
-
export
|
|
66
|
-
all?: EventHandler[];
|
|
67
|
-
[name: string]: EventHandler[];
|
|
68
|
-
}
|
|
65
|
+
export type EventHandlersMap = Record<string, EventHandler[]>;
|
|
69
66
|
|
|
70
67
|
/**
|
|
71
68
|
* @public
|
|
@@ -168,7 +165,9 @@ export type IterateeFunction = (
|
|
|
168
165
|
*/
|
|
169
166
|
export type ComputedProperty<M> = {
|
|
170
167
|
deps: string[];
|
|
171
|
-
|
|
168
|
+
// Method syntax, so that TypeScript checks `fn` bivariantly and
|
|
169
|
+
// `ComputedProperties<Subclass>` stays assignable to `ComputedProperties<Model>`.
|
|
170
|
+
fn(model: M): any;
|
|
172
171
|
};
|
|
173
172
|
|
|
174
173
|
/**
|
|
@@ -198,7 +197,7 @@ export type FetchOrCreateOptions = Options & {
|
|
|
198
197
|
* @public
|
|
199
198
|
*/
|
|
200
199
|
export type CollectionOptions<T extends Model = Model> = Options & {
|
|
201
|
-
model?: new (attributes?:
|
|
200
|
+
model?: new (attributes?: any, options?: any) => T;
|
|
202
201
|
comparator?: Comparator<T>;
|
|
203
202
|
previousModels?: Model[];
|
|
204
203
|
};
|