diff --git a/src/snapshot/__tests__/binary.test.ts b/src/snapshot/__tests__/binary.test.ts index 384be1dbe..d7f762ff5 100644 --- a/src/snapshot/__tests__/binary.test.ts +++ b/src/snapshot/__tests__/binary.test.ts @@ -20,6 +20,15 @@ const data = { }, }; +test('sync and async snapshots are equivalent', async () => { + const { fs } = memfs(data); + fs.symlinkSync('/start/folder1/folder2/file6', '/start/folder1/symlink'); + fs.writeFileSync('/start/binary', new Uint8Array([1, 2, 3])); + const snapshot1 = binary.toBinarySnapshotSync({ fs: fs, path: '/start' })!; + const snapshot2 = await binary.toBinarySnapshot({ fs: fs.promises, path: '/start' })!; + expect(snapshot1).toStrictEqual(snapshot2); +}); + describe('synchronous', () => { test('can create a binary snapshot and un-snapshot it back', () => { const { fs } = memfs(data); diff --git a/src/snapshot/__tests__/json.test.ts b/src/snapshot/__tests__/json.test.ts new file mode 100644 index 000000000..9aae663d5 --- /dev/null +++ b/src/snapshot/__tests__/json.test.ts @@ -0,0 +1,71 @@ +import { memfs } from '../..'; +import {SnapshotNodeType} from '../constants'; +import * as json from '../json'; + +const data = { + '/start': { + file1: 'file1', + file2: 'file2', + 'empty-folder': null, + '/folder1': { + file3: 'file3', + file4: 'file4', + 'empty-folder': null, + '/folder2': { + file5: 'file5', + file6: 'file6', + 'empty-folder': null, + 'empty-folde2': null, + }, + }, + }, +}; + +test('snapshot is a valid JSON', () => { + const { fs } = memfs(data); + fs.symlinkSync('/start/folder1/folder2/file6', '/start/folder1/symlink'); + fs.writeFileSync('/start/binary', new Uint8Array([1, 2, 3])); + const snapshot = json.toJsonSnapshotSync({ fs, path: '/start' })!; + const pojo = JSON.parse(Buffer.from(snapshot).toString()); + expect(Array.isArray(pojo)).toBe(true); + expect(pojo[0]).toBe(SnapshotNodeType.Folder); +}); + +test('sync and async snapshots are equivalent', async () => { + const { fs } = memfs(data); + fs.symlinkSync('/start/folder1/folder2/file6', '/start/folder1/symlink'); + fs.writeFileSync('/start/binary', new Uint8Array([1, 2, 3])); + const snapshot1 = await json.toJsonSnapshotSync({ fs: fs, path: '/start' })!; + const snapshot2 = await json.toJsonSnapshot({ fs: fs.promises, path: '/start' })!;; + expect(snapshot1).toStrictEqual(snapshot2); +}); + +describe('synchronous', () => { + test('can create a binary snapshot and un-snapshot it back', () => { + const { fs } = memfs(data); + fs.symlinkSync('/start/folder1/folder2/file6', '/start/folder1/symlink'); + fs.writeFileSync('/start/binary', new Uint8Array([1, 2, 3])); + const snapshot = json.toJsonSnapshotSync({ fs, path: '/start' })!; + const { fs: fs2, vol: vol2 } = memfs(); + fs2.mkdirSync('/start', { recursive: true }); + json.fromJsonSnapshotSync(snapshot, { fs: fs2, path: '/start' }); + expect(fs2.readFileSync('/start/binary')).toStrictEqual(Buffer.from([1, 2, 3])); + const snapshot2 = json.toJsonSnapshotSync({ fs: fs2, path: '/start' })!; + expect(snapshot2).toStrictEqual(snapshot); + }); +}); + +describe('asynchronous', () => { + test('can create a binary snapshot and un-snapshot it back', async () => { + const { fs } = memfs(data); + fs.symlinkSync('/start/folder1/folder2/file6', '/start/folder1/symlink'); + fs.writeFileSync('/start/binary', new Uint8Array([1, 2, 3])); + const snapshot = await json.toJsonSnapshot({ fs: fs.promises, path: '/start' })!; + const { fs: fs2, vol: vol2 } = memfs(); + fs2.mkdirSync('/start', { recursive: true }); + await json.fromJsonSnapshot(snapshot, { fs: fs2.promises, path: '/start' }); + expect(fs2.readFileSync('/start/binary')).toStrictEqual(Buffer.from([1, 2, 3])); + const snapshot2 = await json.toJsonSnapshot({ fs: fs2.promises, path: '/start' })!; + expect(snapshot2).toStrictEqual(snapshot); + }); +}); diff --git a/src/snapshot/binary.ts b/src/snapshot/binary.ts index 2ead2fa2e..f92ba5153 100644 --- a/src/snapshot/binary.ts +++ b/src/snapshot/binary.ts @@ -2,10 +2,11 @@ import {CborEncoder} from 'json-joy/es6/json-pack/cbor/CborEncoder'; import {CborDecoder} from 'json-joy/es6/json-pack/cbor/CborDecoder'; import {fromSnapshotSync, toSnapshotSync} from './sync'; import {fromSnapshot, toSnapshot} from './async'; +import {writer} from './shared'; import type {CborUint8Array} from 'json-joy/es6/json-pack/cbor/types'; import type {AsyncSnapshotOptions, SnapshotNode, SnapshotOptions} from './types'; -const encoder = new CborEncoder(); +const encoder = new CborEncoder(writer); const decoder = new CborDecoder(); export const toBinarySnapshotSync = (options: SnapshotOptions): CborUint8Array => { diff --git a/src/snapshot/index.ts b/src/snapshot/index.ts index 66b91fb83..5c05d1fda 100644 --- a/src/snapshot/index.ts +++ b/src/snapshot/index.ts @@ -2,3 +2,4 @@ export type * from './types'; export * from './constants'; export * from './sync'; export * from './binary'; +export * from './json'; diff --git a/src/snapshot/json.ts b/src/snapshot/json.ts new file mode 100644 index 000000000..a180ee864 --- /dev/null +++ b/src/snapshot/json.ts @@ -0,0 +1,32 @@ +import {JsonEncoder} from 'json-joy/es6/json-pack/json/JsonEncoder'; +import {JsonDecoder} from 'json-joy/es6/json-pack/json/JsonDecoder'; +import {fromSnapshotSync, toSnapshotSync} from './sync'; +import {fromSnapshot, toSnapshot} from './async'; +import {writer} from './shared'; +import type {AsyncSnapshotOptions, SnapshotNode, SnapshotOptions} from './types'; + +/** @todo Import this type from `json-joy` once it is available. */ +export type JsonUint8Array = Uint8Array & {__BRAND__: 'json'; __TYPE__: T}; + +const encoder = new JsonEncoder(writer); +const decoder = new JsonDecoder(); + +export const toJsonSnapshotSync = (options: SnapshotOptions): JsonUint8Array => { + const snapshot = toSnapshotSync(options); + return encoder.encode(snapshot) as JsonUint8Array; +}; + +export const fromJsonSnapshotSync = (uint8: JsonUint8Array, options: SnapshotOptions): void => { + const snapshot = decoder.read(uint8) as SnapshotNode; + fromSnapshotSync(snapshot, options); +}; + +export const toJsonSnapshot = async (options: AsyncSnapshotOptions): Promise> => { + const snapshot = await toSnapshot(options); + return encoder.encode(snapshot) as JsonUint8Array; +}; + +export const fromJsonSnapshot = async (uint8: JsonUint8Array, options: AsyncSnapshotOptions): Promise => { + const snapshot = decoder.read(uint8) as SnapshotNode; + await fromSnapshot(snapshot, options); +}; diff --git a/src/snapshot/shared.ts b/src/snapshot/shared.ts new file mode 100644 index 000000000..f592b2417 --- /dev/null +++ b/src/snapshot/shared.ts @@ -0,0 +1,3 @@ +import {Writer} from 'json-joy/es6/util/buffers/Writer'; + +export const writer = new Writer(1024 * 32);