@zenfs/core 2.7.7 → 2.8.1

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 (41) hide show
  1. package/README.md +5 -0
  2. package/dist/backends/index.d.ts +3 -1
  3. package/dist/backends/index.js +3 -1
  4. package/dist/backends/single_buffer/backend.d.ts +42 -0
  5. package/dist/backends/single_buffer/backend.js +204 -0
  6. package/dist/backends/{single_buffer.d.ts → single_buffer/v1.d.ts} +24 -34
  7. package/dist/backends/{single_buffer.js → single_buffer/v1.js} +10 -26
  8. package/dist/backends/single_buffer/v2.d.ts +257 -0
  9. package/dist/backends/single_buffer/v2.js +1283 -0
  10. package/dist/backends/store/fs.d.ts +1 -1
  11. package/dist/backends/store/fs.js +34 -15
  12. package/dist/backends/store/map.d.ts +6 -6
  13. package/dist/backends/store/map.js +6 -6
  14. package/dist/backends/store/store.d.ts +18 -14
  15. package/dist/backends/store/store.js +11 -11
  16. package/dist/internal/contexts.d.ts +5 -0
  17. package/dist/internal/contexts.js +7 -1
  18. package/dist/mixins/async.js +26 -7
  19. package/dist/node/promises.js +6 -103
  20. package/dist/node/sync.js +6 -33
  21. package/dist/node/types.d.ts +6 -1
  22. package/dist/vfs/async.d.ts +4 -1
  23. package/dist/vfs/async.js +72 -5
  24. package/dist/vfs/sync.d.ts +4 -1
  25. package/dist/vfs/sync.js +65 -4
  26. package/package.json +3 -3
  27. package/tests/backend/single-buffer-rotate.worker.js +1 -1
  28. package/tests/backend/single-buffer-v2.test.ts +342 -0
  29. package/tests/backend/single-buffer-v2.worker.js +51 -0
  30. package/tests/backend/single-buffer.test.ts +6 -6
  31. package/tests/backend/single-buffer.worker.js +1 -0
  32. package/tests/common/async.test.ts +11 -0
  33. package/tests/common/context.test.ts +30 -0
  34. package/tests/fs/permissions.test.ts +27 -6
  35. package/tests/fs/watch.test.ts +14 -0
  36. package/tests/perf/config.json +31 -1
  37. package/tests/perf/single-buffer-random.ts +38 -0
  38. package/tests/perf/single-buffer-read.ts +28 -0
  39. package/tests/perf/single-buffer-write.ts +41 -0
  40. package/tests/setup/single-buffer-v2.ts +10 -0
  41. package/tests/setup/single-buffer.ts +1 -0
package/README.md CHANGED
@@ -93,3 +93,8 @@ A huge thank you to [deco.cx](https://github.com/deco-cx) for sponsoring ZenFS a
93
93
  ## Contact and Support
94
94
 
95
95
  You can reach out [on Discord](https://zenfs.dev/discord) or by emailing jp@zenfs.dev
96
+
97
+ ## License
98
+
99
+ [ZenFS](https://github.com/zen-fs/core) is licensed under the [LGPL 3.0 or later](https://www.gnu.org/licenses/lgpl-3.0.html) and [COPYING.md](https://github.com/zen-fs/core/blob/main/COPYING.md).
100
+ Copyright © James Prevett and other ZenFS contributors.
@@ -4,7 +4,9 @@ export * from './fetch.js';
4
4
  export * from './memory.js';
5
5
  export * from './passthrough.js';
6
6
  export * from './port.js';
7
- export * from './single_buffer.js';
7
+ export * from './single_buffer/backend.js';
8
+ export { SingleBufferStore } from './single_buffer/v1.js';
9
+ export { SingleBufferStore as SingleBufferStoreV2 } from './single_buffer/v2.js';
8
10
  export * from './store/fs.js';
9
11
  export * from './store/map.js';
10
12
  export * from './store/store.js';
@@ -5,7 +5,9 @@ export * from './fetch.js';
5
5
  export * from './memory.js';
6
6
  export * from './passthrough.js';
7
7
  export * from './port.js';
8
- export * from './single_buffer.js';
8
+ export * from './single_buffer/backend.js';
9
+ export { SingleBufferStore } from './single_buffer/v1.js';
10
+ export { SingleBufferStore as SingleBufferStoreV2 } from './single_buffer/v2.js';
9
11
  export * from './store/fs.js';
10
12
  export * from './store/map.js';
11
13
  export * from './store/store.js';
@@ -0,0 +1,42 @@
1
+ import { StoreFS } from '../store/fs.js';
2
+ import * as v1 from './v1.js';
3
+ import * as v2 from './v2.js';
4
+ /**
5
+ * Options for the `SingleBuffer` backend
6
+ * @category Backends and Configuration
7
+ */
8
+ export interface SingleBufferOptions {
9
+ buffer: ArrayBufferLike | ArrayBufferView;
10
+ /**
11
+ * Create new file systems with version 2 of the format, and upgrade existing ones to it.
12
+ * File systems that already use version 2 always do.
13
+ */
14
+ v2?: boolean;
15
+ }
16
+ declare const _SingleBuffer: {
17
+ readonly name: "SingleBuffer";
18
+ readonly options: {
19
+ readonly buffer: {
20
+ readonly type: "object";
21
+ readonly required: true;
22
+ };
23
+ readonly v2: {
24
+ readonly type: "boolean";
25
+ readonly required: false;
26
+ };
27
+ };
28
+ readonly create: (opt: SingleBufferOptions) => StoreFS<v1.SingleBufferStore | v2.SingleBufferStore>;
29
+ };
30
+ type _SingleBuffer = typeof _SingleBuffer;
31
+ /**
32
+ * A backend that uses a single buffer for storing data
33
+ * @category Backends and Configuration
34
+ */
35
+ export interface SingleBuffer extends _SingleBuffer {
36
+ }
37
+ /**
38
+ * A backend that uses a single buffer for storing data
39
+ * @category Backends and Configuration
40
+ */
41
+ export declare const SingleBuffer: SingleBuffer;
42
+ export {};
@@ -0,0 +1,204 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ // SPDX-License-Identifier: LGPL-3.0-or-later
54
+ import { withErrno } from 'kerium';
55
+ import { crit, err, info, warn } from 'kerium/log';
56
+ import { offsetof, sizeof } from 'memium';
57
+ import { Mutex } from 'memium/locks';
58
+ import { crc32c } from 'utilium/checksum';
59
+ import { _inode_version } from '../../internal/inode.js';
60
+ import { StoreFS } from '../store/fs.js';
61
+ import * as v1 from './v1.js';
62
+ import * as v2 from './v2.js';
63
+ const hex = (value) => '0x' + value.toString(16).padStart(8, '0');
64
+ /** The lock held while a file system is created or upgraded, which is in the padding of a version 1 super block */
65
+ const lockOffset = offsetof(v2.SuperBlock, 'lock');
66
+ /** Checksum of a version 1 super block, treating the lock as zero */
67
+ function v1Checksum(bytes) {
68
+ const data = bytes.slice(4, sizeof(v1.SuperBlock));
69
+ data.fill(0, lockOffset - 4, lockOffset + 4);
70
+ return crc32c(data);
71
+ }
72
+ /** Locks a file system while it is created or upgraded. Only shared buffers are locked. */
73
+ function lockSetup(bytes, view) {
74
+ const word = new BigInt64Array(bytes.buffer, bytes.byteOffset + lockOffset, 1);
75
+ const garbage = Atomics.load(word, 0);
76
+ const isV1 = view.getUint32(4, true) == v1.sb_magic && view.getUint16(8, true) < 2;
77
+ // The padding of a version 1 super block was never cleared
78
+ if (garbage && isV1 && crc32c(bytes.subarray(4, sizeof(v1.SuperBlock))) == view.getUint32(0, true)) {
79
+ if (Atomics.compareExchange(word, 0, garbage, 0n) === garbage)
80
+ view.setUint32(0, v1Checksum(bytes), true);
81
+ }
82
+ if (typeof SharedArrayBuffer != 'undefined' && bytes.buffer instanceof SharedArrayBuffer)
83
+ return new Mutex(bytes.buffer, bytes.byteOffset + lockOffset).lock();
84
+ Atomics.store(word, 0, 0n);
85
+ }
86
+ /**
87
+ * Upgrades a version 1 file system in place.
88
+ * Existing data is not moved, and the version 1 metadata stays intact until the new super block is written.
89
+ */
90
+ function upgrade(bytes, view) {
91
+ info('sbfs: upgrading from version 1');
92
+ const field = (name) => offsetof(v1.SuperBlock, name);
93
+ if (v1Checksum(bytes) != view.getUint32(0, true))
94
+ throw crit(withErrno('EIO', 'sbfs: checksum mismatch for version 1 super block'));
95
+ if (view.getUint16(field('inode_format'), true) != _inode_version)
96
+ throw crit(withErrno('EIO', 'sbfs: inode format mismatch'));
97
+ if (view.getUint32(field('metadata_block_size'), true) != sizeof(v1.MetadataBlock))
98
+ throw crit(withErrno('EIO', 'sbfs: metadata block size mismatch'));
99
+ const total = Number(view.getBigUint64(field('total_bytes'), true));
100
+ if (total > bytes.byteLength)
101
+ throw crit(withErrno('EIO', 'sbfs: invalid size in version 1 super block'));
102
+ const used = [];
103
+ const entries = new Map();
104
+ const blocks = [];
105
+ // Version 1 metadata block offsets are relative to the buffer, but data offsets are relative to the file system
106
+ for (let absolute = view.getUint32(field('metadata_offset'), true); absolute;) {
107
+ const offset = absolute - bytes.byteOffset;
108
+ if (offset < sizeof(v1.SuperBlock) || offset + sizeof(v1.MetadataBlock) > total || blocks.includes(offset))
109
+ throw crit(withErrno('EIO', `sbfs: invalid metadata block at ${hex(absolute)}`));
110
+ const block = new v1.MetadataBlock(bytes.buffer, absolute);
111
+ if (block.checksum != v1.checksum(block)) {
112
+ if (!blocks.length)
113
+ throw crit(withErrno('EIO', 'sbfs: checksum mismatch for metadata block'));
114
+ warn(`sbfs: checksum mismatch for metadata block at ${hex(absolute)}`);
115
+ }
116
+ blocks.push(offset);
117
+ used.push([offset, offset + sizeof(v1.MetadataBlock)]);
118
+ for (const entry of block.items) {
119
+ if (!entry.offset || entries.has(entry.id))
120
+ continue;
121
+ if (entry.offset + entry.size > total)
122
+ throw crit(withErrno('EIO', `sbfs: data for ${hex(entry.id)} is out of bounds`));
123
+ entries.set(entry.id, [entry.size ? entry.offset : 0, entry.size]);
124
+ if (entry.size)
125
+ used.push([entry.offset, entry.offset + entry.size]);
126
+ }
127
+ absolute = block.previous_offset;
128
+ }
129
+ let maxId = 0;
130
+ for (const id of entries.keys())
131
+ maxId = Math.max(maxId, id);
132
+ const store = new v2.SingleBufferStore(bytes.buffer, bytes.byteOffset, bytes.byteLength, {
133
+ format: {
134
+ used,
135
+ uuid: bytes.slice(field('uuid'), field('uuid') + 16),
136
+ label: bytes.slice(field('label'), field('label') + 64),
137
+ nextId: BigInt(Math.max(2, maxId + 1)),
138
+ populate(tx) {
139
+ for (const [id, [offset, size]] of entries)
140
+ tx.index.set(BigInt(id), Object.assign(new v2.DataExtent(), { offset, size, capacity: size }));
141
+ for (const block of blocks)
142
+ tx.retire(block, sizeof(v1.MetadataBlock));
143
+ },
144
+ },
145
+ });
146
+ info(`sbfs: upgraded ${entries.size} entries from version 1`);
147
+ return store;
148
+ }
149
+ const _SingleBuffer = {
150
+ name: 'SingleBuffer',
151
+ options: {
152
+ buffer: { type: 'object', required: true },
153
+ v2: { type: 'boolean', required: false },
154
+ },
155
+ create(opt) {
156
+ const env_1 = { stack: [], error: void 0, hasError: false };
157
+ try {
158
+ const [buffer, byteOffset, byteLength] = ArrayBuffer.isView(opt.buffer)
159
+ ? [opt.buffer.buffer, opt.buffer.byteOffset, opt.buffer.byteLength]
160
+ : [opt.buffer, 0, opt.buffer.byteLength];
161
+ if (byteLength < sizeof(v1.SuperBlock))
162
+ throw crit(withErrno('EINVAL', 'sbfs: buffer is too small for a file system'));
163
+ const bytes = new Uint8Array(buffer, byteOffset, byteLength);
164
+ const view = new DataView(buffer, byteOffset, byteLength);
165
+ const formatted = view.getUint32(4, true) == v1.sb_magic;
166
+ const _ = __addDisposableResource(env_1, opt.v2 || (!formatted && opt.v2 !== false) ? lockSetup(bytes, view) : undefined, false);
167
+ let store;
168
+ switch (view.getUint32(4, true) == v1.sb_magic ? view.getUint16(8, true) : undefined) {
169
+ case undefined:
170
+ store =
171
+ opt.v2 === false
172
+ ? new v1.SingleBufferStore(buffer, byteOffset, byteLength)
173
+ : new v2.SingleBufferStore(buffer, byteOffset, byteLength, { format: true });
174
+ break;
175
+ case 0:
176
+ err('sbfs: got unexpected super block version 0');
177
+ // falls through
178
+ case 1:
179
+ store = opt.v2 ? upgrade(bytes, view) : new v1.SingleBufferStore(buffer, byteOffset, byteLength);
180
+ break;
181
+ case 2:
182
+ store = new v2.SingleBufferStore(buffer, byteOffset, byteLength);
183
+ break;
184
+ default:
185
+ throw crit(withErrno('EIO', 'sbfs: unsupported super block version'));
186
+ }
187
+ const fs = new StoreFS(store);
188
+ fs.checkRootSync();
189
+ return fs;
190
+ }
191
+ catch (e_1) {
192
+ env_1.error = e_1;
193
+ env_1.hasError = true;
194
+ }
195
+ finally {
196
+ __disposeResources(env_1);
197
+ }
198
+ },
199
+ };
200
+ /**
201
+ * A backend that uses a single buffer for storing data
202
+ * @category Backends and Configuration
203
+ */
204
+ export const SingleBuffer = _SingleBuffer;
@@ -1,10 +1,10 @@
1
1
  import type { ArrayOf } from 'memium';
2
2
  import type { UUID } from 'node:crypto';
3
3
  import { BufferView } from 'utilium/buffer';
4
- import type { UsageInfo } from '../internal/filesystem.js';
5
- import { StoreFS } from './store/fs.js';
6
- import { SyncMapTransaction, type SyncMapStore } from './store/map.js';
7
- import type { Store } from './store/store.js';
4
+ import type { UsageInfo } from '../../internal/filesystem.js';
5
+ import type { StoreFS } from '../store/fs.js';
6
+ import { SyncMapTransaction, type SyncMapStore } from '../store/map.js';
7
+ import type { Store } from '../store/store.js';
8
8
  type SBLock = Disposable & (() => void);
9
9
  declare const MetadataEntry_base: import("memium").StructConstructor<BufferView<any>>;
10
10
  declare class MetadataEntry extends MetadataEntry_base {
@@ -54,6 +54,12 @@ export declare class MetadataBlock extends MetadataBlock_base<ArrayBufferLike> {
54
54
  waitUnlocked(depth?: number): void;
55
55
  lock(): SBLock;
56
56
  }
57
+ export declare const sb_magic = 1651715706;
58
+ /**
59
+ * Shortcut for minor perf. bump
60
+ * @internal
61
+ */
62
+ export declare const kUsedBytes = 2;
57
63
  declare const SuperBlock_base: import("memium/decorators").StructFromTypedArray<BigUint64Array<ArrayBuffer>>;
58
64
  /**
59
65
  * The super block structure for a single-buffer file system
@@ -69,7 +75,10 @@ export declare class SuperBlock extends SuperBlock_base<ArrayBufferLike> {
69
75
  accessor checksum: number;
70
76
  /** Signature for the superblock. */
71
77
  accessor magic: number;
72
- /** The version of the on-disk format */
78
+ /**
79
+ * The version of the on-disk format
80
+ * 1: v1.10.0+
81
+ */
73
82
  accessor version: number;
74
83
  /** Which format of `Inode` is used */
75
84
  accessor inode_format: number;
@@ -108,6 +117,16 @@ export declare class SuperBlock extends SuperBlock_base<ArrayBufferLike> {
108
117
  */
109
118
  isUnused(offset: number, length: number): boolean;
110
119
  }
120
+ /**
121
+ * Compute the checksum for a super block or metadata block.
122
+ * Note we don't include the checksum when computing a new one.
123
+ */
124
+ export declare function checksum(value: SuperBlock | MetadataBlock): number;
125
+ /**
126
+ * Update a block's checksum and timestamp.
127
+ * @internal @hidden
128
+ */
129
+ export declare function _update(value: SuperBlock | MetadataBlock): void;
111
130
  /**
112
131
  *
113
132
  * @category Stores and Transactions
@@ -135,33 +154,4 @@ export declare class SingleBufferStore extends BufferView implements SyncMapStor
135
154
  usage(): UsageInfo;
136
155
  transaction(): SyncMapTransaction;
137
156
  }
138
- /**
139
- * Options for the `SingleBuffer` backend
140
- * @category Backends and Configuration
141
- */
142
- export interface SingleBufferOptions {
143
- buffer: ArrayBufferLike | ArrayBufferView;
144
- }
145
- declare const _SingleBuffer: {
146
- readonly name: "SingleBuffer";
147
- readonly options: {
148
- readonly buffer: {
149
- readonly type: "object";
150
- readonly required: true;
151
- };
152
- };
153
- readonly create: (opt: SingleBufferOptions) => StoreFS<SingleBufferStore>;
154
- };
155
- type _SingleBuffer = typeof _SingleBuffer;
156
- /**
157
- * A backend that uses a single buffer for storing data
158
- * @category Backends and Configuration
159
- */
160
- export interface SingleBuffer extends _SingleBuffer {
161
- }
162
- /**
163
- * A backend that uses a single buffer for storing data
164
- * @category Backends and Configuration
165
- */
166
- export declare const SingleBuffer: SingleBuffer;
167
157
  export {};
@@ -92,9 +92,8 @@ import { $from, field, struct, types as t } from 'memium/decorators';
92
92
  import { BufferView } from 'utilium/buffer';
93
93
  import { crc32c } from 'utilium/checksum';
94
94
  import { decodeUUID, encodeUUID } from 'utilium/string';
95
- import { _inode_version, Inode } from '../internal/inode.js';
96
- import { StoreFS } from './store/fs.js';
97
- import { SyncMapTransaction } from './store/map.js';
95
+ import { _inode_version, Inode } from '../../internal/inode.js';
96
+ import { SyncMapTransaction } from '../store/map.js';
98
97
  const hex = (value) => '0x' + value.toString(16).padStart(8, '0');
99
98
  // eslint-disable-next-line @typescript-eslint/unbound-method
100
99
  const { format } = new Intl.NumberFormat('en-US', {
@@ -315,12 +314,12 @@ let MetadataBlock = (() => {
315
314
  export { MetadataBlock };
316
315
  // workaround for https://github.com/microsoft/TypeScript/issues/61862
317
316
  Object.assign(MetadataBlock, { lockIndex: offsetof(MetadataBlock, 'locked') / Int32Array.BYTES_PER_ELEMENT });
318
- const sb_magic = 0x62732e7a; // 'z.sb'
317
+ export const sb_magic = 0x62732e7a; // 'z.sb'
319
318
  /**
320
319
  * Shortcut for minor perf. bump
321
320
  * @internal
322
321
  */
323
- const kUsedBytes = 2;
322
+ export const kUsedBytes = 2;
324
323
  /**
325
324
  * The super block structure for a single-buffer file system
326
325
  */
@@ -448,7 +447,10 @@ let SuperBlock = (() => {
448
447
  get magic() { return this.#magic_accessor_storage; }
449
448
  set magic(value) { this.#magic_accessor_storage = value; }
450
449
  #version_accessor_storage = (__runInitializers(this, _magic_extraInitializers), __runInitializers(this, _version_initializers, void 0));
451
- /** The version of the on-disk format */
450
+ /**
451
+ * The version of the on-disk format
452
+ * 1: v1.10.0+
453
+ */
452
454
  get version() { return this.#version_accessor_storage; }
453
455
  set version(value) { this.#version_accessor_storage = value; }
454
456
  #inode_format_accessor_storage = (__runInitializers(this, _version_extraInitializers), __runInitializers(this, _inode_format_initializers, void 0));
@@ -572,7 +574,7 @@ const kMetadataOffset = offsetof(SuperBlock, 'metadata_offset') / Uint32Array.BY
572
574
  * Compute the checksum for a super block or metadata block.
573
575
  * Note we don't include the checksum when computing a new one.
574
576
  */
575
- function checksum(value) {
577
+ export function checksum(value) {
576
578
  let length = sizeof(value) - 4;
577
579
  if (value instanceof MetadataBlock)
578
580
  length -= Int32Array.BYTES_PER_ELEMENT;
@@ -582,7 +584,7 @@ function checksum(value) {
582
584
  * Update a block's checksum and timestamp.
583
585
  * @internal @hidden
584
586
  */
585
- function _update(value) {
587
+ export function _update(value) {
586
588
  if (value instanceof MetadataBlock)
587
589
  value.timestamp = BigInt(Date.now());
588
590
  value.checksum = checksum(value);
@@ -732,21 +734,3 @@ export class SingleBufferStore extends BufferView {
732
734
  return new SyncMapTransaction(this);
733
735
  }
734
736
  }
735
- const _SingleBuffer = {
736
- name: 'SingleBuffer',
737
- options: {
738
- buffer: { type: 'object', required: true },
739
- },
740
- create(opt) {
741
- const fs = new StoreFS(ArrayBuffer.isView(opt.buffer)
742
- ? new SingleBufferStore(opt.buffer.buffer, opt.buffer.byteOffset, opt.buffer.byteLength)
743
- : new SingleBufferStore(opt.buffer));
744
- fs.checkRootSync();
745
- return fs;
746
- },
747
- };
748
- /**
749
- * A backend that uses a single buffer for storing data
750
- * @category Backends and Configuration
751
- */
752
- export const SingleBuffer = _SingleBuffer;