Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,19 @@ const object = superjson.parse<
// object === { date: new Date(0) }
```

For asynchronous processing at the transport boundary, use
`serializeAsync` and `deserializeAsync`:

```js
const payload = await superjson.serializeAsync(value, { yieldRate: 1024 });
const object = await superjson.deserializeAsync(payload, { yieldRate: 1024 });
```

The synchronous `serialize` and `deserialize` APIs remain unchanged. The
asynchronous APIs are available in browser and Node.js runtimes and process
values in order, yielding to the event loop after the configured number of
visited values.

## Advanced Usage

For cases where you want lower level access to the `json` and `meta` data in the output, you can use the `serialize` and `deserialize` functions.
Expand Down Expand Up @@ -210,6 +223,32 @@ Options

Returns **`your original value`**.

### serializeAsync

Asynchronously serializes a JavaScript value while periodically yielding to the
event loop.

```js
const payload = await serializeAsync(object, { yieldRate: 1024 });
```

Options

- `yieldRate: number`
- Default: `1024`
- Number of visited values between event-loop yields

### deserializeAsync

Asynchronously deserializes the output of `serializeAsync`.

```js
const object = await deserializeAsync(payload, { yieldRate: 1024 });
```

It accepts the same `yieldRate` option as `serializeAsync` and the `inPlace`
option documented for `deserialize`.

### stringify

Serializes and then stringifies your JavaScript value.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@
"dependencies": {
"copy-anything": "^4"
}
}
}
105 changes: 84 additions & 21 deletions src/accessDeep.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
import { isMap, isArray, isPlainObject, isSet } from './is.js';
import { includes } from './util.js';

const getNthKey = (value: Map<any, any> | Set<any>, n: number): any => {
if (n > value.size) throw new Error('index out of bounds');
const keys = value.keys();
while (n > 0) {
keys.next();
n--;
}

return keys.next().value;
};
import { AsyncYieldController } from './async.js';

function validatePath(path: (string | number)[]) {
if (includes(path, '__proto__')) {
Expand All @@ -24,18 +14,38 @@ function validatePath(path: (string | number)[]) {
}
}

export const getDeep = (object: object, path: (string | number)[]): object => {
function* getNthKeyGenerator(
value: Map<any, any> | Set<any>,
n: number
): Generator<void, any, void> {
if (n > value.size) throw new Error('index out of bounds');
const keys = value.keys();
while (n > 0) {
yield;
keys.next();
n--;
}

yield;
return keys.next().value;
}

function* getDeepGenerator(
object: object,
path: (string | number)[]
): Generator<void, object, void> {
validatePath(path);

for (let i = 0; i < path.length; i++) {
yield;
const key = path[i];
if (isSet(object)) {
object = getNthKey(object, +key);
object = yield* getNthKeyGenerator(object, +key);
} else if (isMap(object)) {
const row = +key;
const type = +path[++i] === 0 ? 'key' : 'value';

const keyOfRow = getNthKey(object, row);
const keyOfRow = yield* getNthKeyGenerator(object, row);
switch (type) {
case 'key':
object = keyOfRow;
Expand All @@ -50,13 +60,13 @@ export const getDeep = (object: object, path: (string | number)[]): object => {
}

return object;
};
}

export const setDeep = (
function* setDeepGenerator(
object: any,
path: (string | number)[],
mapper: (v: any) => any
): any => {
): Generator<void, any, void> {
validatePath(path);

if (path.length === 0) {
Expand All @@ -66,6 +76,7 @@ export const setDeep = (
let parent = object;

for (let i = 0; i < path.length - 1; i++) {
yield;
const key = path[i];

if (isArray(parent)) {
Expand All @@ -75,7 +86,7 @@ export const setDeep = (
parent = parent[key];
} else if (isSet(parent)) {
const row = +key;
parent = getNthKey(parent, row);
parent = yield* getNthKeyGenerator(parent, row);
} else if (isMap(parent)) {
const isEnd = i === path.length - 2;
if (isEnd) {
Expand All @@ -85,7 +96,7 @@ export const setDeep = (
const row = +key;
const type = +path[++i] === 0 ? 'key' : 'value';

const keyOfRow = getNthKey(parent, row);
const keyOfRow = yield* getNthKeyGenerator(parent, row);
switch (type) {
case 'key':
parent = keyOfRow;
Expand All @@ -97,6 +108,7 @@ export const setDeep = (
}
}

yield;
const lastKey = path[path.length - 1];

if (isArray(parent)) {
Expand All @@ -106,7 +118,7 @@ export const setDeep = (
}

if (isSet(parent)) {
const oldValue = getNthKey(parent, +lastKey);
const oldValue = yield* getNthKeyGenerator(parent, +lastKey);
const newValue = mapper(oldValue);
if (oldValue !== newValue) {
parent.delete(oldValue);
Expand All @@ -116,7 +128,7 @@ export const setDeep = (

if (isMap(parent)) {
const row = +path[path.length - 2];
const keyToRow = getNthKey(parent, row);
const keyToRow = yield* getNthKeyGenerator(parent, row);

const type = +lastKey === 0 ? 'key' : 'value';
switch (type) {
Expand All @@ -138,4 +150,55 @@ export const setDeep = (
}

return object;
}

export const getDeep = (object: object, path: (string | number)[]): object => {
const iterator = getDeepGenerator(object, path);
let result = iterator.next();
while (!result.done) {
result = iterator.next();
}
return result.value;
};

export const getDeepAsync = async (
object: object,
path: (string | number)[],
scheduler: AsyncYieldController
): Promise<object> => {
const iterator = getDeepGenerator(object, path);
let result = iterator.next();
while (!result.done) {
await scheduler.tick();
result = iterator.next();
}
return result.value;
};

export const setDeep = (
object: any,
path: (string | number)[],
mapper: (v: any) => any,
): any => {
const iterator = setDeepGenerator(object, path, mapper);
let result = iterator.next();
while (!result.done) {
result = iterator.next();
}
return result.value;
};

export const setDeepAsync = async (
object: any,
path: (string | number)[],
mapper: (v: any) => any,
scheduler: AsyncYieldController
): Promise<any> => {
const iterator = setDeepGenerator(object, path, mapper);
let result = iterator.next();
while (!result.done) {
await scheduler.tick();
result = iterator.next();
}
return result.value;
};
Loading