• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

WolferyScripting / resclient-ts / #59

05 Sep 2025 04:51AM UTC coverage: 48.432% (+0.1%) from 48.309%
#59

push

DonovanDMC
1.1.16

230 of 292 branches covered (78.77%)

Branch coverage included in aggregate %.

1670 of 3631 relevant lines covered (45.99%)

9.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.62
/lib/models/ResCollection.ts
1
import type ResClient from "./ResClient.js";
1✔
2
import type CacheItem from "./CacheItem.js";
1✔
3
import { copy } from "../includes/utils/obj.js";
1✔
4
import Properties from "../util/Properties.js";
1✔
5
import { type AnyObject, type AnyFunction } from "../util/types.js";
1✔
6

1✔
7
export interface ResCollectionEvents<V = unknown> {
1✔
8
    add: [data: CollectionAddRemove<V>];
1✔
9
    remove: [data: CollectionAddRemove<V>];
1✔
10
}
1✔
11
export interface CollectionAddRemove<V> { idx: number; item: V; }
1✔
12
export default class ResCollection<V = unknown, ResourceEvents extends { [K in keyof ResourceEvents]: Array<unknown> } = ResCollectionEvents<V>, ModelEvents extends { [K in keyof ModelEvents]: Array<unknown> } = Record<string, Array<unknown>>> implements Iterable<V> {
1✔
13
    private _idCallback?: (item: V) => string;
12✔
14
    private _list!: Array<V>;
12✔
15
    private _map!: Record<string, V> | null;
12✔
16
    protected api!: ResClient;
12✔
17
    rid!: string;
12✔
18
    constructor(api: ResClient, rid: string, options?: { idCallback?(item: V): string; }) {
12✔
19
        options = copy(options ?? {}, {
12✔
20
            idCallback: { type: "?function" }
12✔
21
        });
12✔
22
        this.p
12✔
23
            .writableBulk(["_idCallback", options?.idCallback?.bind(this)], ["_map", options.idCallback ? {} : null])
12✔
24
            .readOnly("api", api)
12✔
25
            .define("rid", false, true, false, rid)
12✔
26
            .define("_list", false, api.enumerableLists, true, []);
12✔
27
    }
12✔
28

12✔
29
    private _hasID(): void {
12✔
30
        if (!this._idCallback) {
14!
31
            throw new Error("No id callback defined");
×
32
        }
×
33
    }
14✔
34

12✔
35
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
12✔
36
    protected async _listen(on: boolean): Promise<void> {
12✔
37
        // empty
×
38
    }
×
39

12✔
40
    protected get p(): Properties {
12✔
41
        return Properties.of(this);
12✔
42
    }
12✔
43

12✔
44
    get cacheItem(): CacheItem<ResCollection> {
12✔
45
        return this.getClient().cache[this.rid] as CacheItem<ResCollection>;
×
46
    }
×
47

12✔
48
    /** If this collection is empty. */
12✔
49
    get empty(): boolean {
12✔
50
        return this.length === 0;
×
51
    }
×
52

12✔
53
    get length(): number {
12✔
54
        return this._list.length;
×
55
    }
×
56

12✔
57
    get list(): Array<V> {
12✔
58
        return Array.from(this._list);
×
59
    }
×
60

12✔
61
    [Symbol.iterator](): Iterator<V, undefined> {
12✔
62
        return this._list[Symbol.iterator]();
×
63
    }
×
64

12✔
65
    add(item: V, index: number): void {
12✔
66
        this._list.splice(index, 0, item);
3✔
67

3✔
68
        if (this._idCallback) {
3✔
69
            const id = String(this._idCallback(item));
2✔
70
            if (["", "undefined", "null"].includes(id) || id.replaceAll(/\W/g, "") === "") {
2!
71
                console.debug(item);
×
72
                throw new Error("No id for item");
×
73
            }
×
74
            if (this._map![id]) {
2✔
75
                throw new Error(`Duplicate id - ${id}`);
1✔
76
            }
1✔
77
            this._map![id] = item;
1✔
78
        }
1✔
79
    }
3✔
80

12✔
81
    at(index: number): V | undefined {
12✔
82
        return this._list[index];
14✔
83
    }
14✔
84

12✔
85
    auth<T = unknown>(method: string, params: unknown): Promise<T> {
12✔
86
        return this.api.authenticate<T>(this.rid, method, params);
×
87
    }
×
88

12✔
89
    call<T = unknown>(method: string, params?: unknown): Promise<T> {
12✔
90
        return this.api.call<T>(this.rid, method, params);
×
91
    }
×
92

12✔
93
    /** Called when the collection is unsubscribed. */
12✔
94
    async dispose(): Promise<void> {
12✔
95
        await this._listen(false);
×
96
    }
×
97

12✔
98
    /** See: {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every | Array#every } */
12✔
99
    every<T extends V, ThisArg = ResCollection<V>>(predicate: (value: V, index: number, array: Array<V>) => value is T, thisArg?: ThisArg): this is Array<T>;
12✔
100
    every<ThisArg = ResCollection<V>>(predicate: (value: V, index: number, array: Array<V>) => unknown, thisArg?: ThisArg): boolean;
12✔
101
    every(predicate: (value: V, index: number, array: Array<V>) => unknown, thisArg?: unknown): boolean {
12✔
102
        return this.toArray().every(predicate, thisArg);
×
103

×
104
    }
×
105

12✔
106
    /** See: {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter | Array#filter } */
12✔
107
    filter<S extends V, ThisArg = ResCollection<V>>(predicate: (this: ThisArg, value: V, index: number, array: Array<V>) => value is S, thisArg?: ThisArg): Array<S>;
12✔
108
    filter<ThisArg = ResCollection<V>>(predicate: (this: ThisArg, value: V, index: number, array: Array<V>) => unknown, thisArg?: ThisArg): Array<V>;
12✔
109
    filter(predicate: (value: V, index: number, array: Array<V>) => unknown, thisArg?: unknown): Array<V> {
12✔
110
        return this.toArray().filter(predicate, thisArg) ;
×
111
    }
×
112

12✔
113
    /** See: {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find | Array#find } */
12✔
114
    find<S extends V, ThisArg = ResCollection<V>>(predicate: (this: ThisArg, value: V, index: number, obj: Array<V>) => value is S, thisArg?: ThisArg): S | undefined;
12✔
115
    find<ThisArg = ResCollection<V>>(predicate: (this: ThisArg, value: V, index: number, obj: Array<V>) => unknown, thisArg?: ThisArg): V | undefined;
12✔
116
    find(predicate: (value: V, index: number, obj: Array<V>) => unknown, thisArg?: unknown): V | undefined {
12✔
117
        return this.toArray().find(predicate, thisArg);
×
118
    }
×
119

12✔
120
    /** See: {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex | Array#findIndex } */
12✔
121
    findIndex(predicate: (value: V, index: number, obj: Array<V>) => unknown, thisArg?: unknown): number {
12✔
122
        return this.toArray().findIndex(predicate, thisArg);
×
123
    }
×
124

12✔
125
    /**
12✔
126
     * Get the first element, or first X elements if a number is provided.
12✔
127
     * @param amount The amount of elements to get.
12✔
128
     */
12✔
129
    first(amount?: undefined): V | undefined;
12✔
130
    first(amount: number): Array<V>;
12✔
131
    first(amount?: number): V | Array<V> | undefined {
12✔
132
        if (amount === undefined) {
×
133
            const iterable = this[Symbol.iterator]();
×
134
            return iterable.next().value;
×
135
        }
×
136

×
137
        if (amount < 0) {
×
138
            return this.last(amount * -1);
×
139
        }
×
140
        amount = Math.min(amount, this.length);
×
141

×
142
        const iterable = this[Symbol.iterator]();
×
143
        return Array.from({ length: amount }, () => iterable.next().value!);
×
144
    }
×
145

12✔
146
    get(id: string | number): V | undefined {
12✔
147
        this._hasID();
14✔
148
        return this._map![id];
14✔
149
    }
14✔
150

12✔
151
    getClient(): ResClient {
12✔
152
        return this.api;
×
153
    }
×
154

12✔
155
    getOrThrow(id: string | number): V {
12✔
156
        const item = this.get(id);
×
157
        if (item === undefined) {
×
158
            throw new TypeError(`${id} not found in ${this.rid}`);
×
159
        }
×
160

×
161
        return item;
×
162
    }
×
163

12✔
164
    has(item: V): boolean {
12✔
165
        return this._list.includes(item);
×
166
    }
×
167

12✔
168
    hasKey(key: string | number): boolean {
12✔
169
        this._hasID();
×
170
        return key in this._map!;
×
171
    }
×
172

12✔
173
    indexOf(item: V): number {
12✔
174
        return this._list.indexOf(item);
×
175
    }
×
176

12✔
177
    async init(data: Array<V> = []): Promise<this> {
12✔
178
        this._list.splice(0, this._list.length, ...data);
12✔
179

12✔
180
        if (this._idCallback) {
12✔
181
            this._map = {};
6✔
182
            for (const v of this._list) {
6✔
183
                const id = String(this._idCallback(v));
18✔
184
                if (["", "undefined", "null"].includes(id) || id.replaceAll(/\W/g, "") === "") {
18!
185
                    throw new Error("No id for item");
×
186
                }
×
187
                if (this._map[id]) {
18!
188
                    throw new Error(`Duplicate id - ${id}`);
×
189
                }
×
190
                this._map[id] = v;
18✔
191
            }
18✔
192
        }
6✔
193

12✔
194
        return this;
12✔
195
    }
12✔
196

12✔
197
    /** Prevent this model from being unsubscribed. */
12✔
198
    keep(): void {
12✔
199
        this.cacheItem.keep();
×
200
    }
×
201

12✔
202
    /**
12✔
203
     * Get the last element, or last X elements if a number is provided.
12✔
204
     * @param amount The amount of elements to get.
12✔
205
     */
12✔
206
    last(amount?: undefined): V | undefined;
12✔
207
    last(amount: number): Array<V>;
12✔
208
    last(amount?: number): V | Array<V> | undefined {
12✔
209
        const iterator = Array.from(this._list);
×
210
        if (amount === undefined) {
×
211
            return iterator.at(-1);
×
212
        }
×
213
        if (amount < 0) {
×
214
            return this.first(amount * -1);
×
215
        }
×
216
        if (!amount) {
×
217
            return [];
×
218
        }
×
219

×
220
        return iterator.slice(-amount);
×
221
    }
×
222

12✔
223
    /** See: {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map | Array#map } */
12✔
224
    map<T>(predicate: (value: V, index: number, obj: Array<V>) => T, thisArg?: unknown): Array<T> {
12✔
225
        return this.toArray().map(predicate, thisArg);
×
226
    }
×
227

12✔
228
    off<K extends keyof ModelEvents>(event: K, handler?: (...args: ModelEvents[K]) => void): this;
12✔
229
    off(events: string | Array<string> | null, handler?: AnyFunction): this;
12✔
230
    off(events: string | Array<string> | null, handler?: AnyFunction): this;
12✔
231
    off(events: string | Array<string> | null, handler?: AnyFunction): this {
12✔
232
        this.api.eventBus.off(this, events, handler);
×
233
        return this;
×
234
    }
×
235

12✔
236
    on<K extends keyof ModelEvents>(event: K, handler: (...args: ModelEvents[K]) => void): this;
12✔
237
    on(events: string | Array<string> | null, handler: AnyFunction): this;
12✔
238
    on(events: string | Array<string> | null, handler: AnyFunction): this {
12✔
239
        this.api.eventBus.on(this, events, handler);
×
240
        return this;
×
241
    }
×
242

12✔
243
    /**
12✔
244
     * Pick a random element from the collection, or undefined if the collection is empty.
12✔
245
     */
12✔
246
    random(): V | undefined {
12✔
247
        if (this.empty) {
×
248
            return undefined;
×
249
        }
×
250
        const iterable = Array.from(this._list);
×
251

×
252
        return iterable[Math.floor(Math.random() * iterable.length)];
×
253
    }
×
254

12✔
255
    /** See: {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce | Array#reduce } */
12✔
256
    reduce(predicate: (previousValue: V, currentValue: V, currentIndex: number, array: Array<V>) => V): V;
12✔
257
    reduce(predicate: (previousValue: V, currentValue: V, currentIndex: number, array: Array<V>) => V, initialValue: V): V;
12✔
258
    reduce<T>(predicate: (previousValue: T, currentValue: V, currentIndex: number, array: Array<V>) => T, initialValue: T): T;
12✔
259
    reduce<T>(predicate: (previousValue: T, currentValue: V, currentIndex: number, array: Array<V>) => T, initialValue?: T): T {
12✔
260
        return this.toArray().reduce(predicate, initialValue!);
×
261
    }
×
262

12✔
263
    /** See: {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight | Array#reduceRight } */
12✔
264
    reduceRight(predicate: (previousValue: V, currentValue: V, currentIndex: number, array: Array<V>) => V): V;
12✔
265
    reduceRight(predicate: (previousValue: V, currentValue: V, currentIndex: number, array: Array<V>) => V, initialValue: V): V;
12✔
266
    reduceRight<T>(predicate: (previousValue: T, currentValue: V, currentIndex: number, array: Array<V>) => T, initialValue: T): T;
12✔
267
    reduceRight<T>(predicate: (previousValue: T, currentValue: V, currentIndex: number, array: Array<V>) => T, initialValue?: T): T {
12✔
268
        return this.toArray().reduceRight(predicate, initialValue!);
×
269
    }
×
270

12✔
271
    remove(index: number): V | undefined {
12✔
272
        const item = this._list[index];
2✔
273
        if (item !== undefined) {
2✔
274
            this._list.splice(index, 1);
2✔
275

2✔
276
            if (this._idCallback) {
2✔
277
                delete this._map![this._idCallback(item)];
1✔
278
            }
1✔
279
        }
2✔
280

2✔
281
        return item;
2✔
282
    }
2✔
283

12✔
284
    resourceOff<K extends keyof ResourceEvents>(event: K, handler?: (...args: ResourceEvents[K]) => void): this;
12✔
285
    resourceOff(events: string | Array<string> | null, handler?: AnyFunction): this;
12✔
286
    resourceOff(events: string | Array<string> | null, handler?: AnyFunction): this {
12✔
287
        this.api.resourceOff(this.rid, events, handler);
×
288
        return this;
×
289
    }
×
290

12✔
291
    resourceOn<K extends keyof ResourceEvents>(event: K, handler: (...args: ResourceEvents[K]) => void): this;
12✔
292
    resourceOn(events: string | Array<string> | null, handler: AnyFunction): this;
12✔
293
    resourceOn(events: string | Array<string> | null, handler: AnyFunction): this {
12✔
294
        this.api.resourceOn(this.rid, events, handler);
×
295
        return this;
×
296
    }
×
297

12✔
298
    /** See: {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some | Array#some } */
12✔
299
    some<ThisArg = ResCollection<V>>(predicate: (value: V, index: number, array: Array<V>) => unknown, thisArg?: ThisArg): boolean {
12✔
300
        return this.toArray().some(predicate, thisArg);
×
301
    }
×
302

12✔
303
    /** Get the values of this collection as an array. */
12✔
304
    toArray(): Array<V> {
12✔
305
        return Array.from(this._list);
×
306
    }
×
307

12✔
308
    toJSON(): Array<unknown> {
12✔
309
        return this._list.map(v => (
×
310
            v !== null && typeof v === "object" && "toJSON" in v
×
311
                ? (v as { toJSON(): AnyObject; }).toJSON()
×
312
                : v
×
313
        ));
×
314
    }
×
315

12✔
316
    /** Undo preventing this model from being unsubscribed. */
12✔
317
    unkeep(): void {
12✔
318
        this.cacheItem.unkeep();
×
319
    }
×
320
}
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc