@zenfs/core 2.7.4 → 2.7.6

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.
Files changed (51) hide show
  1. package/dist/backends/backend.js +0 -2
  2. package/dist/backends/cow.d.ts +2 -2
  3. package/dist/backends/cow.js +6 -14
  4. package/dist/backends/passthrough.d.ts +0 -54
  5. package/dist/backends/passthrough.js +0 -54
  6. package/dist/backends/port.d.ts +2 -6
  7. package/dist/backends/port.js +3 -11
  8. package/dist/backends/store/fs.d.ts +0 -9
  9. package/dist/backends/store/fs.js +1 -21
  10. package/dist/backends/store/store.d.ts +1 -1
  11. package/dist/backends/store/store.js +1 -5
  12. package/dist/internal/contexts.d.ts +1 -2
  13. package/dist/internal/credentials.d.ts +0 -3
  14. package/dist/internal/file_index.js +0 -1
  15. package/dist/internal/filesystem.js +2 -2
  16. package/dist/internal/index_fs.d.ts +2 -2
  17. package/dist/internal/index_fs.js +2 -2
  18. package/dist/internal/inode.d.ts +33 -45
  19. package/dist/internal/inode.js +12 -2
  20. package/dist/internal/ioctl.d.ts +83 -25
  21. package/dist/internal/ioctl.js +50 -185
  22. package/dist/internal/rpc.d.ts +16 -7
  23. package/dist/internal/rpc.js +2 -11
  24. package/dist/mixins/mutexed.d.ts +6 -41
  25. package/dist/mixins/mutexed.js +36 -109
  26. package/dist/mixins/sync.js +0 -2
  27. package/dist/node/async.js +2 -12
  28. package/dist/node/index.d.ts +1 -1
  29. package/dist/node/promises.d.ts +1 -6
  30. package/dist/node/promises.js +1 -1
  31. package/dist/node/stats.d.ts +25 -99
  32. package/dist/node/stats.js +18 -55
  33. package/dist/node/streams.d.ts +1 -37
  34. package/dist/node/sync.d.ts +1 -6
  35. package/dist/node/sync.js +2 -2
  36. package/dist/polyfills.js +9 -0
  37. package/dist/utils.d.ts +0 -4
  38. package/dist/vfs/acl.d.ts +1 -3
  39. package/dist/vfs/acl.js +1 -3
  40. package/dist/vfs/async.js +0 -5
  41. package/dist/vfs/dir.d.ts +3 -0
  42. package/dist/vfs/dir.js +4 -1
  43. package/dist/vfs/file.d.ts +1 -8
  44. package/dist/vfs/file.js +1 -3
  45. package/dist/vfs/sync.js +0 -1
  46. package/dist/vfs/vcache.js +0 -1
  47. package/dist/vfs/xattr.d.ts +2 -0
  48. package/dist/vfs/xattr.js +5 -4
  49. package/package.json +2 -3
  50. package/tests/common/context.test.ts +1 -1
  51. package/tests/common/mutex.test.ts +0 -61
@@ -15,41 +15,23 @@ export class StatsCommon {
15
15
  return this._convert(Math.ceil(Number(this.size) / 512));
16
16
  }
17
17
  set blocks(value) { }
18
- /**
19
- * Unix-style file mode (e.g. 0o644) that includes the type of the item.
20
- */
18
+ /** Unix-style file mode (e.g. 0o644) that includes the type of the item. */
21
19
  mode;
22
- /**
23
- * ID of device containing file
24
- */
20
+ /** ID of device containing file */
25
21
  dev = this._convert(0);
26
- /**
27
- * Inode number
28
- */
22
+ /** Inode number */
29
23
  ino = this._convert(0);
30
- /**
31
- * Device ID (if special file)
32
- */
24
+ /** Device ID (if special file) */
33
25
  rdev = this._convert(0);
34
- /**
35
- * Number of hard links
36
- */
26
+ /** Number of hard links */
37
27
  nlink = this._convert(1);
38
- /**
39
- * Block size for file system I/O
40
- */
28
+ /** Block size for file system I/O */
41
29
  blksize = this._convert(4096);
42
- /**
43
- * User ID of owner
44
- */
30
+ /** User ID of owner */
45
31
  uid = this._convert(0);
46
- /**
47
- * Group ID of owner
48
- */
32
+ /** Group ID of owner */
49
33
  gid = this._convert(0);
50
- /**
51
- * Time of last access, since epoch
52
- */
34
+ /** Time of last access, since epoch */
53
35
  atimeMs;
54
36
  get atime() {
55
37
  return new Date(Number(this.atimeMs));
@@ -63,9 +45,7 @@ export class StatsCommon {
63
45
  set atimeInstant(value) {
64
46
  this.atimeMs = this._convert(value.epochMilliseconds);
65
47
  }
66
- /**
67
- * Time of last modification, since epoch
68
- */
48
+ /** Time of last modification, since epoch */
69
49
  mtimeMs;
70
50
  get mtime() {
71
51
  return new Date(Number(this.mtimeMs));
@@ -79,9 +59,7 @@ export class StatsCommon {
79
59
  set mtimeInstant(value) {
80
60
  this.mtimeMs = this._convert(value.epochMilliseconds);
81
61
  }
82
- /**
83
- * Time of last time file status was changed, since epoch
84
- */
62
+ /** Time of last time file status was changed, since epoch */
85
63
  ctimeMs;
86
64
  get ctime() {
87
65
  return new Date(Number(this.ctimeMs));
@@ -95,9 +73,7 @@ export class StatsCommon {
95
73
  set ctimeInstant(value) {
96
74
  this.ctimeMs = this._convert(value.epochMilliseconds);
97
75
  }
98
- /**
99
- * Time of file creation, since epoch
100
- */
76
+ /** Time of file creation, since epoch */
101
77
  birthtimeMs;
102
78
  get birthtime() {
103
79
  return new Date(Number(this.birthtimeMs));
@@ -111,26 +87,15 @@ export class StatsCommon {
111
87
  set birthtimeInstant(value) {
112
88
  this.birthtimeMs = this._convert(value.epochMilliseconds);
113
89
  }
114
- /**
115
- * Size of the item in bytes.
116
- * For directories/symlinks, this is normally the size of the struct that represents the item.
117
- */
90
+ /** Size of the item in bytes. For directories/symlinks, this may be the size of the struct that represents the item. */
118
91
  size;
119
- /**
120
- * @internal Used by inodes
121
- */
92
+ /** @internal Used by inodes */
122
93
  data;
123
- /**
124
- * @internal Used by inodes
125
- */
94
+ /** @internal Used by inodes */
126
95
  flags;
127
- /**
128
- * @internal Used by inodes
129
- */
96
+ /** @internal Used by inodes */
130
97
  version;
131
- /**
132
- * Creates a new stats instance from a stats-like object. Can be used to copy stats (note)
133
- */
98
+ /** Creates a new stats instance from a stats-like object. Can be used to copy stats (note) */
134
99
  constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, ...rest } = {}) {
135
100
  const now = Date.now();
136
101
  this.atimeMs = this._convert(atimeMs ?? now);
@@ -169,7 +134,7 @@ export class StatsCommon {
169
134
  return (this.mode & c.S_IFMT) === c.S_IFIFO;
170
135
  }
171
136
  toJSON() {
172
- return pick(this, _inode_fields);
137
+ return pick(this, ..._inode_fields, 'dev', 'blksize');
173
138
  }
174
139
  /**
175
140
  * Checks if a given user/group has access to this item
@@ -225,8 +190,6 @@ export function isStatsEqual(left, right) {
225
190
  && +left.ctime == +right.ctime
226
191
  && left.mode == right.mode);
227
192
  }
228
- /** @internal */
229
- export const ZenFsType = 0x7a656e6673; // 'z' 'e' 'n' 'f' 's'
230
193
  /**
231
194
  * @hidden
232
195
  */
@@ -1,45 +1,10 @@
1
- import type { Abortable } from 'node:events';
2
1
  import type * as fs from 'node:fs';
3
2
  import type { CreateReadStreamOptions, CreateWriteStreamOptions } from 'node:fs/promises';
4
3
  import type { Callback } from '../utils.js';
5
4
  import type { FileHandle } from './promises.js';
6
5
  import { Readable } from '@zenfs/streams/readable';
7
6
  import { Writable } from '@zenfs/streams/writable';
8
- interface FSImplementation {
9
- open?: (...args: unknown[]) => unknown;
10
- close?: (...args: unknown[]) => unknown;
11
- }
12
- interface StreamOptions extends Abortable {
13
- flags?: string;
14
- encoding?: BufferEncoding;
15
- fd?: number | FileHandle;
16
- mode?: number;
17
- autoClose?: boolean;
18
- emitClose?: boolean;
19
- start?: number;
20
- highWaterMark?: number;
21
- }
22
- /**
23
- * This type is from node:fs but not exported.
24
- * @hidden
25
- */
26
- export interface ReadStreamOptions extends StreamOptions {
27
- fs?: FSImplementation & {
28
- read: (...args: unknown[]) => unknown;
29
- };
30
- end?: number;
31
- }
32
- /**
33
- * This type is from node:fs but not exported.
34
- * @hidden
35
- */
36
- export interface WriteStreamOptions extends StreamOptions {
37
- flush?: boolean;
38
- fs?: FSImplementation & {
39
- write: (...args: unknown[]) => unknown;
40
- writev?: (...args: unknown[]) => unknown;
41
- };
42
- }
7
+ export type { ReadStreamOptions, WriteStreamOptions } from 'node:fs';
43
8
  /**
44
9
  * A ReadStream implementation that wraps an underlying global ReadableStream.
45
10
  */
@@ -76,4 +41,3 @@ export declare class WriteStream extends Writable implements fs.WriteStream {
76
41
  get path(): string;
77
42
  get bytesWritten(): number;
78
43
  }
79
- export {};
@@ -210,13 +210,8 @@ export declare function mkdtempSync(this: V_Context, prefix: fs.PathLike, option
210
210
  * When the object is disposed, the directory and its contents will be removed if it still exists.
211
211
  * If the directory cannot be deleted, disposal will throw an error.
212
212
  * The object has a `remove()` method which will perform the same task.
213
- * @todo Add `satisfies` and maybe change return type once @types/node adds this.
214
213
  */
215
- export declare function mkdtempDisposableSync(this: V_Context, prefix: fs.PathLike, options?: fs.EncodingOption | fs.BufferEncodingOption): {
216
- path: string;
217
- remove(): void;
218
- [Symbol.dispose](): void;
219
- };
214
+ export declare function mkdtempDisposableSync(this: V_Context, prefix: fs.PathLike, options?: fs.EncodingOption | fs.BufferEncodingOption): fs.DisposableTempDir;
220
215
  /**
221
216
  * Synchronous `copyFile`. Copies a file.
222
217
  * @param flags Optional flags for the copy operation. Currently supports these flags:
package/dist/node/sync.js CHANGED
@@ -541,7 +541,7 @@ export function rmSync(path, options) {
541
541
  path = normalizePath.call(this, path);
542
542
  let stats;
543
543
  try {
544
- stats = lstatSync.bind(this)(path);
544
+ stats = lstatSync.call(this, path);
545
545
  }
546
546
  catch (error) {
547
547
  if (error.code != 'ENOENT' || !options?.force)
@@ -583,7 +583,6 @@ mkdtempSync;
583
583
  * When the object is disposed, the directory and its contents will be removed if it still exists.
584
584
  * If the directory cannot be deleted, disposal will throw an error.
585
585
  * The object has a `remove()` method which will perform the same task.
586
- * @todo Add `satisfies` and maybe change return type once @types/node adds this.
587
586
  */
588
587
  export function mkdtempDisposableSync(prefix, options) {
589
588
  const path = _tempDirName(prefix);
@@ -591,6 +590,7 @@ export function mkdtempDisposableSync(prefix, options) {
591
590
  const remove = () => rmSync(path, { recursive: true, force: true });
592
591
  return { path, remove, [Symbol.dispose]: remove };
593
592
  }
593
+ mkdtempDisposableSync;
594
594
  /**
595
595
  * Synchronous `copyFile`. Copies a file.
596
596
  * @param flags Optional flags for the copy operation. Currently supports these flags:
package/dist/polyfills.js CHANGED
@@ -57,4 +57,13 @@ Uint8Array.fromHex ??=
57
57
  }
58
58
  return bytes;
59
59
  });
60
+ Array.fromAsync ??=
61
+ (warn('Using a polyfill of Array.fromAsync'),
62
+ async function collectAsyncIterator(it) {
63
+ const results = [];
64
+ for await (const result of it) {
65
+ results.push(result);
66
+ }
67
+ return results;
68
+ });
60
69
  /* node:coverage enable */
package/dist/utils.d.ts CHANGED
@@ -3,10 +3,6 @@ import type * as fs from 'node:fs';
3
3
  import type { Worker as NodeWorker } from 'node:worker_threads';
4
4
  import { type OptionalTuple } from 'utilium';
5
5
  import type { V_Context } from './internal/contexts.js';
6
- declare global {
7
- function atob(data: string): string;
8
- function btoa(data: string): string;
9
- }
10
6
  /**
11
7
  * Decodes a directory listing
12
8
  * @hidden
package/dist/vfs/acl.d.ts CHANGED
@@ -12,9 +12,7 @@ export declare const enum Tag {
12
12
  Group = 8,
13
13
  Mask = 16,
14
14
  Other = 32,
15
- /**
16
- * @internal @hidden
17
- */
15
+ /** @internal @hidden */
18
16
  _None = 0
19
17
  }
20
18
  declare const Entry_base: import("memium").StructConstructor<BufferView<any>>;
package/dist/vfs/acl.js CHANGED
@@ -62,9 +62,7 @@ export var Tag;
62
62
  Tag[Tag["Group"] = 8] = "Group";
63
63
  Tag[Tag["Mask"] = 16] = "Mask";
64
64
  Tag[Tag["Other"] = 32] = "Other";
65
- /**
66
- * @internal @hidden
67
- */
65
+ /** @internal @hidden */
68
66
  Tag[Tag["_None"] = 0] = "_None";
69
67
  })(Tag || (Tag = {}));
70
68
  let Entry = (() => {
package/dist/vfs/async.js CHANGED
@@ -80,7 +80,6 @@ export async function resolve($, path, preserveSymlinks, extra) {
80
80
  that means we don't need to perform any resolution for parent directories. */
81
81
  try {
82
82
  const resolved = resolveMount(path, $);
83
- // Stat it to make sure it exists. The vnode cache takes precedence since it may have unsynced changes
84
83
  const stats = cacheOf(resolved.fs).get(resolved.path)?.inode ?? (await resolved.fs.stat(resolved.path));
85
84
  if (!isSymbolicLink(stats)) {
86
85
  return { ...resolved, fullPath: path, stats };
@@ -135,7 +134,6 @@ export async function open($, path, opt) {
135
134
  throw UV('EISDIR', 'open', path);
136
135
  // Serialize entry creation with other operations on the parent directory
137
136
  const _ = __addDisposableResource(env_1, await lockPath(fs, parentPath, 'rw', parentStats), false);
138
- // Another task may have created the file while we waited for the lock
139
137
  stats = cacheOf(fs).get(resolved)?.inode ?? (await fs.stat(resolved).catch(() => undefined));
140
138
  if (!stats) {
141
139
  const { euid: uid, egid: gid } = contextOf($).credentials;
@@ -144,7 +142,6 @@ export async function open($, path, opt) {
144
142
  uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
145
143
  gid: parentStats.mode & constants.S_ISGID ? parentStats.gid : gid,
146
144
  });
147
- // A new entry in the parent directory, which is a 'rename' event
148
145
  emitChange($, 'rename', path);
149
146
  return new Handle($, path, resolved, flag, cacheOf(fs).ref(resolved, inode));
150
147
  }
@@ -239,7 +236,6 @@ export async function mkdir(path, options = {}) {
239
236
  }
240
237
  export async function readdir(path, options = {}) {
241
238
  path = normalizePath.call(this, path);
242
- // Node reports `readdir` failures as `scandir`
243
239
  const $ex = { syscall: 'scandir', path };
244
240
  const { fs, path: resolved, stats } = await resolve(this, path, false, $ex);
245
241
  if (!stats)
@@ -329,7 +325,6 @@ export async function rename(oldPath, newPath) {
329
325
  const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : await lockPath(fs, parents[1][0], 'rw', parents[1][1]), false);
330
326
  await src.fs.rename(src.path, dst.path);
331
327
  cacheOf(fs).rename(src.path, dst.path);
332
- // Both names change which entries exist, so both are 'rename' events
333
328
  emitChange(this, 'rename', oldPath);
334
329
  emitChange(this, 'rename', newPath);
335
330
  }
package/dist/vfs/dir.d.ts CHANGED
@@ -22,6 +22,9 @@ export declare function ifToDt(mode: number): DirType;
22
22
  * @see `DTTOIF` in `dirent.h`
23
23
  */
24
24
  export declare function dtToIf(dt: DirType): number;
25
+ /**
26
+ * @todo [breaking] change this to an interface
27
+ */
25
28
  export declare class Dirent {
26
29
  ino: number;
27
30
  type: DirType;
package/dist/vfs/dir.js CHANGED
@@ -19,7 +19,7 @@ export var DirType;
19
19
  * @see `IFTODT` in `dirent.h`
20
20
  */
21
21
  export function ifToDt(mode) {
22
- return ((mode & 0o170000) >> 12);
22
+ return (mode & 0o170000) >> 12;
23
23
  }
24
24
  /**
25
25
  * Converts a directory type to a file mode.
@@ -28,6 +28,9 @@ export function ifToDt(mode) {
28
28
  export function dtToIf(dt) {
29
29
  return dt << 12;
30
30
  }
31
+ /**
32
+ * @todo [breaking] change this to an interface
33
+ */
31
34
  export class Dirent {
32
35
  ino;
33
36
  type;
@@ -3,11 +3,6 @@ import type { FileSystem, StreamOptions } from '../internal/filesystem.js';
3
3
  import { type InodeLike } from '../internal/inode.js';
4
4
  import '../polyfills.js';
5
5
  import type { VNode } from './vnode.js';
6
- /** @hidden */
7
- export interface FileReadResult<T extends ArrayBufferView> {
8
- bytesRead: number;
9
- buffer: T;
10
- }
11
6
  /**
12
7
  * @internal
13
8
  */
@@ -17,9 +12,7 @@ export declare class Handle {
17
12
  readonly internalPath: string;
18
13
  readonly flag: number;
19
14
  readonly vnode: VNode;
20
- /**
21
- * Current position
22
- */
15
+ /** Current position */
23
16
  protected _position: number;
24
17
  /**
25
18
  * Get the current file position.
package/dist/vfs/file.js CHANGED
@@ -114,9 +114,7 @@ let Handle = (() => {
114
114
  internalPath;
115
115
  flag;
116
116
  vnode;
117
- /**
118
- * Current position
119
- */
117
+ /** Current position */
120
118
  _position = 0;
121
119
  /**
122
120
  * Get the current file position.
package/dist/vfs/sync.js CHANGED
@@ -269,7 +269,6 @@ export function readdir(path, options = {}) {
269
269
  __disposeResources(env_3);
270
270
  }
271
271
  }
272
- // Iterate over entries and handle recursive case if needed
273
272
  const values = [];
274
273
  const addEntry = (entry) => {
275
274
  let entryStat;
@@ -85,7 +85,6 @@ export class VCache {
85
85
  }
86
86
  else {
87
87
  node.paths.add(path);
88
- // The vnode's inode is authoritative when dirty; otherwise refresh it with the newer stats
89
88
  if (!node.dirty && inode !== node.inode)
90
89
  Object.assign(node.inode, inode);
91
90
  }
@@ -44,6 +44,8 @@ export interface SetOptions extends Options {
44
44
  */
45
45
  replace?: boolean;
46
46
  }
47
+ /** Attributes outside the `user` namespace that are supported. */
48
+ export declare const allowedRestrictedNames: Set<`user.${string}` | `trusted.${string}` | `system.${string}` | `security.${string}`>;
47
49
  /**
48
50
  * Gets the value of an extended attribute.
49
51
  *
package/dist/vfs/xattr.js CHANGED
@@ -6,14 +6,15 @@ import { normalizePath } from '../utils.js';
6
6
  import { checkAccess } from './config.js';
7
7
  import { R_OK, W_OK } from '../constants.js';
8
8
  import { resolveMount } from './shared.js';
9
- const _allowedRestrictedNames = [];
9
+ /** Attributes outside the `user` namespace that are supported. */
10
+ export const allowedRestrictedNames = new Set();
10
11
  /**
11
12
  * Check permission for the attribute name.
12
- * For now, only attributes in the 'user' namespace are supported.
13
- * @throws ENOTSUP for attributes in namespaces other than 'user'
13
+ * Only the 'user' namespace and whatever is in {@link allowedRestrictedNames} are supported.
14
+ * @throws ENOTSUP for any other attribute
14
15
  */
15
16
  function checkName($, name, path, syscall) {
16
- if (!name.startsWith('user.') && !_allowedRestrictedNames.includes(name))
17
+ if (!name.startsWith('user.') && !allowedRestrictedNames.has(name))
17
18
  throw UV('ENOTSUP', syscall, path);
18
19
  }
19
20
  export async function get(path, name, opt = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "2.7.4",
3
+ "version": "2.7.6",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -23,7 +23,6 @@
23
23
  "files": [
24
24
  "dist",
25
25
  "tests",
26
- "types",
27
26
  "license.md",
28
27
  "eslint.shared.js"
29
28
  ],
@@ -93,7 +92,7 @@
93
92
  "typedoc": "^0.28.18",
94
93
  "typescript": "^6.0.0",
95
94
  "typescript-eslint": "^8.58.0",
96
- "zbench-js": "^0.1.0"
95
+ "zbench-js": "^0.2.0"
97
96
  },
98
97
  "prettier": {
99
98
  "singleQuote": true,
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-or-later
2
- import { bindContext, configure, fs, InMemory, type BoundContext } from '@zenfs/core';
2
+ import { bindContext, configure, fs, InMemory } from '@zenfs/core';
3
3
  import assert from 'node:assert/strict';
4
4
  import { suite, test } from 'node:test';
5
5
  import { canary } from 'utilium';
@@ -1,61 +0,0 @@
1
- // SPDX-License-Identifier: LGPL-3.0-or-later
2
- import { InMemoryStore, Mutexed, StoreFS } from '@zenfs/core';
3
- import assert from 'node:assert/strict';
4
- import { suite, test } from 'node:test';
5
- import { wait } from 'utilium';
6
-
7
- suite('Mutexed FS', () => {
8
- const fs = new (Mutexed(StoreFS))(new InMemoryStore(0x10000, 'test'));
9
- fs._fs.checkRootSync();
10
-
11
- test('lock/unlock', () => {
12
- const lock = fs.lockSync();
13
- assert(fs.isLocked);
14
- lock.unlock();
15
- assert(!fs.isLocked);
16
- });
17
-
18
- test('queueing multiple locks', async () => {
19
- let lock1Resolved = false;
20
- let lock2Resolved = false;
21
-
22
- const lock1 = fs.lock(100).then(lock => {
23
- lock1Resolved = true;
24
- lock.unlock();
25
- });
26
- const lock2 = fs.lock(100).then(lock => {
27
- lock2Resolved = true;
28
- lock.unlock();
29
- });
30
-
31
- // Both locks are queued, so neither should be resolved initially
32
- assert(!lock1Resolved);
33
- assert(!lock2Resolved);
34
-
35
- // Wait for the first lock to be resolved
36
- await lock1;
37
-
38
- assert(lock1Resolved);
39
- assert(!lock2Resolved);
40
-
41
- // Wait for the second lock to be resolved
42
- await lock2;
43
-
44
- assert(lock1Resolved);
45
- assert(lock2Resolved);
46
- });
47
-
48
- test('test race conditions', async () => {
49
- let x = 1;
50
-
51
- async function foo() {
52
- const lock = await fs.lock(100);
53
- await wait(25);
54
- x++;
55
- lock.unlock();
56
- }
57
-
58
- await Promise.all([foo(), foo(), foo()]);
59
- assert.equal(x, 4);
60
- });
61
- });