@nejs/basic-extensions 1.0.0 → 1.2.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.
@@ -0,0 +1,24 @@
1
+ import { Patch } from '@nejs/extension';
2
+ /**
3
+ * `SymbolExtensions` is a patch for the JavaScript built-in `Symbol` class. It
4
+ * adds utility methods to the `Symbol` class without modifying the global namespace
5
+ * directly. This patch includes methods for key validation, object type checking,
6
+ * and retrieving the string tag of an object. These methods are useful for
7
+ * enhancing the capabilities of the standard `Symbol` class with additional
8
+ * utility functions.
9
+ */
10
+ export const SymbolExtensions = new Patch(Symbol, {
11
+ /**
12
+ * The `isSymbol` method does exactly what one would it expect. It returns
13
+ * true if the string matches typeof or instanceof as a symbol.
14
+ *
15
+ * @param {*} value checks to see if the `value` is a string
16
+ * @returns {boolean} `true` if it is a `Symbol`, `false` otherwise
17
+ */
18
+ isSymbol(value) {
19
+ if (value && (typeof value === 'symbol')) {
20
+ return true;
21
+ }
22
+ return false;
23
+ },
24
+ });
package/package.json CHANGED
@@ -3,10 +3,12 @@
3
3
  "description": "Basic but commonly used extensions",
4
4
  "devDependencies": {
5
5
  "@jest/expect": "^29.7.0",
6
+ "esbuild": "^0.19.10",
6
7
  "jest": "^29.7.0",
7
8
  "jest-cli": "^29.7.0",
8
9
  "jest-localstorage-mock": "^2.4.26",
9
10
  "prompts": "^2.4.2",
11
+ "rimraf": "^5.0.5",
10
12
  "typescript": "^5.2.2"
11
13
  },
12
14
  "engines": {
@@ -37,12 +39,16 @@
37
39
  "module": "dist/mjs/index.js",
38
40
  "name": "@nejs/basic-extensions",
39
41
  "scripts": {
40
- "build": "bin/build",
42
+ "build": "bin/clean && bin/esbuild && bin/build",
43
+ "browser": "bin/esbuild",
44
+ "clean": "bin/clean",
45
+ "module": "bin/build",
41
46
  "test": "jest"
42
47
  },
43
48
  "type": "module",
44
- "version": "1.0.0",
49
+ "version": "1.2.0",
45
50
  "dependencies": {
46
- "@nejs/extension": "^1.0.0"
47
- }
51
+ "@nejs/extension": "^1.2.1"
52
+ },
53
+ "browser": "dist/@nejs/basic-extensions.bundle.1.0.0.js"
48
54
  }
@@ -9,6 +9,41 @@ import { Patch } from '@nejs/extension'
9
9
  * operations on arrays and makes code more expressive and concise.
10
10
  */
11
11
  export const ArrayPrototypeExtensions = new Patch(Array.prototype, {
12
+ /**
13
+ * Sometimes defining even a short function for the invocation of `find`
14
+ * can be troublesome. This helper function performs that job for you. If
15
+ * the specified element is in the array, `true` will be returned.
16
+ *
17
+ * @param {*} value the value to search for. This value must triple equals
18
+ * the array element in order to return true.
19
+ * @returns true if the exact element exists in the array, false otherwise
20
+ */
21
+ contains(value) {
22
+ return !!this.find(entry => entry === value)
23
+ },
24
+
25
+ /**
26
+ * The `findEntry` function searches the entries of the object and returns
27
+ * the `[index, value]` entry array for the first matching value found.
28
+ *
29
+ * @param {function} findFn a function that takes the element to be checked
30
+ * and returns a boolean value
31
+ * @returns if `findFn` returns `true`, an array with two elements, the first
32
+ * being the index, the second being the value, is returned.
33
+ */
34
+ findEntry(findFn) {
35
+ const entries = this.entries()
36
+ const VALUE = 1
37
+
38
+ for (let entry of entries) {
39
+ if (findFn(entry[VALUE])) {
40
+ return entry
41
+ }
42
+ }
43
+
44
+ return undefined
45
+ },
46
+
12
47
  /**
13
48
  * A getter property that returns the first element of the array. If the
14
49
  * array is empty, it returns `undefined`. This property is useful for
@@ -34,5 +69,6 @@ export const ArrayPrototypeExtensions = new Patch(Array.prototype, {
34
69
  */
35
70
  get last() {
36
71
  return this[this.length - 1];
37
- }
72
+ },
73
+
38
74
  })
@@ -0,0 +1,533 @@
1
+ import { Extension } from '@nejs/extension'
2
+ import { ObjectExtensions } from './objectextensions.js'
3
+ import { ReflectExtensions } from './reflectextensions.js'
4
+
5
+ const isObject = ObjectExtensions.patchEntries.isObject.computed
6
+ const isValidKey = ObjectExtensions.patchEntries.isValidKey.computed
7
+ const isString = ObjectExtensions.patchEntries.isString.computed
8
+ const hasSome = ReflectExtensions.patchEntries.hasSome.computed
9
+
10
+ class Descriptor {
11
+ #desc = Descriptor.enigmatic
12
+
13
+ /**
14
+ * Creates a new instance of Descriptor either from another object or
15
+ * around the supplied object descriptor value.
16
+ *
17
+ * @param {object} object either an object descriptor or the object
18
+ * from which to get the descriptor
19
+ * @param {symbol|string} key a valid key for accessing the descriptor
20
+ * on the aforesupplied object.
21
+ */
22
+ constructor(object, key) {
23
+ this.#desc = object
24
+
25
+ if (object && key) {
26
+ this.#desc = Object.getOwnPropertyDescriptor(object, key)
27
+ }
28
+
29
+ if (!Descriptor.isDescriptor(this.#desc)) {
30
+ throw new Error(`Not a valid descriptor:`, this.#desc)
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Getter around the `configurable` object descriptor property of
36
+ * this instance of Descriptor.
37
+ *
38
+ * @returns {boolean} a boolean value or undefined if the internal
39
+ * descriptor store is invalid.
40
+ */
41
+ get configurable() {
42
+ return !!this.#desc?.configurable ?? undefined
43
+ }
44
+
45
+ /**
46
+ * Sets the `configurable` value of this object. If the internal descriptor
47
+ * store store is invalid, the value is thrown away
48
+ *
49
+ * @param {boolean} value the value to set for the `configurable` descriptor
50
+ * property. If this value is not a `boolean` it will be converted to one
51
+ */
52
+ set configurable(value) {
53
+ (this.#desc || {}).configurable = !!value
54
+ }
55
+
56
+ /**
57
+ * Getter around the `enumerable` object descriptor property of
58
+ * this instance of Descriptor.
59
+ *
60
+ * @returns {boolean} a boolean value or undefined if the internal
61
+ * descriptor store is invalid.
62
+ */
63
+ get enumerable() {
64
+ return this.#desc?.enumerable
65
+ }
66
+
67
+ /**
68
+ * Sets the `enumerable` value of this object. If the internal descriptor
69
+ * store is invalid, the value is thrown away
70
+ *
71
+ * @param {boolean} value the value to set for the `enumerable` descriptor
72
+ * property. If this value is not a `boolean` it will be converted to one
73
+ */
74
+ set enumerable(value) {
75
+ (this.#desc || {}).enumerable = value
76
+ }
77
+
78
+ /**
79
+ * Getter around the `writable` object descriptor property of
80
+ * this instance of Descriptor.
81
+ *
82
+ * @returns {boolean} a boolean value or undefined if the internal
83
+ * descriptor store is invalid.
84
+ */
85
+ get writable() {
86
+ return this.#desc?.writable
87
+ }
88
+
89
+ /**
90
+ * Sets the `writable` value of this object. If the internal descriptor
91
+ * store is invalid, the value is thrown away
92
+ *
93
+ * @param {boolean} value the value to set for the `writable` descriptor
94
+ * property. If this value is not a `boolean` it will be converted to one
95
+ */
96
+ set writable(value) {
97
+ (this.#desc || {}).writable = value
98
+ }
99
+
100
+ /**
101
+ * Getter around the `value` object descriptor property of
102
+ * this instance of Descriptor.
103
+ *
104
+ * @returns {any} any value stored in this descriptor
105
+ */
106
+ get value() {
107
+ return this.#desc?.value
108
+ }
109
+
110
+ /**
111
+ * Sets the `value` value of this object. If the internal descriptor
112
+ * store is invalid, the value is thrown away
113
+ *
114
+ * @param {any} value the value to set for the `value` descriptor
115
+ * property.
116
+ */
117
+ set value(value) {
118
+ (this.#desc || {}).value = value
119
+ }
120
+
121
+ /**
122
+ * Getter around the `get` object descriptor property of
123
+ * this instance of Descriptor.
124
+ *
125
+ * @returns {function} a function if the getter for this descriptor is
126
+ * defined or `undefined` if the internal descriptor object or the getter
127
+ * is undefined.
128
+ */
129
+ get get() {
130
+ return this.#desc?.get
131
+ }
132
+
133
+ /**
134
+ * Sets the `get` value of this object. If the internal descriptor
135
+ * store is invalid, the value is thrown away
136
+ *
137
+ * @param {function} value the getter function for this descriptor
138
+ */
139
+ set get(value) {
140
+ (this.#desc || {}).get = value
141
+ }
142
+
143
+ /**
144
+ * Getter around the `set` object descriptor property of
145
+ * this instance of Descriptor.
146
+ *
147
+ * @returns {function} a function if the setter for this descriptor is
148
+ * defined or `undefined` if the internal descriptor object or the setter
149
+ * is undefined.
150
+ */
151
+ get set() {
152
+ return this.#desc?.writable
153
+ }
154
+
155
+ /**
156
+ * Sets the `set` value of this object. If the internal descriptor
157
+ * store is invalid, the value is thrown away
158
+ *
159
+ * @param {function} value the setter function for this descriptor
160
+ */
161
+ set set(value) {
162
+ (this.#desc || {}).set = value
163
+ }
164
+
165
+ /**
166
+ * Take the descriptor defined by this objects values and apply them to
167
+ * the specified object using the specified key.
168
+ *
169
+ * @param {object} object the object to apply this descriptor to
170
+ * @param {string|symbol} forKey the string or symbol for which this
171
+ * descriptor will abe applied
172
+ */
173
+ applyTo(object, forKey) {
174
+ if (!isObject(object) || !isValidKey(forKey)) {
175
+ throw new Error(`Cannot apply descriptor to non-object or invalid key`)
176
+ }
177
+
178
+ Object.defineProperty(object, forKey, this.#desc)
179
+ }
180
+
181
+ /**
182
+ * Converts this descriptor object into a base representation
183
+ *
184
+ * @param {string} hint one of `string`, `number` or default;
185
+ * @returns if the hint is 'string', then a string identifying the enum
186
+ * and its type is returned. `number` will always be NaN since it is incoret
187
+ */
188
+ [Symbol.toPrimitive](hint) {
189
+ switch (hint) {
190
+ case 'string':
191
+ if (this.isAccessor) {
192
+ const hasGetter = Reflect.has(this.#desc, 'get') ? `getter` : ''
193
+ const hasSetter = Reflect.has(this.#desc, 'set') ? `setter` : ''
194
+ const separator = hasGetter && hasSetter ? ', ' : ''
195
+
196
+ return `Accessor (${hasGetter}${separator}${hasSetter})`
197
+ }
198
+ else if (this.isData) {
199
+ const hasGetter = Reflect.has(this.#desc, 'value') ? `value` : ''
200
+ const hasSetter = Reflect.has(this.#desc, 'writable') ? `writable` : ''
201
+ const separator = hasGetter && hasSetter ? ', ' : ''
202
+
203
+ return `Data (${hasGetter}${separator}${hasSetter})`
204
+ }
205
+ break
206
+
207
+ case 'number':
208
+ return NaN
209
+
210
+ default:
211
+ return this.#desc
212
+ }
213
+ }
214
+
215
+ /**
216
+ * The function `getData` retrieves the value of a property from an object if it
217
+ * exists and is a data property.
218
+ *
219
+ * @param object - The "object" parameter is the object from which we want to
220
+ * retrieve data.
221
+ * @param property - The `property` parameter is the name of the property that
222
+ * you want to retrieve the data from.
223
+ * @returns either the value of the specified property if it exists and is a data
224
+ * property, or undefined if the property does not exist or is not a data
225
+ * property.
226
+ */
227
+ static getData(object, property) {
228
+ if (!isObject(object) || !isString(property)) {
229
+ return null;
230
+ }
231
+
232
+ const descriptors = Descriptor.all(object)
233
+ if (descriptors.has(property)) {
234
+ const descriptor = descriptors.get(property)
235
+
236
+ if (Descriptor.isData(descriptor)) {
237
+ return descriptor.value
238
+ }
239
+ }
240
+
241
+ return undefined
242
+ }
243
+
244
+ /**
245
+ * The function `getAccessor` checks if an object has a getter/setter accessor
246
+ * for a given property and returns the accessor functions if found.
247
+ *
248
+ * @param object - The `object` parameter is the object from which we want to
249
+ * retrieve the accessor for a specific property.
250
+ * @param property - The `property` parameter is the name of the property for
251
+ * which we want to get the accessor.
252
+ * @returns an object that contains the getter and setter functions for the
253
+ * specified property of the given object. If the property is an accessor
254
+ * property (defined with a getter and/or setter), the returned object will also
255
+ * have additional properties such as "accessor" and "descriptor". If the
256
+ * property is not found or is not an accessor property, the function returns
257
+ * undefined.
258
+ */
259
+ static getAccessor(object, property) {
260
+ if (!isObject(object))
261
+ return null
262
+
263
+ const [GETTER, SETTER, OBJECT] = [0, 1, 2]
264
+ const results = [undefined, undefined, undefined]
265
+ const descriptors = this.all(object)
266
+ const isDescriptor = Descriptor.isDescriptor(object)
267
+
268
+ if (descriptors.has(property) || isDescriptor) {
269
+ const descriptor = isDescriptor ? object : descriptors.get(property)
270
+
271
+ if (Descriptor.isAccessor(descriptor)) {
272
+ results[OBJECT] = descriptors.object(property)
273
+ results[GETTER] = descriptor?.get
274
+ results[SETTER] = descriptor?.set
275
+
276
+ Object.assign(results, {
277
+ get() { this[GETTER].bind(this[OBJECT])() },
278
+ set(value) { this[SETTER].bind(this[OBJECT])(value) },
279
+ get accessor() { return true },
280
+ get descriptor() { return descriptor },
281
+ get boundDescriptor() {
282
+ return {
283
+ ...descriptor,
284
+ get: descriptor.get?.bind(object),
285
+ set: descriptor.set?.bind(object),
286
+ }
287
+ }
288
+ })
289
+
290
+ return results
291
+ }
292
+ }
293
+
294
+ return undefined
295
+ }
296
+
297
+ /**
298
+ * The function returns an object with enumerable and configurable properties
299
+ * based on the input parameters.
300
+ *
301
+ * @param [enumerable=false] - A boolean value indicating whether the property
302
+ * can be enumerated (listed) when iterating over the object's properties.
303
+ * @param [configurable=false] - The `configurable` parameter determines whether
304
+ * the property can be deleted or its attributes can be modified. If
305
+ * `configurable` is set to `true`, the property can be deleted and its
306
+ * attributes can be changed. If `configurable` is set to `false`, the property
307
+ * cannot be deleted and
308
+ * @returns An object with the properties `enumerable` and `configurable` is
309
+ * being returned. The values of these properties are determined by the arguments
310
+ * passed to the `base` function.
311
+ */
312
+ static base(enumerable = false, configurable = false) {
313
+ return {
314
+ enumerable,
315
+ configurable
316
+ }
317
+ }
318
+
319
+ /**
320
+ * The function "newAccessor" creates a new property descriptor object with a
321
+ * getter and setter function, along with optional enumerable and configurable
322
+ * flags.
323
+ *
324
+ * @param getter - The getter parameter is a function that will be used as the
325
+ * getter for the property. It will be called when the property is accessed.
326
+ * @param setter - The `setter` parameter is a function that will be used as the
327
+ * setter for the property. It will be called whenever the property is assigned a
328
+ * new value.
329
+ * @param [] - - `getter`: A function that will be used as the getter for the
330
+ * property.
331
+ * @returns an object with properties "get", "set", "enumerable", and
332
+ * "configurable".
333
+ */
334
+ static accessor(
335
+ getter,
336
+ setter,
337
+ { enumerable, configurable } = Descriptor.base()
338
+ ) {
339
+ return {
340
+ get: getter,
341
+ set: setter,
342
+ enumerable,
343
+ configurable
344
+ }
345
+ }
346
+
347
+ /**
348
+ * The function "newData" creates a new data object with customizable properties.
349
+ *
350
+ * @param value - The value parameter represents the value that will be assigned
351
+ * to the property.
352
+ * @param [writable=true] - The `writable` parameter determines whether the value
353
+ * of the property can be changed. If `writable` is set to `true`, the value can
354
+ * be changed. If `writable` is set to `false`, the value cannot be changed.
355
+ * @param [] - - `value`: The value to be assigned to the property.
356
+ * @returns an object with properties `value`, `enumerable`, `writable`, and
357
+ * `configurable`.
358
+ */
359
+ static data(
360
+ value,
361
+ writable = true,
362
+ { enumerable, configurable } = Descriptor.base()
363
+ ) {
364
+ return {
365
+ value,
366
+ enumerable,
367
+ writable,
368
+ configurable
369
+ }
370
+ }
371
+
372
+ /**
373
+ * The function checks if an object is a valid object descriptor in JavaScript.
374
+ *
375
+ * @param object - The `object` parameter is the object that we want to check if
376
+ * it is a descriptor.
377
+ * @returns a boolean value.
378
+ */
379
+ static isDescriptor(object) {
380
+ const knownKeys = [
381
+ ...Descriptor.SHARED_KEYS,
382
+ ...Descriptor.ACCESSOR_KEYS,
383
+ ...Descriptor.DATA_KEYS,
384
+ ]
385
+
386
+ let isa = (hasSome(knownKeys) && (
387
+ Descriptor.isAccessor(object) ||
388
+ Descriptor.isData(object)
389
+ )
390
+ )
391
+
392
+ return isa
393
+ }
394
+
395
+ /**
396
+ * The function checks if a given property or descriptor is a data property.
397
+ *
398
+ * @param descriptor_orProp - The `descriptor_orProp` parameter can be either a
399
+ * descriptor or a property name.
400
+ * @param object - The `object` parameter is the object that you want to check
401
+ * for data properties.
402
+ * @returns a boolean value. It returns `true` if the `descriptor` object has any
403
+ * keys that match the `DATA_KEYS` array, otherwise it returns `false`.
404
+ */
405
+ static isData(object_orProp, property) {
406
+ const needsDescriptor = (
407
+ ((typeof object_orProp === 'object') || object_orProp instanceof Object) &&
408
+ property instanceof String
409
+ )
410
+
411
+ const descriptor = (needsDescriptor
412
+ ? Descriptor.for(object_orProp, property)
413
+ : object_orProp
414
+ )
415
+
416
+ const { ACCESSOR_KEYS, DATA_KEYS } = this
417
+ let validData = false
418
+
419
+ if (hasSome(descriptor, ACCESSOR_KEYS)) {
420
+ validData = false
421
+ }
422
+ else if (hasSome(descriptor, DATA_KEYS)) {
423
+ validData = true
424
+ }
425
+
426
+ return false
427
+ }
428
+
429
+ /**
430
+ * The function checks if a given property descriptor or property of an object is
431
+ * an accessor.
432
+ *
433
+ * @param object_orProp - The `descriptor_orProp` parameter can be either a
434
+ * descriptor object or a property name.
435
+ * @param property - The `object` parameter is the object that you want to check
436
+ * for accessor properties.
437
+ * @returns a boolean value. It returns true if the descriptor or property passed
438
+ * as an argument is an accessor descriptor, and false otherwise.
439
+ */
440
+ static isAccessor(object_orProp, property) {
441
+ const needsDescriptor = (
442
+ (object_orProp && property) &&
443
+ ((typeof object_orProp === 'object') || object_orProp instanceof Object) &&
444
+ (property instanceof String || (typeof property === 'symbol'))
445
+ )
446
+
447
+ const descriptor = (needsDescriptor
448
+ ? Descriptor.for(object_orProp, property)
449
+ : object_orProp)
450
+
451
+ const { ACCESSOR_KEYS, DATA_KEYS } = this
452
+ let validAccessor = false
453
+
454
+ if (hasSome(descriptor, DATA_KEYS)) {
455
+ validAccessor = false
456
+ }
457
+ else if (hasSome(descriptor, ACCESSOR_KEYS)) {
458
+ validAccessor = true
459
+ }
460
+
461
+ return validAccessor
462
+ }
463
+
464
+ /**
465
+ * A base descriptor (new for each read) that is both enumerable and configurable
466
+ *
467
+ * @returns The method `flexible` is returning the result of calling the `base`
468
+ * method with the arguments `true` and `true`.
469
+ */
470
+ static get flexible() {
471
+ return this.base(true, true)
472
+ }
473
+
474
+ /**
475
+ * A base descriptor (new for each read) that is not enumerable but is configurable
476
+ *
477
+ * @returns The method `enigmatic` is returning the result of calling the `base`
478
+ * method with the arguments `false` and `true`.
479
+ */
480
+ static get enigmatic() {
481
+ return this.base(false, true)
482
+ }
483
+
484
+ /**
485
+ * A base descriptor (new for each read) that is neither enumerable nor configurable
486
+ *
487
+ * @returns The code is returning the result of calling the `base` method with
488
+ * the arguments `false` and `false`.
489
+ */
490
+ static get intrinsic() {
491
+ return this.base(false, false)
492
+ }
493
+
494
+ /**
495
+ * A base descriptor (new for each read) that enumerable but not configurable
496
+ *
497
+ * @returns The method is returning the result of calling the `base` method with
498
+ * the arguments `true` and `false`.
499
+ */
500
+ static get transparent() {
501
+ return this.base(true, false)
502
+ }
503
+
504
+ /**
505
+ * The function returns an array of shared descriptor keys.
506
+ *
507
+ * @returns An array containing the strings 'configurable' and 'enumerable'.
508
+ */
509
+ static get SHARED_KEYS() {
510
+ return ['configurable', 'enumerable']
511
+ }
512
+
513
+ /**
514
+ * The function returns an array of accessor descriptor keys.
515
+ *
516
+ * @returns An array containing the strings 'get' and 'set' is being returned.
517
+ */
518
+ static get ACCESSOR_KEYS() {
519
+ return ['get', 'set']
520
+ }
521
+
522
+ /**
523
+ * The function returns an array of data descriptor keys.
524
+ *
525
+ * @returns An array containing the strings 'value' and 'writable' is being
526
+ * returned.
527
+ */
528
+ static get DATA_KEYS() {
529
+ return ['value', 'writable']
530
+ }
531
+ }
532
+
533
+ export const DescriptorExtension = new Extension(Descriptor)
package/src/index.js CHANGED
@@ -1,32 +1,52 @@
1
1
  import { FunctionExtensions } from './functionextensions.js'
2
2
  import { ObjectExtensions } from './objectextensions.js'
3
3
  import { ReflectExtensions } from './reflectextensions.js'
4
+ import { StringExtensions } from './stringextensions.js'
5
+ import { SymbolExtensions } from './symbolextensions.js'
4
6
  import { ArrayPrototypeExtensions } from './arrayextensions.js'
7
+ import { DescriptorExtension } from './descriptor.js'
8
+
5
9
  import { Patch } from '@nejs/extension'
6
10
 
7
11
  const Owners = [
8
12
  Object,
9
13
  Function,
10
14
  Reflect,
15
+ String,
16
+ Symbol,
11
17
 
12
18
  Array.prototype,
13
19
  ]
14
20
 
21
+ const NetNew = [
22
+ DescriptorExtension,
23
+ ]
24
+
15
25
  export function enableAll(owners) {
16
26
  (owners || Owners).forEach(owner => {
17
27
  Patch.enableFor(owner)
18
28
  })
29
+
30
+ (NetNew).forEach(extension => {
31
+ extension.apply()
32
+ })
19
33
  }
20
34
 
21
35
  export function disableAll(owners) {
22
36
  (owners || Owners).forEach(owner => {
23
37
  Patch.disableFor(owner)
24
38
  })
39
+
40
+ (NetNew).forEach(extension => {
41
+ extension.revert()
42
+ })
25
43
  }
26
44
 
27
45
  export {
28
46
  ObjectExtensions,
29
47
  FunctionExtensions,
30
48
  ReflectExtensions,
49
+ StringExtensions,
50
+ SymbolExtensions,
31
51
  ArrayPrototypeExtensions,
32
52
  }