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

stackpress / lib / 21348490489

26 Jan 2026 06:32AM UTC coverage: 79.564% (+0.3%) from 79.314%
21348490489

push

github

cblanquera
version bump

494 of 682 branches covered (72.43%)

Branch coverage included in aggregate %.

966 of 1153 relevant lines covered (83.78%)

26.32 hits per line

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

89.61
/src/data/Nest.ts
1
//common
2
import type { 
3
  Key, 
4
  TypeOf,
5
  NestedObject, 
6
  UnknownNest, 
7
  CallableNest 
8
} from '../types.js';
9
import Exception from '../Exception.js';
10
//processors
11
import ArgString from './processors/ArgString.js';
12
import PathString from './processors/PathString.js';
13
import QueryString from './processors/QueryString.js';
14
import FormData from './processors/FormData.js';
15
//local
16
import ReadonlyNest from './ReadonlyNest.js';
17

18
/**
19
 * Nest easily manipulates object data
20
 */
21
export default class Nest<M extends UnknownNest = UnknownNest> 
22
  extends ReadonlyNest<M>
23
{
24
  /**
25
   * Parser for terminal args
26
   */
27
  public withArgs: ArgString;
28

29
  /**
30
   * Parser for multipart/form-data
31
   */
32
  public withFormData: FormData;
33

34
  /**
35
   * Parser for path notations
36
   */
37
  public withPath: PathString;
38

39
  /**
40
   * Parser for query string
41
   */
42
  public withQuery: QueryString;
43

44
  /**
45
   * Returns the raw data
46
   */
47
  public get data(): M {
48
    return this._data;
1✔
49
  }
50

51
  /**
52
   * Safely sets the data
53
   */
54
  public set data(data: M) {
55
    Exception.require(
1✔
56
      data?.constructor === Object, 
57
      'Argument 1 expected Object'
58
    );
59
    this._data = data;
1✔
60
  }
61

62
  /**
63
   * Sets the initial data
64
   */
65
  public constructor(data: M = {} as M) {
142✔
66
    super(data);
163✔
67
    this.withArgs = new ArgString(this);
163✔
68
    this.withFormData = new FormData(this);
163✔
69
    this.withPath = new PathString(this);
163✔
70
    this.withQuery = new QueryString(this);
163✔
71
  }
72

73
  /**
74
   * Clears all the data
75
   */
76
  public clear() {
77
    this._data = {} as M;
2✔
78
    return this;
2✔
79
  }
80

81
  /**
82
   * Removes the data from a specified path
83
   */
84
  public delete(...path: Key[]) {
85
    if (!path.length) {
5!
86
      return this;
×
87
    }
88

89
    if (!this.has(...path)) {
5!
90
      return this;
×
91
    }
92

93
    const last = path.pop() as Key;
5✔
94
    let pointer = this._data as UnknownNest;
5✔
95

96
    path.forEach(step => {
5✔
97
      pointer = pointer[step] as UnknownNest;
5✔
98
    });
99

100
    delete pointer[last];
5✔
101

102
    return this;
5✔
103
  }
104

105
  /**
106
   * Sets the data of a specified path
107
   */
108
  public set(...path: any[]) {
109
    if (path.length < 1) {
256✔
110
      return this;
1✔
111
    }
112

113
    if (typeof path[0] === 'object') {
255✔
114
      Object.keys(path[0]).forEach(key => {
42✔
115
        this.set(key, path[0][key]);
30✔
116
      });
117

118
      return this;
42✔
119
    }
120

121
    const value = path.pop();
213✔
122
    let last = path.pop(), pointer = this._data as UnknownNest;
213✔
123

124
    path.forEach((step, i) => {
213✔
125
      if (step === null || step === '') {
162✔
126
        path[i] = step = Object.keys(pointer).length;
16✔
127
      }
128

129
      if (typeof pointer[step] !== 'object') {
162✔
130
        pointer[step] = {};
65✔
131
      }
132

133
      pointer = pointer[step] as UnknownNest;
162✔
134
    });
135

136
    if (last === null || last === '') {
213✔
137
      last = Object.keys(pointer).length;
44✔
138
    }
139

140
    pointer[last] = value;
213✔
141

142
    //loop through the steps one more time fixing the objects
143
    pointer = this._data;
213✔
144
    path.forEach((step) => {
213✔
145
      const next = pointer[step] as UnknownNest;
162✔
146
      //if next is not an array and next should be an array
147
      if (!Array.isArray(next) && shouldBeAnArray(next)) {
162✔
148
        //transform next into an array
149
        pointer[step] = makeArray(next);
24✔
150
      //if next is an array and next should not be an array
151
      } else if (Array.isArray(next) && !shouldBeAnArray(next)) {
138!
152
        //transform next into an object
153
        pointer[step] = makeObject(next);
×
154
      }
155

156
      pointer = pointer[step] as UnknownNest;
162✔
157
    });
158

159
    return this;
213✔
160
  }
161
}
162

163
/**
164
 * Returns the parsed form data from the request body (if any)
165
 */
166
export function formDataToObject(type: string, body: string) {
167
  return type.endsWith('/json') 
×
168
    ? objectFromJson(body)
169
    : type.endsWith('/x-www-form-urlencoded')
×
170
    ? objectFromQuery(body)
171
    : type.startsWith('multipart/form-data')
×
172
    ? objectFromFormData(body)
173
    : {} as Record<string, unknown>;
174
};
175

176
/**
177
 * Returns true if the value is a native JS object
178
 */
179
export function isObject(value: unknown) {
180
  return typeof value === 'object' && value?.constructor?.name === 'Object';
167✔
181
};
182

183
/**
184
 * Transforms an object into an array
185
 */
186
export function makeArray(object: NestedObject<unknown>): any[] {
187
  const array: any[] = [];
27✔
188
  const keys = Object.keys(object);
27✔
189
  
190
  keys.sort();
27✔
191
  
192
  keys.forEach(function(key) {
27✔
193
    array.push(object[key]);
30✔
194
  })
195

196
  return array;
27✔
197
}
198

199
/**
200
 * Transforms an array into an object
201
 */
202
export function makeObject(array: any[]): NestedObject<unknown> {
203
  return Object.assign({}, array as unknown);
3✔
204
}
205

206
/**
207
 * Returns the parsed data from the given cli arguments
208
 */
209
export function objectFromArgs(args: string) {
210
  if (args) {
4!
211
    const nest = new Nest();
4✔
212
    nest.withArgs.set(args);
4✔
213
    return nest.get();
4✔
214
  }
215
  return {};
×
216
}
217

218
/**
219
 * Transform JSON string into object
220
 * This is usually from body application/json
221
 * or text/json
222
 */
223
export function objectFromJson(json: string) {
224
  if (json.startsWith('{')) {
2✔
225
    return JSON.parse(json) as Record<string, unknown>;
1✔
226
  }
227
  return {} as Record<string, unknown>;
1✔
228
};
229

230
/**
231
 * Transform query string into object
232
 * This is usually from URL.search or 
233
 * body application/x-www-form-urlencoded
234
 */
235
export function objectFromQuery(query: string) {
236
  if (query.startsWith('?')) {
5✔
237
    query = query.substring(1);
3✔
238
  }
239
  if (query) {
5✔
240
    const nest = new Nest();
4✔
241
    nest.withQuery.set(query);
4✔
242
    return nest.get();
4✔
243
  }
244
  return {};
1✔
245
};
246

247
/**
248
 * Transform form data into object
249
 * This is usually from body multipart/form-data
250
 */
251
export function objectFromFormData(data: string) {
252
  if (data) {
2✔
253
    const nest = new Nest();
1✔
254
    nest.withFormData.set(data);
1✔
255
    return nest.get();
1✔
256
  }
257
  return {};
1✔
258
};
259

260
/**
261
 * Returns true if object keys is all numbers
262
 */
263
export function shouldBeAnArray(object: NestedObject<unknown> | null | undefined): boolean {
264
  // Check for null, undefined, or non-object types
265
  if (!object || typeof object !== 'object') {
171✔
266
    return false;
2✔
267
  }
268

269
  const length = Object.keys(object).length
169✔
270

271
  if (!length) {
169✔
272
    return false;
2✔
273
  }
274

275
  for (let i = 0; i < length; i++) {
167✔
276
    if (typeof object[i] === 'undefined') {
283✔
277
      return false;
94✔
278
    }
279
  }
280

281
  return true;
73✔
282
}
283

284
export function nest<M extends UnknownNest = UnknownNest>(data?: M): CallableNest<M> {
285
  const store = new Nest<M>(data);
116✔
286
  const callable = Object.assign(
116✔
287
    <T = any>(...path: Key[]) => store.get<T>(...path),
32✔
288
    {
289
      withArgs: store.withArgs,
290
      withFormData: store.withFormData,
291
      withPath: store.withPath,
292
      withQuery: store.withQuery,
293
      clear() {
294
        return store.clear();
1✔
295
      },
296
      delete(...path: Key[]) {
297
        return store.delete(...path);
2✔
298
      },
299
      entries() {
300
        return store.entries();
1✔
301
      },
302
      forEach(...path: Key[]) {
303
        return store.forEach(...path);
×
304
      },
305
      get<T = any>(...path: Key[]) {
306
        return store.get<T>(...path);
13✔
307
      },
308
      has(...path: Key[]) {
309
        return store.has(...path);
8✔
310
      },
311
      keys() {
312
        return store.keys();
1✔
313
      },
314
      path<T = any>(path: string, defaults?: TypeOf<T>) {
315
        return store.path<T>(path, defaults);
11✔
316
      },
317
      set(...path: any[]) {
318
        return store.set(...path);
71✔
319
      },
320
      toString() {
321
        return store.toString();
2✔
322
      },
323
      values() {
324
        return store.values();
1✔
325
      }
326
    } as Nest<M>
327
  );
328
  //magic size/data property
329
  Object.defineProperty(callable, 'size', { get: () => store.size });
116✔
330
  Object.defineProperty(callable, 'data', { get: () => store.data });
116✔
331
  return callable;
116✔
332
};
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