feat: Passwordless cross-device authentication

- Arkitektur: docs/auth/passwordless-architecture.md
- Backend: iom/quixzoom-auth-service/ (FastAPI + Redis)
- Webb: quixzoom-market-pages/se/login/ (QR-kod + polling)
- App: iom/quixzoom-app/src/features/auth/ (push + deep links)

Flöde: QR-kod → app-godkännande → webb-inloggad
This commit is contained in:
Bernt
2026-07-07 07:11:50 +00:00
parent 4aa984ad74
commit 6989a98d75
61843 changed files with 5491611 additions and 872231 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Kris Zyp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+370
View File
@@ -0,0 +1,370 @@
# msgpackr
[![npm version](https://img.shields.io/npm/v/msgpackr.svg?style=flat-square)](https://www.npmjs.org/package/msgpackr)
[![npm version](https://img.shields.io/npm/dw/msgpackr)](https://www.npmjs.org/package/msgpackr)
[![encode](https://img.shields.io/badge/encode-1.5GB%2Fs-yellow)](benchmark.md)
[![decode](https://img.shields.io/badge/decode-2GB%2Fs-yellow)](benchmark.md)
[![types](https://img.shields.io/npm/types/msgpackr)](README.md)
[![module](https://img.shields.io/badge/module-ESM%2FCJS-blue)](README.md)
[![license](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE)
The msgpackr package is an extremely fast MessagePack NodeJS/JavaScript implementation. Currently, it is significantly faster than any other known implementations, faster than Avro (for JS), and generally faster than native V8 JSON.stringify/parse, on NodeJS. It also includes an optional record extension (the `r` in msgpackr), for defining record structures that makes MessagePack even faster and more compact, often over twice as fast as even native JSON functions, several times faster than other JS implementations, and 15-50% more compact. See the performance section for more details. Structured cloning (with support for cyclical references) is also supported through optional extensions.
## Basic Usage
Install with:
```
npm i msgpackr
```
And `import` or `require` it for basic standard serialization/encoding (`pack`) and deserialization/decoding (`unpack`) functions:
```js
import { unpack, pack } from 'msgpackr';
let serializedAsBuffer = pack(value);
let data = unpack(serializedAsBuffer);
```
This `pack` function will generate standard MessagePack without any extensions that should be compatible with any standard MessagePack parser/decoder. It will serialize JavaScript objects as MessagePack `map`s by default. The `unpack` function will deserialize MessagePack `map`s as an `Object` with the properties from the map.
## Node Usage
The msgpackr package runs on any modern JS platform, but is optimized for NodeJS usage (and will use a node addon for performance boost as an optional dependency).
### Streams
We can use the including streaming functionality (which further improves performance). The `PackrStream` is a NodeJS transform stream that can be used to serialize objects to a binary stream (writing to network/socket, IPC, etc.), and the `UnpackrStream` can be used to deserialize objects from a binary sream (reading from network/socket, etc.):
```js
import { PackrStream } from 'msgpackr';
let stream = new PackrStream();
stream.write(myData);
```
Or for a full example of sending and receiving data on a stream:
```js
import { PackrStream, UnpackrStream } from 'msgpackr';
let sendingStream = new PackrStream();
let receivingStream = new UnpackrStream();
// we are just piping to our own stream, but normally you would send and
// receive over some type of inter-process or network connection.
sendingStream.pipe(receivingStream);
sendingStream.write(myData);
receivingStream.on('data', (data) => {
// received data
});
```
The `PackrStream` and `UnpackrStream` instances will have also the record structure extension enabled by default (see below).
## Deno and Bun Usage
Msgpackr modules are standard ESM modules and can be loaded directly from the [deno.land registry for msgpackr](https://deno.land/x/msgpackr) for use in Deno or using the NPM module loader with `import { unpack } from 'npm:msgpackr'`. The standard pack/encode and unpack/decode functionality is available on Deno, like other platforms. msgpackr can be used like any other package on Bun.
## Browser Usage
Msgpackr works as standalone JavaScript as well, and runs on modern browsers. It includes a bundled script, at `dist/index.js` for ease of direct loading:
```html
<script src="node_modules/msgpackr/dist/index.js"></script>
```
This is UMD based, and will register as a module if possible, or create a `msgpackr` global with all the exported functions.
For module-based development, it is recommended that you directly import the module of interest, to minimize dependencies that get pulled into your application:
```js
import { unpack } from 'msgpackr/unpack' // if you only need to unpack
```
The package also includes a minified bundle in index.min.js.
Additionally, the package includes a version that excludes dynamic code evaluation called index-no-eval.js, for situations where Content Security Policy (CSP) forbids eval/Function in code. The dynamic evaluation provides important performance optimizations (for records), so is not recommended unless required by CSP policy.
## Structured Cloning
You can also use msgpackr for [structured cloning](https://html.spec.whatwg.org/multipage/structured-data.html). By enabling the `structuredClone` option, you can include references to other objects or cyclic references, and object identity will be preserved. Structured cloning also enables preserving certain typed objects like `Error`, `Set`, `RegExp` and TypedArray instances. For example:
```js
let obj = {
set: new Set(['a', 'b']),
regular: /a\spattern/
};
obj.self = obj;
let packr = new Packr({ structuredClone: true });
let serialized = packr.pack(obj);
let copy = packr.unpack(serialized);
copy.self === copy // true
copy.set.has('a') // true
```
This option is disabled by default because it uses extensions and reference checking degrades performance (by about 25-30%). (Note this implementation doesn't serialize every class/type specified in the HTML specification since not all of them make sense for storing across platforms.)
### Alternate Terminology
If you prefer to use encoder/decode terminology, msgpackr exports aliases, so `decode` is equivalent to `unpack`, `encode` is `pack`, `Encoder` is `Packr`, `Decoder` is `Unpackr`, and `EncoderStream` and `DecoderStream` can be used as well.
## Record / Object Structures
There is a critical difference between maps (or dictionaries) that hold an arbitrary set of keys and values (JavaScript `Map` is designed for these), and records or object structures that have a well-defined set of fields. Typical JS objects/records may have many instances re(use) the same structure. By using the record extension, this distinction is preserved in MessagePack and the encoding can reuse structures and not only provides better type preservation, but yield much more compact encodings and increase decoding performance by 2-3x. Msgpackr automatically generates record definitions that are reused and referenced by objects with the same structure. There are a number of ways to use this to our advantage. For large object structures with repeating nested objects with similar structures, simply serializing with the record extension can yield significant benefits. To use the record structures extension, we create a new `Packr` instance. By default a new `Packr` instance will have the record extension enabled:
```js
import { Packr } from 'msgpackr';
let packr = new Packr();
packr.pack(bigDataWithLotsOfObjects);
```
Another way to further leverage the benefits of the msgpackr record structures is to use streams that naturally allow for data to reuse based on previous record structures. The stream classes have the record structure extension enabled by default and provide excellent out-of-the-box performance.
When creating a new `Packr`, `Unpackr`, `PackrStream`, or `UnpackrStream` instance, we can enable or disable the record structure extension with the `useRecords` property. When this is `false`, the record structure extension will be disabled (standard/compatibility mode), and all objects will revert to being serialized using MessagePack `map`s, and all `map`s will be deserialized to JS `Object`s as properties (like the standalone `pack` and `unpack` functions).
Streaming with record structures works by encoding a structure the first time it is seen in a stream and referencing the structure in later messages that are sent across that stream. When an encoder can expect a decoder to understand previous structure references, this can be configured using the `sequential: true` flag, which is auto-enabled by streams, but can also be used with Packr instances.
### Shared Record Structures
Another useful way of using msgpackr, and the record extension, is for storing data in a databases, files, or other storage systems. If a number of objects with common data structures are being stored, a shared structure can be used to greatly improve data storage and deserialization efficiency. In the simplest form, provide a `structures` array, which is updated if any new object structure is encountered:
```js
import { Packr } from 'msgpackr';
let packr = new Packr({
structures: [... structures that were last generated ...]
});
```
If you are working with persisted data, you will need to persist the `structures` data when it is updated. Msgpackr provides an API for loading and saving the `structures` on demand (which is robust and can be used in multiple-process situations where other processes may be updating this same `structures` array), we just need to provide a way to store the generated shared structure so it is available to deserialize stored data in the future:
```js
import { Packr } from 'msgpackr';
let packr = new Packr({
getStructures() {
// storing our data in file (but we could also store in a db or key-value store)
return unpack(readFileSync('my-shared-structures.mp')) || [];
},
saveStructures(structures) {
writeFileSync('my-shared-structures.mp', pack(structures));
}
});
```
Msgpackr will automatically add and saves structures as it encounters any new object structures (up to a limit of 32, by default). It will always add structures in an incremental/compatible way: Any object encoded with an earlier structure can be decoded with a later version (as long as it is persisted).
#### Shared Structures Options
By default there is a limit of 32 shared structures. This default is designed to record common shared structures, but also be resilient against sharing too many structures if there are many objects with dynamic properties that are likely to be repeated. This also allows for slightly more efficient one byte encoding. However, if your application has more structures that are commonly repeated, you can increase this limit by setting `maxSharedStructures` to a higher value. The maximum supported shared structures is 8160.
You can also provide a `shouldShareStructure` function in the options if you want to specifically indicate which structures should be shared. This is called during the encoding process with the array of keys for a structure that is being considered for addition to the shared structure. For example, you might want:
```
maxSharedStructures: 100,
shouldShareStructure(keys) {
return !(keys[0] > 1) // don't share structures that consist of numbers as keys
}
```
### Reading Multiple Values
If you have a buffer with multiple values sequentially encoded, you can choose to parse and read multiple values. This can be done using the `unpackMultiple` function/method, which can return an array of all the values it can sequentially parse within the provided buffer. For example:
```js
let data = new Uint8Array([1, 2, 3]) // encodings of values 1, 2, and 3
let values = unpackMultiple(data) // [1, 2, 3]
```
Alternately, you can provide a callback function that is called as the parsing occurs with each value, and can optionally terminate the parsing by returning `false`:
```js
let data = new Uint8Array([1, 2, 3]) // encodings of values 1, 2, and 3
unpackMultiple(data, (value) => {
// called for each value
// return false if you wish to end the parsing
})
```
If you need to know the start and end offsets of the unpacked values, these are
provided as optional parameters in the callback:
```js
let data = new Uint8Array([1, 2, 3]) // encodings of values 1, 2, and 3
unpackMultiple(data, (value,start,end) => {
// called for each value
// `start` is the data buffer offset where the value was read from
// `end` is `start` plus the byte length of the encoded value
// return false if you wish to end the parsing
})
```
## Options
The following options properties can be provided to the Packr or Unpackr constructor:
* `useRecords` - Setting this to `false` disables the record extension and stores JavaScript objects as MessagePack maps, and unpacks maps as JavaScript `Object`s, which ensures compatibilty with other decoders. Setting this to a function will use records for objects where `useRecords(object)` returns `true`.
* `structures` - Provides the array of structures that is to be used for record extension, if you want the structures saved and used again. This array will be modified in place with new record structures that are serialized (if less than 32 structures are in the array).
* `moreTypes` - Enable serialization of additional built-in types/classes including typed arrays, `Set`s, `Map`s, and `Error`s.
* `structuredClone` - This enables the structured cloning extensions that will encode object/cyclic references. `moreTypes` is enabled by default when this is enabled.
* `mapsAsObjects` - If `true`, this will decode MessagePack maps and JS `Object`s with the map entries decoded to object properties. If `false`, maps are decoded as JavaScript `Map`s. This is disabled by default if `useRecords` is enabled (which allows `Map`s to be preserved), and is enabled by default if `useRecords` is disabled.
* `useFloat32` - This will enable msgpackr to encode non-integer numbers as `float32`. See next section for possible values.
* `variableMapSize` - This will use varying map size definition (fixmap, map16, map32) based on the number of keys when encoding objects, which yields slightly more compact encodings (for small objects), but is typically 5-10% slower during encoding. This is necessary if you need to use objects with more than 65535 keys. This is only relevant when record extension is disabled.
* `bundleStrings` - If `true` this uses a custom extension that bundles strings together, so that they can be decoded more quickly on browsers and Deno that do not have access to the NodeJS addon. This a custom extension, so both encoder and decoder need to support this. This can yield significant decoding performance increases on browsers (30%-50%).
* `copyBuffers` - When decoding a MessagePack with binary data (Buffers are encoded as binary data), copy the buffer rather than providing a slice/view of the buffer. If you want your input data to be collected or modified while the decoded embedded buffer continues to live on, you can use this option (there is extra overhead to copying).
* `useTimestamp32` - Encode JS `Date`s in 32-bit format when possible by dropping the milliseconds. This is a more efficient encoding of dates. You can also cause dates to use 32-bit format by manually setting the milliseconds to zero (`date.setMilliseconds(0)`).
* `sequential` - Encode structures in serialized data, and reference previously encoded structures with expectation that decoder will read the encoded structures in the same order as encoded, with `unpackMultiple`.
* `largeBigIntToFloat` - If a bigint needs to be encoded that is larger than will fit in 64-bit integers, it will be encoded as a float-64 (otherwise will throw a RangeError).
* `largeBigIntToString` - If a bigint needs to be encoded that is larger than will fit in 64-bit integers, it will be encoded as a string (otherwise will throw a RangeError).
* `useBigIntExtension` - If a bigint needs to be encoded that is larger than will fit in 64-bit integers, it will be encoded using a custom extension that supports up to about 1000-bits of integer precision.
* `encodeUndefinedAsNil` - Encodes a value of `undefined` as a MessagePack `nil`, the same as a `null`.
* `int64AsType` - This will decode uint64 and int64 numbers as the specified type. The type can be `bigint` (default), `number`, `string`, or `auto` (where range [-2^53...2^53] is represented by number and everything else by a bigint).
* `skipValues` - This can be an array of property values that will indicate properties that should be skipped when serializing objects. For example, to mimic `JSON.stringify`'s behavior of skipping properties with a value of `undefined`, you can provide `skipValues: [undefined]`. Note, that this will only apply to serializing objects as standard MessagePack maps, not to records. Also, the array is checked by calling the `include` method, so you can provide an object with an `includes` if you want a custom function to skip values.
* `onInvalidDate` - This can be provided as function that will be called when an invalid date is provided. The function can throw an error, or return a value that will be encoded in place of the invalid date. If not provided, an invalid date will be encoded as an invalid timestamp (which decodes with msgpackr back to an invalid date).
* `writeFunction` - This can be provided as function that will be called when a function is encountered. The function can throw an error, or return a value that will be encoded in place of the function. If not provided, a function will be encoded as undefined (similar to `JSON.stringify`).
* `mapAsEmptyObject` - Encodes JS `Map`s as empty objects (for back-compat with older libraries).
* `setAsEmptyObject` - Encodes JS `Set`s as empty objects (for back-compat with older libraries).
* `allowArraysInMapKeys` - Allows arrays to be used as keys in Maps, as long as all elements are strings, numbers, booleans, or bigints. When enabled, such arrays are flattened and converted to a string representation.
### 32-bit Float Options
By default all non-integer numbers are serialized as 64-bit float (double). This is fast, and ensures maximum precision. However, often real-world data doesn't not need 64-bits of precision, and using 32-bit encoding can be much more space efficient. There are several options that provide more efficient encodings. Using the decimal rounding options for encoding and decoding provides lossless storage of common decimal representations like 7.99, in more efficient 32-bit format (rather than 64-bit). The `useFloat32` property has several possible options, available from the module as constants:
```js
import { FLOAT32_OPTIONS } from 'msgpackr';
const { ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } = FLOAT32_OPTIONS;
```
* `ALWAYS` (1) - Always will encode non-integers (absolute less than 2147483648) as 32-bit float.
* `DECIMAL_ROUND` (3) - Always will encode non-integers as 32-bit float, and when decoding 32-bit float, round to the significant decimal digits (usually 7, but 6 or 8 digits for some ranges).
* `DECIMAL_FIT` (4) - Only encode non-integers as 32-bit float if all significant digits (usually up to 7) can be unambiguously encoded as a 32-bit float, and decode/unpack with decimal rounding (same as above). This will ensure round-trip encoding/decoding without loss in precision and uses 32-bit when possible.
Note, that the performance is decreased with decimal rounding by about 20-25%, although if only 5% of your values are floating point, that will only have about a 1% impact overall.
In addition, msgpackr exports a `roundFloat32(number)` function that can be used to round floating point numbers to the maximum significant decimal digits that can be stored in 32-bit float, just as DECIMAL_ROUND does when decoding. This can be useful for determining how a number will be decoded prior to encoding it.
## Performance
### Native Acceleration
Msgpackr employs an optional native node-addon to accelerate the parsing of strings. This should be automatically installed and utilized on NodeJS. However, you can verify this by checking the `isNativeAccelerationEnabled` property that is exported from msgpackr. If this is `false`, the `msgpackr-extract` package may not have been properly installed, and you may want to verify that it is installed correctly:
```js
import { isNativeAccelerationEnabled } from 'msgpackr'
if (!isNativeAccelerationEnabled)
console.warn('Native acceleration not enabled, verify that install finished properly')
```
### Benchmarks
Msgpackr is fast. Really fast. Here is comparison with the next fastest JS projects using the benchmark tool from `msgpack-lite` (and the sample data is from some clinical research data we use that has a good mix of different value types and structures). It also includes comparison to V8 native JSON functionality, and JavaScript Avro (`avsc`, a very optimized Avro implementation):
operation | op | ms | op/s
---------------------------------------------------------- | ------: | ----: | -----:
buf = Buffer(JSON.stringify(obj)); | 81600 | 5002 | 16313
obj = JSON.parse(buf); | 90700 | 5004 | 18125
require("msgpackr").pack(obj); | 169700 | 5000 | 33940
require("msgpackr").unpack(buf); | 109700 | 5003 | 21926
msgpackr w/ shared structures: packr.pack(obj); | 190400 | 5001 | 38072
msgpackr w/ shared structures: packr.unpack(buf); | 422900 | 5000 | 84580
buf = require("msgpack-lite").encode(obj); | 31300 | 5005 | 6253
obj = require("msgpack-lite").decode(buf); | 15700 | 5007 | 3135
buf = require("@msgpack/msgpack").encode(obj); | 103100 | 5003 | 20607
obj = require("@msgpack/msgpack").decode(buf); | 59100 | 5004 | 11810
buf = require("notepack").encode(obj); | 65500 | 5007 | 13081
obj = require("notepack").decode(buf); | 33400 | 5009 | 6667
obj = require("msgpack-unpack").decode(buf); | 6900 | 5036 | 1370
require("avsc")...make schema/type...type.toBuffer(obj); | 89300 | 5005 | 17842
require("avsc")...make schema/type...type.fromBuffer(obj); | 108400 | 5001 | 21675
All benchmarks were performed on Node 15 / V8 8.6 (Windows i7-4770 3.4Ghz).
(`avsc` is schema-based and more comparable in style to msgpackr with shared structures).
Here is a benchmark of streaming data (again borrowed from `msgpack-lite`'s benchmarking), where msgpackr is able to take advantage of the structured record extension and really demonstrate its performance capabilities:
operation (1000000 x 2) | op | ms | op/s
------------------------------------------------ | ------: | ----: | -----:
new PackrStream().write(obj); | 1000000 | 372 | 2688172
new UnpackrStream().write(buf); | 1000000 | 247 | 4048582
stream.write(msgpack.encode(obj)); | 1000000 | 2898 | 345065
stream.write(msgpack.decode(buf)); | 1000000 | 1969 | 507872
stream.write(notepack.encode(obj)); | 1000000 | 901 | 1109877
stream.write(notepack.decode(buf)); | 1000000 | 1012 | 988142
msgpack.Encoder().on("data",ondata).encode(obj); | 1000000 | 1763 | 567214
msgpack.createDecodeStream().write(buf); | 1000000 | 2222 | 450045
msgpack.createEncodeStream().write(obj); | 1000000 | 1577 | 634115
msgpack.Decoder().on("data",ondata).decode(buf); | 1000000 | 2246 | 445235
See the [benchmark.md](benchmark.md) for more benchmarks and information about benchmarking.
## Custom Extensions
You can add your own custom extensions, which can be used to encode specific types/classes in certain ways. This is done by using the `addExtension` function, and specifying the class, extension `type` code (should be a number from 1-100, reserving negatives for MessagePack, 101-127 for msgpackr), and your `pack` and `unpack` functions (or just the one you need).
```js
import { addExtension, Packr } from 'msgpackr';
class MyCustomClass {...}
let extPackr = new Packr();
addExtension({
Class: MyCustomClass,
type: 11, // register your own extension code (a type code from 1-100)
pack(instance) {
// define how your custom class should be encoded
return Buffer.from([instance.myData]); // return a buffer
},
unpack(buffer) {
// define how your custom class should be decoded
let instance = new MyCustomClass();
instance.myData = buffer[0];
return instance; // decoded value from buffer
}
});
```
If you want to use msgpackr to encode and decode the data within your extensions, you can use the `read` and `write` functions and read and write data/objects that will be encoded and decoded by msgpackr, which can be easier and faster than creating and receiving separate buffers:
```js
import { addExtension, Packr } from 'msgpackr';
class MyCustomClass {...}
let extPackr = new Packr();
addExtension({
Class: MyCustomClass,
type: 11, // register your own extension code (a type code from 1-100)
write(instance) {
// define how your custom class should be encoded
return instance.myData; // return some data to be encoded
}
read(data) {
// define how your custom class should be decoded,
// data will already be unpacked/decoded
let instance = new MyCustomClass();
instance.myData = data;
return instance; // return decoded value
}
});
```
Note that you can just return the same object from `write`, and in this case msgpackr will encode it using the default object/array encoding:
```js
addExtension({
Class: MyCustomClass,
type: 12,
read: function(data) {
Object.setPrototypeOf(data, MyCustomClass.prototype)
return data
},
write: function(data) {
return data
}
})
```
You can also create an extension with `Class` and `write` methods, but no `type` (or `read`), if you just want to customize how a class is serialized without using MessagePack extension encoding.
### Additional Performance Optimizations
Msgpackr is already fast, but here are some tips for making it faster:
#### Buffer Reuse
Msgpackr is designed to work well with reusable buffers. Allocating new buffers can be relatively expensive, so if you have Node addons, it can be much faster to reuse buffers and use memcpy to copy data into existing buffers. Then msgpackr `unpack` can be executed on the same buffer, with new data, and optionally take a second paramter indicating the effective size of the available data in the buffer.
#### Arena Allocation (`useBuffer()`)
During the serialization process, data is written to buffers. Again, allocating new buffers is a relatively expensive process, and the `useBuffer` method can help allow reuse of buffers that will further improve performance. With `useBuffer` method, you can provide a buffer, serialize data into it, and when it is known that you are done using that buffer, you can call `useBuffer` again to reuse it. The use of `useBuffer` is never required, buffers will still be handled and cleaned up through GC if not used, it just provides a small performance boost.
## Record Structure Extension Definition
The record struction extension uses extension id 0x72 ("r") to declare the use of this functionality. The extension "data" byte (or bytes) identifies the byte or bytes used to identify the start of a record in the subsequent MessagePack block or stream. The identifier byte (or the first byte in a sequence) must be from 0x40 - 0x7f (and therefore replaces one byte representations of positive integers 64 - 127, which can alternately be represented with int or uint types). The extension declaration must be immediately follow by an MessagePack array that defines the field names of the record structure.
Once a record identifier and record field names have been defined, the parser/decoder should proceed to read the next value. Any subsequent use of the record identifier as a value in the block or stream should parsed as a record instance, and the next n values, where is n is the number of fields (as defined in the array of field names), should be read as the values of the fields. For example, here we have defined a structure with fields "foo" and "bar", with the record identifier 0x40, and then read a record instance that defines the field values of 4 and 2, respectively:
```
+--------+--------+--------+~~~~~~~~~~~~~~~~~~~~~~~~~+--------+--------+
| 0xd4 | 0x72 | 0x40 | array: [ "foo", "bar" ] | 0x04 | 0x02 |
+--------+--------+--------+~~~~~~~~~~~~~~~~~~~~~~~~~+--------+--------+
```
Which should generate an object that would correspond to JSON:
```js
{ "foo": 4, "bar": 2}
```
## Additional value types
msgpackr supports `undefined` (using fixext1 + type: 0 + data: 0 to match other JS implementations), `NaN`, `Infinity`, and `-Infinity` (using standard IEEE 754 representations with doubles/floats).
### Dates
msgpackr saves all JavaScript `Date`s using the standard MessagePack date extension (type -1), using the smallest of 32-bit, 64-bit or 96-bit format needed to store the date without data loss (or using 32-bit if useTimestamp32 options is specified).
### Structured Cloning
With structured cloning enabled, msgpackr will also use extensions to store Set, Map, Error, RegExp, ArrayBufferView objects and preserve their types.
## Alternate Encoding/Package
The high-performance serialization and deserialization algorithms in the msgpackr package are also available in the [cbor-x](https://github.com/kriszyp/cbor-x) for the CBOR format, with the same API and design. A quick summary of the pros and cons of using MessagePack vs CBOR are:
* MessagePack has wider adoption, and, at least with this implementation is slightly more efficient (by roughly 1%).
* CBOR has an [official IETF standardization track](https://tools.ietf.org/html/rfc7049), and the record extensions is conceptually/philosophically a better fit for CBOR tags.
## License
MIT
### Browser Consideration
MessagePack can be a great choice for high-performance data delivery to browsers, as reasonable data size is possible without compression. And msgpackr works very well in modern browsers. However, it is worth noting that if you want highly compact data, brotli or gzip are most effective in compressing, and MessagePack's character frequency tends to defeat Huffman encoding used by these standard compression algorithms, resulting in less compact data than compressed JSON.
### Credits
Various projects have been inspirations for this, and code has been borrowed from https://github.com/msgpack/msgpack-javascript and https://github.com/mtth/avsc.
+11
View File
@@ -0,0 +1,11 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 1.4.x | :white_check_mark: |
## Reporting a Vulnerability
Please report security vulnerabilities to kriszyp@gmail.com.
+77
View File
@@ -0,0 +1,77 @@
Here are more comprehensive benchmarks. This is comparison with the next fastest JS projects using the benchmark tool from `msgpack-lite` (and data is from some clinical research data we use that has a good mix of different value types and structures). It also includes comparison to V8 native JSON functionality, and JavaScript Avro (`avsc`, a very optimized Avro implementation):
---------------------------------------------------------- | ------: | ----: | -----: | -----:
msgpackr w/ shared structures: packr.pack(obj); | 254700 | 5001 | 50929
msgpackr w/ shared structures: packr.unpack(buf); | 711700 | 5000 | 142340
require("msgpackr").pack(obj); | 234000 | 5000 | 46800
require("msgpackr").unpack(buf); | 186500 | 5000 | 37300
buf = Buffer(JSON.stringify(obj)); | 297900 | 5000 | 59580
obj = JSON.parse(buf); | 216600 | 5001 | 43311
buf = require("msgpack-lite").encode(obj); | 114000 | 5001 | 22795
obj = require("msgpack-lite").decode(buf); | 40700 | 5006 | 8130
buf = require("@msgpack/msgpack").encode(obj); | 166100 | 5000 | 33220
obj = require("@msgpack/msgpack").decode(buf); | 136500 | 5002 | 27289
buf = require("msgpack-js-v5").encode(obj); | 41600 | 5000 | 8320
obj = require("msgpack-js-v5").decode(buf); | 70200 | 5004 | 14028
buf = require("msgpack-js").encode(obj); | 40400 | 5012 | 8060
obj = require("msgpack-js").decode(buf); | 67100 | 5002 | 13414
buf = require("msgpack5")().encode(obj); | 15800 | 5024 | 3144
obj = require("msgpack5")().decode(buf); | 30600 | 5004 | 6115
buf = require("notepack").encode(obj); | 125100 | 5002 | 25009
obj = require("notepack").decode(buf); | 98600 | 5004 | 19704
require("what-the-pack")... encoder.encode(obj); | 150300 | 5001 | 30053
require("what-the-pack")... encoder.decode(buf); | 100100 | 5000 | 20020
obj = require("msgpack-unpack").decode(buf); | 14900 | 5031 | 2961
require("avsc")...make schema/type...type.toBuffer(obj); | 266600 | 5000 | 53320
require("avsc")...make schema/type...type.fromBuffer(obj); | 370200 | 5000 | 74040
(`avsc` is schema-based and more comparable in style to msgpackr with shared structures).
(note that benchmarks below are several years old)
Here is a benchmark of streaming data (again borrowed from `msgpack-lite`'s benchmarking), where msgpackr is able to take advantage of the structured record extension and really pull away from other tools:
operation (1000000 x 2) | op | ms | op/s
------------------------------------------------ | ------: | ----: | -----:
new PackrStream().write(obj); | 1000000 | 372 | 2688172
new UnpackrStream().write(buf); | 1000000 | 247 | 4048582
stream.write(msgpack.encode(obj)); | 1000000 | 2898 | 345065
stream.write(msgpack.decode(buf)); | 1000000 | 1969 | 507872
stream.write(notepack.encode(obj)); | 1000000 | 901 | 1109877
stream.write(notepack.decode(buf)); | 1000000 | 1012 | 988142
msgpack.Encoder().on("data",ondata).encode(obj); | 1000000 | 1763 | 567214
msgpack.createDecodeStream().write(buf); | 1000000 | 2222 | 450045
msgpack.createEncodeStream().write(obj); | 1000000 | 1577 | 634115
msgpack.Decoder().on("data",ondata).decode(buf); | 1000000 | 2246 | 445235
These are the benchmarks from notepack package. The larger test data for these benchmarks is very heavily weighted with large binary/buffer data and objects with extreme numbers of keys (much more than I typically see with real-world data, but YMMV):
node ./benchmarks/encode
library | tiny | small | medium | large
---------------- | ----------------: | --------------: | ---------------| -------:
notepack | 2,171,621 ops/sec | 546,905 ops/sec | 29,578 ops/sec | 265 ops/sec
msgpack-js | 967,682 ops/sec | 184,455 ops/sec | 20,556 ops/sec | 259 ops/sec
msgpackr | 2,392,826 ops/sec | 556,915 ops/sec | 70,573 ops/sec | 313 ops/sec
msgpack-lite | 553,143 ops/sec | 132,318 ops/sec | 11,816 ops/sec | 186 ops/sec
@msgpack/msgpack | 2,157,655 ops/sec | 573,236 ops/sec | 25,864 ops/sec | 90.26 ops/sec
node ./benchmarks/decode
library | tiny | small | medium | large
---------------- | ----------------: | --------------: | --------------- | -------:
notepack | 2,220,904 ops/sec | 560,630 ops/sec | 28,177 ops/sec | 275 ops/sec
msgpack-js | 965,719 ops/sec | 222,047 ops/sec | 21,431 ops/sec | 257 ops/sec
msgpackr | 2,320,046 ops/sec | 589,167 ops/sec | 70,299 ops/sec | 329 ops/sec
msgpackr records | 3,750,547 ops/sec | 912,419 ops/sec | 136,853 ops/sec | 733 ops/sec
msgpack-lite | 569,222 ops/sec | 129,008 ops/sec | 12,424 ops/sec | 180 ops/sec
@msgpack/msgpack | 2,089,697 ops/sec | 557,507 ops/sec | 20,256 ops/sec | 85.03 ops/sec
This was run by adding the msgpackr to the benchmarks for notepack.
All benchmarks were performed on Node 14.8.0 (Windows i7-4770 3.4Ghz). They can be run with:
npm install --no-save msgpack msgpack-js @msgpack/msgpack msgpack-lite notepack avsc
node tests/benchmark
+2451
View File
@@ -0,0 +1,2451 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.msgpackr = {}));
})(this, (function (exports) { 'use strict';
var decoder;
try {
decoder = new TextDecoder();
} catch(error) {}
var src;
var srcEnd;
var position$1 = 0;
var currentUnpackr = {};
var currentStructures;
var srcString;
var srcStringStart = 0;
var srcStringEnd = 0;
var bundledStrings$1;
var referenceMap;
var currentExtensions = [];
var dataView;
var defaultOptions = {
useRecords: false,
mapsAsObjects: true
};
class C1Type {}
const C1 = new C1Type();
C1.name = 'MessagePack 0xC1';
var sequentialMode = false;
var inlineObjectReadThreshold = 2;
var readStruct;
var BlockedFunction; // we use search and replace to change the next call to BlockedFunction to avoid CSP issues for
class Unpackr {
constructor(options) {
if (options) {
if (options.useRecords === false && options.mapsAsObjects === undefined)
options.mapsAsObjects = true;
if (options.sequential && options.trusted !== false) {
options.trusted = true;
if (!options.structures && options.useRecords != false) {
options.structures = [];
if (!options.maxSharedStructures)
options.maxSharedStructures = 0;
}
}
if (options.structures)
options.structures.sharedLength = options.structures.length;
else if (options.getStructures) {
(options.structures = []).uninitialized = true; // this is what we use to denote an uninitialized structures
options.structures.sharedLength = 0;
}
if (options.int64AsNumber) {
options.int64AsType = 'number';
}
}
Object.assign(this, options);
}
unpack(source, options) {
if (src) {
// re-entrant execution, save the state and restore it after we do this unpack
return saveState(() => {
clearSource();
return this ? this.unpack(source, options) : Unpackr.prototype.unpack.call(defaultOptions, source, options)
})
}
if (!source.buffer && source.constructor === ArrayBuffer)
source = typeof Buffer !== 'undefined' ? Buffer.from(source) : new Uint8Array(source);
if (typeof options === 'object') {
srcEnd = options.end || source.length;
position$1 = options.start || 0;
} else {
position$1 = 0;
srcEnd = options > -1 ? options : source.length;
}
srcStringEnd = 0;
srcString = null;
bundledStrings$1 = null;
src = source;
// this provides cached access to the data view for a buffer if it is getting reused, which is a recommend
// technique for getting data from a database where it can be copied into an existing buffer instead of creating
// new ones
try {
dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength));
} catch(error) {
// if it doesn't have a buffer, maybe it is the wrong type of object
src = null;
if (source instanceof Uint8Array)
throw error
throw new Error('Source must be a Uint8Array or Buffer but was a ' + ((source && typeof source == 'object') ? source.constructor.name : typeof source))
}
if (this instanceof Unpackr) {
currentUnpackr = this;
if (this.structures) {
currentStructures = this.structures;
return checkedRead(options)
} else if (!currentStructures || currentStructures.length > 0) {
currentStructures = [];
}
} else {
currentUnpackr = defaultOptions;
if (!currentStructures || currentStructures.length > 0)
currentStructures = [];
}
return checkedRead(options)
}
unpackMultiple(source, forEach) {
let values, lastPosition = 0;
try {
sequentialMode = true;
let size = source.length;
let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size);
if (forEach) {
if (forEach(value, lastPosition, position$1) === false) return;
while(position$1 < size) {
lastPosition = position$1;
if (forEach(checkedRead(), lastPosition, position$1) === false) {
return
}
}
}
else {
values = [ value ];
while(position$1 < size) {
lastPosition = position$1;
values.push(checkedRead());
}
return values
}
} catch(error) {
error.lastPosition = lastPosition;
error.values = values;
throw error
} finally {
sequentialMode = false;
clearSource();
}
}
_mergeStructures(loadedStructures, existingStructures) {
loadedStructures = loadedStructures || [];
if (Object.isFrozen(loadedStructures))
loadedStructures = loadedStructures.map(structure => structure.slice(0));
for (let i = 0, l = loadedStructures.length; i < l; i++) {
let structure = loadedStructures[i];
if (structure) {
structure.isShared = true;
if (i >= 32)
structure.highByte = (i - 32) >> 5;
}
}
loadedStructures.sharedLength = loadedStructures.length;
for (let id in existingStructures || []) {
if (id >= 0) {
let structure = loadedStructures[id];
let existing = existingStructures[id];
if (existing) {
if (structure)
(loadedStructures.restoreStructures || (loadedStructures.restoreStructures = []))[id] = structure;
loadedStructures[id] = existing;
}
}
}
return this.structures = loadedStructures
}
decode(source, options) {
return this.unpack(source, options)
}
}
function checkedRead(options) {
try {
if (!currentUnpackr.trusted && !sequentialMode) {
let sharedLength = currentStructures.sharedLength || 0;
if (sharedLength < currentStructures.length)
currentStructures.length = sharedLength;
}
let result;
if (currentUnpackr.randomAccessStructure && src[position$1] < 0x40 && src[position$1] >= 0x20 && readStruct) {
result = readStruct(src, position$1, srcEnd, currentUnpackr);
src = null; // dispose of this so that recursive unpack calls don't save state
if (!(options && options.lazy) && result)
result = result.toJSON();
position$1 = srcEnd;
} else
result = read();
if (bundledStrings$1) { // bundled strings to skip past
position$1 = bundledStrings$1.postBundlePosition;
bundledStrings$1 = null;
}
if (sequentialMode)
// we only need to restore the structures if there was an error, but if we completed a read,
// we can clear this out and keep the structures we read
currentStructures.restoreStructures = null;
if (position$1 == srcEnd) {
// finished reading this source, cleanup references
if (currentStructures && currentStructures.restoreStructures)
restoreStructures();
currentStructures = null;
src = null;
if (referenceMap)
referenceMap = null;
} else if (position$1 > srcEnd) {
// over read
throw new Error('Unexpected end of MessagePack data')
} else if (!sequentialMode) {
let jsonView;
try {
jsonView = JSON.stringify(result, (_, value) => typeof value === "bigint" ? `${value}n` : value).slice(0, 100);
} catch(error) {
jsonView = '(JSON view not available ' + error + ')';
}
throw new Error('Data read, but end of buffer not reached ' + jsonView)
}
// else more to read, but we are reading sequentially, so don't clear source yet
return result
} catch(error) {
if (currentStructures && currentStructures.restoreStructures)
restoreStructures();
clearSource();
if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer') || position$1 > srcEnd) {
error.incomplete = true;
}
throw error
}
}
function restoreStructures() {
for (let id in currentStructures.restoreStructures) {
currentStructures[id] = currentStructures.restoreStructures[id];
}
currentStructures.restoreStructures = null;
}
function read() {
let token = src[position$1++];
if (token < 0xa0) {
if (token < 0x80) {
if (token < 0x40)
return token
else {
let structure = currentStructures[token & 0x3f] ||
currentUnpackr.getStructures && loadStructures()[token & 0x3f];
if (structure) {
if (!structure.read) {
structure.read = createStructureReader(structure, token & 0x3f);
}
return structure.read()
} else
return token
}
} else if (token < 0x90) {
// map
token -= 0x80;
if (currentUnpackr.mapsAsObjects) {
let object = {};
for (let i = 0; i < token; i++) {
let key = readKey();
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
return object
} else {
let map = new Map();
for (let i = 0; i < token; i++) {
map.set(read(), read());
}
return map
}
} else {
token -= 0x90;
let array = new Array(token);
for (let i = 0; i < token; i++) {
array[i] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
} else if (token < 0xc0) {
// fixstr
let length = token - 0xa0;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += length) - srcStringStart)
}
if (srcStringEnd == 0 && srcEnd < 140) {
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length);
if (string != null)
return string
}
return readFixedString(length)
} else {
let value;
switch (token) {
case 0xc0: return null
case 0xc1:
if (bundledStrings$1) {
value = read(); // followed by the length of the string in characters (not bytes!)
if (value > 0)
return bundledStrings$1[1].slice(bundledStrings$1.position1, bundledStrings$1.position1 += value)
else
return bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 -= value)
}
return C1; // "never-used", return special object to denote that
case 0xc2: return false
case 0xc3: return true
case 0xc4:
// bin 8
value = src[position$1++];
if (value === undefined)
throw new Error('Unexpected end of buffer')
return readBin(value)
case 0xc5:
// bin 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readBin(value)
case 0xc6:
// bin 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readBin(value)
case 0xc7:
// ext 8
return readExt(src[position$1++])
case 0xc8:
// ext 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readExt(value)
case 0xc9:
// ext 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readExt(value)
case 0xca:
value = dataView.getFloat32(position$1);
if (currentUnpackr.useFloat32 > 2) {
// this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
let multiplier = mult10[((src[position$1] & 0x7f) << 1) | (src[position$1 + 1] >> 7)];
position$1 += 4;
return ((multiplier * value + (value > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
position$1 += 4;
return value
case 0xcb:
value = dataView.getFloat64(position$1);
position$1 += 8;
return value
// uint handlers
case 0xcc:
return src[position$1++]
case 0xcd:
value = dataView.getUint16(position$1);
position$1 += 2;
return value
case 0xce:
value = dataView.getUint32(position$1);
position$1 += 4;
return value
case 0xcf:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getUint32(position$1) * 0x100000000;
value += dataView.getUint32(position$1 + 4);
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigUint64(position$1).toString();
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigUint64(position$1);
if (value<=BigInt(2)<<BigInt(52)) value=Number(value);
} else
value = dataView.getBigUint64(position$1);
position$1 += 8;
return value
// int handlers
case 0xd0:
return dataView.getInt8(position$1++)
case 0xd1:
value = dataView.getInt16(position$1);
position$1 += 2;
return value
case 0xd2:
value = dataView.getInt32(position$1);
position$1 += 4;
return value
case 0xd3:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getInt32(position$1) * 0x100000000;
value += dataView.getUint32(position$1 + 4);
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigInt64(position$1).toString();
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigInt64(position$1);
if (value>=BigInt(-2)<<BigInt(52)&&value<=BigInt(2)<<BigInt(52)) value=Number(value);
} else
value = dataView.getBigInt64(position$1);
position$1 += 8;
return value
case 0xd4:
// fixext 1
value = src[position$1++];
if (value == 0x72) {
return recordDefinition(src[position$1++] & 0x3f)
} else {
let extension = currentExtensions[value];
if (extension) {
if (extension.read) {
position$1++; // skip filler byte
return extension.read(read())
} else if (extension.noBuffer) {
position$1++; // skip filler byte
return extension()
} else
return extension(src.subarray(position$1, ++position$1))
} else
throw new Error('Unknown extension ' + value)
}
case 0xd5:
// fixext 2
value = src[position$1];
if (value == 0x72) {
position$1++;
return recordDefinition(src[position$1++] & 0x3f, src[position$1++])
} else
return readExt(2)
case 0xd6:
// fixext 4
return readExt(4)
case 0xd7:
// fixext 8
return readExt(8)
case 0xd8:
// fixext 16
return readExt(16)
case 0xd9:
// str 8
value = src[position$1++];
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString8(value)
case 0xda:
// str 16
value = dataView.getUint16(position$1);
position$1 += 2;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString16(value)
case 0xdb:
// str 32
value = dataView.getUint32(position$1);
position$1 += 4;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString32(value)
case 0xdc:
// array 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readArray(value)
case 0xdd:
// array 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readArray(value)
case 0xde:
// map 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readMap(value)
case 0xdf:
// map 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readMap(value)
default: // negative int
if (token >= 0xe0)
return token - 0x100
if (token === undefined) {
let error = new Error('Unexpected end of MessagePack data');
error.incomplete = true;
throw error
}
throw new Error('Unknown MessagePack token ' + token)
}
}
}
const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/;
function createStructureReader(structure, firstId) {
function readObject() {
// This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function
if (readObject.count++ > inlineObjectReadThreshold) {
let optimizedReadObject;
try {
optimizedReadObject = structure.read = (new BlockedFunction ('r', 'return function(){return ' + (currentUnpackr.freezeData ? 'Object.freeze' : '') +
'({' + structure.map(key => key === '__proto__' ? '__proto_:r()' : validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '})}'))(read);
} catch(error) {
// in CF workers, the new BlockedFunction call could begin to fail at any point in time
inlineObjectReadThreshold = Infinity; // disable going forward
return readObject(); // recursively try again
}
structure.read0 = optimizedReadObject; // keep the un-wrapped body reader in sync
if (structure.highByte === 0)
structure.read = createSecondByteReader(firstId, structure.read);
return optimizedReadObject() // second byte is already read, if there is one so immediately read object
}
let object = {};
for (let i = 0, l = structure.length; i < l; i++) {
let key = structure[i];
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(object);
return object
}
readObject.count = 0;
// read0 is the un-wrapped body reader: it reads the record's values directly without
// consuming a leading high byte. recordDefinition uses it for the immediate read that follows
// a record definition (the high byte, if present, was already consumed). For highByte === 0
// structures the public reader is a second-byte reader (used by later references), but the
// definition read itself must not consume that byte.
structure.read0 = readObject;
if (structure.highByte === 0) {
return createSecondByteReader(firstId, readObject)
}
return readObject
}
const createSecondByteReader = (firstId, read0) => {
return function() {
let highByte = src[position$1++];
if (highByte === 0)
return read0()
let id = firstId < 32 ? -(firstId + (highByte << 5)) : firstId + (highByte << 5);
let structure = currentStructures[id] || loadStructures()[id];
if (!structure) {
throw new Error('Record id is not defined for ' + id)
}
if (!structure.read)
structure.read = createStructureReader(structure, firstId);
return structure.read()
}
};
function loadStructures() {
let loadedStructures = saveState(() => {
// save the state in case getStructures modifies our buffer
src = null;
return currentUnpackr.getStructures()
});
return currentStructures = currentUnpackr._mergeStructures(loadedStructures, currentStructures)
}
var readFixedString = readStringJS;
var readString8 = readStringJS;
var readString16 = readStringJS;
var readString32 = readStringJS;
let isNativeAccelerationEnabled = false;
function readStringJS(length) {
let result;
if (length < 16) {
if (result = shortStringInJS(length))
return result
}
if (length > 64 && decoder)
return decoder.decode(src.subarray(position$1, position$1 += length))
const end = position$1 + length;
const units = [];
result = '';
while (position$1 < end) {
const byte1 = src[position$1++];
if ((byte1 & 0x80) === 0) {
// 1 byte
units.push(byte1);
} else if ((byte1 & 0xe0) === 0xc0) {
// 2 bytes
const byte2 = src[position$1++] & 0x3f;
const codePoint = ((byte1 & 0x1f) << 6) | byte2;
// Reject overlong encoding: 2-byte sequences must encode values >= 0x80
if (codePoint < 0x80) {
units.push(0xFFFD); // replacement character
} else {
units.push(codePoint);
}
} else if ((byte1 & 0xf0) === 0xe0) {
// 3 bytes
const byte2 = src[position$1++] & 0x3f;
const byte3 = src[position$1++] & 0x3f;
const codePoint = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3;
// Reject overlong encoding: 3-byte sequences must encode values >= 0x800
// Also reject surrogates (0xD800-0xDFFF)
if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
units.push(0xFFFD); // replacement character
} else {
units.push(codePoint);
}
} else if ((byte1 & 0xf8) === 0xf0) {
// 4 bytes
const byte2 = src[position$1++] & 0x3f;
const byte3 = src[position$1++] & 0x3f;
const byte4 = src[position$1++] & 0x3f;
let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
// Reject overlong encoding: 4-byte sequences must encode values >= 0x10000
// Also reject values > 0x10FFFF (maximum valid Unicode)
if (unit < 0x10000 || unit > 0x10FFFF) {
units.push(0xFFFD); // replacement character
} else if (unit > 0xffff) {
unit -= 0x10000;
units.push(((unit >>> 10) & 0x3ff) | 0xd800);
unit = 0xdc00 | (unit & 0x3ff);
units.push(unit);
} else {
units.push(unit);
}
} else {
units.push(0xFFFD); // replacement character for invalid lead byte
}
if (units.length >= 0x1000) {
result += fromCharCode.apply(String, units);
units.length = 0;
}
}
if (units.length > 0) {
result += fromCharCode.apply(String, units);
}
return result
}
function readArray(length) {
let array = new Array(length);
for (let i = 0; i < length; i++) {
array[i] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
function readMap(length) {
if (currentUnpackr.mapsAsObjects) {
let object = {};
for (let i = 0; i < length; i++) {
let key = readKey();
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
return object
} else {
let map = new Map();
for (let i = 0; i < length; i++) {
map.set(read(), read());
}
return map
}
}
var fromCharCode = String.fromCharCode;
function longStringInJS(length) {
let start = position$1;
let bytes = new Array(length);
for (let i = 0; i < length; i++) {
const byte = src[position$1++];
if ((byte & 0x80) > 0) {
position$1 = start;
return
}
bytes[i] = byte;
}
return fromCharCode.apply(String, bytes)
}
function shortStringInJS(length) {
if (length < 4) {
if (length < 2) {
if (length === 0)
return ''
else {
let a = src[position$1++];
if ((a & 0x80) > 1) {
position$1 -= 1;
return
}
return fromCharCode(a)
}
} else {
let a = src[position$1++];
let b = src[position$1++];
if ((a & 0x80) > 0 || (b & 0x80) > 0) {
position$1 -= 2;
return
}
if (length < 3)
return fromCharCode(a, b)
let c = src[position$1++];
if ((c & 0x80) > 0) {
position$1 -= 3;
return
}
return fromCharCode(a, b, c)
}
} else {
let a = src[position$1++];
let b = src[position$1++];
let c = src[position$1++];
let d = src[position$1++];
if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
position$1 -= 4;
return
}
if (length < 6) {
if (length === 4)
return fromCharCode(a, b, c, d)
else {
let e = src[position$1++];
if ((e & 0x80) > 0) {
position$1 -= 5;
return
}
return fromCharCode(a, b, c, d, e)
}
} else if (length < 8) {
let e = src[position$1++];
let f = src[position$1++];
if ((e & 0x80) > 0 || (f & 0x80) > 0) {
position$1 -= 6;
return
}
if (length < 7)
return fromCharCode(a, b, c, d, e, f)
let g = src[position$1++];
if ((g & 0x80) > 0) {
position$1 -= 7;
return
}
return fromCharCode(a, b, c, d, e, f, g)
} else {
let e = src[position$1++];
let f = src[position$1++];
let g = src[position$1++];
let h = src[position$1++];
if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) {
position$1 -= 8;
return
}
if (length < 10) {
if (length === 8)
return fromCharCode(a, b, c, d, e, f, g, h)
else {
let i = src[position$1++];
if ((i & 0x80) > 0) {
position$1 -= 9;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i)
}
} else if (length < 12) {
let i = src[position$1++];
let j = src[position$1++];
if ((i & 0x80) > 0 || (j & 0x80) > 0) {
position$1 -= 10;
return
}
if (length < 11)
return fromCharCode(a, b, c, d, e, f, g, h, i, j)
let k = src[position$1++];
if ((k & 0x80) > 0) {
position$1 -= 11;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k)
} else {
let i = src[position$1++];
let j = src[position$1++];
let k = src[position$1++];
let l = src[position$1++];
if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) {
position$1 -= 12;
return
}
if (length < 14) {
if (length === 12)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l)
else {
let m = src[position$1++];
if ((m & 0x80) > 0) {
position$1 -= 13;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m)
}
} else {
let m = src[position$1++];
let n = src[position$1++];
if ((m & 0x80) > 0 || (n & 0x80) > 0) {
position$1 -= 14;
return
}
if (length < 15)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n)
let o = src[position$1++];
if ((o & 0x80) > 0) {
position$1 -= 15;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
}
}
}
}
}
function readOnlyJSString() {
let token = src[position$1++];
let length;
if (token < 0xc0) {
// fixstr
length = token - 0xa0;
} else {
switch(token) {
case 0xd9:
// str 8
length = src[position$1++];
break
case 0xda:
// str 16
length = dataView.getUint16(position$1);
position$1 += 2;
break
case 0xdb:
// str 32
length = dataView.getUint32(position$1);
position$1 += 4;
break
default:
throw new Error('Expected string')
}
}
return readStringJS(length)
}
function readBin(length) {
return currentUnpackr.copyBuffers ?
// specifically use the copying slice (not the node one)
Uint8Array.prototype.slice.call(src, position$1, position$1 += length) :
src.subarray(position$1, position$1 += length)
}
function readExt(length) {
let type = src[position$1++];
if (currentExtensions[type]) {
let end;
return currentExtensions[type](src.subarray(position$1, end = (position$1 += length)), (readPosition) => {
position$1 = readPosition;
try {
return read();
} finally {
position$1 = end;
}
})
}
else
throw new Error('Unknown extension type ' + type)
}
var keyCache = new Array(4096);
function readKey() {
let length = src[position$1++];
if (length >= 0xa0 && length < 0xc0) {
// fixstr, potentially use key cache
length = length - 0xa0;
if (srcStringEnd >= position$1) // if it has been extracted, must use it (and faster anyway)
return srcString.slice(position$1 - srcStringStart, (position$1 += length) - srcStringStart)
else if (!(srcStringEnd == 0 && srcEnd < 180))
return readFixedString(length)
} else { // not cacheable, go back and do a standard read
position$1--;
return asSafeString(read())
}
let key = ((length << 5) ^ (length > 1 ? dataView.getUint16(position$1) : length > 0 ? src[position$1] : 0)) & 0xfff;
let entry = keyCache[key];
let checkPosition = position$1;
let end = position$1 + length - 3;
let chunk;
let i = 0;
if (entry && entry.bytes == length) {
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition);
if (chunk != entry[i++]) {
checkPosition = 0x70000000;
break
}
checkPosition += 4;
}
end += 3;
while (checkPosition < end) {
chunk = src[checkPosition++];
if (chunk != entry[i++]) {
checkPosition = 0x70000000;
break
}
}
if (checkPosition === end) {
position$1 = checkPosition;
return entry.string
}
end -= 3;
checkPosition = position$1;
}
entry = [];
keyCache[key] = entry;
entry.bytes = length;
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition);
entry.push(chunk);
checkPosition += 4;
}
end += 3;
while (checkPosition < end) {
chunk = src[checkPosition++];
entry.push(chunk);
}
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length);
if (string != null)
return entry.string = string
return entry.string = readFixedString(length)
}
function asSafeString(property) {
// protect against expensive (DoS) string conversions
if (typeof property === 'string') return property;
if (typeof property === 'number' || typeof property === 'boolean' || typeof property === 'bigint') return property.toString();
if (property == null) return property + '';
if (currentUnpackr.allowArraysInMapKeys && Array.isArray(property) && property.flat().every(item => ['string', 'number', 'boolean', 'bigint'].includes(typeof item))) {
return property.flat().toString();
}
throw new Error(`Invalid property type for record: ${typeof property}`);
}
// the registration of the record definition extension (as "r")
const recordDefinition = (id, highByte) => {
let structure = read().map(asSafeString); // ensure that all keys are strings and
// that the array is mutable
let firstByte = id;
if (highByte !== undefined) {
id = id < 32 ? -((highByte << 5) + id) : ((highByte << 5) + id);
structure.highByte = highByte;
}
let existingStructure = currentStructures[id];
// If it is a shared structure, we need to restore any changes after reading.
// Also in sequential mode, we may get incomplete reads and thus errors, and we need to restore
// to the state prior to an incomplete read in order to properly resume.
if (existingStructure && (existingStructure.isShared || sequentialMode)) {
(currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure;
}
currentStructures[id] = structure;
structure.read = createStructureReader(structure, firstByte);
// The high byte (if any) was already consumed as the `highByte` argument above, so read the
// record body directly. Going through structure.read (a second-byte reader when highByte === 0)
// would misinterpret the first value byte as a high byte — corrupting two-byte own-record
// definitions (0xd5 0x72 ...). createStructureReader stashes the un-wrapped body reader on
// structure.read0 precisely for this immediate post-definition read.
return (structure.read0 || structure.read)()
};
currentExtensions[0] = () => {}; // notepack defines extension 0 to mean undefined, so use that as the default here
currentExtensions[0].noBuffer = true;
currentExtensions[0x42] = data => {
let headLength = (data.byteLength % 8) || 8;
let head = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0]);
for (let i = 1; i < headLength; i++) {
head <<= BigInt(8);
head += BigInt(data[i]);
}
if (data.byteLength !== headLength) {
let view = new DataView(data.buffer, data.byteOffset, data.byteLength);
let decode = (start, end) => {
let length = end - start;
if (length <= 40) {
let out = view.getBigUint64(start);
for (let i = start + 8; i < end; i += 8) {
out <<= BigInt(64);
out |= view.getBigUint64(i);
}
return out
}
// if (length === 8) return view.getBigUint64(start)
let middle = start + (length >> 4 << 3);
let left = decode(start, middle);
let right = decode(middle, end);
return (left << BigInt((end - middle) * 8)) | right
};
head = (head << BigInt((view.byteLength - headLength) * 8)) | decode(headLength, view.byteLength);
}
return head
};
let errors = {
Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError: typeof AggregateError === 'function' ? AggregateError : null,
};
currentExtensions[0x65] = () => {
let data = read();
if (!errors[data[0]]) {
let error = Error(data[1], { cause: data[2] });
error.name = data[0];
return error
}
return errors[data[0]](data[1], { cause: data[2] })
};
currentExtensions[0x69] = (data) => {
// id extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position$1 - 4);
if (!referenceMap)
referenceMap = new Map();
let token = src[position$1];
let target;
// TODO: handle any other types that can cycle and make the code more robust if there are other extensions
if (token >= 0x90 && token < 0xa0 || token == 0xdc || token == 0xdd)
target = [];
else if (token >= 0x80 && token < 0x90 || token == 0xde || token == 0xdf)
target = new Map();
else if ((token >= 0xc7 && token <= 0xc9 || token >= 0xd4 && token <= 0xd8) && src[position$1 + 1] === 0x73)
target = new Set();
else
target = {};
let refEntry = { target }; // a placeholder object
referenceMap.set(id, refEntry);
let targetProperties = read(); // read the next value as the target object to id
if (!refEntry.used) {
// no cycle, can just use the returned read object
return refEntry.target = targetProperties // replace the placeholder with the real one
} else {
// there is a cycle, so we have to assign properties to original target
Object.assign(target, targetProperties);
}
// copy over map/set entries if we're able to
if (target instanceof Map)
for (let [k, v] of targetProperties.entries()) target.set(k, v);
if (target instanceof Set)
for (let i of Array.from(targetProperties)) target.add(i);
return target
};
currentExtensions[0x70] = (data) => {
// pointer extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position$1 - 4);
let refEntry = referenceMap.get(id);
refEntry.used = true;
return refEntry.target
};
currentExtensions[0x73] = () => new Set(read());
const typedArrays = ['Int8','Uint8','Uint8Clamped','Int16','Uint16','Int32','Uint32','Float32','Float64','BigInt64','BigUint64'].map(type => type + 'Array');
let glbl = typeof globalThis === 'object' ? globalThis : window;
currentExtensions[0x74] = (data) => {
let typeCode = data[0];
// we always have to slice to get a new ArrayBuffer that is aligned
let buffer = Uint8Array.prototype.slice.call(data, 1).buffer;
let typedArrayName = typedArrays[typeCode];
if (!typedArrayName) {
if (typeCode === 16) return buffer
if (typeCode === 17) return new DataView(buffer)
throw new Error('Could not find typed array for code ' + typeCode)
}
return new glbl[typedArrayName](buffer)
};
currentExtensions[0x78] = () => {
let data = read();
return new RegExp(data[0], data[1])
};
const TEMP_BUNDLE = [];
currentExtensions[0x62] = (data) => {
let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3];
let dataPosition = position$1;
position$1 += dataSize - data.length;
bundledStrings$1 = TEMP_BUNDLE;
bundledStrings$1 = [readOnlyJSString(), readOnlyJSString()];
bundledStrings$1.position0 = 0;
bundledStrings$1.position1 = 0;
bundledStrings$1.postBundlePosition = position$1;
position$1 = dataPosition;
return read()
};
currentExtensions[0xff] = (data) => {
// 32-bit date extension
if (data.length == 4)
return new Date((data[0] * 0x1000000 + (data[1] << 16) + (data[2] << 8) + data[3]) * 1000)
else if (data.length == 8)
return new Date(
((data[0] << 22) + (data[1] << 14) + (data[2] << 6) + (data[3] >> 2)) / 1000000 +
((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000)
else if (data.length == 12)
return new Date(
((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
(((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000)
else
return new Date('invalid')
};
// registration of bulk record definition?
// currentExtensions[0x52] = () =>
function saveState(callback) {
let savedSrcEnd = srcEnd;
let savedPosition = position$1;
let savedSrcStringStart = srcStringStart;
let savedSrcStringEnd = srcStringEnd;
let savedSrcString = srcString;
let savedReferenceMap = referenceMap;
let savedBundledStrings = bundledStrings$1;
// TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
let savedStructures = currentStructures;
let savedStructuresContents = currentStructures.slice(0, currentStructures.length);
let savedPackr = currentUnpackr;
let savedSequentialMode = sequentialMode;
let value = callback();
srcEnd = savedSrcEnd;
position$1 = savedPosition;
srcStringStart = savedSrcStringStart;
srcStringEnd = savedSrcStringEnd;
srcString = savedSrcString;
referenceMap = savedReferenceMap;
bundledStrings$1 = savedBundledStrings;
src = savedSrc;
sequentialMode = savedSequentialMode;
currentStructures = savedStructures;
currentStructures.splice(0, currentStructures.length, ...savedStructuresContents);
currentUnpackr = savedPackr;
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
return value
}
function clearSource() {
src = null;
referenceMap = null;
currentStructures = null;
}
function addExtension$1(extension) {
if (extension.unpack)
currentExtensions[extension.type] = extension.unpack;
else
currentExtensions[extension.type] = extension;
}
const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding
for (let i = 0; i < 256; i++) {
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103));
}
const Decoder = Unpackr;
var defaultUnpackr = new Unpackr({ useRecords: false });
const unpack = defaultUnpackr.unpack;
const unpackMultiple = defaultUnpackr.unpackMultiple;
const decode = defaultUnpackr.unpack;
const FLOAT32_OPTIONS = {
NEVER: 0,
ALWAYS: 1,
DECIMAL_ROUND: 3,
DECIMAL_FIT: 4
};
let f32Array = new Float32Array(1);
let u8Array = new Uint8Array(f32Array.buffer, 0, 4);
function roundFloat32(float32Number) {
f32Array[0] = float32Number;
let multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)];
return ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
let textEncoder;
try {
textEncoder = new TextEncoder();
} catch (error) {}
let extensions, extensionClasses;
const hasNodeBuffer = typeof Buffer !== 'undefined';
const ByteArrayAllocate = hasNodeBuffer ?
function(length) { return Buffer.allocUnsafeSlow(length) } : Uint8Array;
const ByteArray = hasNodeBuffer ? Buffer : Uint8Array;
const MAX_BUFFER_SIZE = hasNodeBuffer ? 0x100000000 : 0x7fd00000;
let target, keysTarget;
let targetView;
let position = 0;
let safeEnd;
let bundledStrings = null;
let writeStructSlots;
const MAX_BUNDLE_SIZE = 0x5500; // maximum characters such that the encoded bytes fits in 16 bits.
const hasNonLatin = /[\u0080-\uFFFF]/;
const RECORD_SYMBOL = Symbol('record-id');
class Packr extends Unpackr {
constructor(options) {
super(options);
this.offset = 0;
let start;
let hasSharedUpdate;
let structures;
let referenceMap;
let encodeUtf8 = ByteArray.prototype.utf8Write ? function(string, position) {
return target.utf8Write(string, position, target.byteLength - position)
} : (textEncoder && textEncoder.encodeInto) ?
function(string, position) {
return textEncoder.encodeInto(string, target.subarray(position)).written
} : false;
let packr = this;
if (!options)
options = {};
let isSequential = options && options.sequential;
let hasSharedStructures = options.structures || options.saveStructures;
let maxSharedStructures = options.maxSharedStructures;
if (maxSharedStructures == null)
maxSharedStructures = hasSharedStructures ? 32 : 0;
if (maxSharedStructures > 8160)
throw new Error('Maximum maxSharedStructure is 8160')
if (options.structuredClone && options.moreTypes == undefined) {
this.moreTypes = true;
}
let maxOwnStructures = options.maxOwnStructures;
if (maxOwnStructures == null)
maxOwnStructures = hasSharedStructures ? 32 : 64;
if (!this.structures && options.useRecords != false)
this.structures = [];
// two byte record ids for shared structures
let useTwoByteRecords = maxSharedStructures > 32 || (maxOwnStructures + maxSharedStructures > 64);
let sharedLimitId = maxSharedStructures + 0x40;
let maxStructureId = maxSharedStructures + maxOwnStructures + 0x40;
if (maxStructureId > 8256) {
throw new Error('Maximum maxSharedStructure + maxOwnStructure is 8192')
}
let recordIdsToRemove = [];
let transitionsCount = 0;
let serializationsSinceTransitionRebuild = 0;
this.pack = this.encode = function(value, encodeOptions) {
if (!target) {
target = new ByteArrayAllocate(8192);
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, 8192));
position = 0;
}
safeEnd = target.length - 10;
if (safeEnd - position < 0x800) {
// don't start too close to the end,
target = new ByteArrayAllocate(target.length);
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, target.length));
safeEnd = target.length - 10;
position = 0;
} else
position = (position + 7) & 0x7ffffff8; // Word align to make any future copying of this buffer faster
start = position;
if (encodeOptions & RESERVE_START_SPACE) position += (encodeOptions & 0xff);
referenceMap = packr.structuredClone ? new Map() : null;
if (packr.bundleStrings && typeof value !== 'string') {
bundledStrings = [];
bundledStrings.size = Infinity; // force a new bundle start on first string
} else
bundledStrings = null;
structures = packr.structures;
if (structures) {
if (structures.uninitialized)
structures = packr._mergeStructures(packr.getStructures());
let sharedLength = structures.sharedLength || 0;
if (sharedLength > maxSharedStructures) {
//if (maxSharedStructures <= 32 && structures.sharedLength > 32) // TODO: could support this, but would need to update the limit ids
throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' + structures.sharedLength)
}
if (!structures.transitions) {
// rebuild our structure transitions
structures.transitions = Object.create(null);
for (let i = 0; i < sharedLength; i++) {
let keys = structures[i];
if (!keys)
continue
let nextTransition, transition = structures.transitions;
for (let j = 0, l = keys.length; j < l; j++) {
let key = keys[j];
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
}
transition = nextTransition;
}
transition[RECORD_SYMBOL] = i + 0x40;
}
this.lastNamedStructuresLength = sharedLength;
}
if (!isSequential) {
structures.nextId = sharedLength + 0x40;
}
}
if (hasSharedUpdate)
hasSharedUpdate = false;
let encodingError;
try {
// readOnlyStructures: skip the random-access struct write path so NO new struct is
// minted. randomAccessStructure stays true (the struct READ path and the struct-safe
// integer boundary are preserved, so existing struct data still decodes), but objects
// fall through to the normal pack()->writeObject->writeRecord path and are written as
// classic shared-structure records (byte range 0x40-0x7f, disjoint from struct headers
// at 0x20-0x3f) — the bounded, width-agnostic encoding used before struct mode.
if (packr.randomAccessStructure && !packr.readOnlyStructures && value && typeof value === 'object') {
if (value.constructor === Object) writeStruct(value); // simple object
else if (value.constructor !== Map && !Array.isArray(value) && !extensionClasses.some(extClass => value instanceof extClass)) {
// allow user classes, if they don't need special handling (but do use toJSON if available)
writeStruct(value.toJSON ? value.toJSON() : value);
} else pack(value);
} else
pack(value);
let lastBundle = bundledStrings;
if (bundledStrings)
writeBundles(start, pack, 0);
if (referenceMap && referenceMap.idsToInsert) {
let idsToInsert = referenceMap.idsToInsert.sort((a, b) => a.offset > b.offset ? 1 : -1);
let i = idsToInsert.length;
let incrementPosition = -1;
while (lastBundle && i > 0) {
let insertionPoint = idsToInsert[--i].offset + start;
if (insertionPoint < (lastBundle.stringsPosition + start) && incrementPosition === -1)
incrementPosition = 0;
if (insertionPoint > (lastBundle.position + start)) {
if (incrementPosition >= 0)
incrementPosition += 6;
} else {
if (incrementPosition >= 0) {
// update the bundle reference now
targetView.setUint32(lastBundle.position + start,
targetView.getUint32(lastBundle.position + start) + incrementPosition);
incrementPosition = -1; // reset
}
lastBundle = lastBundle.previous;
i++;
}
}
if (incrementPosition >= 0 && lastBundle) {
// update the bundle reference now
targetView.setUint32(lastBundle.position + start,
targetView.getUint32(lastBundle.position + start) + incrementPosition);
}
position += idsToInsert.length * 6;
if (position > safeEnd)
makeRoom(position);
packr.offset = position;
let serialized = insertIds(target.subarray(start, position), idsToInsert);
referenceMap = null;
return serialized
}
packr.offset = position; // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially
if (encodeOptions & REUSE_BUFFER_MODE) {
target.start = start;
target.end = position;
return target
}
return target.subarray(start, position) // position can change if we call pack again in saveStructures, so we get the buffer now
} catch(error) {
encodingError = error;
throw error;
} finally {
if (structures) {
resetStructures();
if (hasSharedUpdate && packr.saveStructures) {
let sharedLength = structures.sharedLength || 0;
// we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save
let returnBuffer = target.subarray(start, position);
let newSharedData = prepareStructures(structures, packr);
if (!encodingError) { // TODO: If there is an encoding error, should make the structures as uninitialized so they get rebuilt next time
if (packr.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
// The save was declined (a concurrent writer updated the shared structures,
// or the store transaction did not durably commit). Our in-memory
// structures + transition trie may now reference record ids that were
// never persisted; re-packing as-is would re-emit the same record pointing
// at an unpersisted structure (-> "Record id is not defined" on decode).
// Mark structures uninitialized so the re-pack reloads durable structures
// via getStructures, rebuilds the transition trie, and re-mints + re-saves.
structures.uninitialized = true;
return packr.pack(value, encodeOptions)
}
packr.lastNamedStructuresLength = sharedLength;
// don't keep large buffers around
if (target.length > 0x40000000) target = null;
return returnBuffer
}
}
}
// don't keep large buffers around, they take too much memory and cause problems (limit at 1GB)
if (target.length > 0x40000000) target = null;
if (encodeOptions & RESET_BUFFER_MODE)
position = start;
}
};
const resetStructures = () => {
if (serializationsSinceTransitionRebuild < 10)
serializationsSinceTransitionRebuild++;
let sharedLength = structures.sharedLength || 0;
if (structures.length > sharedLength && !isSequential)
structures.length = sharedLength;
if (transitionsCount > 10000) {
// force a rebuild occasionally after a lot of transitions so it can get cleaned up
structures.transitions = null;
serializationsSinceTransitionRebuild = 0;
transitionsCount = 0;
if (recordIdsToRemove.length > 0)
recordIdsToRemove = [];
} else if (recordIdsToRemove.length > 0 && !isSequential) {
for (let i = 0, l = recordIdsToRemove.length; i < l; i++) {
recordIdsToRemove[i][RECORD_SYMBOL] = 0;
}
recordIdsToRemove = [];
}
};
const packArray = (value) => {
var length = value.length;
if (length < 0x10) {
target[position++] = 0x90 | length;
} else if (length < 0x10000) {
target[position++] = 0xdc;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdd;
targetView.setUint32(position, length);
position += 4;
}
for (let i = 0; i < length; i++) {
pack(value[i]);
}
};
const pack = (value) => {
if (position > safeEnd)
target = makeRoom(position);
var type = typeof value;
var length;
if (type === 'string') {
let strLength = value.length;
if (bundledStrings && strLength >= 4 && strLength < 0x1000) {
if ((bundledStrings.size += strLength) > MAX_BUNDLE_SIZE) {
let extStart;
let maxBytes = (bundledStrings[0] ? bundledStrings[0].length * 3 + bundledStrings[1].length : 0) + 10;
if (position + maxBytes > safeEnd)
target = makeRoom(position + maxBytes);
let lastBundle;
if (bundledStrings.position) { // here we use the 0x62 extension to write the last bundle and reserve space for the reference pointer to the next/current bundle
lastBundle = bundledStrings;
target[position] = 0xc8; // ext 16
position += 3; // reserve for the writing bundle size
target[position++] = 0x62; // 'b'
extStart = position - start;
position += 4; // reserve for writing bundle reference
writeBundles(start, pack, 0); // write the last bundles
targetView.setUint16(extStart + start - 3, position - start - extStart);
} else { // here we use the 0x62 extension just to reserve the space for the reference pointer to the bundle (will be updated once the bundle is written)
target[position++] = 0xd6; // fixext 4
target[position++] = 0x62; // 'b'
extStart = position - start;
position += 4; // reserve for writing bundle reference
}
bundledStrings = ['', '']; // create new ones
bundledStrings.previous = lastBundle;
bundledStrings.size = 0;
bundledStrings.position = extStart;
}
let twoByte = hasNonLatin.test(value);
bundledStrings[twoByte ? 0 : 1] += value;
target[position++] = 0xc1;
pack(twoByte ? -strLength : strLength);
return
}
let headerSize;
// first we estimate the header size, so we can write to the correct location
if (strLength < 0x20) {
headerSize = 1;
} else if (strLength < 0x100) {
headerSize = 2;
} else if (strLength < 0x10000) {
headerSize = 3;
} else {
headerSize = 5;
}
let maxBytes = strLength * 3;
if (position + maxBytes > safeEnd)
target = makeRoom(position + maxBytes);
if (strLength < 0x40 || !encodeUtf8) {
let i, c1, c2, strPosition = position + headerSize;
for (i = 0; i < strLength; i++) {
c1 = value.charCodeAt(i);
if (c1 < 0x80) {
target[strPosition++] = c1;
} else if (c1 < 0x800) {
target[strPosition++] = c1 >> 6 | 0xc0;
target[strPosition++] = c1 & 0x3f | 0x80;
} else if (
(c1 & 0xfc00) === 0xd800 &&
((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
) {
c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
i++;
target[strPosition++] = c1 >> 18 | 0xf0;
target[strPosition++] = c1 >> 12 & 0x3f | 0x80;
target[strPosition++] = c1 >> 6 & 0x3f | 0x80;
target[strPosition++] = c1 & 0x3f | 0x80;
} else {
target[strPosition++] = c1 >> 12 | 0xe0;
target[strPosition++] = c1 >> 6 & 0x3f | 0x80;
target[strPosition++] = c1 & 0x3f | 0x80;
}
}
length = strPosition - position - headerSize;
} else {
length = encodeUtf8(value, position + headerSize);
}
if (length < 0x20) {
target[position++] = 0xa0 | length;
} else if (length < 0x100) {
if (headerSize < 2) {
target.copyWithin(position + 2, position + 1, position + 1 + length);
}
target[position++] = 0xd9;
target[position++] = length;
} else if (length < 0x10000) {
if (headerSize < 3) {
target.copyWithin(position + 3, position + 2, position + 2 + length);
}
target[position++] = 0xda;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
if (headerSize < 5) {
target.copyWithin(position + 5, position + 3, position + 3 + length);
}
target[position++] = 0xdb;
targetView.setUint32(position, length);
position += 4;
}
position += length;
} else if (type === 'number') {
if (value >>> 0 === value) {// positive integer, 32-bit or less
// positive uint
if (value < 0x20 || (value < 0x80 && this.useRecords === false) || (value < 0x40 && !this.randomAccessStructure)) {
target[position++] = value;
} else if (value < 0x100) {
target[position++] = 0xcc;
target[position++] = value;
} else if (value < 0x10000) {
target[position++] = 0xcd;
target[position++] = value >> 8;
target[position++] = value & 0xff;
} else {
target[position++] = 0xce;
targetView.setUint32(position, value);
position += 4;
}
} else if (value >> 0 === value) { // negative integer
if (value >= -0x20) {
target[position++] = 0x100 + value;
} else if (value >= -0x80) {
target[position++] = 0xd0;
target[position++] = value + 0x100;
} else if (value >= -0x8000) {
target[position++] = 0xd1;
targetView.setInt16(position, value);
position += 2;
} else {
target[position++] = 0xd2;
targetView.setInt32(position, value);
position += 4;
}
} else {
let useFloat32;
if ((useFloat32 = this.useFloat32) > 0 && value < 0x100000000 && value >= -0x80000000) {
target[position++] = 0xca;
targetView.setFloat32(position, value);
let xShifted;
if (useFloat32 < 4 ||
// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
((xShifted = value * mult10[((target[position] & 0x7f) << 1) | (target[position + 1] >> 7)]) >> 0) === xShifted) {
position += 4;
return
} else
position--; // move back into position for writing a double
}
target[position++] = 0xcb;
targetView.setFloat64(position, value);
position += 8;
}
} else if (type === 'object' || type === 'function') {
if (!value)
target[position++] = 0xc0;
else {
if (referenceMap) {
let referee = referenceMap.get(value);
if (referee) {
if (!referee.id) {
let idsToInsert = referenceMap.idsToInsert || (referenceMap.idsToInsert = []);
referee.id = idsToInsert.push(referee);
}
target[position++] = 0xd6; // fixext 4
target[position++] = 0x70; // "p" for pointer
targetView.setUint32(position, referee.id);
position += 4;
return
} else
referenceMap.set(value, { offset: position - start });
}
let constructor = value.constructor;
if (constructor === Object) {
writeObject(value);
} else if (constructor === Array) {
packArray(value);
} else if (constructor === Map) {
if (this.mapAsEmptyObject) target[position++] = 0x80;
else {
length = value.size;
if (length < 0x10) {
target[position++] = 0x80 | length;
} else if (length < 0x10000) {
target[position++] = 0xde;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdf;
targetView.setUint32(position, length);
position += 4;
}
for (let [key, entryValue] of value) {
pack(key);
pack(entryValue);
}
}
} else {
for (let i = 0, l = extensions.length; i < l; i++) {
let extensionClass = extensionClasses[i];
if (value instanceof extensionClass) {
let extension = extensions[i];
if (extension.write) {
if (extension.type) {
target[position++] = 0xd4; // one byte "tag" extension
target[position++] = extension.type;
target[position++] = 0;
}
let writeResult = extension.write.call(this, value);
if (writeResult === value) { // avoid infinite recursion
if (Array.isArray(value)) {
packArray(value);
} else {
writeObject(value);
}
} else {
pack(writeResult);
}
return
}
let currentTarget = target;
let currentTargetView = targetView;
let currentPosition = position;
target = null;
let result;
try {
result = extension.pack.call(this, value, (size) => {
// restore target and use it
target = currentTarget;
currentTarget = null;
position += size;
if (position > safeEnd)
makeRoom(position);
return {
target, targetView, position: position - size
}
}, pack);
} finally {
// restore current target information (unless already restored)
if (currentTarget) {
target = currentTarget;
targetView = currentTargetView;
position = currentPosition;
safeEnd = target.length - 10;
}
}
if (result) {
if (result.length + position > safeEnd)
makeRoom(result.length + position);
position = writeExtensionData(result, target, position, extension.type);
}
return
}
}
// check isArray after extensions, because extensions can extend Array
if (Array.isArray(value)) {
packArray(value);
} else {
// use this as an alternate mechanism for expressing how to serialize
if (value.toJSON) {
const json = value.toJSON();
// if for some reason value.toJSON returns itself it'll loop forever
if (json !== value)
return pack(json)
}
// if there is a writeFunction, use it, otherwise just encode as undefined
if (type === 'function')
return pack(this.writeFunction && this.writeFunction(value));
// no extension found, write as plain object
writeObject(value);
}
}
}
} else if (type === 'boolean') {
target[position++] = value ? 0xc3 : 0xc2;
} else if (type === 'bigint') {
if (value < 0x8000000000000000 && value >= -0x8000000000000000) {
// use a signed int as long as it fits
target[position++] = 0xd3;
targetView.setBigInt64(position, value);
} else if (value < 0x10000000000000000 && value > 0) {
// if we can fit an unsigned int, use that
target[position++] = 0xcf;
targetView.setBigUint64(position, value);
} else {
// overflow
if (this.largeBigIntToFloat) {
target[position++] = 0xcb;
targetView.setFloat64(position, Number(value));
} else if (this.largeBigIntToString) {
return pack(value.toString());
} else if (this.useBigIntExtension || this.moreTypes) {
let empty = value < 0 ? BigInt(-1) : BigInt(0);
let array;
if (value >> BigInt(0x10000) === empty) {
let mask = BigInt(0x10000000000000000) - BigInt(1); // literal would overflow
let chunks = [];
while (true) {
chunks.push(value & mask);
if ((value >> BigInt(63)) === empty) break
value >>= BigInt(64);
}
array = new Uint8Array(new BigUint64Array(chunks).buffer);
array.reverse();
} else {
let invert = value < 0;
let string = (invert ? ~value : value).toString(16);
if (string.length % 2) {
string = '0' + string;
} else if (parseInt(string.charAt(0), 16) >= 8) {
string = '00' + string;
}
if (hasNodeBuffer) {
array = Buffer.from(string, 'hex');
} else {
array = new Uint8Array(string.length / 2);
for (let i = 0; i < array.length; i++) {
array[i] = parseInt(string.slice(i * 2, i * 2 + 2), 16);
}
}
if (invert) {
for (let i = 0; i < array.length; i++) array[i] = ~array[i];
}
}
if (array.length + position > safeEnd)
makeRoom(array.length + position);
position = writeExtensionData(array, target, position, 0x42);
return
} else {
throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, use' +
' useBigIntExtension, or set largeBigIntToFloat to convert to float-64, or set' +
' largeBigIntToString to convert to string')
}
}
position += 8;
} else if (type === 'undefined') {
if (this.encodeUndefinedAsNil)
target[position++] = 0xc0;
else {
target[position++] = 0xd4; // a number of implementations use fixext1 with type 0, data 0 to denote undefined, so we follow suite
target[position++] = 0;
target[position++] = 0;
}
} else {
throw new Error('Unknown type: ' + type)
}
};
const writePlainObject = (this.variableMapSize || this.coercibleKeyAsNumber || this.skipValues) ? (object) => {
// this method is slightly slower, but generates "preferred serialization" (optimally small for smaller objects)
let keys;
if (this.skipValues) {
keys = [];
for (let key in object) {
if ((typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) &&
!this.skipValues.includes(object[key]))
keys.push(key);
}
} else {
keys = Object.keys(object);
}
let length = keys.length;
if (length < 0x10) {
target[position++] = 0x80 | length;
} else if (length < 0x10000) {
target[position++] = 0xde;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdf;
targetView.setUint32(position, length);
position += 4;
}
let key;
if (this.coercibleKeyAsNumber) {
for (let i = 0; i < length; i++) {
key = keys[i];
let num = Number(key);
pack(isNaN(num) ? key : num);
pack(object[key]);
}
} else {
for (let i = 0; i < length; i++) {
pack(key = keys[i]);
pack(object[key]);
}
}
} :
(object) => {
target[position++] = 0xde; // always using map 16, so we can preallocate and set the length afterwards
let objectOffset = position - start;
position += 2;
let size = 0;
for (let key in object) {
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
pack(key);
pack(object[key]);
size++;
}
}
if (size > 0xffff) {
throw new Error('Object is too large to serialize with fast 16-bit map size,' +
' use the "variableMapSize" option to serialize this object');
}
target[objectOffset++ + start] = size >> 8;
target[objectOffset + start] = size & 0xff;
};
const writeRecord = this.useRecords === false ? writePlainObject :
(options.progressiveRecords && !useTwoByteRecords) ? // this is about 2% faster for highly stable structures, since it only requires one for-in loop (but much more expensive when new structure needs to be written)
(object) => {
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
let objectOffset = position++ - start;
let wroteKeys;
for (let key in object) {
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
nextTransition = transition[key];
if (nextTransition)
transition = nextTransition;
else {
// record doesn't exist, create full new record and insert it
let keys = Object.keys(object);
let lastTransition = transition;
transition = structures.transitions;
let newTransitions = 0;
for (let i = 0, l = keys.length; i < l; i++) {
let key = keys[i];
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
newTransitions++;
}
transition = nextTransition;
}
if (objectOffset + start + 1 == position) {
// first key, so we don't need to insert, we can just write record directly
position--;
newRecord(transition, keys, newTransitions);
} else // otherwise we need to insert the record, moving existing data after the record
insertNewRecord(transition, keys, objectOffset, newTransitions);
wroteKeys = true;
transition = lastTransition[key];
}
pack(object[key]);
}
}
if (!wroteKeys) {
let recordId = transition[RECORD_SYMBOL];
if (recordId)
target[objectOffset + start] = recordId;
else
insertNewRecord(transition, Object.keys(object), objectOffset, 0);
}
} :
(object) => {
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
let newTransitions = 0;
for (let key in object) if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
newTransitions++;
}
transition = nextTransition;
}
let recordId = transition[RECORD_SYMBOL];
if (recordId) {
if (recordId >= 0x60 && useTwoByteRecords) {
target[position++] = ((recordId -= 0x60) & 0x1f) + 0x60;
target[position++] = recordId >> 5;
} else
target[position++] = recordId;
} else {
newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions);
}
// now write the values
for (let key in object)
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
pack(object[key]);
}
};
// create reference to useRecords if useRecords is a function
const checkUseRecords = typeof this.useRecords == 'function' && this.useRecords;
const writeObject = checkUseRecords ? (object) => {
checkUseRecords(object) ? writeRecord(object) : writePlainObject(object);
} : writeRecord;
const makeRoom = (end) => {
let newSize;
if (end > 0x1000000) {
// special handling for really large buffers
if ((end - start) > MAX_BUFFER_SIZE)
throw new Error('Packed buffer would be larger than maximum buffer size')
newSize = Math.min(MAX_BUFFER_SIZE,
Math.round(Math.max((end - start) * (end > 0x4000000 ? 1.25 : 2), 0x400000) / 0x1000) * 0x1000);
} else // faster handling for smaller buffers
newSize = ((Math.max((end - start) << 2, target.length - 1) >> 12) + 1) << 12;
let newBuffer = new ByteArrayAllocate(newSize);
targetView = newBuffer.dataView || (newBuffer.dataView = new DataView(newBuffer.buffer, 0, newSize));
end = Math.min(end, target.length);
if (target.copy)
target.copy(newBuffer, 0, start, end);
else
newBuffer.set(target.slice(start, end));
position -= start;
start = 0;
safeEnd = newBuffer.length - 10;
return target = newBuffer
};
const newRecord = (transition, keys, newTransitions) => {
let recordId = structures.nextId;
if (!recordId)
recordId = 0x40;
if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
recordId = structures.nextOwnId;
if (!(recordId < maxStructureId))
recordId = sharedLimitId;
structures.nextOwnId = recordId + 1;
} else {
if (recordId >= maxStructureId)// cycle back around
recordId = sharedLimitId;
structures.nextId = recordId + 1;
}
let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1;
transition[RECORD_SYMBOL] = recordId;
transition.__keys__ = keys;
structures[recordId - 0x40] = keys;
if (recordId < sharedLimitId) {
keys.isShared = true;
structures.sharedLength = recordId - 0x3f;
hasSharedUpdate = true;
if (highByte >= 0) {
target[position++] = (recordId & 0x1f) + 0x60;
target[position++] = highByte;
} else {
target[position++] = recordId;
}
} else {
if (highByte >= 0) {
target[position++] = 0xd5; // fixext 2
target[position++] = 0x72; // "r" record defintion extension type
target[position++] = (recordId & 0x1f) + 0x60;
target[position++] = highByte;
} else {
target[position++] = 0xd4; // fixext 1
target[position++] = 0x72; // "r" record defintion extension type
target[position++] = recordId;
}
if (newTransitions)
transitionsCount += serializationsSinceTransitionRebuild * newTransitions;
// record the removal of the id, we can maintain our shared structure
if (recordIdsToRemove.length >= maxOwnStructures)
recordIdsToRemove.shift()[RECORD_SYMBOL] = 0; // we are cycling back through, and have to remove old ones
recordIdsToRemove.push(transition);
pack(keys);
}
};
const insertNewRecord = (transition, keys, insertionOffset, newTransitions) => {
let mainTarget = target;
let mainPosition = position;
let mainSafeEnd = safeEnd;
let mainStart = start;
target = keysTarget;
position = 0;
start = 0;
if (!target)
keysTarget = target = new ByteArrayAllocate(8192);
safeEnd = target.length - 10;
newRecord(transition, keys, newTransitions);
keysTarget = target;
let keysPosition = position;
target = mainTarget;
position = mainPosition;
safeEnd = mainSafeEnd;
start = mainStart;
if (keysPosition > 1) {
let newEnd = position + keysPosition - 1;
if (newEnd > safeEnd)
makeRoom(newEnd);
let insertionPosition = insertionOffset + start;
target.copyWithin(insertionPosition + keysPosition, insertionPosition + 1, position);
target.set(keysTarget.slice(0, keysPosition), insertionPosition);
position = newEnd;
} else {
target[insertionOffset + start] = keysTarget[0];
}
};
const writeStruct = (object) => {
let newPosition = writeStructSlots(object, target, start, position, structures, makeRoom, (value, newPosition, notifySharedUpdate) => {
if (notifySharedUpdate)
return hasSharedUpdate = true;
position = newPosition;
let startTarget = target;
pack(value);
resetStructures();
if (startTarget !== target) {
return { position, targetView, target }; // indicate the buffer was re-allocated
}
return position;
}, this);
if (newPosition === 0) // bail and go to a msgpack object
return writeObject(object);
position = newPosition;
};
}
useBuffer(buffer) {
// this means we are finished using our own buffer and we can write over it safely
target = buffer;
target.dataView || (target.dataView = new DataView(target.buffer, target.byteOffset, target.byteLength));
targetView = target.dataView;
position = 0;
}
set position (value) {
position = value;
}
get position() {
return position;
}
clearSharedData() {
if (this.structures)
this.structures = [];
if (this.typedStructs)
this.typedStructs = [];
}
}
extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, DataView, C1Type ];
extensions = [{
pack(date, allocateForWrite, pack) {
let seconds = date.getTime() / 1000;
if ((this.useTimestamp32 || date.getMilliseconds() === 0) && seconds >= 0 && seconds < 0x100000000) {
// Timestamp 32
let { target, targetView, position} = allocateForWrite(6);
target[position++] = 0xd6;
target[position++] = 0xff;
targetView.setUint32(position, seconds);
} else if (seconds > 0 && seconds < 0x100000000) {
// Timestamp 64
let { target, targetView, position} = allocateForWrite(10);
target[position++] = 0xd7;
target[position++] = 0xff;
targetView.setUint32(position, date.getMilliseconds() * 4000000 + ((seconds / 1000 / 0x100000000) >> 0));
targetView.setUint32(position + 4, seconds);
} else if (isNaN(seconds)) {
if (this.onInvalidDate) {
allocateForWrite(0);
return pack(this.onInvalidDate())
}
// Intentionally invalid timestamp
let { target, targetView, position} = allocateForWrite(3);
target[position++] = 0xd4;
target[position++] = 0xff;
target[position++] = 0xff;
} else {
// Timestamp 96
let { target, targetView, position} = allocateForWrite(15);
target[position++] = 0xc7;
target[position++] = 12;
target[position++] = 0xff;
targetView.setUint32(position, date.getMilliseconds() * 1000000);
targetView.setBigInt64(position + 4, BigInt(Math.floor(seconds)));
}
}
}, {
pack(set, allocateForWrite, pack) {
if (this.setAsEmptyObject) {
allocateForWrite(0);
return pack({})
}
let array = Array.from(set);
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x73; // 's' for Set
target[position++] = 0;
}
pack(array);
}
}, {
pack(error, allocateForWrite, pack) {
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x65; // 'e' for error
target[position++] = 0;
}
pack([ error.name, error.message, error.cause ]);
}
}, {
pack(regex, allocateForWrite, pack) {
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x78; // 'x' for regeXp
target[position++] = 0;
}
pack([ regex.source, regex.flags ]);
}
}, {
pack(arrayBuffer, allocateForWrite) {
if (this.moreTypes)
writeExtBuffer(arrayBuffer, 0x10, allocateForWrite);
else
writeBuffer(hasNodeBuffer ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
}
}, {
pack(typedArray, allocateForWrite) {
let constructor = typedArray.constructor;
if (constructor !== ByteArray && this.moreTypes)
writeExtBuffer(typedArray, typedArrays.indexOf(constructor.name), allocateForWrite);
else
writeBuffer(typedArray, allocateForWrite);
}
}, {
pack(arrayBuffer, allocateForWrite) {
if (this.moreTypes)
writeExtBuffer(arrayBuffer, 0x11, allocateForWrite);
else
writeBuffer(hasNodeBuffer ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
}
}, {
pack(c1, allocateForWrite) { // specific 0xC1 object
let { target, position} = allocateForWrite(1);
target[position] = 0xc1;
}
}];
function writeExtBuffer(typedArray, type, allocateForWrite, encode) {
let length = typedArray.byteLength;
if (length + 1 < 0x100) {
var { target, position } = allocateForWrite(4 + length);
target[position++] = 0xc7;
target[position++] = length + 1;
} else if (length + 1 < 0x10000) {
var { target, position } = allocateForWrite(5 + length);
target[position++] = 0xc8;
target[position++] = (length + 1) >> 8;
target[position++] = (length + 1) & 0xff;
} else {
var { target, position, targetView } = allocateForWrite(7 + length);
target[position++] = 0xc9;
targetView.setUint32(position, length + 1); // plus one for the type byte
position += 4;
}
target[position++] = 0x74; // "t" for typed array
target[position++] = type;
if (!typedArray.buffer) typedArray = new Uint8Array(typedArray);
target.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength), position);
}
function writeBuffer(buffer, allocateForWrite) {
let length = buffer.byteLength;
var target, position;
if (length < 0x100) {
var { target, position } = allocateForWrite(length + 2);
target[position++] = 0xc4;
target[position++] = length;
} else if (length < 0x10000) {
var { target, position } = allocateForWrite(length + 3);
target[position++] = 0xc5;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
var { target, position, targetView } = allocateForWrite(length + 5);
target[position++] = 0xc6;
targetView.setUint32(position, length);
position += 4;
}
target.set(buffer, position);
}
function writeExtensionData(result, target, position, type) {
let length = result.length;
switch (length) {
case 1:
target[position++] = 0xd4;
break
case 2:
target[position++] = 0xd5;
break
case 4:
target[position++] = 0xd6;
break
case 8:
target[position++] = 0xd7;
break
case 16:
target[position++] = 0xd8;
break
default:
if (length < 0x100) {
target[position++] = 0xc7;
target[position++] = length;
} else if (length < 0x10000) {
target[position++] = 0xc8;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xc9;
target[position++] = length >> 24;
target[position++] = (length >> 16) & 0xff;
target[position++] = (length >> 8) & 0xff;
target[position++] = length & 0xff;
}
}
target[position++] = type;
target.set(result, position);
position += length;
return position
}
function insertIds(serialized, idsToInsert) {
// insert the ids that need to be referenced for structured clones
let nextId;
let distanceToMove = idsToInsert.length * 6;
let lastEnd = serialized.length - distanceToMove;
while (nextId = idsToInsert.pop()) {
let offset = nextId.offset;
let id = nextId.id;
serialized.copyWithin(offset + distanceToMove, offset, lastEnd);
distanceToMove -= 6;
let position = offset + distanceToMove;
serialized[position++] = 0xd6;
serialized[position++] = 0x69; // 'i'
serialized[position++] = id >> 24;
serialized[position++] = (id >> 16) & 0xff;
serialized[position++] = (id >> 8) & 0xff;
serialized[position++] = id & 0xff;
lastEnd = offset;
}
return serialized
}
function writeBundles(start, pack, incrementPosition) {
if (bundledStrings.length > 0) {
targetView.setUint32(bundledStrings.position + start, position + incrementPosition - bundledStrings.position - start);
bundledStrings.stringsPosition = position - start;
let writeStrings = bundledStrings;
bundledStrings = null;
pack(writeStrings[0]);
pack(writeStrings[1]);
}
}
function addExtension(extension) {
if (extension.Class) {
if (!extension.pack && !extension.write)
throw new Error('Extension has no pack or write function')
if (extension.pack && !extension.type)
throw new Error('Extension has no type (numeric code to identify the extension)')
extensionClasses.unshift(extension.Class);
extensions.unshift(extension);
}
addExtension$1(extension);
}
function prepareStructures(structures, packr) {
structures.isCompatible = (existingStructures) => {
let compatible = !existingStructures || ((packr.lastNamedStructuresLength || 0) === existingStructures.length);
if (!compatible) // we want to merge these existing structures immediately since we already have it and we are in the right transaction
packr._mergeStructures(existingStructures);
return compatible;
};
return structures
}
let defaultPackr = new Packr({ useRecords: false });
const pack = defaultPackr.pack;
const encode = defaultPackr.pack;
const Encoder = Packr;
const { NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } = FLOAT32_OPTIONS;
const REUSE_BUFFER_MODE = 512;
const RESET_BUFFER_MODE = 1024;
const RESERVE_START_SPACE = 2048;
/**
* Given an Iterable first argument, returns an Iterable where each value is packed as a Buffer
* If the argument is only Async Iterable, the return value will be an Async Iterable.
* @param {Iterable|Iterator|AsyncIterable|AsyncIterator} objectIterator - iterable source, like a Readable object stream, an array, Set, or custom object
* @param {options} [options] - msgpackr pack options
* @returns {IterableIterator|Promise.<AsyncIterableIterator>}
*/
function packIter (objectIterator, options = {}) {
if (!objectIterator || typeof objectIterator !== 'object') {
throw new Error('first argument must be an Iterable, Async Iterable, or a Promise for an Async Iterable')
} else if (typeof objectIterator[Symbol.iterator] === 'function') {
return packIterSync(objectIterator, options)
} else if (typeof objectIterator.then === 'function' || typeof objectIterator[Symbol.asyncIterator] === 'function') {
return packIterAsync(objectIterator, options)
} else {
throw new Error('first argument must be an Iterable, Async Iterable, Iterator, Async Iterator, or a Promise')
}
}
function * packIterSync (objectIterator, options) {
const packr = new Packr(options);
for (const value of objectIterator) {
yield packr.pack(value);
}
}
async function * packIterAsync (objectIterator, options) {
const packr = new Packr(options);
for await (const value of objectIterator) {
yield packr.pack(value);
}
}
/**
* Given an Iterable/Iterator input which yields buffers, returns an IterableIterator which yields sync decoded objects
* Or, given an Async Iterable/Iterator which yields promises resolving in buffers, returns an AsyncIterableIterator.
* @param {Iterable|Iterator|AsyncIterable|AsyncIterableIterator} bufferIterator
* @param {object} [options] - unpackr options
* @returns {IterableIterator|Promise.<AsyncIterableIterator}
*/
function unpackIter (bufferIterator, options = {}) {
if (!bufferIterator || typeof bufferIterator !== 'object') {
throw new Error('first argument must be an Iterable, Async Iterable, Iterator, Async Iterator, or a promise')
}
const unpackr = new Unpackr(options);
let incomplete;
const parser = (chunk) => {
let yields;
// if there's incomplete data from previous chunk, concatinate and try again
if (incomplete) {
chunk = Buffer.concat([incomplete, chunk]);
incomplete = undefined;
}
try {
yields = unpackr.unpackMultiple(chunk);
} catch (err) {
if (err.incomplete) {
incomplete = chunk.slice(err.lastPosition);
yields = err.values;
} else {
throw err
}
}
return yields
};
if (typeof bufferIterator[Symbol.iterator] === 'function') {
return (function * iter () {
for (const value of bufferIterator) {
yield * parser(value);
}
})()
} else if (typeof bufferIterator[Symbol.asyncIterator] === 'function') {
return (async function * iter () {
for await (const value of bufferIterator) {
yield * parser(value);
}
})()
}
}
const decodeIter = unpackIter;
const encodeIter = packIter;
const useRecords = false;
const mapsAsObjects = true;
exports.ALWAYS = ALWAYS;
exports.C1 = C1;
exports.DECIMAL_FIT = DECIMAL_FIT;
exports.DECIMAL_ROUND = DECIMAL_ROUND;
exports.Decoder = Decoder;
exports.Encoder = Encoder;
exports.FLOAT32_OPTIONS = FLOAT32_OPTIONS;
exports.NEVER = NEVER;
exports.Packr = Packr;
exports.RESERVE_START_SPACE = RESERVE_START_SPACE;
exports.RESET_BUFFER_MODE = RESET_BUFFER_MODE;
exports.REUSE_BUFFER_MODE = REUSE_BUFFER_MODE;
exports.Unpackr = Unpackr;
exports.addExtension = addExtension;
exports.clearSource = clearSource;
exports.decode = decode;
exports.decodeIter = decodeIter;
exports.encode = encode;
exports.encodeIter = encodeIter;
exports.isNativeAccelerationEnabled = isNativeAccelerationEnabled;
exports.mapsAsObjects = mapsAsObjects;
exports.pack = pack;
exports.roundFloat32 = roundFloat32;
exports.unpack = unpack;
exports.unpackMultiple = unpackMultiple;
exports.useRecords = useRecords;
}));
//# sourceMappingURL=index-no-eval.cjs.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2450
View File
@@ -0,0 +1,2450 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.msgpackr = {}));
})(this, (function (exports) { 'use strict';
var decoder;
try {
decoder = new TextDecoder();
} catch(error) {}
var src;
var srcEnd;
var position$1 = 0;
var currentUnpackr = {};
var currentStructures;
var srcString;
var srcStringStart = 0;
var srcStringEnd = 0;
var bundledStrings$1;
var referenceMap;
var currentExtensions = [];
var dataView;
var defaultOptions = {
useRecords: false,
mapsAsObjects: true
};
class C1Type {}
const C1 = new C1Type();
C1.name = 'MessagePack 0xC1';
var sequentialMode = false;
var inlineObjectReadThreshold = 2;
var readStruct;
class Unpackr {
constructor(options) {
if (options) {
if (options.useRecords === false && options.mapsAsObjects === undefined)
options.mapsAsObjects = true;
if (options.sequential && options.trusted !== false) {
options.trusted = true;
if (!options.structures && options.useRecords != false) {
options.structures = [];
if (!options.maxSharedStructures)
options.maxSharedStructures = 0;
}
}
if (options.structures)
options.structures.sharedLength = options.structures.length;
else if (options.getStructures) {
(options.structures = []).uninitialized = true; // this is what we use to denote an uninitialized structures
options.structures.sharedLength = 0;
}
if (options.int64AsNumber) {
options.int64AsType = 'number';
}
}
Object.assign(this, options);
}
unpack(source, options) {
if (src) {
// re-entrant execution, save the state and restore it after we do this unpack
return saveState(() => {
clearSource();
return this ? this.unpack(source, options) : Unpackr.prototype.unpack.call(defaultOptions, source, options)
})
}
if (!source.buffer && source.constructor === ArrayBuffer)
source = typeof Buffer !== 'undefined' ? Buffer.from(source) : new Uint8Array(source);
if (typeof options === 'object') {
srcEnd = options.end || source.length;
position$1 = options.start || 0;
} else {
position$1 = 0;
srcEnd = options > -1 ? options : source.length;
}
srcStringEnd = 0;
srcString = null;
bundledStrings$1 = null;
src = source;
// this provides cached access to the data view for a buffer if it is getting reused, which is a recommend
// technique for getting data from a database where it can be copied into an existing buffer instead of creating
// new ones
try {
dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength));
} catch(error) {
// if it doesn't have a buffer, maybe it is the wrong type of object
src = null;
if (source instanceof Uint8Array)
throw error
throw new Error('Source must be a Uint8Array or Buffer but was a ' + ((source && typeof source == 'object') ? source.constructor.name : typeof source))
}
if (this instanceof Unpackr) {
currentUnpackr = this;
if (this.structures) {
currentStructures = this.structures;
return checkedRead(options)
} else if (!currentStructures || currentStructures.length > 0) {
currentStructures = [];
}
} else {
currentUnpackr = defaultOptions;
if (!currentStructures || currentStructures.length > 0)
currentStructures = [];
}
return checkedRead(options)
}
unpackMultiple(source, forEach) {
let values, lastPosition = 0;
try {
sequentialMode = true;
let size = source.length;
let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size);
if (forEach) {
if (forEach(value, lastPosition, position$1) === false) return;
while(position$1 < size) {
lastPosition = position$1;
if (forEach(checkedRead(), lastPosition, position$1) === false) {
return
}
}
}
else {
values = [ value ];
while(position$1 < size) {
lastPosition = position$1;
values.push(checkedRead());
}
return values
}
} catch(error) {
error.lastPosition = lastPosition;
error.values = values;
throw error
} finally {
sequentialMode = false;
clearSource();
}
}
_mergeStructures(loadedStructures, existingStructures) {
loadedStructures = loadedStructures || [];
if (Object.isFrozen(loadedStructures))
loadedStructures = loadedStructures.map(structure => structure.slice(0));
for (let i = 0, l = loadedStructures.length; i < l; i++) {
let structure = loadedStructures[i];
if (structure) {
structure.isShared = true;
if (i >= 32)
structure.highByte = (i - 32) >> 5;
}
}
loadedStructures.sharedLength = loadedStructures.length;
for (let id in existingStructures || []) {
if (id >= 0) {
let structure = loadedStructures[id];
let existing = existingStructures[id];
if (existing) {
if (structure)
(loadedStructures.restoreStructures || (loadedStructures.restoreStructures = []))[id] = structure;
loadedStructures[id] = existing;
}
}
}
return this.structures = loadedStructures
}
decode(source, options) {
return this.unpack(source, options)
}
}
function checkedRead(options) {
try {
if (!currentUnpackr.trusted && !sequentialMode) {
let sharedLength = currentStructures.sharedLength || 0;
if (sharedLength < currentStructures.length)
currentStructures.length = sharedLength;
}
let result;
if (currentUnpackr.randomAccessStructure && src[position$1] < 0x40 && src[position$1] >= 0x20 && readStruct) {
result = readStruct(src, position$1, srcEnd, currentUnpackr);
src = null; // dispose of this so that recursive unpack calls don't save state
if (!(options && options.lazy) && result)
result = result.toJSON();
position$1 = srcEnd;
} else
result = read();
if (bundledStrings$1) { // bundled strings to skip past
position$1 = bundledStrings$1.postBundlePosition;
bundledStrings$1 = null;
}
if (sequentialMode)
// we only need to restore the structures if there was an error, but if we completed a read,
// we can clear this out and keep the structures we read
currentStructures.restoreStructures = null;
if (position$1 == srcEnd) {
// finished reading this source, cleanup references
if (currentStructures && currentStructures.restoreStructures)
restoreStructures();
currentStructures = null;
src = null;
if (referenceMap)
referenceMap = null;
} else if (position$1 > srcEnd) {
// over read
throw new Error('Unexpected end of MessagePack data')
} else if (!sequentialMode) {
let jsonView;
try {
jsonView = JSON.stringify(result, (_, value) => typeof value === "bigint" ? `${value}n` : value).slice(0, 100);
} catch(error) {
jsonView = '(JSON view not available ' + error + ')';
}
throw new Error('Data read, but end of buffer not reached ' + jsonView)
}
// else more to read, but we are reading sequentially, so don't clear source yet
return result
} catch(error) {
if (currentStructures && currentStructures.restoreStructures)
restoreStructures();
clearSource();
if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer') || position$1 > srcEnd) {
error.incomplete = true;
}
throw error
}
}
function restoreStructures() {
for (let id in currentStructures.restoreStructures) {
currentStructures[id] = currentStructures.restoreStructures[id];
}
currentStructures.restoreStructures = null;
}
function read() {
let token = src[position$1++];
if (token < 0xa0) {
if (token < 0x80) {
if (token < 0x40)
return token
else {
let structure = currentStructures[token & 0x3f] ||
currentUnpackr.getStructures && loadStructures()[token & 0x3f];
if (structure) {
if (!structure.read) {
structure.read = createStructureReader(structure, token & 0x3f);
}
return structure.read()
} else
return token
}
} else if (token < 0x90) {
// map
token -= 0x80;
if (currentUnpackr.mapsAsObjects) {
let object = {};
for (let i = 0; i < token; i++) {
let key = readKey();
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
return object
} else {
let map = new Map();
for (let i = 0; i < token; i++) {
map.set(read(), read());
}
return map
}
} else {
token -= 0x90;
let array = new Array(token);
for (let i = 0; i < token; i++) {
array[i] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
} else if (token < 0xc0) {
// fixstr
let length = token - 0xa0;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += length) - srcStringStart)
}
if (srcStringEnd == 0 && srcEnd < 140) {
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length);
if (string != null)
return string
}
return readFixedString(length)
} else {
let value;
switch (token) {
case 0xc0: return null
case 0xc1:
if (bundledStrings$1) {
value = read(); // followed by the length of the string in characters (not bytes!)
if (value > 0)
return bundledStrings$1[1].slice(bundledStrings$1.position1, bundledStrings$1.position1 += value)
else
return bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 -= value)
}
return C1; // "never-used", return special object to denote that
case 0xc2: return false
case 0xc3: return true
case 0xc4:
// bin 8
value = src[position$1++];
if (value === undefined)
throw new Error('Unexpected end of buffer')
return readBin(value)
case 0xc5:
// bin 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readBin(value)
case 0xc6:
// bin 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readBin(value)
case 0xc7:
// ext 8
return readExt(src[position$1++])
case 0xc8:
// ext 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readExt(value)
case 0xc9:
// ext 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readExt(value)
case 0xca:
value = dataView.getFloat32(position$1);
if (currentUnpackr.useFloat32 > 2) {
// this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
let multiplier = mult10[((src[position$1] & 0x7f) << 1) | (src[position$1 + 1] >> 7)];
position$1 += 4;
return ((multiplier * value + (value > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
position$1 += 4;
return value
case 0xcb:
value = dataView.getFloat64(position$1);
position$1 += 8;
return value
// uint handlers
case 0xcc:
return src[position$1++]
case 0xcd:
value = dataView.getUint16(position$1);
position$1 += 2;
return value
case 0xce:
value = dataView.getUint32(position$1);
position$1 += 4;
return value
case 0xcf:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getUint32(position$1) * 0x100000000;
value += dataView.getUint32(position$1 + 4);
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigUint64(position$1).toString();
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigUint64(position$1);
if (value<=BigInt(2)<<BigInt(52)) value=Number(value);
} else
value = dataView.getBigUint64(position$1);
position$1 += 8;
return value
// int handlers
case 0xd0:
return dataView.getInt8(position$1++)
case 0xd1:
value = dataView.getInt16(position$1);
position$1 += 2;
return value
case 0xd2:
value = dataView.getInt32(position$1);
position$1 += 4;
return value
case 0xd3:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getInt32(position$1) * 0x100000000;
value += dataView.getUint32(position$1 + 4);
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigInt64(position$1).toString();
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigInt64(position$1);
if (value>=BigInt(-2)<<BigInt(52)&&value<=BigInt(2)<<BigInt(52)) value=Number(value);
} else
value = dataView.getBigInt64(position$1);
position$1 += 8;
return value
case 0xd4:
// fixext 1
value = src[position$1++];
if (value == 0x72) {
return recordDefinition(src[position$1++] & 0x3f)
} else {
let extension = currentExtensions[value];
if (extension) {
if (extension.read) {
position$1++; // skip filler byte
return extension.read(read())
} else if (extension.noBuffer) {
position$1++; // skip filler byte
return extension()
} else
return extension(src.subarray(position$1, ++position$1))
} else
throw new Error('Unknown extension ' + value)
}
case 0xd5:
// fixext 2
value = src[position$1];
if (value == 0x72) {
position$1++;
return recordDefinition(src[position$1++] & 0x3f, src[position$1++])
} else
return readExt(2)
case 0xd6:
// fixext 4
return readExt(4)
case 0xd7:
// fixext 8
return readExt(8)
case 0xd8:
// fixext 16
return readExt(16)
case 0xd9:
// str 8
value = src[position$1++];
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString8(value)
case 0xda:
// str 16
value = dataView.getUint16(position$1);
position$1 += 2;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString16(value)
case 0xdb:
// str 32
value = dataView.getUint32(position$1);
position$1 += 4;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString32(value)
case 0xdc:
// array 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readArray(value)
case 0xdd:
// array 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readArray(value)
case 0xde:
// map 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readMap(value)
case 0xdf:
// map 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readMap(value)
default: // negative int
if (token >= 0xe0)
return token - 0x100
if (token === undefined) {
let error = new Error('Unexpected end of MessagePack data');
error.incomplete = true;
throw error
}
throw new Error('Unknown MessagePack token ' + token)
}
}
}
const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/;
function createStructureReader(structure, firstId) {
function readObject() {
// This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function
if (readObject.count++ > inlineObjectReadThreshold) {
let optimizedReadObject;
try {
optimizedReadObject = structure.read = (new Function('r', 'return function(){return ' + (currentUnpackr.freezeData ? 'Object.freeze' : '') +
'({' + structure.map(key => key === '__proto__' ? '__proto_:r()' : validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '})}'))(read);
} catch(error) {
// in CF workers, the new Function call could begin to fail at any point in time
inlineObjectReadThreshold = Infinity; // disable going forward
return readObject(); // recursively try again
}
structure.read0 = optimizedReadObject; // keep the un-wrapped body reader in sync
if (structure.highByte === 0)
structure.read = createSecondByteReader(firstId, structure.read);
return optimizedReadObject() // second byte is already read, if there is one so immediately read object
}
let object = {};
for (let i = 0, l = structure.length; i < l; i++) {
let key = structure[i];
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(object);
return object
}
readObject.count = 0;
// read0 is the un-wrapped body reader: it reads the record's values directly without
// consuming a leading high byte. recordDefinition uses it for the immediate read that follows
// a record definition (the high byte, if present, was already consumed). For highByte === 0
// structures the public reader is a second-byte reader (used by later references), but the
// definition read itself must not consume that byte.
structure.read0 = readObject;
if (structure.highByte === 0) {
return createSecondByteReader(firstId, readObject)
}
return readObject
}
const createSecondByteReader = (firstId, read0) => {
return function() {
let highByte = src[position$1++];
if (highByte === 0)
return read0()
let id = firstId < 32 ? -(firstId + (highByte << 5)) : firstId + (highByte << 5);
let structure = currentStructures[id] || loadStructures()[id];
if (!structure) {
throw new Error('Record id is not defined for ' + id)
}
if (!structure.read)
structure.read = createStructureReader(structure, firstId);
return structure.read()
}
};
function loadStructures() {
let loadedStructures = saveState(() => {
// save the state in case getStructures modifies our buffer
src = null;
return currentUnpackr.getStructures()
});
return currentStructures = currentUnpackr._mergeStructures(loadedStructures, currentStructures)
}
var readFixedString = readStringJS;
var readString8 = readStringJS;
var readString16 = readStringJS;
var readString32 = readStringJS;
let isNativeAccelerationEnabled = false;
function readStringJS(length) {
let result;
if (length < 16) {
if (result = shortStringInJS(length))
return result
}
if (length > 64 && decoder)
return decoder.decode(src.subarray(position$1, position$1 += length))
const end = position$1 + length;
const units = [];
result = '';
while (position$1 < end) {
const byte1 = src[position$1++];
if ((byte1 & 0x80) === 0) {
// 1 byte
units.push(byte1);
} else if ((byte1 & 0xe0) === 0xc0) {
// 2 bytes
const byte2 = src[position$1++] & 0x3f;
const codePoint = ((byte1 & 0x1f) << 6) | byte2;
// Reject overlong encoding: 2-byte sequences must encode values >= 0x80
if (codePoint < 0x80) {
units.push(0xFFFD); // replacement character
} else {
units.push(codePoint);
}
} else if ((byte1 & 0xf0) === 0xe0) {
// 3 bytes
const byte2 = src[position$1++] & 0x3f;
const byte3 = src[position$1++] & 0x3f;
const codePoint = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3;
// Reject overlong encoding: 3-byte sequences must encode values >= 0x800
// Also reject surrogates (0xD800-0xDFFF)
if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
units.push(0xFFFD); // replacement character
} else {
units.push(codePoint);
}
} else if ((byte1 & 0xf8) === 0xf0) {
// 4 bytes
const byte2 = src[position$1++] & 0x3f;
const byte3 = src[position$1++] & 0x3f;
const byte4 = src[position$1++] & 0x3f;
let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
// Reject overlong encoding: 4-byte sequences must encode values >= 0x10000
// Also reject values > 0x10FFFF (maximum valid Unicode)
if (unit < 0x10000 || unit > 0x10FFFF) {
units.push(0xFFFD); // replacement character
} else if (unit > 0xffff) {
unit -= 0x10000;
units.push(((unit >>> 10) & 0x3ff) | 0xd800);
unit = 0xdc00 | (unit & 0x3ff);
units.push(unit);
} else {
units.push(unit);
}
} else {
units.push(0xFFFD); // replacement character for invalid lead byte
}
if (units.length >= 0x1000) {
result += fromCharCode.apply(String, units);
units.length = 0;
}
}
if (units.length > 0) {
result += fromCharCode.apply(String, units);
}
return result
}
function readArray(length) {
let array = new Array(length);
for (let i = 0; i < length; i++) {
array[i] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
function readMap(length) {
if (currentUnpackr.mapsAsObjects) {
let object = {};
for (let i = 0; i < length; i++) {
let key = readKey();
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
return object
} else {
let map = new Map();
for (let i = 0; i < length; i++) {
map.set(read(), read());
}
return map
}
}
var fromCharCode = String.fromCharCode;
function longStringInJS(length) {
let start = position$1;
let bytes = new Array(length);
for (let i = 0; i < length; i++) {
const byte = src[position$1++];
if ((byte & 0x80) > 0) {
position$1 = start;
return
}
bytes[i] = byte;
}
return fromCharCode.apply(String, bytes)
}
function shortStringInJS(length) {
if (length < 4) {
if (length < 2) {
if (length === 0)
return ''
else {
let a = src[position$1++];
if ((a & 0x80) > 1) {
position$1 -= 1;
return
}
return fromCharCode(a)
}
} else {
let a = src[position$1++];
let b = src[position$1++];
if ((a & 0x80) > 0 || (b & 0x80) > 0) {
position$1 -= 2;
return
}
if (length < 3)
return fromCharCode(a, b)
let c = src[position$1++];
if ((c & 0x80) > 0) {
position$1 -= 3;
return
}
return fromCharCode(a, b, c)
}
} else {
let a = src[position$1++];
let b = src[position$1++];
let c = src[position$1++];
let d = src[position$1++];
if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
position$1 -= 4;
return
}
if (length < 6) {
if (length === 4)
return fromCharCode(a, b, c, d)
else {
let e = src[position$1++];
if ((e & 0x80) > 0) {
position$1 -= 5;
return
}
return fromCharCode(a, b, c, d, e)
}
} else if (length < 8) {
let e = src[position$1++];
let f = src[position$1++];
if ((e & 0x80) > 0 || (f & 0x80) > 0) {
position$1 -= 6;
return
}
if (length < 7)
return fromCharCode(a, b, c, d, e, f)
let g = src[position$1++];
if ((g & 0x80) > 0) {
position$1 -= 7;
return
}
return fromCharCode(a, b, c, d, e, f, g)
} else {
let e = src[position$1++];
let f = src[position$1++];
let g = src[position$1++];
let h = src[position$1++];
if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) {
position$1 -= 8;
return
}
if (length < 10) {
if (length === 8)
return fromCharCode(a, b, c, d, e, f, g, h)
else {
let i = src[position$1++];
if ((i & 0x80) > 0) {
position$1 -= 9;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i)
}
} else if (length < 12) {
let i = src[position$1++];
let j = src[position$1++];
if ((i & 0x80) > 0 || (j & 0x80) > 0) {
position$1 -= 10;
return
}
if (length < 11)
return fromCharCode(a, b, c, d, e, f, g, h, i, j)
let k = src[position$1++];
if ((k & 0x80) > 0) {
position$1 -= 11;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k)
} else {
let i = src[position$1++];
let j = src[position$1++];
let k = src[position$1++];
let l = src[position$1++];
if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) {
position$1 -= 12;
return
}
if (length < 14) {
if (length === 12)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l)
else {
let m = src[position$1++];
if ((m & 0x80) > 0) {
position$1 -= 13;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m)
}
} else {
let m = src[position$1++];
let n = src[position$1++];
if ((m & 0x80) > 0 || (n & 0x80) > 0) {
position$1 -= 14;
return
}
if (length < 15)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n)
let o = src[position$1++];
if ((o & 0x80) > 0) {
position$1 -= 15;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
}
}
}
}
}
function readOnlyJSString() {
let token = src[position$1++];
let length;
if (token < 0xc0) {
// fixstr
length = token - 0xa0;
} else {
switch(token) {
case 0xd9:
// str 8
length = src[position$1++];
break
case 0xda:
// str 16
length = dataView.getUint16(position$1);
position$1 += 2;
break
case 0xdb:
// str 32
length = dataView.getUint32(position$1);
position$1 += 4;
break
default:
throw new Error('Expected string')
}
}
return readStringJS(length)
}
function readBin(length) {
return currentUnpackr.copyBuffers ?
// specifically use the copying slice (not the node one)
Uint8Array.prototype.slice.call(src, position$1, position$1 += length) :
src.subarray(position$1, position$1 += length)
}
function readExt(length) {
let type = src[position$1++];
if (currentExtensions[type]) {
let end;
return currentExtensions[type](src.subarray(position$1, end = (position$1 += length)), (readPosition) => {
position$1 = readPosition;
try {
return read();
} finally {
position$1 = end;
}
})
}
else
throw new Error('Unknown extension type ' + type)
}
var keyCache = new Array(4096);
function readKey() {
let length = src[position$1++];
if (length >= 0xa0 && length < 0xc0) {
// fixstr, potentially use key cache
length = length - 0xa0;
if (srcStringEnd >= position$1) // if it has been extracted, must use it (and faster anyway)
return srcString.slice(position$1 - srcStringStart, (position$1 += length) - srcStringStart)
else if (!(srcStringEnd == 0 && srcEnd < 180))
return readFixedString(length)
} else { // not cacheable, go back and do a standard read
position$1--;
return asSafeString(read())
}
let key = ((length << 5) ^ (length > 1 ? dataView.getUint16(position$1) : length > 0 ? src[position$1] : 0)) & 0xfff;
let entry = keyCache[key];
let checkPosition = position$1;
let end = position$1 + length - 3;
let chunk;
let i = 0;
if (entry && entry.bytes == length) {
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition);
if (chunk != entry[i++]) {
checkPosition = 0x70000000;
break
}
checkPosition += 4;
}
end += 3;
while (checkPosition < end) {
chunk = src[checkPosition++];
if (chunk != entry[i++]) {
checkPosition = 0x70000000;
break
}
}
if (checkPosition === end) {
position$1 = checkPosition;
return entry.string
}
end -= 3;
checkPosition = position$1;
}
entry = [];
keyCache[key] = entry;
entry.bytes = length;
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition);
entry.push(chunk);
checkPosition += 4;
}
end += 3;
while (checkPosition < end) {
chunk = src[checkPosition++];
entry.push(chunk);
}
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length);
if (string != null)
return entry.string = string
return entry.string = readFixedString(length)
}
function asSafeString(property) {
// protect against expensive (DoS) string conversions
if (typeof property === 'string') return property;
if (typeof property === 'number' || typeof property === 'boolean' || typeof property === 'bigint') return property.toString();
if (property == null) return property + '';
if (currentUnpackr.allowArraysInMapKeys && Array.isArray(property) && property.flat().every(item => ['string', 'number', 'boolean', 'bigint'].includes(typeof item))) {
return property.flat().toString();
}
throw new Error(`Invalid property type for record: ${typeof property}`);
}
// the registration of the record definition extension (as "r")
const recordDefinition = (id, highByte) => {
let structure = read().map(asSafeString); // ensure that all keys are strings and
// that the array is mutable
let firstByte = id;
if (highByte !== undefined) {
id = id < 32 ? -((highByte << 5) + id) : ((highByte << 5) + id);
structure.highByte = highByte;
}
let existingStructure = currentStructures[id];
// If it is a shared structure, we need to restore any changes after reading.
// Also in sequential mode, we may get incomplete reads and thus errors, and we need to restore
// to the state prior to an incomplete read in order to properly resume.
if (existingStructure && (existingStructure.isShared || sequentialMode)) {
(currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure;
}
currentStructures[id] = structure;
structure.read = createStructureReader(structure, firstByte);
// The high byte (if any) was already consumed as the `highByte` argument above, so read the
// record body directly. Going through structure.read (a second-byte reader when highByte === 0)
// would misinterpret the first value byte as a high byte — corrupting two-byte own-record
// definitions (0xd5 0x72 ...). createStructureReader stashes the un-wrapped body reader on
// structure.read0 precisely for this immediate post-definition read.
return (structure.read0 || structure.read)()
};
currentExtensions[0] = () => {}; // notepack defines extension 0 to mean undefined, so use that as the default here
currentExtensions[0].noBuffer = true;
currentExtensions[0x42] = data => {
let headLength = (data.byteLength % 8) || 8;
let head = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0]);
for (let i = 1; i < headLength; i++) {
head <<= BigInt(8);
head += BigInt(data[i]);
}
if (data.byteLength !== headLength) {
let view = new DataView(data.buffer, data.byteOffset, data.byteLength);
let decode = (start, end) => {
let length = end - start;
if (length <= 40) {
let out = view.getBigUint64(start);
for (let i = start + 8; i < end; i += 8) {
out <<= BigInt(64);
out |= view.getBigUint64(i);
}
return out
}
// if (length === 8) return view.getBigUint64(start)
let middle = start + (length >> 4 << 3);
let left = decode(start, middle);
let right = decode(middle, end);
return (left << BigInt((end - middle) * 8)) | right
};
head = (head << BigInt((view.byteLength - headLength) * 8)) | decode(headLength, view.byteLength);
}
return head
};
let errors = {
Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError: typeof AggregateError === 'function' ? AggregateError : null,
};
currentExtensions[0x65] = () => {
let data = read();
if (!errors[data[0]]) {
let error = Error(data[1], { cause: data[2] });
error.name = data[0];
return error
}
return errors[data[0]](data[1], { cause: data[2] })
};
currentExtensions[0x69] = (data) => {
// id extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position$1 - 4);
if (!referenceMap)
referenceMap = new Map();
let token = src[position$1];
let target;
// TODO: handle any other types that can cycle and make the code more robust if there are other extensions
if (token >= 0x90 && token < 0xa0 || token == 0xdc || token == 0xdd)
target = [];
else if (token >= 0x80 && token < 0x90 || token == 0xde || token == 0xdf)
target = new Map();
else if ((token >= 0xc7 && token <= 0xc9 || token >= 0xd4 && token <= 0xd8) && src[position$1 + 1] === 0x73)
target = new Set();
else
target = {};
let refEntry = { target }; // a placeholder object
referenceMap.set(id, refEntry);
let targetProperties = read(); // read the next value as the target object to id
if (!refEntry.used) {
// no cycle, can just use the returned read object
return refEntry.target = targetProperties // replace the placeholder with the real one
} else {
// there is a cycle, so we have to assign properties to original target
Object.assign(target, targetProperties);
}
// copy over map/set entries if we're able to
if (target instanceof Map)
for (let [k, v] of targetProperties.entries()) target.set(k, v);
if (target instanceof Set)
for (let i of Array.from(targetProperties)) target.add(i);
return target
};
currentExtensions[0x70] = (data) => {
// pointer extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position$1 - 4);
let refEntry = referenceMap.get(id);
refEntry.used = true;
return refEntry.target
};
currentExtensions[0x73] = () => new Set(read());
const typedArrays = ['Int8','Uint8','Uint8Clamped','Int16','Uint16','Int32','Uint32','Float32','Float64','BigInt64','BigUint64'].map(type => type + 'Array');
let glbl = typeof globalThis === 'object' ? globalThis : window;
currentExtensions[0x74] = (data) => {
let typeCode = data[0];
// we always have to slice to get a new ArrayBuffer that is aligned
let buffer = Uint8Array.prototype.slice.call(data, 1).buffer;
let typedArrayName = typedArrays[typeCode];
if (!typedArrayName) {
if (typeCode === 16) return buffer
if (typeCode === 17) return new DataView(buffer)
throw new Error('Could not find typed array for code ' + typeCode)
}
return new glbl[typedArrayName](buffer)
};
currentExtensions[0x78] = () => {
let data = read();
return new RegExp(data[0], data[1])
};
const TEMP_BUNDLE = [];
currentExtensions[0x62] = (data) => {
let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3];
let dataPosition = position$1;
position$1 += dataSize - data.length;
bundledStrings$1 = TEMP_BUNDLE;
bundledStrings$1 = [readOnlyJSString(), readOnlyJSString()];
bundledStrings$1.position0 = 0;
bundledStrings$1.position1 = 0;
bundledStrings$1.postBundlePosition = position$1;
position$1 = dataPosition;
return read()
};
currentExtensions[0xff] = (data) => {
// 32-bit date extension
if (data.length == 4)
return new Date((data[0] * 0x1000000 + (data[1] << 16) + (data[2] << 8) + data[3]) * 1000)
else if (data.length == 8)
return new Date(
((data[0] << 22) + (data[1] << 14) + (data[2] << 6) + (data[3] >> 2)) / 1000000 +
((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000)
else if (data.length == 12)
return new Date(
((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
(((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000)
else
return new Date('invalid')
};
// registration of bulk record definition?
// currentExtensions[0x52] = () =>
function saveState(callback) {
let savedSrcEnd = srcEnd;
let savedPosition = position$1;
let savedSrcStringStart = srcStringStart;
let savedSrcStringEnd = srcStringEnd;
let savedSrcString = srcString;
let savedReferenceMap = referenceMap;
let savedBundledStrings = bundledStrings$1;
// TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
let savedStructures = currentStructures;
let savedStructuresContents = currentStructures.slice(0, currentStructures.length);
let savedPackr = currentUnpackr;
let savedSequentialMode = sequentialMode;
let value = callback();
srcEnd = savedSrcEnd;
position$1 = savedPosition;
srcStringStart = savedSrcStringStart;
srcStringEnd = savedSrcStringEnd;
srcString = savedSrcString;
referenceMap = savedReferenceMap;
bundledStrings$1 = savedBundledStrings;
src = savedSrc;
sequentialMode = savedSequentialMode;
currentStructures = savedStructures;
currentStructures.splice(0, currentStructures.length, ...savedStructuresContents);
currentUnpackr = savedPackr;
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
return value
}
function clearSource() {
src = null;
referenceMap = null;
currentStructures = null;
}
function addExtension$1(extension) {
if (extension.unpack)
currentExtensions[extension.type] = extension.unpack;
else
currentExtensions[extension.type] = extension;
}
const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding
for (let i = 0; i < 256; i++) {
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103));
}
const Decoder = Unpackr;
var defaultUnpackr = new Unpackr({ useRecords: false });
const unpack = defaultUnpackr.unpack;
const unpackMultiple = defaultUnpackr.unpackMultiple;
const decode = defaultUnpackr.unpack;
const FLOAT32_OPTIONS = {
NEVER: 0,
ALWAYS: 1,
DECIMAL_ROUND: 3,
DECIMAL_FIT: 4
};
let f32Array = new Float32Array(1);
let u8Array = new Uint8Array(f32Array.buffer, 0, 4);
function roundFloat32(float32Number) {
f32Array[0] = float32Number;
let multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)];
return ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
let textEncoder;
try {
textEncoder = new TextEncoder();
} catch (error) {}
let extensions, extensionClasses;
const hasNodeBuffer = typeof Buffer !== 'undefined';
const ByteArrayAllocate = hasNodeBuffer ?
function(length) { return Buffer.allocUnsafeSlow(length) } : Uint8Array;
const ByteArray = hasNodeBuffer ? Buffer : Uint8Array;
const MAX_BUFFER_SIZE = hasNodeBuffer ? 0x100000000 : 0x7fd00000;
let target, keysTarget;
let targetView;
let position = 0;
let safeEnd;
let bundledStrings = null;
let writeStructSlots;
const MAX_BUNDLE_SIZE = 0x5500; // maximum characters such that the encoded bytes fits in 16 bits.
const hasNonLatin = /[\u0080-\uFFFF]/;
const RECORD_SYMBOL = Symbol('record-id');
class Packr extends Unpackr {
constructor(options) {
super(options);
this.offset = 0;
let start;
let hasSharedUpdate;
let structures;
let referenceMap;
let encodeUtf8 = ByteArray.prototype.utf8Write ? function(string, position) {
return target.utf8Write(string, position, target.byteLength - position)
} : (textEncoder && textEncoder.encodeInto) ?
function(string, position) {
return textEncoder.encodeInto(string, target.subarray(position)).written
} : false;
let packr = this;
if (!options)
options = {};
let isSequential = options && options.sequential;
let hasSharedStructures = options.structures || options.saveStructures;
let maxSharedStructures = options.maxSharedStructures;
if (maxSharedStructures == null)
maxSharedStructures = hasSharedStructures ? 32 : 0;
if (maxSharedStructures > 8160)
throw new Error('Maximum maxSharedStructure is 8160')
if (options.structuredClone && options.moreTypes == undefined) {
this.moreTypes = true;
}
let maxOwnStructures = options.maxOwnStructures;
if (maxOwnStructures == null)
maxOwnStructures = hasSharedStructures ? 32 : 64;
if (!this.structures && options.useRecords != false)
this.structures = [];
// two byte record ids for shared structures
let useTwoByteRecords = maxSharedStructures > 32 || (maxOwnStructures + maxSharedStructures > 64);
let sharedLimitId = maxSharedStructures + 0x40;
let maxStructureId = maxSharedStructures + maxOwnStructures + 0x40;
if (maxStructureId > 8256) {
throw new Error('Maximum maxSharedStructure + maxOwnStructure is 8192')
}
let recordIdsToRemove = [];
let transitionsCount = 0;
let serializationsSinceTransitionRebuild = 0;
this.pack = this.encode = function(value, encodeOptions) {
if (!target) {
target = new ByteArrayAllocate(8192);
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, 8192));
position = 0;
}
safeEnd = target.length - 10;
if (safeEnd - position < 0x800) {
// don't start too close to the end,
target = new ByteArrayAllocate(target.length);
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, target.length));
safeEnd = target.length - 10;
position = 0;
} else
position = (position + 7) & 0x7ffffff8; // Word align to make any future copying of this buffer faster
start = position;
if (encodeOptions & RESERVE_START_SPACE) position += (encodeOptions & 0xff);
referenceMap = packr.structuredClone ? new Map() : null;
if (packr.bundleStrings && typeof value !== 'string') {
bundledStrings = [];
bundledStrings.size = Infinity; // force a new bundle start on first string
} else
bundledStrings = null;
structures = packr.structures;
if (structures) {
if (structures.uninitialized)
structures = packr._mergeStructures(packr.getStructures());
let sharedLength = structures.sharedLength || 0;
if (sharedLength > maxSharedStructures) {
//if (maxSharedStructures <= 32 && structures.sharedLength > 32) // TODO: could support this, but would need to update the limit ids
throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' + structures.sharedLength)
}
if (!structures.transitions) {
// rebuild our structure transitions
structures.transitions = Object.create(null);
for (let i = 0; i < sharedLength; i++) {
let keys = structures[i];
if (!keys)
continue
let nextTransition, transition = structures.transitions;
for (let j = 0, l = keys.length; j < l; j++) {
let key = keys[j];
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
}
transition = nextTransition;
}
transition[RECORD_SYMBOL] = i + 0x40;
}
this.lastNamedStructuresLength = sharedLength;
}
if (!isSequential) {
structures.nextId = sharedLength + 0x40;
}
}
if (hasSharedUpdate)
hasSharedUpdate = false;
let encodingError;
try {
// readOnlyStructures: skip the random-access struct write path so NO new struct is
// minted. randomAccessStructure stays true (the struct READ path and the struct-safe
// integer boundary are preserved, so existing struct data still decodes), but objects
// fall through to the normal pack()->writeObject->writeRecord path and are written as
// classic shared-structure records (byte range 0x40-0x7f, disjoint from struct headers
// at 0x20-0x3f) — the bounded, width-agnostic encoding used before struct mode.
if (packr.randomAccessStructure && !packr.readOnlyStructures && value && typeof value === 'object') {
if (value.constructor === Object) writeStruct(value); // simple object
else if (value.constructor !== Map && !Array.isArray(value) && !extensionClasses.some(extClass => value instanceof extClass)) {
// allow user classes, if they don't need special handling (but do use toJSON if available)
writeStruct(value.toJSON ? value.toJSON() : value);
} else pack(value);
} else
pack(value);
let lastBundle = bundledStrings;
if (bundledStrings)
writeBundles(start, pack, 0);
if (referenceMap && referenceMap.idsToInsert) {
let idsToInsert = referenceMap.idsToInsert.sort((a, b) => a.offset > b.offset ? 1 : -1);
let i = idsToInsert.length;
let incrementPosition = -1;
while (lastBundle && i > 0) {
let insertionPoint = idsToInsert[--i].offset + start;
if (insertionPoint < (lastBundle.stringsPosition + start) && incrementPosition === -1)
incrementPosition = 0;
if (insertionPoint > (lastBundle.position + start)) {
if (incrementPosition >= 0)
incrementPosition += 6;
} else {
if (incrementPosition >= 0) {
// update the bundle reference now
targetView.setUint32(lastBundle.position + start,
targetView.getUint32(lastBundle.position + start) + incrementPosition);
incrementPosition = -1; // reset
}
lastBundle = lastBundle.previous;
i++;
}
}
if (incrementPosition >= 0 && lastBundle) {
// update the bundle reference now
targetView.setUint32(lastBundle.position + start,
targetView.getUint32(lastBundle.position + start) + incrementPosition);
}
position += idsToInsert.length * 6;
if (position > safeEnd)
makeRoom(position);
packr.offset = position;
let serialized = insertIds(target.subarray(start, position), idsToInsert);
referenceMap = null;
return serialized
}
packr.offset = position; // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially
if (encodeOptions & REUSE_BUFFER_MODE) {
target.start = start;
target.end = position;
return target
}
return target.subarray(start, position) // position can change if we call pack again in saveStructures, so we get the buffer now
} catch(error) {
encodingError = error;
throw error;
} finally {
if (structures) {
resetStructures();
if (hasSharedUpdate && packr.saveStructures) {
let sharedLength = structures.sharedLength || 0;
// we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save
let returnBuffer = target.subarray(start, position);
let newSharedData = prepareStructures(structures, packr);
if (!encodingError) { // TODO: If there is an encoding error, should make the structures as uninitialized so they get rebuilt next time
if (packr.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
// The save was declined (a concurrent writer updated the shared structures,
// or the store transaction did not durably commit). Our in-memory
// structures + transition trie may now reference record ids that were
// never persisted; re-packing as-is would re-emit the same record pointing
// at an unpersisted structure (-> "Record id is not defined" on decode).
// Mark structures uninitialized so the re-pack reloads durable structures
// via getStructures, rebuilds the transition trie, and re-mints + re-saves.
structures.uninitialized = true;
return packr.pack(value, encodeOptions)
}
packr.lastNamedStructuresLength = sharedLength;
// don't keep large buffers around
if (target.length > 0x40000000) target = null;
return returnBuffer
}
}
}
// don't keep large buffers around, they take too much memory and cause problems (limit at 1GB)
if (target.length > 0x40000000) target = null;
if (encodeOptions & RESET_BUFFER_MODE)
position = start;
}
};
const resetStructures = () => {
if (serializationsSinceTransitionRebuild < 10)
serializationsSinceTransitionRebuild++;
let sharedLength = structures.sharedLength || 0;
if (structures.length > sharedLength && !isSequential)
structures.length = sharedLength;
if (transitionsCount > 10000) {
// force a rebuild occasionally after a lot of transitions so it can get cleaned up
structures.transitions = null;
serializationsSinceTransitionRebuild = 0;
transitionsCount = 0;
if (recordIdsToRemove.length > 0)
recordIdsToRemove = [];
} else if (recordIdsToRemove.length > 0 && !isSequential) {
for (let i = 0, l = recordIdsToRemove.length; i < l; i++) {
recordIdsToRemove[i][RECORD_SYMBOL] = 0;
}
recordIdsToRemove = [];
}
};
const packArray = (value) => {
var length = value.length;
if (length < 0x10) {
target[position++] = 0x90 | length;
} else if (length < 0x10000) {
target[position++] = 0xdc;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdd;
targetView.setUint32(position, length);
position += 4;
}
for (let i = 0; i < length; i++) {
pack(value[i]);
}
};
const pack = (value) => {
if (position > safeEnd)
target = makeRoom(position);
var type = typeof value;
var length;
if (type === 'string') {
let strLength = value.length;
if (bundledStrings && strLength >= 4 && strLength < 0x1000) {
if ((bundledStrings.size += strLength) > MAX_BUNDLE_SIZE) {
let extStart;
let maxBytes = (bundledStrings[0] ? bundledStrings[0].length * 3 + bundledStrings[1].length : 0) + 10;
if (position + maxBytes > safeEnd)
target = makeRoom(position + maxBytes);
let lastBundle;
if (bundledStrings.position) { // here we use the 0x62 extension to write the last bundle and reserve space for the reference pointer to the next/current bundle
lastBundle = bundledStrings;
target[position] = 0xc8; // ext 16
position += 3; // reserve for the writing bundle size
target[position++] = 0x62; // 'b'
extStart = position - start;
position += 4; // reserve for writing bundle reference
writeBundles(start, pack, 0); // write the last bundles
targetView.setUint16(extStart + start - 3, position - start - extStart);
} else { // here we use the 0x62 extension just to reserve the space for the reference pointer to the bundle (will be updated once the bundle is written)
target[position++] = 0xd6; // fixext 4
target[position++] = 0x62; // 'b'
extStart = position - start;
position += 4; // reserve for writing bundle reference
}
bundledStrings = ['', '']; // create new ones
bundledStrings.previous = lastBundle;
bundledStrings.size = 0;
bundledStrings.position = extStart;
}
let twoByte = hasNonLatin.test(value);
bundledStrings[twoByte ? 0 : 1] += value;
target[position++] = 0xc1;
pack(twoByte ? -strLength : strLength);
return
}
let headerSize;
// first we estimate the header size, so we can write to the correct location
if (strLength < 0x20) {
headerSize = 1;
} else if (strLength < 0x100) {
headerSize = 2;
} else if (strLength < 0x10000) {
headerSize = 3;
} else {
headerSize = 5;
}
let maxBytes = strLength * 3;
if (position + maxBytes > safeEnd)
target = makeRoom(position + maxBytes);
if (strLength < 0x40 || !encodeUtf8) {
let i, c1, c2, strPosition = position + headerSize;
for (i = 0; i < strLength; i++) {
c1 = value.charCodeAt(i);
if (c1 < 0x80) {
target[strPosition++] = c1;
} else if (c1 < 0x800) {
target[strPosition++] = c1 >> 6 | 0xc0;
target[strPosition++] = c1 & 0x3f | 0x80;
} else if (
(c1 & 0xfc00) === 0xd800 &&
((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
) {
c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
i++;
target[strPosition++] = c1 >> 18 | 0xf0;
target[strPosition++] = c1 >> 12 & 0x3f | 0x80;
target[strPosition++] = c1 >> 6 & 0x3f | 0x80;
target[strPosition++] = c1 & 0x3f | 0x80;
} else {
target[strPosition++] = c1 >> 12 | 0xe0;
target[strPosition++] = c1 >> 6 & 0x3f | 0x80;
target[strPosition++] = c1 & 0x3f | 0x80;
}
}
length = strPosition - position - headerSize;
} else {
length = encodeUtf8(value, position + headerSize);
}
if (length < 0x20) {
target[position++] = 0xa0 | length;
} else if (length < 0x100) {
if (headerSize < 2) {
target.copyWithin(position + 2, position + 1, position + 1 + length);
}
target[position++] = 0xd9;
target[position++] = length;
} else if (length < 0x10000) {
if (headerSize < 3) {
target.copyWithin(position + 3, position + 2, position + 2 + length);
}
target[position++] = 0xda;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
if (headerSize < 5) {
target.copyWithin(position + 5, position + 3, position + 3 + length);
}
target[position++] = 0xdb;
targetView.setUint32(position, length);
position += 4;
}
position += length;
} else if (type === 'number') {
if (value >>> 0 === value) {// positive integer, 32-bit or less
// positive uint
if (value < 0x20 || (value < 0x80 && this.useRecords === false) || (value < 0x40 && !this.randomAccessStructure)) {
target[position++] = value;
} else if (value < 0x100) {
target[position++] = 0xcc;
target[position++] = value;
} else if (value < 0x10000) {
target[position++] = 0xcd;
target[position++] = value >> 8;
target[position++] = value & 0xff;
} else {
target[position++] = 0xce;
targetView.setUint32(position, value);
position += 4;
}
} else if (value >> 0 === value) { // negative integer
if (value >= -0x20) {
target[position++] = 0x100 + value;
} else if (value >= -0x80) {
target[position++] = 0xd0;
target[position++] = value + 0x100;
} else if (value >= -0x8000) {
target[position++] = 0xd1;
targetView.setInt16(position, value);
position += 2;
} else {
target[position++] = 0xd2;
targetView.setInt32(position, value);
position += 4;
}
} else {
let useFloat32;
if ((useFloat32 = this.useFloat32) > 0 && value < 0x100000000 && value >= -0x80000000) {
target[position++] = 0xca;
targetView.setFloat32(position, value);
let xShifted;
if (useFloat32 < 4 ||
// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
((xShifted = value * mult10[((target[position] & 0x7f) << 1) | (target[position + 1] >> 7)]) >> 0) === xShifted) {
position += 4;
return
} else
position--; // move back into position for writing a double
}
target[position++] = 0xcb;
targetView.setFloat64(position, value);
position += 8;
}
} else if (type === 'object' || type === 'function') {
if (!value)
target[position++] = 0xc0;
else {
if (referenceMap) {
let referee = referenceMap.get(value);
if (referee) {
if (!referee.id) {
let idsToInsert = referenceMap.idsToInsert || (referenceMap.idsToInsert = []);
referee.id = idsToInsert.push(referee);
}
target[position++] = 0xd6; // fixext 4
target[position++] = 0x70; // "p" for pointer
targetView.setUint32(position, referee.id);
position += 4;
return
} else
referenceMap.set(value, { offset: position - start });
}
let constructor = value.constructor;
if (constructor === Object) {
writeObject(value);
} else if (constructor === Array) {
packArray(value);
} else if (constructor === Map) {
if (this.mapAsEmptyObject) target[position++] = 0x80;
else {
length = value.size;
if (length < 0x10) {
target[position++] = 0x80 | length;
} else if (length < 0x10000) {
target[position++] = 0xde;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdf;
targetView.setUint32(position, length);
position += 4;
}
for (let [key, entryValue] of value) {
pack(key);
pack(entryValue);
}
}
} else {
for (let i = 0, l = extensions.length; i < l; i++) {
let extensionClass = extensionClasses[i];
if (value instanceof extensionClass) {
let extension = extensions[i];
if (extension.write) {
if (extension.type) {
target[position++] = 0xd4; // one byte "tag" extension
target[position++] = extension.type;
target[position++] = 0;
}
let writeResult = extension.write.call(this, value);
if (writeResult === value) { // avoid infinite recursion
if (Array.isArray(value)) {
packArray(value);
} else {
writeObject(value);
}
} else {
pack(writeResult);
}
return
}
let currentTarget = target;
let currentTargetView = targetView;
let currentPosition = position;
target = null;
let result;
try {
result = extension.pack.call(this, value, (size) => {
// restore target and use it
target = currentTarget;
currentTarget = null;
position += size;
if (position > safeEnd)
makeRoom(position);
return {
target, targetView, position: position - size
}
}, pack);
} finally {
// restore current target information (unless already restored)
if (currentTarget) {
target = currentTarget;
targetView = currentTargetView;
position = currentPosition;
safeEnd = target.length - 10;
}
}
if (result) {
if (result.length + position > safeEnd)
makeRoom(result.length + position);
position = writeExtensionData(result, target, position, extension.type);
}
return
}
}
// check isArray after extensions, because extensions can extend Array
if (Array.isArray(value)) {
packArray(value);
} else {
// use this as an alternate mechanism for expressing how to serialize
if (value.toJSON) {
const json = value.toJSON();
// if for some reason value.toJSON returns itself it'll loop forever
if (json !== value)
return pack(json)
}
// if there is a writeFunction, use it, otherwise just encode as undefined
if (type === 'function')
return pack(this.writeFunction && this.writeFunction(value));
// no extension found, write as plain object
writeObject(value);
}
}
}
} else if (type === 'boolean') {
target[position++] = value ? 0xc3 : 0xc2;
} else if (type === 'bigint') {
if (value < 0x8000000000000000 && value >= -0x8000000000000000) {
// use a signed int as long as it fits
target[position++] = 0xd3;
targetView.setBigInt64(position, value);
} else if (value < 0x10000000000000000 && value > 0) {
// if we can fit an unsigned int, use that
target[position++] = 0xcf;
targetView.setBigUint64(position, value);
} else {
// overflow
if (this.largeBigIntToFloat) {
target[position++] = 0xcb;
targetView.setFloat64(position, Number(value));
} else if (this.largeBigIntToString) {
return pack(value.toString());
} else if (this.useBigIntExtension || this.moreTypes) {
let empty = value < 0 ? BigInt(-1) : BigInt(0);
let array;
if (value >> BigInt(0x10000) === empty) {
let mask = BigInt(0x10000000000000000) - BigInt(1); // literal would overflow
let chunks = [];
while (true) {
chunks.push(value & mask);
if ((value >> BigInt(63)) === empty) break
value >>= BigInt(64);
}
array = new Uint8Array(new BigUint64Array(chunks).buffer);
array.reverse();
} else {
let invert = value < 0;
let string = (invert ? ~value : value).toString(16);
if (string.length % 2) {
string = '0' + string;
} else if (parseInt(string.charAt(0), 16) >= 8) {
string = '00' + string;
}
if (hasNodeBuffer) {
array = Buffer.from(string, 'hex');
} else {
array = new Uint8Array(string.length / 2);
for (let i = 0; i < array.length; i++) {
array[i] = parseInt(string.slice(i * 2, i * 2 + 2), 16);
}
}
if (invert) {
for (let i = 0; i < array.length; i++) array[i] = ~array[i];
}
}
if (array.length + position > safeEnd)
makeRoom(array.length + position);
position = writeExtensionData(array, target, position, 0x42);
return
} else {
throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, use' +
' useBigIntExtension, or set largeBigIntToFloat to convert to float-64, or set' +
' largeBigIntToString to convert to string')
}
}
position += 8;
} else if (type === 'undefined') {
if (this.encodeUndefinedAsNil)
target[position++] = 0xc0;
else {
target[position++] = 0xd4; // a number of implementations use fixext1 with type 0, data 0 to denote undefined, so we follow suite
target[position++] = 0;
target[position++] = 0;
}
} else {
throw new Error('Unknown type: ' + type)
}
};
const writePlainObject = (this.variableMapSize || this.coercibleKeyAsNumber || this.skipValues) ? (object) => {
// this method is slightly slower, but generates "preferred serialization" (optimally small for smaller objects)
let keys;
if (this.skipValues) {
keys = [];
for (let key in object) {
if ((typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) &&
!this.skipValues.includes(object[key]))
keys.push(key);
}
} else {
keys = Object.keys(object);
}
let length = keys.length;
if (length < 0x10) {
target[position++] = 0x80 | length;
} else if (length < 0x10000) {
target[position++] = 0xde;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdf;
targetView.setUint32(position, length);
position += 4;
}
let key;
if (this.coercibleKeyAsNumber) {
for (let i = 0; i < length; i++) {
key = keys[i];
let num = Number(key);
pack(isNaN(num) ? key : num);
pack(object[key]);
}
} else {
for (let i = 0; i < length; i++) {
pack(key = keys[i]);
pack(object[key]);
}
}
} :
(object) => {
target[position++] = 0xde; // always using map 16, so we can preallocate and set the length afterwards
let objectOffset = position - start;
position += 2;
let size = 0;
for (let key in object) {
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
pack(key);
pack(object[key]);
size++;
}
}
if (size > 0xffff) {
throw new Error('Object is too large to serialize with fast 16-bit map size,' +
' use the "variableMapSize" option to serialize this object');
}
target[objectOffset++ + start] = size >> 8;
target[objectOffset + start] = size & 0xff;
};
const writeRecord = this.useRecords === false ? writePlainObject :
(options.progressiveRecords && !useTwoByteRecords) ? // this is about 2% faster for highly stable structures, since it only requires one for-in loop (but much more expensive when new structure needs to be written)
(object) => {
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
let objectOffset = position++ - start;
let wroteKeys;
for (let key in object) {
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
nextTransition = transition[key];
if (nextTransition)
transition = nextTransition;
else {
// record doesn't exist, create full new record and insert it
let keys = Object.keys(object);
let lastTransition = transition;
transition = structures.transitions;
let newTransitions = 0;
for (let i = 0, l = keys.length; i < l; i++) {
let key = keys[i];
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
newTransitions++;
}
transition = nextTransition;
}
if (objectOffset + start + 1 == position) {
// first key, so we don't need to insert, we can just write record directly
position--;
newRecord(transition, keys, newTransitions);
} else // otherwise we need to insert the record, moving existing data after the record
insertNewRecord(transition, keys, objectOffset, newTransitions);
wroteKeys = true;
transition = lastTransition[key];
}
pack(object[key]);
}
}
if (!wroteKeys) {
let recordId = transition[RECORD_SYMBOL];
if (recordId)
target[objectOffset + start] = recordId;
else
insertNewRecord(transition, Object.keys(object), objectOffset, 0);
}
} :
(object) => {
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
let newTransitions = 0;
for (let key in object) if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
newTransitions++;
}
transition = nextTransition;
}
let recordId = transition[RECORD_SYMBOL];
if (recordId) {
if (recordId >= 0x60 && useTwoByteRecords) {
target[position++] = ((recordId -= 0x60) & 0x1f) + 0x60;
target[position++] = recordId >> 5;
} else
target[position++] = recordId;
} else {
newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions);
}
// now write the values
for (let key in object)
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
pack(object[key]);
}
};
// create reference to useRecords if useRecords is a function
const checkUseRecords = typeof this.useRecords == 'function' && this.useRecords;
const writeObject = checkUseRecords ? (object) => {
checkUseRecords(object) ? writeRecord(object) : writePlainObject(object);
} : writeRecord;
const makeRoom = (end) => {
let newSize;
if (end > 0x1000000) {
// special handling for really large buffers
if ((end - start) > MAX_BUFFER_SIZE)
throw new Error('Packed buffer would be larger than maximum buffer size')
newSize = Math.min(MAX_BUFFER_SIZE,
Math.round(Math.max((end - start) * (end > 0x4000000 ? 1.25 : 2), 0x400000) / 0x1000) * 0x1000);
} else // faster handling for smaller buffers
newSize = ((Math.max((end - start) << 2, target.length - 1) >> 12) + 1) << 12;
let newBuffer = new ByteArrayAllocate(newSize);
targetView = newBuffer.dataView || (newBuffer.dataView = new DataView(newBuffer.buffer, 0, newSize));
end = Math.min(end, target.length);
if (target.copy)
target.copy(newBuffer, 0, start, end);
else
newBuffer.set(target.slice(start, end));
position -= start;
start = 0;
safeEnd = newBuffer.length - 10;
return target = newBuffer
};
const newRecord = (transition, keys, newTransitions) => {
let recordId = structures.nextId;
if (!recordId)
recordId = 0x40;
if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
recordId = structures.nextOwnId;
if (!(recordId < maxStructureId))
recordId = sharedLimitId;
structures.nextOwnId = recordId + 1;
} else {
if (recordId >= maxStructureId)// cycle back around
recordId = sharedLimitId;
structures.nextId = recordId + 1;
}
let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1;
transition[RECORD_SYMBOL] = recordId;
transition.__keys__ = keys;
structures[recordId - 0x40] = keys;
if (recordId < sharedLimitId) {
keys.isShared = true;
structures.sharedLength = recordId - 0x3f;
hasSharedUpdate = true;
if (highByte >= 0) {
target[position++] = (recordId & 0x1f) + 0x60;
target[position++] = highByte;
} else {
target[position++] = recordId;
}
} else {
if (highByte >= 0) {
target[position++] = 0xd5; // fixext 2
target[position++] = 0x72; // "r" record defintion extension type
target[position++] = (recordId & 0x1f) + 0x60;
target[position++] = highByte;
} else {
target[position++] = 0xd4; // fixext 1
target[position++] = 0x72; // "r" record defintion extension type
target[position++] = recordId;
}
if (newTransitions)
transitionsCount += serializationsSinceTransitionRebuild * newTransitions;
// record the removal of the id, we can maintain our shared structure
if (recordIdsToRemove.length >= maxOwnStructures)
recordIdsToRemove.shift()[RECORD_SYMBOL] = 0; // we are cycling back through, and have to remove old ones
recordIdsToRemove.push(transition);
pack(keys);
}
};
const insertNewRecord = (transition, keys, insertionOffset, newTransitions) => {
let mainTarget = target;
let mainPosition = position;
let mainSafeEnd = safeEnd;
let mainStart = start;
target = keysTarget;
position = 0;
start = 0;
if (!target)
keysTarget = target = new ByteArrayAllocate(8192);
safeEnd = target.length - 10;
newRecord(transition, keys, newTransitions);
keysTarget = target;
let keysPosition = position;
target = mainTarget;
position = mainPosition;
safeEnd = mainSafeEnd;
start = mainStart;
if (keysPosition > 1) {
let newEnd = position + keysPosition - 1;
if (newEnd > safeEnd)
makeRoom(newEnd);
let insertionPosition = insertionOffset + start;
target.copyWithin(insertionPosition + keysPosition, insertionPosition + 1, position);
target.set(keysTarget.slice(0, keysPosition), insertionPosition);
position = newEnd;
} else {
target[insertionOffset + start] = keysTarget[0];
}
};
const writeStruct = (object) => {
let newPosition = writeStructSlots(object, target, start, position, structures, makeRoom, (value, newPosition, notifySharedUpdate) => {
if (notifySharedUpdate)
return hasSharedUpdate = true;
position = newPosition;
let startTarget = target;
pack(value);
resetStructures();
if (startTarget !== target) {
return { position, targetView, target }; // indicate the buffer was re-allocated
}
return position;
}, this);
if (newPosition === 0) // bail and go to a msgpack object
return writeObject(object);
position = newPosition;
};
}
useBuffer(buffer) {
// this means we are finished using our own buffer and we can write over it safely
target = buffer;
target.dataView || (target.dataView = new DataView(target.buffer, target.byteOffset, target.byteLength));
targetView = target.dataView;
position = 0;
}
set position (value) {
position = value;
}
get position() {
return position;
}
clearSharedData() {
if (this.structures)
this.structures = [];
if (this.typedStructs)
this.typedStructs = [];
}
}
extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, DataView, C1Type ];
extensions = [{
pack(date, allocateForWrite, pack) {
let seconds = date.getTime() / 1000;
if ((this.useTimestamp32 || date.getMilliseconds() === 0) && seconds >= 0 && seconds < 0x100000000) {
// Timestamp 32
let { target, targetView, position} = allocateForWrite(6);
target[position++] = 0xd6;
target[position++] = 0xff;
targetView.setUint32(position, seconds);
} else if (seconds > 0 && seconds < 0x100000000) {
// Timestamp 64
let { target, targetView, position} = allocateForWrite(10);
target[position++] = 0xd7;
target[position++] = 0xff;
targetView.setUint32(position, date.getMilliseconds() * 4000000 + ((seconds / 1000 / 0x100000000) >> 0));
targetView.setUint32(position + 4, seconds);
} else if (isNaN(seconds)) {
if (this.onInvalidDate) {
allocateForWrite(0);
return pack(this.onInvalidDate())
}
// Intentionally invalid timestamp
let { target, targetView, position} = allocateForWrite(3);
target[position++] = 0xd4;
target[position++] = 0xff;
target[position++] = 0xff;
} else {
// Timestamp 96
let { target, targetView, position} = allocateForWrite(15);
target[position++] = 0xc7;
target[position++] = 12;
target[position++] = 0xff;
targetView.setUint32(position, date.getMilliseconds() * 1000000);
targetView.setBigInt64(position + 4, BigInt(Math.floor(seconds)));
}
}
}, {
pack(set, allocateForWrite, pack) {
if (this.setAsEmptyObject) {
allocateForWrite(0);
return pack({})
}
let array = Array.from(set);
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x73; // 's' for Set
target[position++] = 0;
}
pack(array);
}
}, {
pack(error, allocateForWrite, pack) {
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x65; // 'e' for error
target[position++] = 0;
}
pack([ error.name, error.message, error.cause ]);
}
}, {
pack(regex, allocateForWrite, pack) {
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x78; // 'x' for regeXp
target[position++] = 0;
}
pack([ regex.source, regex.flags ]);
}
}, {
pack(arrayBuffer, allocateForWrite) {
if (this.moreTypes)
writeExtBuffer(arrayBuffer, 0x10, allocateForWrite);
else
writeBuffer(hasNodeBuffer ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
}
}, {
pack(typedArray, allocateForWrite) {
let constructor = typedArray.constructor;
if (constructor !== ByteArray && this.moreTypes)
writeExtBuffer(typedArray, typedArrays.indexOf(constructor.name), allocateForWrite);
else
writeBuffer(typedArray, allocateForWrite);
}
}, {
pack(arrayBuffer, allocateForWrite) {
if (this.moreTypes)
writeExtBuffer(arrayBuffer, 0x11, allocateForWrite);
else
writeBuffer(hasNodeBuffer ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
}
}, {
pack(c1, allocateForWrite) { // specific 0xC1 object
let { target, position} = allocateForWrite(1);
target[position] = 0xc1;
}
}];
function writeExtBuffer(typedArray, type, allocateForWrite, encode) {
let length = typedArray.byteLength;
if (length + 1 < 0x100) {
var { target, position } = allocateForWrite(4 + length);
target[position++] = 0xc7;
target[position++] = length + 1;
} else if (length + 1 < 0x10000) {
var { target, position } = allocateForWrite(5 + length);
target[position++] = 0xc8;
target[position++] = (length + 1) >> 8;
target[position++] = (length + 1) & 0xff;
} else {
var { target, position, targetView } = allocateForWrite(7 + length);
target[position++] = 0xc9;
targetView.setUint32(position, length + 1); // plus one for the type byte
position += 4;
}
target[position++] = 0x74; // "t" for typed array
target[position++] = type;
if (!typedArray.buffer) typedArray = new Uint8Array(typedArray);
target.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength), position);
}
function writeBuffer(buffer, allocateForWrite) {
let length = buffer.byteLength;
var target, position;
if (length < 0x100) {
var { target, position } = allocateForWrite(length + 2);
target[position++] = 0xc4;
target[position++] = length;
} else if (length < 0x10000) {
var { target, position } = allocateForWrite(length + 3);
target[position++] = 0xc5;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
var { target, position, targetView } = allocateForWrite(length + 5);
target[position++] = 0xc6;
targetView.setUint32(position, length);
position += 4;
}
target.set(buffer, position);
}
function writeExtensionData(result, target, position, type) {
let length = result.length;
switch (length) {
case 1:
target[position++] = 0xd4;
break
case 2:
target[position++] = 0xd5;
break
case 4:
target[position++] = 0xd6;
break
case 8:
target[position++] = 0xd7;
break
case 16:
target[position++] = 0xd8;
break
default:
if (length < 0x100) {
target[position++] = 0xc7;
target[position++] = length;
} else if (length < 0x10000) {
target[position++] = 0xc8;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xc9;
target[position++] = length >> 24;
target[position++] = (length >> 16) & 0xff;
target[position++] = (length >> 8) & 0xff;
target[position++] = length & 0xff;
}
}
target[position++] = type;
target.set(result, position);
position += length;
return position
}
function insertIds(serialized, idsToInsert) {
// insert the ids that need to be referenced for structured clones
let nextId;
let distanceToMove = idsToInsert.length * 6;
let lastEnd = serialized.length - distanceToMove;
while (nextId = idsToInsert.pop()) {
let offset = nextId.offset;
let id = nextId.id;
serialized.copyWithin(offset + distanceToMove, offset, lastEnd);
distanceToMove -= 6;
let position = offset + distanceToMove;
serialized[position++] = 0xd6;
serialized[position++] = 0x69; // 'i'
serialized[position++] = id >> 24;
serialized[position++] = (id >> 16) & 0xff;
serialized[position++] = (id >> 8) & 0xff;
serialized[position++] = id & 0xff;
lastEnd = offset;
}
return serialized
}
function writeBundles(start, pack, incrementPosition) {
if (bundledStrings.length > 0) {
targetView.setUint32(bundledStrings.position + start, position + incrementPosition - bundledStrings.position - start);
bundledStrings.stringsPosition = position - start;
let writeStrings = bundledStrings;
bundledStrings = null;
pack(writeStrings[0]);
pack(writeStrings[1]);
}
}
function addExtension(extension) {
if (extension.Class) {
if (!extension.pack && !extension.write)
throw new Error('Extension has no pack or write function')
if (extension.pack && !extension.type)
throw new Error('Extension has no type (numeric code to identify the extension)')
extensionClasses.unshift(extension.Class);
extensions.unshift(extension);
}
addExtension$1(extension);
}
function prepareStructures(structures, packr) {
structures.isCompatible = (existingStructures) => {
let compatible = !existingStructures || ((packr.lastNamedStructuresLength || 0) === existingStructures.length);
if (!compatible) // we want to merge these existing structures immediately since we already have it and we are in the right transaction
packr._mergeStructures(existingStructures);
return compatible;
};
return structures
}
let defaultPackr = new Packr({ useRecords: false });
const pack = defaultPackr.pack;
const encode = defaultPackr.pack;
const Encoder = Packr;
const { NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } = FLOAT32_OPTIONS;
const REUSE_BUFFER_MODE = 512;
const RESET_BUFFER_MODE = 1024;
const RESERVE_START_SPACE = 2048;
/**
* Given an Iterable first argument, returns an Iterable where each value is packed as a Buffer
* If the argument is only Async Iterable, the return value will be an Async Iterable.
* @param {Iterable|Iterator|AsyncIterable|AsyncIterator} objectIterator - iterable source, like a Readable object stream, an array, Set, or custom object
* @param {options} [options] - msgpackr pack options
* @returns {IterableIterator|Promise.<AsyncIterableIterator>}
*/
function packIter (objectIterator, options = {}) {
if (!objectIterator || typeof objectIterator !== 'object') {
throw new Error('first argument must be an Iterable, Async Iterable, or a Promise for an Async Iterable')
} else if (typeof objectIterator[Symbol.iterator] === 'function') {
return packIterSync(objectIterator, options)
} else if (typeof objectIterator.then === 'function' || typeof objectIterator[Symbol.asyncIterator] === 'function') {
return packIterAsync(objectIterator, options)
} else {
throw new Error('first argument must be an Iterable, Async Iterable, Iterator, Async Iterator, or a Promise')
}
}
function * packIterSync (objectIterator, options) {
const packr = new Packr(options);
for (const value of objectIterator) {
yield packr.pack(value);
}
}
async function * packIterAsync (objectIterator, options) {
const packr = new Packr(options);
for await (const value of objectIterator) {
yield packr.pack(value);
}
}
/**
* Given an Iterable/Iterator input which yields buffers, returns an IterableIterator which yields sync decoded objects
* Or, given an Async Iterable/Iterator which yields promises resolving in buffers, returns an AsyncIterableIterator.
* @param {Iterable|Iterator|AsyncIterable|AsyncIterableIterator} bufferIterator
* @param {object} [options] - unpackr options
* @returns {IterableIterator|Promise.<AsyncIterableIterator}
*/
function unpackIter (bufferIterator, options = {}) {
if (!bufferIterator || typeof bufferIterator !== 'object') {
throw new Error('first argument must be an Iterable, Async Iterable, Iterator, Async Iterator, or a promise')
}
const unpackr = new Unpackr(options);
let incomplete;
const parser = (chunk) => {
let yields;
// if there's incomplete data from previous chunk, concatinate and try again
if (incomplete) {
chunk = Buffer.concat([incomplete, chunk]);
incomplete = undefined;
}
try {
yields = unpackr.unpackMultiple(chunk);
} catch (err) {
if (err.incomplete) {
incomplete = chunk.slice(err.lastPosition);
yields = err.values;
} else {
throw err
}
}
return yields
};
if (typeof bufferIterator[Symbol.iterator] === 'function') {
return (function * iter () {
for (const value of bufferIterator) {
yield * parser(value);
}
})()
} else if (typeof bufferIterator[Symbol.asyncIterator] === 'function') {
return (async function * iter () {
for await (const value of bufferIterator) {
yield * parser(value);
}
})()
}
}
const decodeIter = unpackIter;
const encodeIter = packIter;
const useRecords = false;
const mapsAsObjects = true;
exports.ALWAYS = ALWAYS;
exports.C1 = C1;
exports.DECIMAL_FIT = DECIMAL_FIT;
exports.DECIMAL_ROUND = DECIMAL_ROUND;
exports.Decoder = Decoder;
exports.Encoder = Encoder;
exports.FLOAT32_OPTIONS = FLOAT32_OPTIONS;
exports.NEVER = NEVER;
exports.Packr = Packr;
exports.RESERVE_START_SPACE = RESERVE_START_SPACE;
exports.RESET_BUFFER_MODE = RESET_BUFFER_MODE;
exports.REUSE_BUFFER_MODE = REUSE_BUFFER_MODE;
exports.Unpackr = Unpackr;
exports.addExtension = addExtension;
exports.clearSource = clearSource;
exports.decode = decode;
exports.decodeIter = decodeIter;
exports.encode = encode;
exports.encodeIter = encodeIter;
exports.isNativeAccelerationEnabled = isNativeAccelerationEnabled;
exports.mapsAsObjects = mapsAsObjects;
exports.pack = pack;
exports.roundFloat32 = roundFloat32;
exports.unpack = unpack;
exports.unpackMultiple = unpackMultiple;
exports.useRecords = useRecords;
}));
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3438
View File
@@ -0,0 +1,3438 @@
'use strict';
var stream = require('stream');
var module$1 = require('module');
var decoder;
try {
decoder = new TextDecoder();
} catch(error) {}
var src;
var srcEnd;
var position$1 = 0;
const EMPTY_ARRAY = [];
var strings = EMPTY_ARRAY;
var stringPosition = 0;
var currentUnpackr = {};
var currentStructures;
var srcString;
var srcStringStart = 0;
var srcStringEnd = 0;
var bundledStrings$1;
var referenceMap;
var currentExtensions = [];
var dataView;
var defaultOptions = {
useRecords: false,
mapsAsObjects: true
};
class C1Type {}
const C1 = new C1Type();
C1.name = 'MessagePack 0xC1';
var sequentialMode = false;
var inlineObjectReadThreshold = 2;
var readStruct$1, onLoadedStructures$1, onSaveState;
class Unpackr {
constructor(options) {
if (options) {
if (options.useRecords === false && options.mapsAsObjects === undefined)
options.mapsAsObjects = true;
if (options.sequential && options.trusted !== false) {
options.trusted = true;
if (!options.structures && options.useRecords != false) {
options.structures = [];
if (!options.maxSharedStructures)
options.maxSharedStructures = 0;
}
}
if (options.structures)
options.structures.sharedLength = options.structures.length;
else if (options.getStructures) {
(options.structures = []).uninitialized = true; // this is what we use to denote an uninitialized structures
options.structures.sharedLength = 0;
}
if (options.int64AsNumber) {
options.int64AsType = 'number';
}
}
Object.assign(this, options);
}
unpack(source, options) {
if (src) {
// re-entrant execution, save the state and restore it after we do this unpack
return saveState$1(() => {
clearSource();
return this ? this.unpack(source, options) : Unpackr.prototype.unpack.call(defaultOptions, source, options)
})
}
if (!source.buffer && source.constructor === ArrayBuffer)
source = typeof Buffer !== 'undefined' ? Buffer.from(source) : new Uint8Array(source);
if (typeof options === 'object') {
srcEnd = options.end || source.length;
position$1 = options.start || 0;
} else {
position$1 = 0;
srcEnd = options > -1 ? options : source.length;
}
stringPosition = 0;
srcStringEnd = 0;
srcString = null;
strings = EMPTY_ARRAY;
bundledStrings$1 = null;
src = source;
// this provides cached access to the data view for a buffer if it is getting reused, which is a recommend
// technique for getting data from a database where it can be copied into an existing buffer instead of creating
// new ones
try {
dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength));
} catch(error) {
// if it doesn't have a buffer, maybe it is the wrong type of object
src = null;
if (source instanceof Uint8Array)
throw error
throw new Error('Source must be a Uint8Array or Buffer but was a ' + ((source && typeof source == 'object') ? source.constructor.name : typeof source))
}
if (this instanceof Unpackr) {
currentUnpackr = this;
if (this.structures) {
currentStructures = this.structures;
return checkedRead(options)
} else if (!currentStructures || currentStructures.length > 0) {
currentStructures = [];
}
} else {
currentUnpackr = defaultOptions;
if (!currentStructures || currentStructures.length > 0)
currentStructures = [];
}
return checkedRead(options)
}
unpackMultiple(source, forEach) {
let values, lastPosition = 0;
try {
sequentialMode = true;
let size = source.length;
let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size);
if (forEach) {
if (forEach(value, lastPosition, position$1) === false) return;
while(position$1 < size) {
lastPosition = position$1;
if (forEach(checkedRead(), lastPosition, position$1) === false) {
return
}
}
}
else {
values = [ value ];
while(position$1 < size) {
lastPosition = position$1;
values.push(checkedRead());
}
return values
}
} catch(error) {
error.lastPosition = lastPosition;
error.values = values;
throw error
} finally {
sequentialMode = false;
clearSource();
}
}
_mergeStructures(loadedStructures, existingStructures) {
if (onLoadedStructures$1)
loadedStructures = onLoadedStructures$1.call(this, loadedStructures);
loadedStructures = loadedStructures || [];
if (Object.isFrozen(loadedStructures))
loadedStructures = loadedStructures.map(structure => structure.slice(0));
for (let i = 0, l = loadedStructures.length; i < l; i++) {
let structure = loadedStructures[i];
if (structure) {
structure.isShared = true;
if (i >= 32)
structure.highByte = (i - 32) >> 5;
}
}
loadedStructures.sharedLength = loadedStructures.length;
for (let id in existingStructures || []) {
if (id >= 0) {
let structure = loadedStructures[id];
let existing = existingStructures[id];
if (existing) {
if (structure)
(loadedStructures.restoreStructures || (loadedStructures.restoreStructures = []))[id] = structure;
loadedStructures[id] = existing;
}
}
}
return this.structures = loadedStructures
}
decode(source, options) {
return this.unpack(source, options)
}
}
function checkedRead(options) {
try {
if (!currentUnpackr.trusted && !sequentialMode) {
let sharedLength = currentStructures.sharedLength || 0;
if (sharedLength < currentStructures.length)
currentStructures.length = sharedLength;
}
let result;
if (currentUnpackr.randomAccessStructure && src[position$1] < 0x40 && src[position$1] >= 0x20 && readStruct$1) {
result = readStruct$1(src, position$1, srcEnd, currentUnpackr);
src = null; // dispose of this so that recursive unpack calls don't save state
if (!(options && options.lazy) && result)
result = result.toJSON();
position$1 = srcEnd;
} else
result = read();
if (bundledStrings$1) { // bundled strings to skip past
position$1 = bundledStrings$1.postBundlePosition;
bundledStrings$1 = null;
}
if (sequentialMode)
// we only need to restore the structures if there was an error, but if we completed a read,
// we can clear this out and keep the structures we read
currentStructures.restoreStructures = null;
if (position$1 == srcEnd) {
// finished reading this source, cleanup references
if (currentStructures && currentStructures.restoreStructures)
restoreStructures();
currentStructures = null;
src = null;
if (referenceMap)
referenceMap = null;
} else if (position$1 > srcEnd) {
// over read
throw new Error('Unexpected end of MessagePack data')
} else if (!sequentialMode) {
let jsonView;
try {
jsonView = JSON.stringify(result, (_, value) => typeof value === "bigint" ? `${value}n` : value).slice(0, 100);
} catch(error) {
jsonView = '(JSON view not available ' + error + ')';
}
throw new Error('Data read, but end of buffer not reached ' + jsonView)
}
// else more to read, but we are reading sequentially, so don't clear source yet
return result
} catch(error) {
if (currentStructures && currentStructures.restoreStructures)
restoreStructures();
clearSource();
if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer') || position$1 > srcEnd) {
error.incomplete = true;
}
throw error
}
}
function restoreStructures() {
for (let id in currentStructures.restoreStructures) {
currentStructures[id] = currentStructures.restoreStructures[id];
}
currentStructures.restoreStructures = null;
}
function read() {
let token = src[position$1++];
if (token < 0xa0) {
if (token < 0x80) {
if (token < 0x40)
return token
else {
let structure = currentStructures[token & 0x3f] ||
currentUnpackr.getStructures && loadStructures()[token & 0x3f];
if (structure) {
if (!structure.read) {
structure.read = createStructureReader(structure, token & 0x3f);
}
return structure.read()
} else
return token
}
} else if (token < 0x90) {
// map
token -= 0x80;
if (currentUnpackr.mapsAsObjects) {
let object = {};
for (let i = 0; i < token; i++) {
let key = readKey();
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
return object
} else {
let map = new Map();
for (let i = 0; i < token; i++) {
map.set(read(), read());
}
return map
}
} else {
token -= 0x90;
let array = new Array(token);
for (let i = 0; i < token; i++) {
array[i] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
} else if (token < 0xc0) {
// fixstr
let length = token - 0xa0;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += length) - srcStringStart)
}
if (srcStringEnd == 0 && srcEnd < 140) {
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length);
if (string != null)
return string
}
return readFixedString(length)
} else {
let value;
switch (token) {
case 0xc0: return null
case 0xc1:
if (bundledStrings$1) {
value = read(); // followed by the length of the string in characters (not bytes!)
if (value > 0)
return bundledStrings$1[1].slice(bundledStrings$1.position1, bundledStrings$1.position1 += value)
else
return bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 -= value)
}
return C1; // "never-used", return special object to denote that
case 0xc2: return false
case 0xc3: return true
case 0xc4:
// bin 8
value = src[position$1++];
if (value === undefined)
throw new Error('Unexpected end of buffer')
return readBin(value)
case 0xc5:
// bin 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readBin(value)
case 0xc6:
// bin 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readBin(value)
case 0xc7:
// ext 8
return readExt(src[position$1++])
case 0xc8:
// ext 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readExt(value)
case 0xc9:
// ext 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readExt(value)
case 0xca:
value = dataView.getFloat32(position$1);
if (currentUnpackr.useFloat32 > 2) {
// this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
let multiplier = mult10[((src[position$1] & 0x7f) << 1) | (src[position$1 + 1] >> 7)];
position$1 += 4;
return ((multiplier * value + (value > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
position$1 += 4;
return value
case 0xcb:
value = dataView.getFloat64(position$1);
position$1 += 8;
return value
// uint handlers
case 0xcc:
return src[position$1++]
case 0xcd:
value = dataView.getUint16(position$1);
position$1 += 2;
return value
case 0xce:
value = dataView.getUint32(position$1);
position$1 += 4;
return value
case 0xcf:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getUint32(position$1) * 0x100000000;
value += dataView.getUint32(position$1 + 4);
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigUint64(position$1).toString();
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigUint64(position$1);
if (value<=BigInt(2)<<BigInt(52)) value=Number(value);
} else
value = dataView.getBigUint64(position$1);
position$1 += 8;
return value
// int handlers
case 0xd0:
return dataView.getInt8(position$1++)
case 0xd1:
value = dataView.getInt16(position$1);
position$1 += 2;
return value
case 0xd2:
value = dataView.getInt32(position$1);
position$1 += 4;
return value
case 0xd3:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getInt32(position$1) * 0x100000000;
value += dataView.getUint32(position$1 + 4);
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigInt64(position$1).toString();
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigInt64(position$1);
if (value>=BigInt(-2)<<BigInt(52)&&value<=BigInt(2)<<BigInt(52)) value=Number(value);
} else
value = dataView.getBigInt64(position$1);
position$1 += 8;
return value
case 0xd4:
// fixext 1
value = src[position$1++];
if (value == 0x72) {
return recordDefinition(src[position$1++] & 0x3f)
} else {
let extension = currentExtensions[value];
if (extension) {
if (extension.read) {
position$1++; // skip filler byte
return extension.read(read())
} else if (extension.noBuffer) {
position$1++; // skip filler byte
return extension()
} else
return extension(src.subarray(position$1, ++position$1))
} else
throw new Error('Unknown extension ' + value)
}
case 0xd5:
// fixext 2
value = src[position$1];
if (value == 0x72) {
position$1++;
return recordDefinition(src[position$1++] & 0x3f, src[position$1++])
} else
return readExt(2)
case 0xd6:
// fixext 4
return readExt(4)
case 0xd7:
// fixext 8
return readExt(8)
case 0xd8:
// fixext 16
return readExt(16)
case 0xd9:
// str 8
value = src[position$1++];
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString8(value)
case 0xda:
// str 16
value = dataView.getUint16(position$1);
position$1 += 2;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString16(value)
case 0xdb:
// str 32
value = dataView.getUint32(position$1);
position$1 += 4;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString32(value)
case 0xdc:
// array 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readArray(value)
case 0xdd:
// array 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readArray(value)
case 0xde:
// map 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readMap(value)
case 0xdf:
// map 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readMap(value)
default: // negative int
if (token >= 0xe0)
return token - 0x100
if (token === undefined) {
let error = new Error('Unexpected end of MessagePack data');
error.incomplete = true;
throw error
}
throw new Error('Unknown MessagePack token ' + token)
}
}
}
const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/;
function createStructureReader(structure, firstId) {
function readObject() {
// This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function
if (readObject.count++ > inlineObjectReadThreshold) {
let optimizedReadObject;
try {
optimizedReadObject = structure.read = (new Function('r', 'return function(){return ' + (currentUnpackr.freezeData ? 'Object.freeze' : '') +
'({' + structure.map(key => key === '__proto__' ? '__proto_:r()' : validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '})}'))(read);
} catch(error) {
// in CF workers, the new Function call could begin to fail at any point in time
inlineObjectReadThreshold = Infinity; // disable going forward
return readObject(); // recursively try again
}
structure.read0 = optimizedReadObject; // keep the un-wrapped body reader in sync
if (structure.highByte === 0)
structure.read = createSecondByteReader(firstId, structure.read);
return optimizedReadObject() // second byte is already read, if there is one so immediately read object
}
let object = {};
for (let i = 0, l = structure.length; i < l; i++) {
let key = structure[i];
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(object);
return object
}
readObject.count = 0;
// read0 is the un-wrapped body reader: it reads the record's values directly without
// consuming a leading high byte. recordDefinition uses it for the immediate read that follows
// a record definition (the high byte, if present, was already consumed). For highByte === 0
// structures the public reader is a second-byte reader (used by later references), but the
// definition read itself must not consume that byte.
structure.read0 = readObject;
if (structure.highByte === 0) {
return createSecondByteReader(firstId, readObject)
}
return readObject
}
const createSecondByteReader = (firstId, read0) => {
return function() {
let highByte = src[position$1++];
if (highByte === 0)
return read0()
let id = firstId < 32 ? -(firstId + (highByte << 5)) : firstId + (highByte << 5);
let structure = currentStructures[id] || loadStructures()[id];
if (!structure) {
throw new Error('Record id is not defined for ' + id)
}
if (!structure.read)
structure.read = createStructureReader(structure, firstId);
return structure.read()
}
};
function loadStructures() {
let loadedStructures = saveState$1(() => {
// save the state in case getStructures modifies our buffer
src = null;
return currentUnpackr.getStructures()
});
return currentStructures = currentUnpackr._mergeStructures(loadedStructures, currentStructures)
}
var readFixedString = readStringJS;
var readString8 = readStringJS;
var readString16 = readStringJS;
var readString32 = readStringJS;
exports.isNativeAccelerationEnabled = false;
function setExtractor(extractStrings) {
exports.isNativeAccelerationEnabled = true;
readFixedString = readString(1);
readString8 = readString(2);
readString16 = readString(3);
readString32 = readString(5);
function readString(headerLength) {
return function readString(length) {
let string = strings[stringPosition++];
if (string == null) {
if (bundledStrings$1)
return readStringJS(length)
let byteOffset = src.byteOffset;
let extraction = extractStrings(position$1 - headerLength + byteOffset, srcEnd + byteOffset, src.buffer);
if (typeof extraction == 'string') {
string = extraction;
strings = EMPTY_ARRAY;
} else {
strings = extraction;
stringPosition = 1;
srcStringEnd = 1; // even if a utf-8 string was decoded, must indicate we are in the midst of extracted strings and can't skip strings
string = strings[0];
if (string === undefined)
throw new Error('Unexpected end of buffer')
}
}
let srcStringLength = string.length;
if (srcStringLength <= length) {
position$1 += length;
return string
}
srcString = string;
srcStringStart = position$1;
srcStringEnd = position$1 + srcStringLength;
position$1 += length;
return string.slice(0, length) // we know we just want the beginning
}
}
}
function readStringJS(length) {
let result;
if (length < 16) {
if (result = shortStringInJS(length))
return result
}
if (length > 64 && decoder)
return decoder.decode(src.subarray(position$1, position$1 += length))
const end = position$1 + length;
const units = [];
result = '';
while (position$1 < end) {
const byte1 = src[position$1++];
if ((byte1 & 0x80) === 0) {
// 1 byte
units.push(byte1);
} else if ((byte1 & 0xe0) === 0xc0) {
// 2 bytes
const byte2 = src[position$1++] & 0x3f;
const codePoint = ((byte1 & 0x1f) << 6) | byte2;
// Reject overlong encoding: 2-byte sequences must encode values >= 0x80
if (codePoint < 0x80) {
units.push(0xFFFD); // replacement character
} else {
units.push(codePoint);
}
} else if ((byte1 & 0xf0) === 0xe0) {
// 3 bytes
const byte2 = src[position$1++] & 0x3f;
const byte3 = src[position$1++] & 0x3f;
const codePoint = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3;
// Reject overlong encoding: 3-byte sequences must encode values >= 0x800
// Also reject surrogates (0xD800-0xDFFF)
if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
units.push(0xFFFD); // replacement character
} else {
units.push(codePoint);
}
} else if ((byte1 & 0xf8) === 0xf0) {
// 4 bytes
const byte2 = src[position$1++] & 0x3f;
const byte3 = src[position$1++] & 0x3f;
const byte4 = src[position$1++] & 0x3f;
let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
// Reject overlong encoding: 4-byte sequences must encode values >= 0x10000
// Also reject values > 0x10FFFF (maximum valid Unicode)
if (unit < 0x10000 || unit > 0x10FFFF) {
units.push(0xFFFD); // replacement character
} else if (unit > 0xffff) {
unit -= 0x10000;
units.push(((unit >>> 10) & 0x3ff) | 0xd800);
unit = 0xdc00 | (unit & 0x3ff);
units.push(unit);
} else {
units.push(unit);
}
} else {
units.push(0xFFFD); // replacement character for invalid lead byte
}
if (units.length >= 0x1000) {
result += fromCharCode.apply(String, units);
units.length = 0;
}
}
if (units.length > 0) {
result += fromCharCode.apply(String, units);
}
return result
}
function readString(source, start, length) {
let existingSrc = src;
src = source;
position$1 = start;
try {
return readStringJS(length);
} finally {
src = existingSrc;
}
}
function readArray(length) {
let array = new Array(length);
for (let i = 0; i < length; i++) {
array[i] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
function readMap(length) {
if (currentUnpackr.mapsAsObjects) {
let object = {};
for (let i = 0; i < length; i++) {
let key = readKey();
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
return object
} else {
let map = new Map();
for (let i = 0; i < length; i++) {
map.set(read(), read());
}
return map
}
}
var fromCharCode = String.fromCharCode;
function longStringInJS(length) {
let start = position$1;
let bytes = new Array(length);
for (let i = 0; i < length; i++) {
const byte = src[position$1++];
if ((byte & 0x80) > 0) {
position$1 = start;
return
}
bytes[i] = byte;
}
return fromCharCode.apply(String, bytes)
}
function shortStringInJS(length) {
if (length < 4) {
if (length < 2) {
if (length === 0)
return ''
else {
let a = src[position$1++];
if ((a & 0x80) > 1) {
position$1 -= 1;
return
}
return fromCharCode(a)
}
} else {
let a = src[position$1++];
let b = src[position$1++];
if ((a & 0x80) > 0 || (b & 0x80) > 0) {
position$1 -= 2;
return
}
if (length < 3)
return fromCharCode(a, b)
let c = src[position$1++];
if ((c & 0x80) > 0) {
position$1 -= 3;
return
}
return fromCharCode(a, b, c)
}
} else {
let a = src[position$1++];
let b = src[position$1++];
let c = src[position$1++];
let d = src[position$1++];
if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
position$1 -= 4;
return
}
if (length < 6) {
if (length === 4)
return fromCharCode(a, b, c, d)
else {
let e = src[position$1++];
if ((e & 0x80) > 0) {
position$1 -= 5;
return
}
return fromCharCode(a, b, c, d, e)
}
} else if (length < 8) {
let e = src[position$1++];
let f = src[position$1++];
if ((e & 0x80) > 0 || (f & 0x80) > 0) {
position$1 -= 6;
return
}
if (length < 7)
return fromCharCode(a, b, c, d, e, f)
let g = src[position$1++];
if ((g & 0x80) > 0) {
position$1 -= 7;
return
}
return fromCharCode(a, b, c, d, e, f, g)
} else {
let e = src[position$1++];
let f = src[position$1++];
let g = src[position$1++];
let h = src[position$1++];
if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) {
position$1 -= 8;
return
}
if (length < 10) {
if (length === 8)
return fromCharCode(a, b, c, d, e, f, g, h)
else {
let i = src[position$1++];
if ((i & 0x80) > 0) {
position$1 -= 9;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i)
}
} else if (length < 12) {
let i = src[position$1++];
let j = src[position$1++];
if ((i & 0x80) > 0 || (j & 0x80) > 0) {
position$1 -= 10;
return
}
if (length < 11)
return fromCharCode(a, b, c, d, e, f, g, h, i, j)
let k = src[position$1++];
if ((k & 0x80) > 0) {
position$1 -= 11;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k)
} else {
let i = src[position$1++];
let j = src[position$1++];
let k = src[position$1++];
let l = src[position$1++];
if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) {
position$1 -= 12;
return
}
if (length < 14) {
if (length === 12)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l)
else {
let m = src[position$1++];
if ((m & 0x80) > 0) {
position$1 -= 13;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m)
}
} else {
let m = src[position$1++];
let n = src[position$1++];
if ((m & 0x80) > 0 || (n & 0x80) > 0) {
position$1 -= 14;
return
}
if (length < 15)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n)
let o = src[position$1++];
if ((o & 0x80) > 0) {
position$1 -= 15;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
}
}
}
}
}
function readOnlyJSString() {
let token = src[position$1++];
let length;
if (token < 0xc0) {
// fixstr
length = token - 0xa0;
} else {
switch(token) {
case 0xd9:
// str 8
length = src[position$1++];
break
case 0xda:
// str 16
length = dataView.getUint16(position$1);
position$1 += 2;
break
case 0xdb:
// str 32
length = dataView.getUint32(position$1);
position$1 += 4;
break
default:
throw new Error('Expected string')
}
}
return readStringJS(length)
}
function readBin(length) {
return currentUnpackr.copyBuffers ?
// specifically use the copying slice (not the node one)
Uint8Array.prototype.slice.call(src, position$1, position$1 += length) :
src.subarray(position$1, position$1 += length)
}
function readExt(length) {
let type = src[position$1++];
if (currentExtensions[type]) {
let end;
return currentExtensions[type](src.subarray(position$1, end = (position$1 += length)), (readPosition) => {
position$1 = readPosition;
try {
return read();
} finally {
position$1 = end;
}
})
}
else
throw new Error('Unknown extension type ' + type)
}
var keyCache = new Array(4096);
function readKey() {
let length = src[position$1++];
if (length >= 0xa0 && length < 0xc0) {
// fixstr, potentially use key cache
length = length - 0xa0;
if (srcStringEnd >= position$1) // if it has been extracted, must use it (and faster anyway)
return srcString.slice(position$1 - srcStringStart, (position$1 += length) - srcStringStart)
else if (!(srcStringEnd == 0 && srcEnd < 180))
return readFixedString(length)
} else { // not cacheable, go back and do a standard read
position$1--;
return asSafeString(read())
}
let key = ((length << 5) ^ (length > 1 ? dataView.getUint16(position$1) : length > 0 ? src[position$1] : 0)) & 0xfff;
let entry = keyCache[key];
let checkPosition = position$1;
let end = position$1 + length - 3;
let chunk;
let i = 0;
if (entry && entry.bytes == length) {
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition);
if (chunk != entry[i++]) {
checkPosition = 0x70000000;
break
}
checkPosition += 4;
}
end += 3;
while (checkPosition < end) {
chunk = src[checkPosition++];
if (chunk != entry[i++]) {
checkPosition = 0x70000000;
break
}
}
if (checkPosition === end) {
position$1 = checkPosition;
return entry.string
}
end -= 3;
checkPosition = position$1;
}
entry = [];
keyCache[key] = entry;
entry.bytes = length;
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition);
entry.push(chunk);
checkPosition += 4;
}
end += 3;
while (checkPosition < end) {
chunk = src[checkPosition++];
entry.push(chunk);
}
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length);
if (string != null)
return entry.string = string
return entry.string = readFixedString(length)
}
function asSafeString(property) {
// protect against expensive (DoS) string conversions
if (typeof property === 'string') return property;
if (typeof property === 'number' || typeof property === 'boolean' || typeof property === 'bigint') return property.toString();
if (property == null) return property + '';
if (currentUnpackr.allowArraysInMapKeys && Array.isArray(property) && property.flat().every(item => ['string', 'number', 'boolean', 'bigint'].includes(typeof item))) {
return property.flat().toString();
}
throw new Error(`Invalid property type for record: ${typeof property}`);
}
// the registration of the record definition extension (as "r")
const recordDefinition = (id, highByte) => {
let structure = read().map(asSafeString); // ensure that all keys are strings and
// that the array is mutable
let firstByte = id;
if (highByte !== undefined) {
id = id < 32 ? -((highByte << 5) + id) : ((highByte << 5) + id);
structure.highByte = highByte;
}
let existingStructure = currentStructures[id];
// If it is a shared structure, we need to restore any changes after reading.
// Also in sequential mode, we may get incomplete reads and thus errors, and we need to restore
// to the state prior to an incomplete read in order to properly resume.
if (existingStructure && (existingStructure.isShared || sequentialMode)) {
(currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure;
}
currentStructures[id] = structure;
structure.read = createStructureReader(structure, firstByte);
// The high byte (if any) was already consumed as the `highByte` argument above, so read the
// record body directly. Going through structure.read (a second-byte reader when highByte === 0)
// would misinterpret the first value byte as a high byte — corrupting two-byte own-record
// definitions (0xd5 0x72 ...). createStructureReader stashes the un-wrapped body reader on
// structure.read0 precisely for this immediate post-definition read.
return (structure.read0 || structure.read)()
};
currentExtensions[0] = () => {}; // notepack defines extension 0 to mean undefined, so use that as the default here
currentExtensions[0].noBuffer = true;
currentExtensions[0x42] = data => {
let headLength = (data.byteLength % 8) || 8;
let head = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0]);
for (let i = 1; i < headLength; i++) {
head <<= BigInt(8);
head += BigInt(data[i]);
}
if (data.byteLength !== headLength) {
let view = new DataView(data.buffer, data.byteOffset, data.byteLength);
let decode = (start, end) => {
let length = end - start;
if (length <= 40) {
let out = view.getBigUint64(start);
for (let i = start + 8; i < end; i += 8) {
out <<= BigInt(64);
out |= view.getBigUint64(i);
}
return out
}
// if (length === 8) return view.getBigUint64(start)
let middle = start + (length >> 4 << 3);
let left = decode(start, middle);
let right = decode(middle, end);
return (left << BigInt((end - middle) * 8)) | right
};
head = (head << BigInt((view.byteLength - headLength) * 8)) | decode(headLength, view.byteLength);
}
return head
};
let errors = {
Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError: typeof AggregateError === 'function' ? AggregateError : null,
};
currentExtensions[0x65] = () => {
let data = read();
if (!errors[data[0]]) {
let error = Error(data[1], { cause: data[2] });
error.name = data[0];
return error
}
return errors[data[0]](data[1], { cause: data[2] })
};
currentExtensions[0x69] = (data) => {
// id extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position$1 - 4);
if (!referenceMap)
referenceMap = new Map();
let token = src[position$1];
let target;
// TODO: handle any other types that can cycle and make the code more robust if there are other extensions
if (token >= 0x90 && token < 0xa0 || token == 0xdc || token == 0xdd)
target = [];
else if (token >= 0x80 && token < 0x90 || token == 0xde || token == 0xdf)
target = new Map();
else if ((token >= 0xc7 && token <= 0xc9 || token >= 0xd4 && token <= 0xd8) && src[position$1 + 1] === 0x73)
target = new Set();
else
target = {};
let refEntry = { target }; // a placeholder object
referenceMap.set(id, refEntry);
let targetProperties = read(); // read the next value as the target object to id
if (!refEntry.used) {
// no cycle, can just use the returned read object
return refEntry.target = targetProperties // replace the placeholder with the real one
} else {
// there is a cycle, so we have to assign properties to original target
Object.assign(target, targetProperties);
}
// copy over map/set entries if we're able to
if (target instanceof Map)
for (let [k, v] of targetProperties.entries()) target.set(k, v);
if (target instanceof Set)
for (let i of Array.from(targetProperties)) target.add(i);
return target
};
currentExtensions[0x70] = (data) => {
// pointer extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position$1 - 4);
let refEntry = referenceMap.get(id);
refEntry.used = true;
return refEntry.target
};
currentExtensions[0x73] = () => new Set(read());
const typedArrays = ['Int8','Uint8','Uint8Clamped','Int16','Uint16','Int32','Uint32','Float32','Float64','BigInt64','BigUint64'].map(type => type + 'Array');
let glbl = typeof globalThis === 'object' ? globalThis : window;
currentExtensions[0x74] = (data) => {
let typeCode = data[0];
// we always have to slice to get a new ArrayBuffer that is aligned
let buffer = Uint8Array.prototype.slice.call(data, 1).buffer;
let typedArrayName = typedArrays[typeCode];
if (!typedArrayName) {
if (typeCode === 16) return buffer
if (typeCode === 17) return new DataView(buffer)
throw new Error('Could not find typed array for code ' + typeCode)
}
return new glbl[typedArrayName](buffer)
};
currentExtensions[0x78] = () => {
let data = read();
return new RegExp(data[0], data[1])
};
const TEMP_BUNDLE = [];
currentExtensions[0x62] = (data) => {
let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3];
let dataPosition = position$1;
position$1 += dataSize - data.length;
bundledStrings$1 = TEMP_BUNDLE;
bundledStrings$1 = [readOnlyJSString(), readOnlyJSString()];
bundledStrings$1.position0 = 0;
bundledStrings$1.position1 = 0;
bundledStrings$1.postBundlePosition = position$1;
position$1 = dataPosition;
return read()
};
currentExtensions[0xff] = (data) => {
// 32-bit date extension
if (data.length == 4)
return new Date((data[0] * 0x1000000 + (data[1] << 16) + (data[2] << 8) + data[3]) * 1000)
else if (data.length == 8)
return new Date(
((data[0] << 22) + (data[1] << 14) + (data[2] << 6) + (data[3] >> 2)) / 1000000 +
((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000)
else if (data.length == 12)
return new Date(
((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
(((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000)
else
return new Date('invalid')
};
// registration of bulk record definition?
// currentExtensions[0x52] = () =>
function saveState$1(callback) {
if (onSaveState)
onSaveState();
let savedSrcEnd = srcEnd;
let savedPosition = position$1;
let savedStringPosition = stringPosition;
let savedSrcStringStart = srcStringStart;
let savedSrcStringEnd = srcStringEnd;
let savedSrcString = srcString;
let savedStrings = strings;
let savedReferenceMap = referenceMap;
let savedBundledStrings = bundledStrings$1;
// TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
let savedStructures = currentStructures;
let savedStructuresContents = currentStructures.slice(0, currentStructures.length);
let savedPackr = currentUnpackr;
let savedSequentialMode = sequentialMode;
let value = callback();
srcEnd = savedSrcEnd;
position$1 = savedPosition;
stringPosition = savedStringPosition;
srcStringStart = savedSrcStringStart;
srcStringEnd = savedSrcStringEnd;
srcString = savedSrcString;
strings = savedStrings;
referenceMap = savedReferenceMap;
bundledStrings$1 = savedBundledStrings;
src = savedSrc;
sequentialMode = savedSequentialMode;
currentStructures = savedStructures;
currentStructures.splice(0, currentStructures.length, ...savedStructuresContents);
currentUnpackr = savedPackr;
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
return value
}
function clearSource() {
src = null;
referenceMap = null;
currentStructures = null;
}
function addExtension$1(extension) {
if (extension.unpack)
currentExtensions[extension.type] = extension.unpack;
else
currentExtensions[extension.type] = extension;
}
const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding
for (let i = 0; i < 256; i++) {
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103));
}
const Decoder = Unpackr;
var defaultUnpackr = new Unpackr({ useRecords: false });
const unpack = defaultUnpackr.unpack;
const unpackMultiple = defaultUnpackr.unpackMultiple;
const decode = defaultUnpackr.unpack;
const FLOAT32_OPTIONS = {
NEVER: 0,
ALWAYS: 1,
DECIMAL_ROUND: 3,
DECIMAL_FIT: 4
};
let f32Array = new Float32Array(1);
let u8Array = new Uint8Array(f32Array.buffer, 0, 4);
function roundFloat32(float32Number) {
f32Array[0] = float32Number;
let multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)];
return ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
function setReadStruct(updatedReadStruct, loadedStructs, saveState) {
readStruct$1 = updatedReadStruct;
onLoadedStructures$1 = loadedStructs;
onSaveState = saveState;
}
let textEncoder$1;
try {
textEncoder$1 = new TextEncoder();
} catch (error) {}
let extensions, extensionClasses;
const hasNodeBuffer$1 = typeof Buffer !== 'undefined';
const ByteArrayAllocate = hasNodeBuffer$1 ?
function(length) { return Buffer.allocUnsafeSlow(length) } : Uint8Array;
const ByteArray = hasNodeBuffer$1 ? Buffer : Uint8Array;
const MAX_BUFFER_SIZE = hasNodeBuffer$1 ? 0x100000000 : 0x7fd00000;
let target, keysTarget;
let targetView;
let position = 0;
let safeEnd;
let bundledStrings = null;
let writeStructSlots;
const MAX_BUNDLE_SIZE = 0x5500; // maximum characters such that the encoded bytes fits in 16 bits.
const hasNonLatin = /[\u0080-\uFFFF]/;
const RECORD_SYMBOL = Symbol('record-id');
class Packr extends Unpackr {
constructor(options) {
super(options);
this.offset = 0;
let start;
let hasSharedUpdate;
let structures;
let referenceMap;
let encodeUtf8 = ByteArray.prototype.utf8Write ? function(string, position) {
return target.utf8Write(string, position, target.byteLength - position)
} : (textEncoder$1 && textEncoder$1.encodeInto) ?
function(string, position) {
return textEncoder$1.encodeInto(string, target.subarray(position)).written
} : false;
let packr = this;
if (!options)
options = {};
let isSequential = options && options.sequential;
let hasSharedStructures = options.structures || options.saveStructures;
let maxSharedStructures = options.maxSharedStructures;
if (maxSharedStructures == null)
maxSharedStructures = hasSharedStructures ? 32 : 0;
if (maxSharedStructures > 8160)
throw new Error('Maximum maxSharedStructure is 8160')
if (options.structuredClone && options.moreTypes == undefined) {
this.moreTypes = true;
}
let maxOwnStructures = options.maxOwnStructures;
if (maxOwnStructures == null)
maxOwnStructures = hasSharedStructures ? 32 : 64;
if (!this.structures && options.useRecords != false)
this.structures = [];
// two byte record ids for shared structures
let useTwoByteRecords = maxSharedStructures > 32 || (maxOwnStructures + maxSharedStructures > 64);
let sharedLimitId = maxSharedStructures + 0x40;
let maxStructureId = maxSharedStructures + maxOwnStructures + 0x40;
if (maxStructureId > 8256) {
throw new Error('Maximum maxSharedStructure + maxOwnStructure is 8192')
}
let recordIdsToRemove = [];
let transitionsCount = 0;
let serializationsSinceTransitionRebuild = 0;
this.pack = this.encode = function(value, encodeOptions) {
if (!target) {
target = new ByteArrayAllocate(8192);
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, 8192));
position = 0;
}
safeEnd = target.length - 10;
if (safeEnd - position < 0x800) {
// don't start too close to the end,
target = new ByteArrayAllocate(target.length);
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, target.length));
safeEnd = target.length - 10;
position = 0;
} else
position = (position + 7) & 0x7ffffff8; // Word align to make any future copying of this buffer faster
start = position;
if (encodeOptions & RESERVE_START_SPACE) position += (encodeOptions & 0xff);
referenceMap = packr.structuredClone ? new Map() : null;
if (packr.bundleStrings && typeof value !== 'string') {
bundledStrings = [];
bundledStrings.size = Infinity; // force a new bundle start on first string
} else
bundledStrings = null;
structures = packr.structures;
if (structures) {
if (structures.uninitialized)
structures = packr._mergeStructures(packr.getStructures());
let sharedLength = structures.sharedLength || 0;
if (sharedLength > maxSharedStructures) {
//if (maxSharedStructures <= 32 && structures.sharedLength > 32) // TODO: could support this, but would need to update the limit ids
throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' + structures.sharedLength)
}
if (!structures.transitions) {
// rebuild our structure transitions
structures.transitions = Object.create(null);
for (let i = 0; i < sharedLength; i++) {
let keys = structures[i];
if (!keys)
continue
let nextTransition, transition = structures.transitions;
for (let j = 0, l = keys.length; j < l; j++) {
let key = keys[j];
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
}
transition = nextTransition;
}
transition[RECORD_SYMBOL] = i + 0x40;
}
this.lastNamedStructuresLength = sharedLength;
}
if (!isSequential) {
structures.nextId = sharedLength + 0x40;
}
}
if (hasSharedUpdate)
hasSharedUpdate = false;
let encodingError;
try {
// readOnlyStructures: skip the random-access struct write path so NO new struct is
// minted. randomAccessStructure stays true (the struct READ path and the struct-safe
// integer boundary are preserved, so existing struct data still decodes), but objects
// fall through to the normal pack()->writeObject->writeRecord path and are written as
// classic shared-structure records (byte range 0x40-0x7f, disjoint from struct headers
// at 0x20-0x3f) — the bounded, width-agnostic encoding used before struct mode.
if (packr.randomAccessStructure && !packr.readOnlyStructures && value && typeof value === 'object') {
if (value.constructor === Object) writeStruct(value); // simple object
else if (value.constructor !== Map && !Array.isArray(value) && !extensionClasses.some(extClass => value instanceof extClass)) {
// allow user classes, if they don't need special handling (but do use toJSON if available)
writeStruct(value.toJSON ? value.toJSON() : value);
} else pack(value);
} else
pack(value);
let lastBundle = bundledStrings;
if (bundledStrings)
writeBundles(start, pack, 0);
if (referenceMap && referenceMap.idsToInsert) {
let idsToInsert = referenceMap.idsToInsert.sort((a, b) => a.offset > b.offset ? 1 : -1);
let i = idsToInsert.length;
let incrementPosition = -1;
while (lastBundle && i > 0) {
let insertionPoint = idsToInsert[--i].offset + start;
if (insertionPoint < (lastBundle.stringsPosition + start) && incrementPosition === -1)
incrementPosition = 0;
if (insertionPoint > (lastBundle.position + start)) {
if (incrementPosition >= 0)
incrementPosition += 6;
} else {
if (incrementPosition >= 0) {
// update the bundle reference now
targetView.setUint32(lastBundle.position + start,
targetView.getUint32(lastBundle.position + start) + incrementPosition);
incrementPosition = -1; // reset
}
lastBundle = lastBundle.previous;
i++;
}
}
if (incrementPosition >= 0 && lastBundle) {
// update the bundle reference now
targetView.setUint32(lastBundle.position + start,
targetView.getUint32(lastBundle.position + start) + incrementPosition);
}
position += idsToInsert.length * 6;
if (position > safeEnd)
makeRoom(position);
packr.offset = position;
let serialized = insertIds(target.subarray(start, position), idsToInsert);
referenceMap = null;
return serialized
}
packr.offset = position; // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially
if (encodeOptions & REUSE_BUFFER_MODE) {
target.start = start;
target.end = position;
return target
}
return target.subarray(start, position) // position can change if we call pack again in saveStructures, so we get the buffer now
} catch(error) {
encodingError = error;
throw error;
} finally {
if (structures) {
resetStructures();
if (hasSharedUpdate && packr.saveStructures) {
let sharedLength = structures.sharedLength || 0;
// we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save
let returnBuffer = target.subarray(start, position);
let newSharedData = prepareStructures$1(structures, packr);
if (!encodingError) { // TODO: If there is an encoding error, should make the structures as uninitialized so they get rebuilt next time
if (packr.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
// The save was declined (a concurrent writer updated the shared structures,
// or the store transaction did not durably commit). Our in-memory
// structures + transition trie may now reference record ids that were
// never persisted; re-packing as-is would re-emit the same record pointing
// at an unpersisted structure (-> "Record id is not defined" on decode).
// Mark structures uninitialized so the re-pack reloads durable structures
// via getStructures, rebuilds the transition trie, and re-mints + re-saves.
structures.uninitialized = true;
return packr.pack(value, encodeOptions)
}
packr.lastNamedStructuresLength = sharedLength;
// don't keep large buffers around
if (target.length > 0x40000000) target = null;
return returnBuffer
}
}
}
// don't keep large buffers around, they take too much memory and cause problems (limit at 1GB)
if (target.length > 0x40000000) target = null;
if (encodeOptions & RESET_BUFFER_MODE)
position = start;
}
};
const resetStructures = () => {
if (serializationsSinceTransitionRebuild < 10)
serializationsSinceTransitionRebuild++;
let sharedLength = structures.sharedLength || 0;
if (structures.length > sharedLength && !isSequential)
structures.length = sharedLength;
if (transitionsCount > 10000) {
// force a rebuild occasionally after a lot of transitions so it can get cleaned up
structures.transitions = null;
serializationsSinceTransitionRebuild = 0;
transitionsCount = 0;
if (recordIdsToRemove.length > 0)
recordIdsToRemove = [];
} else if (recordIdsToRemove.length > 0 && !isSequential) {
for (let i = 0, l = recordIdsToRemove.length; i < l; i++) {
recordIdsToRemove[i][RECORD_SYMBOL] = 0;
}
recordIdsToRemove = [];
}
};
const packArray = (value) => {
var length = value.length;
if (length < 0x10) {
target[position++] = 0x90 | length;
} else if (length < 0x10000) {
target[position++] = 0xdc;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdd;
targetView.setUint32(position, length);
position += 4;
}
for (let i = 0; i < length; i++) {
pack(value[i]);
}
};
const pack = (value) => {
if (position > safeEnd)
target = makeRoom(position);
var type = typeof value;
var length;
if (type === 'string') {
let strLength = value.length;
if (bundledStrings && strLength >= 4 && strLength < 0x1000) {
if ((bundledStrings.size += strLength) > MAX_BUNDLE_SIZE) {
let extStart;
let maxBytes = (bundledStrings[0] ? bundledStrings[0].length * 3 + bundledStrings[1].length : 0) + 10;
if (position + maxBytes > safeEnd)
target = makeRoom(position + maxBytes);
let lastBundle;
if (bundledStrings.position) { // here we use the 0x62 extension to write the last bundle and reserve space for the reference pointer to the next/current bundle
lastBundle = bundledStrings;
target[position] = 0xc8; // ext 16
position += 3; // reserve for the writing bundle size
target[position++] = 0x62; // 'b'
extStart = position - start;
position += 4; // reserve for writing bundle reference
writeBundles(start, pack, 0); // write the last bundles
targetView.setUint16(extStart + start - 3, position - start - extStart);
} else { // here we use the 0x62 extension just to reserve the space for the reference pointer to the bundle (will be updated once the bundle is written)
target[position++] = 0xd6; // fixext 4
target[position++] = 0x62; // 'b'
extStart = position - start;
position += 4; // reserve for writing bundle reference
}
bundledStrings = ['', '']; // create new ones
bundledStrings.previous = lastBundle;
bundledStrings.size = 0;
bundledStrings.position = extStart;
}
let twoByte = hasNonLatin.test(value);
bundledStrings[twoByte ? 0 : 1] += value;
target[position++] = 0xc1;
pack(twoByte ? -strLength : strLength);
return
}
let headerSize;
// first we estimate the header size, so we can write to the correct location
if (strLength < 0x20) {
headerSize = 1;
} else if (strLength < 0x100) {
headerSize = 2;
} else if (strLength < 0x10000) {
headerSize = 3;
} else {
headerSize = 5;
}
let maxBytes = strLength * 3;
if (position + maxBytes > safeEnd)
target = makeRoom(position + maxBytes);
if (strLength < 0x40 || !encodeUtf8) {
let i, c1, c2, strPosition = position + headerSize;
for (i = 0; i < strLength; i++) {
c1 = value.charCodeAt(i);
if (c1 < 0x80) {
target[strPosition++] = c1;
} else if (c1 < 0x800) {
target[strPosition++] = c1 >> 6 | 0xc0;
target[strPosition++] = c1 & 0x3f | 0x80;
} else if (
(c1 & 0xfc00) === 0xd800 &&
((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
) {
c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
i++;
target[strPosition++] = c1 >> 18 | 0xf0;
target[strPosition++] = c1 >> 12 & 0x3f | 0x80;
target[strPosition++] = c1 >> 6 & 0x3f | 0x80;
target[strPosition++] = c1 & 0x3f | 0x80;
} else {
target[strPosition++] = c1 >> 12 | 0xe0;
target[strPosition++] = c1 >> 6 & 0x3f | 0x80;
target[strPosition++] = c1 & 0x3f | 0x80;
}
}
length = strPosition - position - headerSize;
} else {
length = encodeUtf8(value, position + headerSize);
}
if (length < 0x20) {
target[position++] = 0xa0 | length;
} else if (length < 0x100) {
if (headerSize < 2) {
target.copyWithin(position + 2, position + 1, position + 1 + length);
}
target[position++] = 0xd9;
target[position++] = length;
} else if (length < 0x10000) {
if (headerSize < 3) {
target.copyWithin(position + 3, position + 2, position + 2 + length);
}
target[position++] = 0xda;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
if (headerSize < 5) {
target.copyWithin(position + 5, position + 3, position + 3 + length);
}
target[position++] = 0xdb;
targetView.setUint32(position, length);
position += 4;
}
position += length;
} else if (type === 'number') {
if (value >>> 0 === value) {// positive integer, 32-bit or less
// positive uint
if (value < 0x20 || (value < 0x80 && this.useRecords === false) || (value < 0x40 && !this.randomAccessStructure)) {
target[position++] = value;
} else if (value < 0x100) {
target[position++] = 0xcc;
target[position++] = value;
} else if (value < 0x10000) {
target[position++] = 0xcd;
target[position++] = value >> 8;
target[position++] = value & 0xff;
} else {
target[position++] = 0xce;
targetView.setUint32(position, value);
position += 4;
}
} else if (value >> 0 === value) { // negative integer
if (value >= -0x20) {
target[position++] = 0x100 + value;
} else if (value >= -0x80) {
target[position++] = 0xd0;
target[position++] = value + 0x100;
} else if (value >= -0x8000) {
target[position++] = 0xd1;
targetView.setInt16(position, value);
position += 2;
} else {
target[position++] = 0xd2;
targetView.setInt32(position, value);
position += 4;
}
} else {
let useFloat32;
if ((useFloat32 = this.useFloat32) > 0 && value < 0x100000000 && value >= -0x80000000) {
target[position++] = 0xca;
targetView.setFloat32(position, value);
let xShifted;
if (useFloat32 < 4 ||
// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
((xShifted = value * mult10[((target[position] & 0x7f) << 1) | (target[position + 1] >> 7)]) >> 0) === xShifted) {
position += 4;
return
} else
position--; // move back into position for writing a double
}
target[position++] = 0xcb;
targetView.setFloat64(position, value);
position += 8;
}
} else if (type === 'object' || type === 'function') {
if (!value)
target[position++] = 0xc0;
else {
if (referenceMap) {
let referee = referenceMap.get(value);
if (referee) {
if (!referee.id) {
let idsToInsert = referenceMap.idsToInsert || (referenceMap.idsToInsert = []);
referee.id = idsToInsert.push(referee);
}
target[position++] = 0xd6; // fixext 4
target[position++] = 0x70; // "p" for pointer
targetView.setUint32(position, referee.id);
position += 4;
return
} else
referenceMap.set(value, { offset: position - start });
}
let constructor = value.constructor;
if (constructor === Object) {
writeObject(value);
} else if (constructor === Array) {
packArray(value);
} else if (constructor === Map) {
if (this.mapAsEmptyObject) target[position++] = 0x80;
else {
length = value.size;
if (length < 0x10) {
target[position++] = 0x80 | length;
} else if (length < 0x10000) {
target[position++] = 0xde;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdf;
targetView.setUint32(position, length);
position += 4;
}
for (let [key, entryValue] of value) {
pack(key);
pack(entryValue);
}
}
} else {
for (let i = 0, l = extensions.length; i < l; i++) {
let extensionClass = extensionClasses[i];
if (value instanceof extensionClass) {
let extension = extensions[i];
if (extension.write) {
if (extension.type) {
target[position++] = 0xd4; // one byte "tag" extension
target[position++] = extension.type;
target[position++] = 0;
}
let writeResult = extension.write.call(this, value);
if (writeResult === value) { // avoid infinite recursion
if (Array.isArray(value)) {
packArray(value);
} else {
writeObject(value);
}
} else {
pack(writeResult);
}
return
}
let currentTarget = target;
let currentTargetView = targetView;
let currentPosition = position;
target = null;
let result;
try {
result = extension.pack.call(this, value, (size) => {
// restore target and use it
target = currentTarget;
currentTarget = null;
position += size;
if (position > safeEnd)
makeRoom(position);
return {
target, targetView, position: position - size
}
}, pack);
} finally {
// restore current target information (unless already restored)
if (currentTarget) {
target = currentTarget;
targetView = currentTargetView;
position = currentPosition;
safeEnd = target.length - 10;
}
}
if (result) {
if (result.length + position > safeEnd)
makeRoom(result.length + position);
position = writeExtensionData(result, target, position, extension.type);
}
return
}
}
// check isArray after extensions, because extensions can extend Array
if (Array.isArray(value)) {
packArray(value);
} else {
// use this as an alternate mechanism for expressing how to serialize
if (value.toJSON) {
const json = value.toJSON();
// if for some reason value.toJSON returns itself it'll loop forever
if (json !== value)
return pack(json)
}
// if there is a writeFunction, use it, otherwise just encode as undefined
if (type === 'function')
return pack(this.writeFunction && this.writeFunction(value));
// no extension found, write as plain object
writeObject(value);
}
}
}
} else if (type === 'boolean') {
target[position++] = value ? 0xc3 : 0xc2;
} else if (type === 'bigint') {
if (value < 0x8000000000000000 && value >= -0x8000000000000000) {
// use a signed int as long as it fits
target[position++] = 0xd3;
targetView.setBigInt64(position, value);
} else if (value < 0x10000000000000000 && value > 0) {
// if we can fit an unsigned int, use that
target[position++] = 0xcf;
targetView.setBigUint64(position, value);
} else {
// overflow
if (this.largeBigIntToFloat) {
target[position++] = 0xcb;
targetView.setFloat64(position, Number(value));
} else if (this.largeBigIntToString) {
return pack(value.toString());
} else if (this.useBigIntExtension || this.moreTypes) {
let empty = value < 0 ? BigInt(-1) : BigInt(0);
let array;
if (value >> BigInt(0x10000) === empty) {
let mask = BigInt(0x10000000000000000) - BigInt(1); // literal would overflow
let chunks = [];
while (true) {
chunks.push(value & mask);
if ((value >> BigInt(63)) === empty) break
value >>= BigInt(64);
}
array = new Uint8Array(new BigUint64Array(chunks).buffer);
array.reverse();
} else {
let invert = value < 0;
let string = (invert ? ~value : value).toString(16);
if (string.length % 2) {
string = '0' + string;
} else if (parseInt(string.charAt(0), 16) >= 8) {
string = '00' + string;
}
if (hasNodeBuffer$1) {
array = Buffer.from(string, 'hex');
} else {
array = new Uint8Array(string.length / 2);
for (let i = 0; i < array.length; i++) {
array[i] = parseInt(string.slice(i * 2, i * 2 + 2), 16);
}
}
if (invert) {
for (let i = 0; i < array.length; i++) array[i] = ~array[i];
}
}
if (array.length + position > safeEnd)
makeRoom(array.length + position);
position = writeExtensionData(array, target, position, 0x42);
return
} else {
throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, use' +
' useBigIntExtension, or set largeBigIntToFloat to convert to float-64, or set' +
' largeBigIntToString to convert to string')
}
}
position += 8;
} else if (type === 'undefined') {
if (this.encodeUndefinedAsNil)
target[position++] = 0xc0;
else {
target[position++] = 0xd4; // a number of implementations use fixext1 with type 0, data 0 to denote undefined, so we follow suite
target[position++] = 0;
target[position++] = 0;
}
} else {
throw new Error('Unknown type: ' + type)
}
};
const writePlainObject = (this.variableMapSize || this.coercibleKeyAsNumber || this.skipValues) ? (object) => {
// this method is slightly slower, but generates "preferred serialization" (optimally small for smaller objects)
let keys;
if (this.skipValues) {
keys = [];
for (let key in object) {
if ((typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) &&
!this.skipValues.includes(object[key]))
keys.push(key);
}
} else {
keys = Object.keys(object);
}
let length = keys.length;
if (length < 0x10) {
target[position++] = 0x80 | length;
} else if (length < 0x10000) {
target[position++] = 0xde;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdf;
targetView.setUint32(position, length);
position += 4;
}
let key;
if (this.coercibleKeyAsNumber) {
for (let i = 0; i < length; i++) {
key = keys[i];
let num = Number(key);
pack(isNaN(num) ? key : num);
pack(object[key]);
}
} else {
for (let i = 0; i < length; i++) {
pack(key = keys[i]);
pack(object[key]);
}
}
} :
(object) => {
target[position++] = 0xde; // always using map 16, so we can preallocate and set the length afterwards
let objectOffset = position - start;
position += 2;
let size = 0;
for (let key in object) {
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
pack(key);
pack(object[key]);
size++;
}
}
if (size > 0xffff) {
throw new Error('Object is too large to serialize with fast 16-bit map size,' +
' use the "variableMapSize" option to serialize this object');
}
target[objectOffset++ + start] = size >> 8;
target[objectOffset + start] = size & 0xff;
};
const writeRecord = this.useRecords === false ? writePlainObject :
(options.progressiveRecords && !useTwoByteRecords) ? // this is about 2% faster for highly stable structures, since it only requires one for-in loop (but much more expensive when new structure needs to be written)
(object) => {
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
let objectOffset = position++ - start;
let wroteKeys;
for (let key in object) {
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
nextTransition = transition[key];
if (nextTransition)
transition = nextTransition;
else {
// record doesn't exist, create full new record and insert it
let keys = Object.keys(object);
let lastTransition = transition;
transition = structures.transitions;
let newTransitions = 0;
for (let i = 0, l = keys.length; i < l; i++) {
let key = keys[i];
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
newTransitions++;
}
transition = nextTransition;
}
if (objectOffset + start + 1 == position) {
// first key, so we don't need to insert, we can just write record directly
position--;
newRecord(transition, keys, newTransitions);
} else // otherwise we need to insert the record, moving existing data after the record
insertNewRecord(transition, keys, objectOffset, newTransitions);
wroteKeys = true;
transition = lastTransition[key];
}
pack(object[key]);
}
}
if (!wroteKeys) {
let recordId = transition[RECORD_SYMBOL];
if (recordId)
target[objectOffset + start] = recordId;
else
insertNewRecord(transition, Object.keys(object), objectOffset, 0);
}
} :
(object) => {
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
let newTransitions = 0;
for (let key in object) if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
newTransitions++;
}
transition = nextTransition;
}
let recordId = transition[RECORD_SYMBOL];
if (recordId) {
if (recordId >= 0x60 && useTwoByteRecords) {
target[position++] = ((recordId -= 0x60) & 0x1f) + 0x60;
target[position++] = recordId >> 5;
} else
target[position++] = recordId;
} else {
newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions);
}
// now write the values
for (let key in object)
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
pack(object[key]);
}
};
// create reference to useRecords if useRecords is a function
const checkUseRecords = typeof this.useRecords == 'function' && this.useRecords;
const writeObject = checkUseRecords ? (object) => {
checkUseRecords(object) ? writeRecord(object) : writePlainObject(object);
} : writeRecord;
const makeRoom = (end) => {
let newSize;
if (end > 0x1000000) {
// special handling for really large buffers
if ((end - start) > MAX_BUFFER_SIZE)
throw new Error('Packed buffer would be larger than maximum buffer size')
newSize = Math.min(MAX_BUFFER_SIZE,
Math.round(Math.max((end - start) * (end > 0x4000000 ? 1.25 : 2), 0x400000) / 0x1000) * 0x1000);
} else // faster handling for smaller buffers
newSize = ((Math.max((end - start) << 2, target.length - 1) >> 12) + 1) << 12;
let newBuffer = new ByteArrayAllocate(newSize);
targetView = newBuffer.dataView || (newBuffer.dataView = new DataView(newBuffer.buffer, 0, newSize));
end = Math.min(end, target.length);
if (target.copy)
target.copy(newBuffer, 0, start, end);
else
newBuffer.set(target.slice(start, end));
position -= start;
start = 0;
safeEnd = newBuffer.length - 10;
return target = newBuffer
};
const newRecord = (transition, keys, newTransitions) => {
let recordId = structures.nextId;
if (!recordId)
recordId = 0x40;
if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
recordId = structures.nextOwnId;
if (!(recordId < maxStructureId))
recordId = sharedLimitId;
structures.nextOwnId = recordId + 1;
} else {
if (recordId >= maxStructureId)// cycle back around
recordId = sharedLimitId;
structures.nextId = recordId + 1;
}
let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1;
transition[RECORD_SYMBOL] = recordId;
transition.__keys__ = keys;
structures[recordId - 0x40] = keys;
if (recordId < sharedLimitId) {
keys.isShared = true;
structures.sharedLength = recordId - 0x3f;
hasSharedUpdate = true;
if (highByte >= 0) {
target[position++] = (recordId & 0x1f) + 0x60;
target[position++] = highByte;
} else {
target[position++] = recordId;
}
} else {
if (highByte >= 0) {
target[position++] = 0xd5; // fixext 2
target[position++] = 0x72; // "r" record defintion extension type
target[position++] = (recordId & 0x1f) + 0x60;
target[position++] = highByte;
} else {
target[position++] = 0xd4; // fixext 1
target[position++] = 0x72; // "r" record defintion extension type
target[position++] = recordId;
}
if (newTransitions)
transitionsCount += serializationsSinceTransitionRebuild * newTransitions;
// record the removal of the id, we can maintain our shared structure
if (recordIdsToRemove.length >= maxOwnStructures)
recordIdsToRemove.shift()[RECORD_SYMBOL] = 0; // we are cycling back through, and have to remove old ones
recordIdsToRemove.push(transition);
pack(keys);
}
};
const insertNewRecord = (transition, keys, insertionOffset, newTransitions) => {
let mainTarget = target;
let mainPosition = position;
let mainSafeEnd = safeEnd;
let mainStart = start;
target = keysTarget;
position = 0;
start = 0;
if (!target)
keysTarget = target = new ByteArrayAllocate(8192);
safeEnd = target.length - 10;
newRecord(transition, keys, newTransitions);
keysTarget = target;
let keysPosition = position;
target = mainTarget;
position = mainPosition;
safeEnd = mainSafeEnd;
start = mainStart;
if (keysPosition > 1) {
let newEnd = position + keysPosition - 1;
if (newEnd > safeEnd)
makeRoom(newEnd);
let insertionPosition = insertionOffset + start;
target.copyWithin(insertionPosition + keysPosition, insertionPosition + 1, position);
target.set(keysTarget.slice(0, keysPosition), insertionPosition);
position = newEnd;
} else {
target[insertionOffset + start] = keysTarget[0];
}
};
const writeStruct = (object) => {
let newPosition = writeStructSlots(object, target, start, position, structures, makeRoom, (value, newPosition, notifySharedUpdate) => {
if (notifySharedUpdate)
return hasSharedUpdate = true;
position = newPosition;
let startTarget = target;
pack(value);
resetStructures();
if (startTarget !== target) {
return { position, targetView, target }; // indicate the buffer was re-allocated
}
return position;
}, this);
if (newPosition === 0) // bail and go to a msgpack object
return writeObject(object);
position = newPosition;
};
}
useBuffer(buffer) {
// this means we are finished using our own buffer and we can write over it safely
target = buffer;
target.dataView || (target.dataView = new DataView(target.buffer, target.byteOffset, target.byteLength));
targetView = target.dataView;
position = 0;
}
set position (value) {
position = value;
}
get position() {
return position;
}
clearSharedData() {
if (this.structures)
this.structures = [];
if (this.typedStructs)
this.typedStructs = [];
}
}
extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, DataView, C1Type ];
extensions = [{
pack(date, allocateForWrite, pack) {
let seconds = date.getTime() / 1000;
if ((this.useTimestamp32 || date.getMilliseconds() === 0) && seconds >= 0 && seconds < 0x100000000) {
// Timestamp 32
let { target, targetView, position} = allocateForWrite(6);
target[position++] = 0xd6;
target[position++] = 0xff;
targetView.setUint32(position, seconds);
} else if (seconds > 0 && seconds < 0x100000000) {
// Timestamp 64
let { target, targetView, position} = allocateForWrite(10);
target[position++] = 0xd7;
target[position++] = 0xff;
targetView.setUint32(position, date.getMilliseconds() * 4000000 + ((seconds / 1000 / 0x100000000) >> 0));
targetView.setUint32(position + 4, seconds);
} else if (isNaN(seconds)) {
if (this.onInvalidDate) {
allocateForWrite(0);
return pack(this.onInvalidDate())
}
// Intentionally invalid timestamp
let { target, targetView, position} = allocateForWrite(3);
target[position++] = 0xd4;
target[position++] = 0xff;
target[position++] = 0xff;
} else {
// Timestamp 96
let { target, targetView, position} = allocateForWrite(15);
target[position++] = 0xc7;
target[position++] = 12;
target[position++] = 0xff;
targetView.setUint32(position, date.getMilliseconds() * 1000000);
targetView.setBigInt64(position + 4, BigInt(Math.floor(seconds)));
}
}
}, {
pack(set, allocateForWrite, pack) {
if (this.setAsEmptyObject) {
allocateForWrite(0);
return pack({})
}
let array = Array.from(set);
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x73; // 's' for Set
target[position++] = 0;
}
pack(array);
}
}, {
pack(error, allocateForWrite, pack) {
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x65; // 'e' for error
target[position++] = 0;
}
pack([ error.name, error.message, error.cause ]);
}
}, {
pack(regex, allocateForWrite, pack) {
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x78; // 'x' for regeXp
target[position++] = 0;
}
pack([ regex.source, regex.flags ]);
}
}, {
pack(arrayBuffer, allocateForWrite) {
if (this.moreTypes)
writeExtBuffer(arrayBuffer, 0x10, allocateForWrite);
else
writeBuffer(hasNodeBuffer$1 ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
}
}, {
pack(typedArray, allocateForWrite) {
let constructor = typedArray.constructor;
if (constructor !== ByteArray && this.moreTypes)
writeExtBuffer(typedArray, typedArrays.indexOf(constructor.name), allocateForWrite);
else
writeBuffer(typedArray, allocateForWrite);
}
}, {
pack(arrayBuffer, allocateForWrite) {
if (this.moreTypes)
writeExtBuffer(arrayBuffer, 0x11, allocateForWrite);
else
writeBuffer(hasNodeBuffer$1 ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
}
}, {
pack(c1, allocateForWrite) { // specific 0xC1 object
let { target, position} = allocateForWrite(1);
target[position] = 0xc1;
}
}];
function writeExtBuffer(typedArray, type, allocateForWrite, encode) {
let length = typedArray.byteLength;
if (length + 1 < 0x100) {
var { target, position } = allocateForWrite(4 + length);
target[position++] = 0xc7;
target[position++] = length + 1;
} else if (length + 1 < 0x10000) {
var { target, position } = allocateForWrite(5 + length);
target[position++] = 0xc8;
target[position++] = (length + 1) >> 8;
target[position++] = (length + 1) & 0xff;
} else {
var { target, position, targetView } = allocateForWrite(7 + length);
target[position++] = 0xc9;
targetView.setUint32(position, length + 1); // plus one for the type byte
position += 4;
}
target[position++] = 0x74; // "t" for typed array
target[position++] = type;
if (!typedArray.buffer) typedArray = new Uint8Array(typedArray);
target.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength), position);
}
function writeBuffer(buffer, allocateForWrite) {
let length = buffer.byteLength;
var target, position;
if (length < 0x100) {
var { target, position } = allocateForWrite(length + 2);
target[position++] = 0xc4;
target[position++] = length;
} else if (length < 0x10000) {
var { target, position } = allocateForWrite(length + 3);
target[position++] = 0xc5;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
var { target, position, targetView } = allocateForWrite(length + 5);
target[position++] = 0xc6;
targetView.setUint32(position, length);
position += 4;
}
target.set(buffer, position);
}
function writeExtensionData(result, target, position, type) {
let length = result.length;
switch (length) {
case 1:
target[position++] = 0xd4;
break
case 2:
target[position++] = 0xd5;
break
case 4:
target[position++] = 0xd6;
break
case 8:
target[position++] = 0xd7;
break
case 16:
target[position++] = 0xd8;
break
default:
if (length < 0x100) {
target[position++] = 0xc7;
target[position++] = length;
} else if (length < 0x10000) {
target[position++] = 0xc8;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xc9;
target[position++] = length >> 24;
target[position++] = (length >> 16) & 0xff;
target[position++] = (length >> 8) & 0xff;
target[position++] = length & 0xff;
}
}
target[position++] = type;
target.set(result, position);
position += length;
return position
}
function insertIds(serialized, idsToInsert) {
// insert the ids that need to be referenced for structured clones
let nextId;
let distanceToMove = idsToInsert.length * 6;
let lastEnd = serialized.length - distanceToMove;
while (nextId = idsToInsert.pop()) {
let offset = nextId.offset;
let id = nextId.id;
serialized.copyWithin(offset + distanceToMove, offset, lastEnd);
distanceToMove -= 6;
let position = offset + distanceToMove;
serialized[position++] = 0xd6;
serialized[position++] = 0x69; // 'i'
serialized[position++] = id >> 24;
serialized[position++] = (id >> 16) & 0xff;
serialized[position++] = (id >> 8) & 0xff;
serialized[position++] = id & 0xff;
lastEnd = offset;
}
return serialized
}
function writeBundles(start, pack, incrementPosition) {
if (bundledStrings.length > 0) {
targetView.setUint32(bundledStrings.position + start, position + incrementPosition - bundledStrings.position - start);
bundledStrings.stringsPosition = position - start;
let writeStrings = bundledStrings;
bundledStrings = null;
pack(writeStrings[0]);
pack(writeStrings[1]);
}
}
function addExtension(extension) {
if (extension.Class) {
if (!extension.pack && !extension.write)
throw new Error('Extension has no pack or write function')
if (extension.pack && !extension.type)
throw new Error('Extension has no type (numeric code to identify the extension)')
extensionClasses.unshift(extension.Class);
extensions.unshift(extension);
}
addExtension$1(extension);
}
function prepareStructures$1(structures, packr) {
structures.isCompatible = (existingStructures) => {
let compatible = !existingStructures || ((packr.lastNamedStructuresLength || 0) === existingStructures.length);
if (!compatible) // we want to merge these existing structures immediately since we already have it and we are in the right transaction
packr._mergeStructures(existingStructures);
return compatible;
};
return structures
}
function setWriteStructSlots(writeSlots, makeStructures) {
writeStructSlots = writeSlots;
prepareStructures$1 = makeStructures;
}
let defaultPackr = new Packr({ useRecords: false });
const pack = defaultPackr.pack;
const encode = defaultPackr.pack;
const Encoder = Packr;
const { NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } = FLOAT32_OPTIONS;
const REUSE_BUFFER_MODE = 512;
const RESET_BUFFER_MODE = 1024;
const RESERVE_START_SPACE = 2048;
const ASCII = 3; // the MIBenum from https://www.iana.org/assignments/character-sets/character-sets.xhtml (and other character encodings could be referenced by MIBenum)
const NUMBER = 0;
const UTF8 = 2;
const OBJECT_DATA = 1;
const DATE = 16;
const TYPE_NAMES = ['num', 'object', 'string', 'ascii'];
TYPE_NAMES[DATE] = 'date';
const float32Headers = [false, true, true, false, false, true, true, false];
let evalSupported;
try {
new Function('');
evalSupported = true;
} catch(error) {
// if eval variants are not supported, do not create inline object readers ever
}
let updatedPosition;
const hasNodeBuffer = typeof Buffer !== 'undefined';
let textEncoder, currentSource;
try {
textEncoder = new TextEncoder();
} catch (error) {}
const encodeUtf8 = hasNodeBuffer ? function(target, string, position) {
return target.utf8Write(string, position, target.byteLength - position)
} : (textEncoder && textEncoder.encodeInto) ?
function(target, string, position) {
return textEncoder.encodeInto(string, target.subarray(position)).written
} : false;
setWriteStructSlots(writeStruct, prepareStructures);
function writeStruct(object, target, encodingStart, position, structures, makeRoom, pack, packr, structureKnown) {
let typedStructs = packr.typedStructs || (packr.typedStructs = []);
// note that we rely on pack.js to load stored structures before we get to this point
// structureKnown is set only on the internal layout-retry below: attempt 1 already minted
// this record's structure, so the retry re-encodes a known shape and must not re-apply the
// cap (which could otherwise bail after attempt 1 already packed refs → corrupt fallback).
// `frozen` is a local (from this instance's typedStructs) — never a shared global — so a
// re-entrant encode on another instance (e.g. via an enumerable getter) can't flip it.
const cap = packr.maxOwnStructures ?? Infinity;
const frozen = !structureKnown && typedStructs.length >= cap;
let targetView = target.dataView;
let refsStartPosition = (typedStructs.lastStringStart || 100) + position;
let safeEnd = target.length - 10;
let start = position;
if (position > safeEnd) {
target = makeRoom(position);
targetView = target.dataView;
position -= encodingStart;
start -= encodingStart;
refsStartPosition -= encodingStart;
encodingStart = 0;
safeEnd = target.length - 10;
}
let refOffset, refPosition = refsStartPosition;
let transition = typedStructs.transitions || (typedStructs.transitions = Object.create(null));
let nextId = typedStructs.nextId || typedStructs.length;
let headerSize =
nextId < 0xf ? 1 :
nextId < 0xf0 ? 2 :
nextId < 0xf000 ? 3 :
nextId < 0xf00000 ? 4 : 0;
if (headerSize === 0)
return 0;
position += headerSize;
let queuedReferences = [];
let usedAscii0;
let keyIndex = 0;
for (let key in object) {
let nextTransition = transition[key];
// Resolve the key transition BEFORE reading the value: when frozen and the key is new we
// bail here, so an enumerable getter isn't invoked during this (failed) struct attempt and
// then again by the plain fallback (which would double-read a side-effecting accessor).
if (!nextTransition) {
if (frozen) return 0;
transition[key] = nextTransition = {
key,
parent: transition,
enumerationOffset: 0,
ascii0: null,
ascii8: null,
num8: null,
string16: null,
object16: null,
num32: null,
float64: null,
date64: null
};
}
let value = object[key];
if (position > safeEnd) {
target = makeRoom(position);
targetView = target.dataView;
position -= encodingStart;
start -= encodingStart;
refsStartPosition -= encodingStart;
refPosition -= encodingStart;
encodingStart = 0;
safeEnd = target.length - 10;
}
switch (typeof value) {
case 'number':
let number = value;
// first check to see if we are using a lot of ids and should default to wide/common format
if (nextId < 200 || !nextTransition.num64) {
if (number >> 0 === number && number < 0x20000000 && number > -0x1f000000) {
if (number < 0xf6 && number >= 0 && (nextTransition.num8 && !(nextId > 200 && nextTransition.num32) || number < 0x20 && !nextTransition.num32)) {
transition = nextTransition.num8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
target[position++] = number;
} else {
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
targetView.setUint32(position, number, true);
position += 4;
}
break;
} else if (number < 0x100000000 && number >= -0x80000000) {
targetView.setFloat32(position, number, true);
if (float32Headers[target[position + 3] >>> 5]) {
let xShifted;
// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
if (((xShifted = number * mult10[((target[position + 3] & 0x7f) << 1) | (target[position + 2] >> 7)]) >> 0) === xShifted) {
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
position += 4;
break;
}
}
}
}
transition = nextTransition.num64 || createTypeTransition(nextTransition, NUMBER, 8, frozen);
targetView.setFloat64(position, number, true);
position += 8;
break;
case 'string':
let strLength = value.length;
refOffset = refPosition - refsStartPosition;
if ((strLength << 2) + refPosition > safeEnd) {
target = makeRoom((strLength << 2) + refPosition);
targetView = target.dataView;
position -= encodingStart;
start -= encodingStart;
refsStartPosition -= encodingStart;
refPosition -= encodingStart;
encodingStart = 0;
safeEnd = target.length - 10;
}
if (strLength > ((0xff00 + refOffset) >> 2)) {
queuedReferences.push(key, value, position - start);
break;
}
let isNotAscii;
let strStart = refPosition;
if (strLength < 0x40) {
let i, c1, c2;
for (i = 0; i < strLength; i++) {
c1 = value.charCodeAt(i);
if (c1 < 0x80) {
target[refPosition++] = c1;
} else if (c1 < 0x800) {
isNotAscii = true;
target[refPosition++] = c1 >> 6 | 0xc0;
target[refPosition++] = c1 & 0x3f | 0x80;
} else if (
(c1 & 0xfc00) === 0xd800 &&
((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
) {
isNotAscii = true;
c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
i++;
target[refPosition++] = c1 >> 18 | 0xf0;
target[refPosition++] = c1 >> 12 & 0x3f | 0x80;
target[refPosition++] = c1 >> 6 & 0x3f | 0x80;
target[refPosition++] = c1 & 0x3f | 0x80;
} else {
isNotAscii = true;
target[refPosition++] = c1 >> 12 | 0xe0;
target[refPosition++] = c1 >> 6 & 0x3f | 0x80;
target[refPosition++] = c1 & 0x3f | 0x80;
}
}
} else {
refPosition += encodeUtf8(target, value, refPosition);
isNotAscii = refPosition - strStart > strLength;
}
if (refOffset < 0xa0 || (refOffset < 0xf6 && (nextTransition.ascii8 || nextTransition.string8))) {
// short strings
if (isNotAscii) {
if (!(transition = nextTransition.string8)) {
if (typedStructs.length > 10 && (transition = nextTransition.ascii8)) {
// we can safely change ascii to utf8 in place since they are compatible
transition.__type = UTF8;
nextTransition.ascii8 = null;
nextTransition.string8 = transition;
pack(null, 0, true); // special call to notify that structures have been updated
} else {
transition = createTypeTransition(nextTransition, UTF8, 1, frozen);
}
}
} else if (refOffset === 0 && !usedAscii0) {
usedAscii0 = true;
transition = nextTransition.ascii0 || createTypeTransition(nextTransition, ASCII, 0, frozen);
break; // don't increment position
}// else ascii:
else if (!(transition = nextTransition.ascii8) && !(typedStructs.length > 10 && (transition = nextTransition.string8)))
transition = createTypeTransition(nextTransition, ASCII, 1, frozen);
target[position++] = refOffset;
} else {
// TODO: Enable ascii16 at some point, but get the logic right
//if (isNotAscii)
transition = nextTransition.string16 || createTypeTransition(nextTransition, UTF8, 2, frozen);
//else
//transition = nextTransition.ascii16 || createTypeTransition(nextTransition, ASCII, 2);
targetView.setUint16(position, refOffset, true);
position += 2;
}
break;
case 'object':
if (value) {
if (value.constructor === Date) {
transition = nextTransition.date64 || createTypeTransition(nextTransition, DATE, 8, frozen);
targetView.setFloat64(position, value.getTime(), true);
position += 8;
} else {
queuedReferences.push(key, value, keyIndex);
}
break;
} else { // null
nextTransition = anyType(nextTransition, position, targetView, -10); // match CBOR with this
if (nextTransition) {
transition = nextTransition;
position = updatedPosition;
} else queuedReferences.push(key, value, keyIndex);
}
break;
case 'boolean':
transition = nextTransition.num8 || nextTransition.ascii8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
target[position++] = value ? 0xf9 : 0xf8; // match CBOR with these
break;
case 'undefined':
nextTransition = anyType(nextTransition, position, targetView, -9); // match CBOR with this
if (nextTransition) {
transition = nextTransition;
position = updatedPosition;
} else queuedReferences.push(key, value, keyIndex);
break;
default:
queuedReferences.push(key, value, keyIndex);
}
if (transition === undefined) return 0; // frozen: structure cap reached
keyIndex++;
}
// Cap enforcement for queued (nested-object / null) references. pack() advances msgpackr's
// shared write position and we cannot cleanly bail afterward, so preflight the whole queued
// chain through EXISTING transitions first: if the cap is reached and any field would need a
// new structure, fall back to plain encoding now (return 0) — before touching the shared
// position. Uses a FRESH length read (not the entry-time `frozen`): a getter invoked while
// reading values above may have minted on this same instance since entry.
if (!structureKnown && queuedReferences.length > 0 && typedStructs.length >= cap) {
let t = transition;
for (let i = 0, l = queuedReferences.length; i < l; i += 3) {
// A non-null (object/Date) ref is pack()ed into the shared buffer, advancing
// msgpackr's write position. Its structure variant (object16 vs object32) depends on
// the runtime ref-section offset (inline strings + earlier refs), which we can't know
// before packing — and we can't bail after a pack without corrupting the fallback. So
// under the cap, any record with a packing ref falls back to plain encoding now,
// before any pack(). null/undefined refs don't pack, so they're walked normally.
if (queuedReferences[i + 1] != null) return 0;
const nt = t[queuedReferences[i]];
if (!nt) return 0;
const next = nt.object16; // null/undefined ref → OBJECT_DATA size 2
if (!next) return 0;
t = next;
}
if (t[RECORD_SYMBOL] == null) return 0; // exact structure not yet minted
}
// Past the preflight the chain is known, so no minting happens — except a rare offset
// divergence (a known shape whose ref section now crosses 0xff00 and needs object32 where
// the preflight matched object16). Once a ref is packed we can no longer bail, so we finish
// via the unfrozen forceTypeTransition: a bounded, self-converging overshoot for that one
// record. packedRef keeps the record-id mint from bailing after a pack.
let packedRef = false;
for (let i = 0, l = queuedReferences.length; i < l;) {
let key = queuedReferences[i++];
let value = queuedReferences[i++];
let propertyIndex = queuedReferences[i++];
let nextTransition = transition[key];
if (!nextTransition) {
transition[key] = nextTransition = {
key,
parent: transition,
enumerationOffset: propertyIndex - keyIndex,
ascii0: null,
ascii8: null,
num8: null,
string16: null,
object16: null,
num32: null,
float64: null
};
}
let newPosition;
if (value) {
let size;
refOffset = refPosition - refsStartPosition;
if (refOffset < 0xff00) {
transition = nextTransition.object16;
if (transition)
size = 2;
else if ((transition = nextTransition.object32))
size = 4;
else {
transition = forceTypeTransition(nextTransition, OBJECT_DATA, 2);
size = 2;
}
} else {
transition = nextTransition.object32 || forceTypeTransition(nextTransition, OBJECT_DATA, 4);
size = 4;
}
newPosition = pack(value, refPosition);
packedRef = true;
if (typeof newPosition === 'object') {
// re-allocated
refPosition = newPosition.position;
targetView = newPosition.targetView;
target = newPosition.target;
refsStartPosition -= encodingStart;
position -= encodingStart;
start -= encodingStart;
encodingStart = 0;
} else
refPosition = newPosition;
if (size === 2) {
targetView.setUint16(position, refOffset, true);
position += 2;
} else {
targetView.setUint32(position, refOffset, true);
position += 4;
}
} else { // null or undefined
transition = nextTransition.object16 || forceTypeTransition(nextTransition, OBJECT_DATA, 2);
targetView.setInt16(position, value === null ? -10 : -9, true);
position += 2;
}
keyIndex++;
}
let recordId = transition[RECORD_SYMBOL];
if (recordId == null) {
// Flat records (no queued refs) reach here without packing, so the cap is enforced
// cleanly. Records that packed nested refs already passed the preflight; either way
// bailing now after refs were packed would corrupt the fallback.
if (!packedRef && typedStructs.length >= cap) return 0;
recordId = packr.typedStructs.length;
let structure = [];
let nextTransition = transition;
let key, type;
while ((type = nextTransition.__type) !== undefined) {
let size = nextTransition.__size;
nextTransition = nextTransition.__parent;
key = nextTransition.key;
let property = [type, size, key];
if (nextTransition.enumerationOffset)
property.push(nextTransition.enumerationOffset);
structure.push(property);
nextTransition = nextTransition.parent;
}
structure.reverse();
transition[RECORD_SYMBOL] = recordId;
packr.typedStructs[recordId] = structure;
pack(null, 0, true); // special call to notify that structures have been updated
}
switch (headerSize) {
case 1:
if (recordId >= 0x10) return 0;
target[start] = recordId + 0x20;
break;
case 2:
if (recordId >= 0x100) return 0;
target[start] = 0x38;
target[start + 1] = recordId;
break;
case 3:
if (recordId >= 0x10000) return 0;
target[start] = 0x39;
targetView.setUint16(start + 1, recordId, true);
break;
case 4:
if (recordId >= 0x1000000) return 0;
targetView.setUint32(start, (recordId << 8) + 0x3a, true);
break;
}
if (position < refsStartPosition) {
if (refsStartPosition === refPosition)
return position; // no refs
// adjust positioning
target.copyWithin(position, refsStartPosition, refPosition);
refPosition += position - refsStartPosition;
typedStructs.lastStringStart = position - start;
} else if (position > refsStartPosition) {
if (refsStartPosition === refPosition)
return position; // no refs
typedStructs.lastStringStart = position - start;
// Fixed section overflowed our estimate — retry with the corrected size. The structure
// is already minted at this point, so pass structureKnown=true to skip the cap check
// (otherwise a record that became frozen during attempt 1 would bail mid-retry, after
// refs were already packed, and corrupt the fallback).
return writeStruct(object, target, encodingStart, start, structures, makeRoom, pack, packr, true);
}
return refPosition;
}
function anyType(transition, position, targetView, value) {
let nextTransition;
if ((nextTransition = transition.ascii8 || transition.num8)) {
targetView.setInt8(position, value, true);
updatedPosition = position + 1;
return nextTransition;
}
if ((nextTransition = transition.string16 || transition.object16)) {
targetView.setInt16(position, value, true);
updatedPosition = position + 2;
return nextTransition;
}
if (nextTransition = transition.num32) {
targetView.setUint32(position, 0xe0000100 + value, true);
updatedPosition = position + 4;
return nextTransition;
}
// transition.float64
if (nextTransition = transition.num64) {
targetView.setFloat64(position, NaN, true);
targetView.setInt8(position, value);
updatedPosition = position + 8;
return nextTransition;
}
updatedPosition = position;
// TODO: can we do an "any" type where we defer the decision?
return;
}
// When the typed-structure dictionary reaches maxOwnStructures we stop minting new
// structures/transitions. typedStructs is append-only and pinned on the long-lived
// encoder (records reference structures by recordId), so an unbounded shape space —
// e.g. a wide, sparsely/variably-populated schema — would otherwise grow the
// dictionary + transition trie without limit. `frozen` is passed in (derived from the
// encoding instance's own typedStructs.length, never a shared global) so a re-entrant
// encode on another instance can't flip it; while frozen, a missing transition returns
// undefined so the caller bails and the record falls back to plain encoding.
function createTypeTransition(transition, type, size, frozen) {
let typeName = TYPE_NAMES[type] + (size << 3);
let newTransition = transition[typeName];
if (newTransition) return newTransition;
if (frozen) return undefined;
newTransition = transition[typeName] = Object.create(null);
newTransition.__type = type;
newTransition.__size = size;
newTransition.__parent = transition;
return newTransition;
}
// Unfrozen variant: always mints. Used in the queued-ref loop once a nested value has
// already been pack()ed — at that point pack() has advanced msgpackr's shared write
// position, so bailing with `return 0` would corrupt the fallback. We must finish the
// encode instead, even if that means minting a (bounded) handful of structures past the
// cap. The cap is still enforced up front via the preflight, before the first pack().
function forceTypeTransition(transition, type, size) {
let typeName = TYPE_NAMES[type] + (size << 3);
let newTransition = transition[typeName];
if (newTransition) return newTransition;
newTransition = transition[typeName] = Object.create(null);
newTransition.__type = type;
newTransition.__size = size;
newTransition.__parent = transition;
return newTransition;
}
function onLoadedStructures(sharedData) {
if (!(sharedData instanceof Map))
return sharedData;
let typed = sharedData.get('typed') || [];
if (Object.isFrozen(typed))
typed = typed.map(structure => structure.slice(0));
let named = sharedData.get('named');
let transitions = Object.create(null);
for (let i = 0, l = typed.length; i < l; i++) {
let structure = typed[i];
let transition = transitions;
for (let [type, size, key] of structure) {
let nextTransition = transition[key];
if (!nextTransition) {
transition[key] = nextTransition = {
key,
parent: transition,
enumerationOffset: 0,
ascii0: null,
ascii8: null,
num8: null,
string16: null,
object16: null,
num32: null,
float64: null,
date64: null,
};
}
// Replaying persisted structures is never subject to the cap — always mint.
transition = createTypeTransition(nextTransition, type, size, false);
}
transition[RECORD_SYMBOL] = i;
}
typed.transitions = transitions;
this.typedStructs = typed;
this.lastTypedStructuresLength = typed.length;
return named;
}
var sourceSymbol = Symbol.for('source');
function readStruct(src, position, srcEnd, unpackr) {
let recordId = src[position++] - 0x20;
if (recordId >= 24) {
switch(recordId) {
case 24: recordId = src[position++]; break;
// little endian:
case 25: recordId = src[position++] + (src[position++] << 8); break;
case 26: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16); break;
case 27: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16) + (src[position++] << 24); break;
}
}
let structure = unpackr.typedStructs && unpackr.typedStructs[recordId];
if (!structure) {
// copy src buffer because getStructures will override it
src = Uint8Array.prototype.slice.call(src, position, srcEnd);
srcEnd -= position;
position = 0;
if (!unpackr.getStructures)
throw new Error(`Reference to shared structure ${recordId} without getStructures method`);
unpackr._mergeStructures(unpackr.getStructures());
if (!unpackr.typedStructs)
throw new Error('Could not find any shared typed structures');
unpackr.lastTypedStructuresLength = unpackr.typedStructs.length;
structure = unpackr.typedStructs[recordId];
if (!structure)
throw new Error('Could not find typed structure ' + recordId);
}
var construct = structure.construct;
var fullConstruct = structure.fullConstruct;
if (!construct) {
construct = structure.construct = function LazyObject() {
};
fullConstruct = structure.fullConstruct = function LoadedObject() {
};
fullConstruct.prototype = unpackr.structPrototype || {};
var prototype = construct.prototype = unpackr.structPrototype ? Object.create(unpackr.structPrototype) : {};
let properties = [];
let currentOffset = 0;
let lastRefProperty;
for (let i = 0, l = structure.length; i < l; i++) {
let definition = structure[i];
let [ type, size, key, enumerationOffset ] = definition;
if (key === '__proto__')
key = '__proto_';
let property = {
key,
offset: currentOffset,
};
if (enumerationOffset)
properties.splice(i + enumerationOffset, 0, property);
else
properties.push(property);
let getRef;
switch(size) { // TODO: Move into a separate function
case 0: getRef = () => 0; break;
case 1:
getRef = (source, position) => {
let ref = source.bytes[position + property.offset];
return ref >= 0xf6 ? toConstant(ref) : ref;
};
break;
case 2:
getRef = (source, position) => {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let ref = dataView.getUint16(position + property.offset, true);
return ref >= 0xff00 ? toConstant(ref & 0xff) : ref;
};
break;
case 4:
getRef = (source, position) => {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let ref = dataView.getUint32(position + property.offset, true);
return ref >= 0xffffff00 ? toConstant(ref & 0xff) : ref;
};
break;
}
property.getRef = getRef;
currentOffset += size;
let get;
switch(type) {
case ASCII:
if (lastRefProperty && !lastRefProperty.next)
lastRefProperty.next = property;
lastRefProperty = property;
property.multiGetCount = 0;
get = function(source) {
let src = source.bytes;
let position = source.position;
let refStart = currentOffset + position;
let ref = getRef(source, position);
if (typeof ref !== 'number') return ref;
let end, next = property.next;
while(next) {
end = next.getRef(source, position);
if (typeof end === 'number')
break;
else
end = null;
next = next.next;
}
if (end == null)
end = source.bytesEnd - refStart;
if (source.srcString) {
return source.srcString.slice(ref, end);
}
/*if (property.multiGetCount > 0) {
let asciiEnd;
next = firstRefProperty;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
do {
asciiEnd = dataView.getUint16(source.position + next.offset, true);
if (asciiEnd < 0xff00)
break;
else
asciiEnd = null;
} while((next = next.next));
if (asciiEnd == null)
asciiEnd = source.bytesEnd - refStart
source.srcString = src.toString('latin1', refStart, refStart + asciiEnd);
return source.srcString.slice(ref, end);
}
if (source.prevStringGet) {
source.prevStringGet.multiGetCount += 2;
} else {
source.prevStringGet = property;
property.multiGetCount--;
}*/
return readString(src, ref + refStart, end - ref);
//return src.toString('latin1', ref + refStart, end + refStart);
};
break;
case UTF8: case OBJECT_DATA:
if (lastRefProperty && !lastRefProperty.next)
lastRefProperty.next = property;
lastRefProperty = property;
get = function(source) {
let position = source.position;
let refStart = currentOffset + position;
let ref = getRef(source, position);
if (typeof ref !== 'number') return ref;
let src = source.bytes;
let end, next = property.next;
while(next) {
end = next.getRef(source, position);
if (typeof end === 'number')
break;
else
end = null;
next = next.next;
}
if (end == null)
end = source.bytesEnd - refStart;
if (type === UTF8) {
return src.toString('utf8', ref + refStart, end + refStart);
} else {
currentSource = source;
try {
return unpackr.unpack(src, { start: ref + refStart, end: end + refStart });
} finally {
currentSource = null;
}
}
};
break;
case NUMBER:
switch(size) {
case 4:
get = function (source) {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let position = source.position + property.offset;
let value = dataView.getInt32(position, true);
if (value < 0x20000000) {
if (value > -0x1f000000)
return value;
if (value > -0x20000000)
return toConstant(value & 0xff);
}
let fValue = dataView.getFloat32(position, true);
// this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
let multiplier = mult10[((src[position + 3] & 0x7f) << 1) | (src[position + 2] >> 7)];
return ((multiplier * fValue + (fValue > 0 ? 0.5 : -0.5)) >> 0) / multiplier;
};
break;
case 8:
get = function (source) {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let value = dataView.getFloat64(source.position + property.offset, true);
if (isNaN(value)) {
let byte = src[source.position + property.offset];
if (byte >= 0xf6)
return toConstant(byte);
}
return value;
};
break;
case 1:
get = function (source) {
let src = source.bytes;
let value = src[source.position + property.offset];
return value < 0xf6 ? value : toConstant(value);
};
break;
}
break;
case DATE:
get = function (source) {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
return new Date(dataView.getFloat64(source.position + property.offset, true));
};
break;
}
property.get = get;
}
// TODO: load the srcString for faster string decoding on toJSON
if (evalSupported) {
let objectLiteralProperties = [];
let args = [];
let i = 0;
let hasInheritedProperties;
for (let property of properties) { // assign in enumeration order
if (unpackr.alwaysLazyProperty && unpackr.alwaysLazyProperty(property.key)) {
// these properties are not eagerly evaluated and this can be used for creating properties
// that are not serialized as JSON
hasInheritedProperties = true;
continue;
}
Object.defineProperty(prototype, property.key, { get: withSource(property.get), enumerable: true });
let valueFunction = 'v' + i++;
args.push(valueFunction);
objectLiteralProperties.push('o[' + JSON.stringify(property.key) + ']=' + valueFunction + '(s)');
}
if (hasInheritedProperties) {
objectLiteralProperties.push('__proto__:this');
}
let toObject = (new Function(...args, 'var c=this;return function(s){var o=new c();' + objectLiteralProperties.join(';') + ';return o;}')).apply(fullConstruct, properties.map(prop => prop.get));
Object.defineProperty(prototype, 'toJSON', {
value(omitUnderscoredProperties) {
return toObject.call(this, this[sourceSymbol]);
}
});
} else {
Object.defineProperty(prototype, 'toJSON', {
value(omitUnderscoredProperties) {
// return an enumerable object with own properties to JSON stringify
let resolved = {};
for (let i = 0, l = properties.length; i < l; i++) {
// TODO: check alwaysLazyProperty
let key = properties[i].key;
resolved[key] = this[key];
}
return resolved;
},
// not enumerable or anything
});
}
}
var instance = new construct();
instance[sourceSymbol] = {
bytes: src,
position,
srcString: '',
bytesEnd: srcEnd
};
return instance;
}
function toConstant(code) {
switch(code) {
case 0xf6: return null;
case 0xf7: return undefined;
case 0xf8: return false;
case 0xf9: return true;
}
throw new Error('Unknown constant');
}
function withSource(get) {
return function() {
return get(this[sourceSymbol]);
}
}
function saveState() {
if (currentSource) {
currentSource.bytes = Uint8Array.prototype.slice.call(currentSource.bytes, currentSource.position, currentSource.bytesEnd);
currentSource.position = 0;
currentSource.bytesEnd = currentSource.bytes.length;
}
}
function prepareStructures(structures, packr) {
if (packr.typedStructs) {
let structMap = new Map();
structMap.set('named', structures);
structMap.set('typed', packr.typedStructs);
structures = structMap;
}
let lastTypedStructuresLength = packr.lastTypedStructuresLength || 0;
structures.isCompatible = existing => {
let compatible = true;
if (existing instanceof Map) {
let named = existing.get('named') || [];
if (named.length !== (packr.lastNamedStructuresLength || 0))
compatible = false;
let typed = existing.get('typed') || [];
if (typed.length !== lastTypedStructuresLength)
compatible = false;
} else if (existing instanceof Array || Array.isArray(existing)) {
if (existing.length !== (packr.lastNamedStructuresLength || 0))
compatible = false;
}
if (!compatible)
packr._mergeStructures(existing);
return compatible;
};
packr.lastTypedStructuresLength = packr.typedStructs && packr.typedStructs.length;
return structures;
}
setReadStruct(readStruct, onLoadedStructures, saveState);
class PackrStream extends stream.Transform {
constructor(options) {
if (!options)
options = {};
options.writableObjectMode = true;
super(options);
options.sequential = true;
this.packr = options.packr || new Packr(options);
}
_transform(value, encoding, callback) {
this.push(this.packr.pack(value));
callback();
}
}
class UnpackrStream extends stream.Transform {
constructor(options) {
if (!options)
options = {};
options.objectMode = true;
super(options);
options.structures = [];
this.maxIncompleteBufferSize = options.maxIncompleteBufferSize !== undefined ? options.maxIncompleteBufferSize : 0x4000000;
this.unpackr = options.unpackr || new Unpackr(options);
}
_transform(chunk, encoding, callback) {
if (this.incompleteBuffer) {
chunk = Buffer.concat([this.incompleteBuffer, chunk]);
this.incompleteBuffer = null;
}
let values;
try {
values = this.unpackr.unpackMultiple(chunk);
} catch(error) {
if (error.incomplete) {
let incompleteBuffer = chunk.slice(error.lastPosition);
if (incompleteBuffer.length > this.maxIncompleteBufferSize) {
this.incompleteBuffer = null;
return callback(new Error('Maximum incomplete buffer size exceeded'))
}
this.incompleteBuffer = incompleteBuffer;
values = error.values;
} else {
return callback(error)
}
}
for (let value of values || []) {
if (value === null)
value = this.getNullValue();
this.push(value);
}
callback();
}
getNullValue() {
return Symbol.for(null)
}
}
/**
* Given an Iterable first argument, returns an Iterable where each value is packed as a Buffer
* If the argument is only Async Iterable, the return value will be an Async Iterable.
* @param {Iterable|Iterator|AsyncIterable|AsyncIterator} objectIterator - iterable source, like a Readable object stream, an array, Set, or custom object
* @param {options} [options] - msgpackr pack options
* @returns {IterableIterator|Promise.<AsyncIterableIterator>}
*/
function packIter (objectIterator, options = {}) {
if (!objectIterator || typeof objectIterator !== 'object') {
throw new Error('first argument must be an Iterable, Async Iterable, or a Promise for an Async Iterable')
} else if (typeof objectIterator[Symbol.iterator] === 'function') {
return packIterSync(objectIterator, options)
} else if (typeof objectIterator.then === 'function' || typeof objectIterator[Symbol.asyncIterator] === 'function') {
return packIterAsync(objectIterator, options)
} else {
throw new Error('first argument must be an Iterable, Async Iterable, Iterator, Async Iterator, or a Promise')
}
}
function * packIterSync (objectIterator, options) {
const packr = new Packr(options);
for (const value of objectIterator) {
yield packr.pack(value);
}
}
async function * packIterAsync (objectIterator, options) {
const packr = new Packr(options);
for await (const value of objectIterator) {
yield packr.pack(value);
}
}
/**
* Given an Iterable/Iterator input which yields buffers, returns an IterableIterator which yields sync decoded objects
* Or, given an Async Iterable/Iterator which yields promises resolving in buffers, returns an AsyncIterableIterator.
* @param {Iterable|Iterator|AsyncIterable|AsyncIterableIterator} bufferIterator
* @param {object} [options] - unpackr options
* @returns {IterableIterator|Promise.<AsyncIterableIterator}
*/
function unpackIter (bufferIterator, options = {}) {
if (!bufferIterator || typeof bufferIterator !== 'object') {
throw new Error('first argument must be an Iterable, Async Iterable, Iterator, Async Iterator, or a promise')
}
const unpackr = new Unpackr(options);
let incomplete;
const parser = (chunk) => {
let yields;
// if there's incomplete data from previous chunk, concatinate and try again
if (incomplete) {
chunk = Buffer.concat([incomplete, chunk]);
incomplete = undefined;
}
try {
yields = unpackr.unpackMultiple(chunk);
} catch (err) {
if (err.incomplete) {
incomplete = chunk.slice(err.lastPosition);
yields = err.values;
} else {
throw err
}
}
return yields
};
if (typeof bufferIterator[Symbol.iterator] === 'function') {
return (function * iter () {
for (const value of bufferIterator) {
yield * parser(value);
}
})()
} else if (typeof bufferIterator[Symbol.asyncIterator] === 'function') {
return (async function * iter () {
for await (const value of bufferIterator) {
yield * parser(value);
}
})()
}
}
const decodeIter = unpackIter;
const encodeIter = packIter;
const useRecords = false;
const mapsAsObjects = true;
const nativeAccelerationDisabled = process.env.MSGPACKR_NATIVE_ACCELERATION_DISABLED !== undefined && process.env.MSGPACKR_NATIVE_ACCELERATION_DISABLED.toLowerCase() === 'true';
if (!nativeAccelerationDisabled) {
let extractor;
try {
if (typeof require == 'function')
extractor = require('msgpackr-extract');
else
extractor = module$1.createRequire((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('node.cjs', document.baseURI).href)))('msgpackr-extract');
if (extractor)
setExtractor(extractor.extractStrings);
} catch (error) {
// native module is optional
}
}
exports.ALWAYS = ALWAYS;
exports.C1 = C1;
exports.DECIMAL_FIT = DECIMAL_FIT;
exports.DECIMAL_ROUND = DECIMAL_ROUND;
exports.Decoder = Decoder;
exports.DecoderStream = UnpackrStream;
exports.Encoder = Encoder;
exports.EncoderStream = PackrStream;
exports.FLOAT32_OPTIONS = FLOAT32_OPTIONS;
exports.NEVER = NEVER;
exports.Packr = Packr;
exports.PackrStream = PackrStream;
exports.RESERVE_START_SPACE = RESERVE_START_SPACE;
exports.RESET_BUFFER_MODE = RESET_BUFFER_MODE;
exports.REUSE_BUFFER_MODE = REUSE_BUFFER_MODE;
exports.Unpackr = Unpackr;
exports.UnpackrStream = UnpackrStream;
exports.addExtension = addExtension;
exports.clearSource = clearSource;
exports.decode = decode;
exports.decodeIter = decodeIter;
exports.encode = encode;
exports.encodeIter = encodeIter;
exports.mapsAsObjects = mapsAsObjects;
exports.pack = pack;
exports.roundFloat32 = roundFloat32;
exports.unpack = unpack;
exports.unpackMultiple = unpackMultiple;
exports.useRecords = useRecords;
//# sourceMappingURL=node.cjs.map
File diff suppressed because one or more lines are too long
+5032
View File
@@ -0,0 +1,5032 @@
(function (chai, stream, module, fs) {
'use strict';
var decoder;
try {
decoder = new TextDecoder();
} catch(error) {}
var src;
var srcEnd;
var position$1 = 0;
const EMPTY_ARRAY = [];
var strings = EMPTY_ARRAY;
var stringPosition = 0;
var currentUnpackr = {};
var currentStructures;
var srcString;
var srcStringStart = 0;
var srcStringEnd = 0;
var bundledStrings$1;
var referenceMap;
var currentExtensions = [];
var dataView;
var defaultOptions = {
useRecords: false,
mapsAsObjects: true
};
class C1Type {}
const C1 = new C1Type();
C1.name = 'MessagePack 0xC1';
var sequentialMode = false;
var inlineObjectReadThreshold = 2;
var readStruct$1, onLoadedStructures$1, onSaveState;
let Unpackr$1 = class Unpackr {
constructor(options) {
if (options) {
if (options.useRecords === false && options.mapsAsObjects === undefined)
options.mapsAsObjects = true;
if (options.sequential && options.trusted !== false) {
options.trusted = true;
if (!options.structures && options.useRecords != false) {
options.structures = [];
if (!options.maxSharedStructures)
options.maxSharedStructures = 0;
}
}
if (options.structures)
options.structures.sharedLength = options.structures.length;
else if (options.getStructures) {
(options.structures = []).uninitialized = true; // this is what we use to denote an uninitialized structures
options.structures.sharedLength = 0;
}
if (options.int64AsNumber) {
options.int64AsType = 'number';
}
}
Object.assign(this, options);
}
unpack(source, options) {
if (src) {
// re-entrant execution, save the state and restore it after we do this unpack
return saveState$1(() => {
clearSource();
return this ? this.unpack(source, options) : Unpackr$1.prototype.unpack.call(defaultOptions, source, options)
})
}
if (!source.buffer && source.constructor === ArrayBuffer)
source = typeof Buffer !== 'undefined' ? Buffer.from(source) : new Uint8Array(source);
if (typeof options === 'object') {
srcEnd = options.end || source.length;
position$1 = options.start || 0;
} else {
position$1 = 0;
srcEnd = options > -1 ? options : source.length;
}
stringPosition = 0;
srcStringEnd = 0;
srcString = null;
strings = EMPTY_ARRAY;
bundledStrings$1 = null;
src = source;
// this provides cached access to the data view for a buffer if it is getting reused, which is a recommend
// technique for getting data from a database where it can be copied into an existing buffer instead of creating
// new ones
try {
dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength));
} catch(error) {
// if it doesn't have a buffer, maybe it is the wrong type of object
src = null;
if (source instanceof Uint8Array)
throw error
throw new Error('Source must be a Uint8Array or Buffer but was a ' + ((source && typeof source == 'object') ? source.constructor.name : typeof source))
}
if (this instanceof Unpackr$1) {
currentUnpackr = this;
if (this.structures) {
currentStructures = this.structures;
return checkedRead(options)
} else if (!currentStructures || currentStructures.length > 0) {
currentStructures = [];
}
} else {
currentUnpackr = defaultOptions;
if (!currentStructures || currentStructures.length > 0)
currentStructures = [];
}
return checkedRead(options)
}
unpackMultiple(source, forEach) {
let values, lastPosition = 0;
try {
sequentialMode = true;
let size = source.length;
let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size);
if (forEach) {
if (forEach(value, lastPosition, position$1) === false) return;
while(position$1 < size) {
lastPosition = position$1;
if (forEach(checkedRead(), lastPosition, position$1) === false) {
return
}
}
}
else {
values = [ value ];
while(position$1 < size) {
lastPosition = position$1;
values.push(checkedRead());
}
return values
}
} catch(error) {
error.lastPosition = lastPosition;
error.values = values;
throw error
} finally {
sequentialMode = false;
clearSource();
}
}
_mergeStructures(loadedStructures, existingStructures) {
if (onLoadedStructures$1)
loadedStructures = onLoadedStructures$1.call(this, loadedStructures);
loadedStructures = loadedStructures || [];
if (Object.isFrozen(loadedStructures))
loadedStructures = loadedStructures.map(structure => structure.slice(0));
for (let i = 0, l = loadedStructures.length; i < l; i++) {
let structure = loadedStructures[i];
if (structure) {
structure.isShared = true;
if (i >= 32)
structure.highByte = (i - 32) >> 5;
}
}
loadedStructures.sharedLength = loadedStructures.length;
for (let id in existingStructures || []) {
if (id >= 0) {
let structure = loadedStructures[id];
let existing = existingStructures[id];
if (existing) {
if (structure)
(loadedStructures.restoreStructures || (loadedStructures.restoreStructures = []))[id] = structure;
loadedStructures[id] = existing;
}
}
}
return this.structures = loadedStructures
}
decode(source, options) {
return this.unpack(source, options)
}
};
function checkedRead(options) {
try {
if (!currentUnpackr.trusted && !sequentialMode) {
let sharedLength = currentStructures.sharedLength || 0;
if (sharedLength < currentStructures.length)
currentStructures.length = sharedLength;
}
let result;
if (currentUnpackr.randomAccessStructure && src[position$1] < 0x40 && src[position$1] >= 0x20 && readStruct$1) {
result = readStruct$1(src, position$1, srcEnd, currentUnpackr);
src = null; // dispose of this so that recursive unpack calls don't save state
if (!(options && options.lazy) && result)
result = result.toJSON();
position$1 = srcEnd;
} else
result = read();
if (bundledStrings$1) { // bundled strings to skip past
position$1 = bundledStrings$1.postBundlePosition;
bundledStrings$1 = null;
}
if (sequentialMode)
// we only need to restore the structures if there was an error, but if we completed a read,
// we can clear this out and keep the structures we read
currentStructures.restoreStructures = null;
if (position$1 == srcEnd) {
// finished reading this source, cleanup references
if (currentStructures && currentStructures.restoreStructures)
restoreStructures();
currentStructures = null;
src = null;
if (referenceMap)
referenceMap = null;
} else if (position$1 > srcEnd) {
// over read
throw new Error('Unexpected end of MessagePack data')
} else if (!sequentialMode) {
let jsonView;
try {
jsonView = JSON.stringify(result, (_, value) => typeof value === "bigint" ? `${value}n` : value).slice(0, 100);
} catch(error) {
jsonView = '(JSON view not available ' + error + ')';
}
throw new Error('Data read, but end of buffer not reached ' + jsonView)
}
// else more to read, but we are reading sequentially, so don't clear source yet
return result
} catch(error) {
if (currentStructures && currentStructures.restoreStructures)
restoreStructures();
clearSource();
if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer') || position$1 > srcEnd) {
error.incomplete = true;
}
throw error
}
}
function restoreStructures() {
for (let id in currentStructures.restoreStructures) {
currentStructures[id] = currentStructures.restoreStructures[id];
}
currentStructures.restoreStructures = null;
}
function read() {
let token = src[position$1++];
if (token < 0xa0) {
if (token < 0x80) {
if (token < 0x40)
return token
else {
let structure = currentStructures[token & 0x3f] ||
currentUnpackr.getStructures && loadStructures()[token & 0x3f];
if (structure) {
if (!structure.read) {
structure.read = createStructureReader(structure, token & 0x3f);
}
return structure.read()
} else
return token
}
} else if (token < 0x90) {
// map
token -= 0x80;
if (currentUnpackr.mapsAsObjects) {
let object = {};
for (let i = 0; i < token; i++) {
let key = readKey();
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
return object
} else {
let map = new Map();
for (let i = 0; i < token; i++) {
map.set(read(), read());
}
return map
}
} else {
token -= 0x90;
let array = new Array(token);
for (let i = 0; i < token; i++) {
array[i] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
} else if (token < 0xc0) {
// fixstr
let length = token - 0xa0;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += length) - srcStringStart)
}
if (srcStringEnd == 0 && srcEnd < 140) {
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length);
if (string != null)
return string
}
return readFixedString(length)
} else {
let value;
switch (token) {
case 0xc0: return null
case 0xc1:
if (bundledStrings$1) {
value = read(); // followed by the length of the string in characters (not bytes!)
if (value > 0)
return bundledStrings$1[1].slice(bundledStrings$1.position1, bundledStrings$1.position1 += value)
else
return bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 -= value)
}
return C1; // "never-used", return special object to denote that
case 0xc2: return false
case 0xc3: return true
case 0xc4:
// bin 8
value = src[position$1++];
if (value === undefined)
throw new Error('Unexpected end of buffer')
return readBin(value)
case 0xc5:
// bin 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readBin(value)
case 0xc6:
// bin 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readBin(value)
case 0xc7:
// ext 8
return readExt(src[position$1++])
case 0xc8:
// ext 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readExt(value)
case 0xc9:
// ext 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readExt(value)
case 0xca:
value = dataView.getFloat32(position$1);
if (currentUnpackr.useFloat32 > 2) {
// this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
let multiplier = mult10[((src[position$1] & 0x7f) << 1) | (src[position$1 + 1] >> 7)];
position$1 += 4;
return ((multiplier * value + (value > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
position$1 += 4;
return value
case 0xcb:
value = dataView.getFloat64(position$1);
position$1 += 8;
return value
// uint handlers
case 0xcc:
return src[position$1++]
case 0xcd:
value = dataView.getUint16(position$1);
position$1 += 2;
return value
case 0xce:
value = dataView.getUint32(position$1);
position$1 += 4;
return value
case 0xcf:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getUint32(position$1) * 0x100000000;
value += dataView.getUint32(position$1 + 4);
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigUint64(position$1).toString();
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigUint64(position$1);
if (value<=BigInt(2)<<BigInt(52)) value=Number(value);
} else
value = dataView.getBigUint64(position$1);
position$1 += 8;
return value
// int handlers
case 0xd0:
return dataView.getInt8(position$1++)
case 0xd1:
value = dataView.getInt16(position$1);
position$1 += 2;
return value
case 0xd2:
value = dataView.getInt32(position$1);
position$1 += 4;
return value
case 0xd3:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getInt32(position$1) * 0x100000000;
value += dataView.getUint32(position$1 + 4);
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigInt64(position$1).toString();
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigInt64(position$1);
if (value>=BigInt(-2)<<BigInt(52)&&value<=BigInt(2)<<BigInt(52)) value=Number(value);
} else
value = dataView.getBigInt64(position$1);
position$1 += 8;
return value
case 0xd4:
// fixext 1
value = src[position$1++];
if (value == 0x72) {
return recordDefinition(src[position$1++] & 0x3f)
} else {
let extension = currentExtensions[value];
if (extension) {
if (extension.read) {
position$1++; // skip filler byte
return extension.read(read())
} else if (extension.noBuffer) {
position$1++; // skip filler byte
return extension()
} else
return extension(src.subarray(position$1, ++position$1))
} else
throw new Error('Unknown extension ' + value)
}
case 0xd5:
// fixext 2
value = src[position$1];
if (value == 0x72) {
position$1++;
return recordDefinition(src[position$1++] & 0x3f, src[position$1++])
} else
return readExt(2)
case 0xd6:
// fixext 4
return readExt(4)
case 0xd7:
// fixext 8
return readExt(8)
case 0xd8:
// fixext 16
return readExt(16)
case 0xd9:
// str 8
value = src[position$1++];
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString8(value)
case 0xda:
// str 16
value = dataView.getUint16(position$1);
position$1 += 2;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString16(value)
case 0xdb:
// str 32
value = dataView.getUint32(position$1);
position$1 += 4;
if (srcStringEnd >= position$1) {
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
}
return readString32(value)
case 0xdc:
// array 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readArray(value)
case 0xdd:
// array 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readArray(value)
case 0xde:
// map 16
value = dataView.getUint16(position$1);
position$1 += 2;
return readMap(value)
case 0xdf:
// map 32
value = dataView.getUint32(position$1);
position$1 += 4;
return readMap(value)
default: // negative int
if (token >= 0xe0)
return token - 0x100
if (token === undefined) {
let error = new Error('Unexpected end of MessagePack data');
error.incomplete = true;
throw error
}
throw new Error('Unknown MessagePack token ' + token)
}
}
}
const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/;
function createStructureReader(structure, firstId) {
function readObject() {
// This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function
if (readObject.count++ > inlineObjectReadThreshold) {
let optimizedReadObject;
try {
optimizedReadObject = structure.read = (new Function('r', 'return function(){return ' + (currentUnpackr.freezeData ? 'Object.freeze' : '') +
'({' + structure.map(key => key === '__proto__' ? '__proto_:r()' : validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '})}'))(read);
} catch(error) {
// in CF workers, the new Function call could begin to fail at any point in time
inlineObjectReadThreshold = Infinity; // disable going forward
return readObject(); // recursively try again
}
structure.read0 = optimizedReadObject; // keep the un-wrapped body reader in sync
if (structure.highByte === 0)
structure.read = createSecondByteReader(firstId, structure.read);
return optimizedReadObject() // second byte is already read, if there is one so immediately read object
}
let object = {};
for (let i = 0, l = structure.length; i < l; i++) {
let key = structure[i];
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(object);
return object
}
readObject.count = 0;
// read0 is the un-wrapped body reader: it reads the record's values directly without
// consuming a leading high byte. recordDefinition uses it for the immediate read that follows
// a record definition (the high byte, if present, was already consumed). For highByte === 0
// structures the public reader is a second-byte reader (used by later references), but the
// definition read itself must not consume that byte.
structure.read0 = readObject;
if (structure.highByte === 0) {
return createSecondByteReader(firstId, readObject)
}
return readObject
}
const createSecondByteReader = (firstId, read0) => {
return function() {
let highByte = src[position$1++];
if (highByte === 0)
return read0()
let id = firstId < 32 ? -(firstId + (highByte << 5)) : firstId + (highByte << 5);
let structure = currentStructures[id] || loadStructures()[id];
if (!structure) {
throw new Error('Record id is not defined for ' + id)
}
if (!structure.read)
structure.read = createStructureReader(structure, firstId);
return structure.read()
}
};
function loadStructures() {
let loadedStructures = saveState$1(() => {
// save the state in case getStructures modifies our buffer
src = null;
return currentUnpackr.getStructures()
});
return currentStructures = currentUnpackr._mergeStructures(loadedStructures, currentStructures)
}
var readFixedString = readStringJS;
var readString8 = readStringJS;
var readString16 = readStringJS;
var readString32 = readStringJS;
function setExtractor(extractStrings) {
readFixedString = readString(1);
readString8 = readString(2);
readString16 = readString(3);
readString32 = readString(5);
function readString(headerLength) {
return function readString(length) {
let string = strings[stringPosition++];
if (string == null) {
if (bundledStrings$1)
return readStringJS(length)
let byteOffset = src.byteOffset;
let extraction = extractStrings(position$1 - headerLength + byteOffset, srcEnd + byteOffset, src.buffer);
if (typeof extraction == 'string') {
string = extraction;
strings = EMPTY_ARRAY;
} else {
strings = extraction;
stringPosition = 1;
srcStringEnd = 1; // even if a utf-8 string was decoded, must indicate we are in the midst of extracted strings and can't skip strings
string = strings[0];
if (string === undefined)
throw new Error('Unexpected end of buffer')
}
}
let srcStringLength = string.length;
if (srcStringLength <= length) {
position$1 += length;
return string
}
srcString = string;
srcStringStart = position$1;
srcStringEnd = position$1 + srcStringLength;
position$1 += length;
return string.slice(0, length) // we know we just want the beginning
}
}
}
function readStringJS(length) {
let result;
if (length < 16) {
if (result = shortStringInJS(length))
return result
}
if (length > 64 && decoder)
return decoder.decode(src.subarray(position$1, position$1 += length))
const end = position$1 + length;
const units = [];
result = '';
while (position$1 < end) {
const byte1 = src[position$1++];
if ((byte1 & 0x80) === 0) {
// 1 byte
units.push(byte1);
} else if ((byte1 & 0xe0) === 0xc0) {
// 2 bytes
const byte2 = src[position$1++] & 0x3f;
const codePoint = ((byte1 & 0x1f) << 6) | byte2;
// Reject overlong encoding: 2-byte sequences must encode values >= 0x80
if (codePoint < 0x80) {
units.push(0xFFFD); // replacement character
} else {
units.push(codePoint);
}
} else if ((byte1 & 0xf0) === 0xe0) {
// 3 bytes
const byte2 = src[position$1++] & 0x3f;
const byte3 = src[position$1++] & 0x3f;
const codePoint = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3;
// Reject overlong encoding: 3-byte sequences must encode values >= 0x800
// Also reject surrogates (0xD800-0xDFFF)
if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
units.push(0xFFFD); // replacement character
} else {
units.push(codePoint);
}
} else if ((byte1 & 0xf8) === 0xf0) {
// 4 bytes
const byte2 = src[position$1++] & 0x3f;
const byte3 = src[position$1++] & 0x3f;
const byte4 = src[position$1++] & 0x3f;
let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
// Reject overlong encoding: 4-byte sequences must encode values >= 0x10000
// Also reject values > 0x10FFFF (maximum valid Unicode)
if (unit < 0x10000 || unit > 0x10FFFF) {
units.push(0xFFFD); // replacement character
} else if (unit > 0xffff) {
unit -= 0x10000;
units.push(((unit >>> 10) & 0x3ff) | 0xd800);
unit = 0xdc00 | (unit & 0x3ff);
units.push(unit);
} else {
units.push(unit);
}
} else {
units.push(0xFFFD); // replacement character for invalid lead byte
}
if (units.length >= 0x1000) {
result += fromCharCode.apply(String, units);
units.length = 0;
}
}
if (units.length > 0) {
result += fromCharCode.apply(String, units);
}
return result
}
function readString(source, start, length) {
let existingSrc = src;
src = source;
position$1 = start;
try {
return readStringJS(length);
} finally {
src = existingSrc;
}
}
function readArray(length) {
let array = new Array(length);
for (let i = 0; i < length; i++) {
array[i] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
function readMap(length) {
if (currentUnpackr.mapsAsObjects) {
let object = {};
for (let i = 0; i < length; i++) {
let key = readKey();
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
return object
} else {
let map = new Map();
for (let i = 0; i < length; i++) {
map.set(read(), read());
}
return map
}
}
var fromCharCode = String.fromCharCode;
function longStringInJS(length) {
let start = position$1;
let bytes = new Array(length);
for (let i = 0; i < length; i++) {
const byte = src[position$1++];
if ((byte & 0x80) > 0) {
position$1 = start;
return
}
bytes[i] = byte;
}
return fromCharCode.apply(String, bytes)
}
function shortStringInJS(length) {
if (length < 4) {
if (length < 2) {
if (length === 0)
return ''
else {
let a = src[position$1++];
if ((a & 0x80) > 1) {
position$1 -= 1;
return
}
return fromCharCode(a)
}
} else {
let a = src[position$1++];
let b = src[position$1++];
if ((a & 0x80) > 0 || (b & 0x80) > 0) {
position$1 -= 2;
return
}
if (length < 3)
return fromCharCode(a, b)
let c = src[position$1++];
if ((c & 0x80) > 0) {
position$1 -= 3;
return
}
return fromCharCode(a, b, c)
}
} else {
let a = src[position$1++];
let b = src[position$1++];
let c = src[position$1++];
let d = src[position$1++];
if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
position$1 -= 4;
return
}
if (length < 6) {
if (length === 4)
return fromCharCode(a, b, c, d)
else {
let e = src[position$1++];
if ((e & 0x80) > 0) {
position$1 -= 5;
return
}
return fromCharCode(a, b, c, d, e)
}
} else if (length < 8) {
let e = src[position$1++];
let f = src[position$1++];
if ((e & 0x80) > 0 || (f & 0x80) > 0) {
position$1 -= 6;
return
}
if (length < 7)
return fromCharCode(a, b, c, d, e, f)
let g = src[position$1++];
if ((g & 0x80) > 0) {
position$1 -= 7;
return
}
return fromCharCode(a, b, c, d, e, f, g)
} else {
let e = src[position$1++];
let f = src[position$1++];
let g = src[position$1++];
let h = src[position$1++];
if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) {
position$1 -= 8;
return
}
if (length < 10) {
if (length === 8)
return fromCharCode(a, b, c, d, e, f, g, h)
else {
let i = src[position$1++];
if ((i & 0x80) > 0) {
position$1 -= 9;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i)
}
} else if (length < 12) {
let i = src[position$1++];
let j = src[position$1++];
if ((i & 0x80) > 0 || (j & 0x80) > 0) {
position$1 -= 10;
return
}
if (length < 11)
return fromCharCode(a, b, c, d, e, f, g, h, i, j)
let k = src[position$1++];
if ((k & 0x80) > 0) {
position$1 -= 11;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k)
} else {
let i = src[position$1++];
let j = src[position$1++];
let k = src[position$1++];
let l = src[position$1++];
if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) {
position$1 -= 12;
return
}
if (length < 14) {
if (length === 12)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l)
else {
let m = src[position$1++];
if ((m & 0x80) > 0) {
position$1 -= 13;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m)
}
} else {
let m = src[position$1++];
let n = src[position$1++];
if ((m & 0x80) > 0 || (n & 0x80) > 0) {
position$1 -= 14;
return
}
if (length < 15)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n)
let o = src[position$1++];
if ((o & 0x80) > 0) {
position$1 -= 15;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
}
}
}
}
}
function readOnlyJSString() {
let token = src[position$1++];
let length;
if (token < 0xc0) {
// fixstr
length = token - 0xa0;
} else {
switch(token) {
case 0xd9:
// str 8
length = src[position$1++];
break
case 0xda:
// str 16
length = dataView.getUint16(position$1);
position$1 += 2;
break
case 0xdb:
// str 32
length = dataView.getUint32(position$1);
position$1 += 4;
break
default:
throw new Error('Expected string')
}
}
return readStringJS(length)
}
function readBin(length) {
return currentUnpackr.copyBuffers ?
// specifically use the copying slice (not the node one)
Uint8Array.prototype.slice.call(src, position$1, position$1 += length) :
src.subarray(position$1, position$1 += length)
}
function readExt(length) {
let type = src[position$1++];
if (currentExtensions[type]) {
let end;
return currentExtensions[type](src.subarray(position$1, end = (position$1 += length)), (readPosition) => {
position$1 = readPosition;
try {
return read();
} finally {
position$1 = end;
}
})
}
else
throw new Error('Unknown extension type ' + type)
}
var keyCache = new Array(4096);
function readKey() {
let length = src[position$1++];
if (length >= 0xa0 && length < 0xc0) {
// fixstr, potentially use key cache
length = length - 0xa0;
if (srcStringEnd >= position$1) // if it has been extracted, must use it (and faster anyway)
return srcString.slice(position$1 - srcStringStart, (position$1 += length) - srcStringStart)
else if (!(srcStringEnd == 0 && srcEnd < 180))
return readFixedString(length)
} else { // not cacheable, go back and do a standard read
position$1--;
return asSafeString(read())
}
let key = ((length << 5) ^ (length > 1 ? dataView.getUint16(position$1) : length > 0 ? src[position$1] : 0)) & 0xfff;
let entry = keyCache[key];
let checkPosition = position$1;
let end = position$1 + length - 3;
let chunk;
let i = 0;
if (entry && entry.bytes == length) {
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition);
if (chunk != entry[i++]) {
checkPosition = 0x70000000;
break
}
checkPosition += 4;
}
end += 3;
while (checkPosition < end) {
chunk = src[checkPosition++];
if (chunk != entry[i++]) {
checkPosition = 0x70000000;
break
}
}
if (checkPosition === end) {
position$1 = checkPosition;
return entry.string
}
end -= 3;
checkPosition = position$1;
}
entry = [];
keyCache[key] = entry;
entry.bytes = length;
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition);
entry.push(chunk);
checkPosition += 4;
}
end += 3;
while (checkPosition < end) {
chunk = src[checkPosition++];
entry.push(chunk);
}
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length);
if (string != null)
return entry.string = string
return entry.string = readFixedString(length)
}
function asSafeString(property) {
// protect against expensive (DoS) string conversions
if (typeof property === 'string') return property;
if (typeof property === 'number' || typeof property === 'boolean' || typeof property === 'bigint') return property.toString();
if (property == null) return property + '';
if (currentUnpackr.allowArraysInMapKeys && Array.isArray(property) && property.flat().every(item => ['string', 'number', 'boolean', 'bigint'].includes(typeof item))) {
return property.flat().toString();
}
throw new Error(`Invalid property type for record: ${typeof property}`);
}
// the registration of the record definition extension (as "r")
const recordDefinition = (id, highByte) => {
let structure = read().map(asSafeString); // ensure that all keys are strings and
// that the array is mutable
let firstByte = id;
if (highByte !== undefined) {
id = id < 32 ? -((highByte << 5) + id) : ((highByte << 5) + id);
structure.highByte = highByte;
}
let existingStructure = currentStructures[id];
// If it is a shared structure, we need to restore any changes after reading.
// Also in sequential mode, we may get incomplete reads and thus errors, and we need to restore
// to the state prior to an incomplete read in order to properly resume.
if (existingStructure && (existingStructure.isShared || sequentialMode)) {
(currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure;
}
currentStructures[id] = structure;
structure.read = createStructureReader(structure, firstByte);
// The high byte (if any) was already consumed as the `highByte` argument above, so read the
// record body directly. Going through structure.read (a second-byte reader when highByte === 0)
// would misinterpret the first value byte as a high byte — corrupting two-byte own-record
// definitions (0xd5 0x72 ...). createStructureReader stashes the un-wrapped body reader on
// structure.read0 precisely for this immediate post-definition read.
return (structure.read0 || structure.read)()
};
currentExtensions[0] = () => {}; // notepack defines extension 0 to mean undefined, so use that as the default here
currentExtensions[0].noBuffer = true;
currentExtensions[0x42] = data => {
let headLength = (data.byteLength % 8) || 8;
let head = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0]);
for (let i = 1; i < headLength; i++) {
head <<= BigInt(8);
head += BigInt(data[i]);
}
if (data.byteLength !== headLength) {
let view = new DataView(data.buffer, data.byteOffset, data.byteLength);
let decode = (start, end) => {
let length = end - start;
if (length <= 40) {
let out = view.getBigUint64(start);
for (let i = start + 8; i < end; i += 8) {
out <<= BigInt(64);
out |= view.getBigUint64(i);
}
return out
}
// if (length === 8) return view.getBigUint64(start)
let middle = start + (length >> 4 << 3);
let left = decode(start, middle);
let right = decode(middle, end);
return (left << BigInt((end - middle) * 8)) | right
};
head = (head << BigInt((view.byteLength - headLength) * 8)) | decode(headLength, view.byteLength);
}
return head
};
let errors = {
Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError: typeof AggregateError === 'function' ? AggregateError : null,
};
currentExtensions[0x65] = () => {
let data = read();
if (!errors[data[0]]) {
let error = Error(data[1], { cause: data[2] });
error.name = data[0];
return error
}
return errors[data[0]](data[1], { cause: data[2] })
};
currentExtensions[0x69] = (data) => {
// id extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position$1 - 4);
if (!referenceMap)
referenceMap = new Map();
let token = src[position$1];
let target;
// TODO: handle any other types that can cycle and make the code more robust if there are other extensions
if (token >= 0x90 && token < 0xa0 || token == 0xdc || token == 0xdd)
target = [];
else if (token >= 0x80 && token < 0x90 || token == 0xde || token == 0xdf)
target = new Map();
else if ((token >= 0xc7 && token <= 0xc9 || token >= 0xd4 && token <= 0xd8) && src[position$1 + 1] === 0x73)
target = new Set();
else
target = {};
let refEntry = { target }; // a placeholder object
referenceMap.set(id, refEntry);
let targetProperties = read(); // read the next value as the target object to id
if (!refEntry.used) {
// no cycle, can just use the returned read object
return refEntry.target = targetProperties // replace the placeholder with the real one
} else {
// there is a cycle, so we have to assign properties to original target
Object.assign(target, targetProperties);
}
// copy over map/set entries if we're able to
if (target instanceof Map)
for (let [k, v] of targetProperties.entries()) target.set(k, v);
if (target instanceof Set)
for (let i of Array.from(targetProperties)) target.add(i);
return target
};
currentExtensions[0x70] = (data) => {
// pointer extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position$1 - 4);
let refEntry = referenceMap.get(id);
refEntry.used = true;
return refEntry.target
};
currentExtensions[0x73] = () => new Set(read());
const typedArrays = ['Int8','Uint8','Uint8Clamped','Int16','Uint16','Int32','Uint32','Float32','Float64','BigInt64','BigUint64'].map(type => type + 'Array');
let glbl = typeof globalThis === 'object' ? globalThis : window;
currentExtensions[0x74] = (data) => {
let typeCode = data[0];
// we always have to slice to get a new ArrayBuffer that is aligned
let buffer = Uint8Array.prototype.slice.call(data, 1).buffer;
let typedArrayName = typedArrays[typeCode];
if (!typedArrayName) {
if (typeCode === 16) return buffer
if (typeCode === 17) return new DataView(buffer)
throw new Error('Could not find typed array for code ' + typeCode)
}
return new glbl[typedArrayName](buffer)
};
currentExtensions[0x78] = () => {
let data = read();
return new RegExp(data[0], data[1])
};
const TEMP_BUNDLE = [];
currentExtensions[0x62] = (data) => {
let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3];
let dataPosition = position$1;
position$1 += dataSize - data.length;
bundledStrings$1 = TEMP_BUNDLE;
bundledStrings$1 = [readOnlyJSString(), readOnlyJSString()];
bundledStrings$1.position0 = 0;
bundledStrings$1.position1 = 0;
bundledStrings$1.postBundlePosition = position$1;
position$1 = dataPosition;
return read()
};
currentExtensions[0xff] = (data) => {
// 32-bit date extension
if (data.length == 4)
return new Date((data[0] * 0x1000000 + (data[1] << 16) + (data[2] << 8) + data[3]) * 1000)
else if (data.length == 8)
return new Date(
((data[0] << 22) + (data[1] << 14) + (data[2] << 6) + (data[3] >> 2)) / 1000000 +
((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000)
else if (data.length == 12)
return new Date(
((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
(((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000)
else
return new Date('invalid')
};
// registration of bulk record definition?
// currentExtensions[0x52] = () =>
function saveState$1(callback) {
if (onSaveState)
onSaveState();
let savedSrcEnd = srcEnd;
let savedPosition = position$1;
let savedStringPosition = stringPosition;
let savedSrcStringStart = srcStringStart;
let savedSrcStringEnd = srcStringEnd;
let savedSrcString = srcString;
let savedStrings = strings;
let savedReferenceMap = referenceMap;
let savedBundledStrings = bundledStrings$1;
// TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
let savedStructures = currentStructures;
let savedStructuresContents = currentStructures.slice(0, currentStructures.length);
let savedPackr = currentUnpackr;
let savedSequentialMode = sequentialMode;
let value = callback();
srcEnd = savedSrcEnd;
position$1 = savedPosition;
stringPosition = savedStringPosition;
srcStringStart = savedSrcStringStart;
srcStringEnd = savedSrcStringEnd;
srcString = savedSrcString;
strings = savedStrings;
referenceMap = savedReferenceMap;
bundledStrings$1 = savedBundledStrings;
src = savedSrc;
sequentialMode = savedSequentialMode;
currentStructures = savedStructures;
currentStructures.splice(0, currentStructures.length, ...savedStructuresContents);
currentUnpackr = savedPackr;
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
return value
}
function clearSource() {
src = null;
referenceMap = null;
currentStructures = null;
}
function addExtension$2(extension) {
if (extension.unpack)
currentExtensions[extension.type] = extension.unpack;
else
currentExtensions[extension.type] = extension;
}
const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding
for (let i = 0; i < 256; i++) {
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103));
}
var defaultUnpackr = new Unpackr$1({ useRecords: false });
const unpack$1 = defaultUnpackr.unpack;
const unpackMultiple$1 = defaultUnpackr.unpackMultiple;
defaultUnpackr.unpack;
const FLOAT32_OPTIONS = {
NEVER: 0,
ALWAYS: 1,
DECIMAL_ROUND: 3,
DECIMAL_FIT: 4
};
let f32Array = new Float32Array(1);
let u8Array = new Uint8Array(f32Array.buffer, 0, 4);
function roundFloat32$1(float32Number) {
f32Array[0] = float32Number;
let multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)];
return ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
function setReadStruct(updatedReadStruct, loadedStructs, saveState) {
readStruct$1 = updatedReadStruct;
onLoadedStructures$1 = loadedStructs;
onSaveState = saveState;
}
let textEncoder$1;
try {
textEncoder$1 = new TextEncoder();
} catch (error) {}
let extensions, extensionClasses;
const hasNodeBuffer$1 = typeof Buffer !== 'undefined';
const ByteArrayAllocate = hasNodeBuffer$1 ?
function(length) { return Buffer.allocUnsafeSlow(length) } : Uint8Array;
const ByteArray = hasNodeBuffer$1 ? Buffer : Uint8Array;
const MAX_BUFFER_SIZE = hasNodeBuffer$1 ? 0x100000000 : 0x7fd00000;
let target, keysTarget;
let targetView;
let position = 0;
let safeEnd;
let bundledStrings = null;
let writeStructSlots;
const MAX_BUNDLE_SIZE = 0x5500; // maximum characters such that the encoded bytes fits in 16 bits.
const hasNonLatin = /[\u0080-\uFFFF]/;
const RECORD_SYMBOL = Symbol('record-id');
let Packr$1 = class Packr extends Unpackr$1 {
constructor(options) {
super(options);
this.offset = 0;
let start;
let hasSharedUpdate;
let structures;
let referenceMap;
let encodeUtf8 = ByteArray.prototype.utf8Write ? function(string, position) {
return target.utf8Write(string, position, target.byteLength - position)
} : (textEncoder$1 && textEncoder$1.encodeInto) ?
function(string, position) {
return textEncoder$1.encodeInto(string, target.subarray(position)).written
} : false;
let packr = this;
if (!options)
options = {};
let isSequential = options && options.sequential;
let hasSharedStructures = options.structures || options.saveStructures;
let maxSharedStructures = options.maxSharedStructures;
if (maxSharedStructures == null)
maxSharedStructures = hasSharedStructures ? 32 : 0;
if (maxSharedStructures > 8160)
throw new Error('Maximum maxSharedStructure is 8160')
if (options.structuredClone && options.moreTypes == undefined) {
this.moreTypes = true;
}
let maxOwnStructures = options.maxOwnStructures;
if (maxOwnStructures == null)
maxOwnStructures = hasSharedStructures ? 32 : 64;
if (!this.structures && options.useRecords != false)
this.structures = [];
// two byte record ids for shared structures
let useTwoByteRecords = maxSharedStructures > 32 || (maxOwnStructures + maxSharedStructures > 64);
let sharedLimitId = maxSharedStructures + 0x40;
let maxStructureId = maxSharedStructures + maxOwnStructures + 0x40;
if (maxStructureId > 8256) {
throw new Error('Maximum maxSharedStructure + maxOwnStructure is 8192')
}
let recordIdsToRemove = [];
let transitionsCount = 0;
let serializationsSinceTransitionRebuild = 0;
this.pack = this.encode = function(value, encodeOptions) {
if (!target) {
target = new ByteArrayAllocate(8192);
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, 8192));
position = 0;
}
safeEnd = target.length - 10;
if (safeEnd - position < 0x800) {
// don't start too close to the end,
target = new ByteArrayAllocate(target.length);
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, target.length));
safeEnd = target.length - 10;
position = 0;
} else
position = (position + 7) & 0x7ffffff8; // Word align to make any future copying of this buffer faster
start = position;
if (encodeOptions & RESERVE_START_SPACE) position += (encodeOptions & 0xff);
referenceMap = packr.structuredClone ? new Map() : null;
if (packr.bundleStrings && typeof value !== 'string') {
bundledStrings = [];
bundledStrings.size = Infinity; // force a new bundle start on first string
} else
bundledStrings = null;
structures = packr.structures;
if (structures) {
if (structures.uninitialized)
structures = packr._mergeStructures(packr.getStructures());
let sharedLength = structures.sharedLength || 0;
if (sharedLength > maxSharedStructures) {
//if (maxSharedStructures <= 32 && structures.sharedLength > 32) // TODO: could support this, but would need to update the limit ids
throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' + structures.sharedLength)
}
if (!structures.transitions) {
// rebuild our structure transitions
structures.transitions = Object.create(null);
for (let i = 0; i < sharedLength; i++) {
let keys = structures[i];
if (!keys)
continue
let nextTransition, transition = structures.transitions;
for (let j = 0, l = keys.length; j < l; j++) {
let key = keys[j];
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
}
transition = nextTransition;
}
transition[RECORD_SYMBOL] = i + 0x40;
}
this.lastNamedStructuresLength = sharedLength;
}
if (!isSequential) {
structures.nextId = sharedLength + 0x40;
}
}
if (hasSharedUpdate)
hasSharedUpdate = false;
let encodingError;
try {
// readOnlyStructures: skip the random-access struct write path so NO new struct is
// minted. randomAccessStructure stays true (the struct READ path and the struct-safe
// integer boundary are preserved, so existing struct data still decodes), but objects
// fall through to the normal pack()->writeObject->writeRecord path and are written as
// classic shared-structure records (byte range 0x40-0x7f, disjoint from struct headers
// at 0x20-0x3f) — the bounded, width-agnostic encoding used before struct mode.
if (packr.randomAccessStructure && !packr.readOnlyStructures && value && typeof value === 'object') {
if (value.constructor === Object) writeStruct(value); // simple object
else if (value.constructor !== Map && !Array.isArray(value) && !extensionClasses.some(extClass => value instanceof extClass)) {
// allow user classes, if they don't need special handling (but do use toJSON if available)
writeStruct(value.toJSON ? value.toJSON() : value);
} else pack(value);
} else
pack(value);
let lastBundle = bundledStrings;
if (bundledStrings)
writeBundles(start, pack, 0);
if (referenceMap && referenceMap.idsToInsert) {
let idsToInsert = referenceMap.idsToInsert.sort((a, b) => a.offset > b.offset ? 1 : -1);
let i = idsToInsert.length;
let incrementPosition = -1;
while (lastBundle && i > 0) {
let insertionPoint = idsToInsert[--i].offset + start;
if (insertionPoint < (lastBundle.stringsPosition + start) && incrementPosition === -1)
incrementPosition = 0;
if (insertionPoint > (lastBundle.position + start)) {
if (incrementPosition >= 0)
incrementPosition += 6;
} else {
if (incrementPosition >= 0) {
// update the bundle reference now
targetView.setUint32(lastBundle.position + start,
targetView.getUint32(lastBundle.position + start) + incrementPosition);
incrementPosition = -1; // reset
}
lastBundle = lastBundle.previous;
i++;
}
}
if (incrementPosition >= 0 && lastBundle) {
// update the bundle reference now
targetView.setUint32(lastBundle.position + start,
targetView.getUint32(lastBundle.position + start) + incrementPosition);
}
position += idsToInsert.length * 6;
if (position > safeEnd)
makeRoom(position);
packr.offset = position;
let serialized = insertIds(target.subarray(start, position), idsToInsert);
referenceMap = null;
return serialized
}
packr.offset = position; // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially
if (encodeOptions & REUSE_BUFFER_MODE) {
target.start = start;
target.end = position;
return target
}
return target.subarray(start, position) // position can change if we call pack again in saveStructures, so we get the buffer now
} catch(error) {
encodingError = error;
throw error;
} finally {
if (structures) {
resetStructures();
if (hasSharedUpdate && packr.saveStructures) {
let sharedLength = structures.sharedLength || 0;
// we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save
let returnBuffer = target.subarray(start, position);
let newSharedData = prepareStructures$1(structures, packr);
if (!encodingError) { // TODO: If there is an encoding error, should make the structures as uninitialized so they get rebuilt next time
if (packr.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
// The save was declined (a concurrent writer updated the shared structures,
// or the store transaction did not durably commit). Our in-memory
// structures + transition trie may now reference record ids that were
// never persisted; re-packing as-is would re-emit the same record pointing
// at an unpersisted structure (-> "Record id is not defined" on decode).
// Mark structures uninitialized so the re-pack reloads durable structures
// via getStructures, rebuilds the transition trie, and re-mints + re-saves.
structures.uninitialized = true;
return packr.pack(value, encodeOptions)
}
packr.lastNamedStructuresLength = sharedLength;
// don't keep large buffers around
if (target.length > 0x40000000) target = null;
return returnBuffer
}
}
}
// don't keep large buffers around, they take too much memory and cause problems (limit at 1GB)
if (target.length > 0x40000000) target = null;
if (encodeOptions & RESET_BUFFER_MODE)
position = start;
}
};
const resetStructures = () => {
if (serializationsSinceTransitionRebuild < 10)
serializationsSinceTransitionRebuild++;
let sharedLength = structures.sharedLength || 0;
if (structures.length > sharedLength && !isSequential)
structures.length = sharedLength;
if (transitionsCount > 10000) {
// force a rebuild occasionally after a lot of transitions so it can get cleaned up
structures.transitions = null;
serializationsSinceTransitionRebuild = 0;
transitionsCount = 0;
if (recordIdsToRemove.length > 0)
recordIdsToRemove = [];
} else if (recordIdsToRemove.length > 0 && !isSequential) {
for (let i = 0, l = recordIdsToRemove.length; i < l; i++) {
recordIdsToRemove[i][RECORD_SYMBOL] = 0;
}
recordIdsToRemove = [];
}
};
const packArray = (value) => {
var length = value.length;
if (length < 0x10) {
target[position++] = 0x90 | length;
} else if (length < 0x10000) {
target[position++] = 0xdc;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdd;
targetView.setUint32(position, length);
position += 4;
}
for (let i = 0; i < length; i++) {
pack(value[i]);
}
};
const pack = (value) => {
if (position > safeEnd)
target = makeRoom(position);
var type = typeof value;
var length;
if (type === 'string') {
let strLength = value.length;
if (bundledStrings && strLength >= 4 && strLength < 0x1000) {
if ((bundledStrings.size += strLength) > MAX_BUNDLE_SIZE) {
let extStart;
let maxBytes = (bundledStrings[0] ? bundledStrings[0].length * 3 + bundledStrings[1].length : 0) + 10;
if (position + maxBytes > safeEnd)
target = makeRoom(position + maxBytes);
let lastBundle;
if (bundledStrings.position) { // here we use the 0x62 extension to write the last bundle and reserve space for the reference pointer to the next/current bundle
lastBundle = bundledStrings;
target[position] = 0xc8; // ext 16
position += 3; // reserve for the writing bundle size
target[position++] = 0x62; // 'b'
extStart = position - start;
position += 4; // reserve for writing bundle reference
writeBundles(start, pack, 0); // write the last bundles
targetView.setUint16(extStart + start - 3, position - start - extStart);
} else { // here we use the 0x62 extension just to reserve the space for the reference pointer to the bundle (will be updated once the bundle is written)
target[position++] = 0xd6; // fixext 4
target[position++] = 0x62; // 'b'
extStart = position - start;
position += 4; // reserve for writing bundle reference
}
bundledStrings = ['', '']; // create new ones
bundledStrings.previous = lastBundle;
bundledStrings.size = 0;
bundledStrings.position = extStart;
}
let twoByte = hasNonLatin.test(value);
bundledStrings[twoByte ? 0 : 1] += value;
target[position++] = 0xc1;
pack(twoByte ? -strLength : strLength);
return
}
let headerSize;
// first we estimate the header size, so we can write to the correct location
if (strLength < 0x20) {
headerSize = 1;
} else if (strLength < 0x100) {
headerSize = 2;
} else if (strLength < 0x10000) {
headerSize = 3;
} else {
headerSize = 5;
}
let maxBytes = strLength * 3;
if (position + maxBytes > safeEnd)
target = makeRoom(position + maxBytes);
if (strLength < 0x40 || !encodeUtf8) {
let i, c1, c2, strPosition = position + headerSize;
for (i = 0; i < strLength; i++) {
c1 = value.charCodeAt(i);
if (c1 < 0x80) {
target[strPosition++] = c1;
} else if (c1 < 0x800) {
target[strPosition++] = c1 >> 6 | 0xc0;
target[strPosition++] = c1 & 0x3f | 0x80;
} else if (
(c1 & 0xfc00) === 0xd800 &&
((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
) {
c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
i++;
target[strPosition++] = c1 >> 18 | 0xf0;
target[strPosition++] = c1 >> 12 & 0x3f | 0x80;
target[strPosition++] = c1 >> 6 & 0x3f | 0x80;
target[strPosition++] = c1 & 0x3f | 0x80;
} else {
target[strPosition++] = c1 >> 12 | 0xe0;
target[strPosition++] = c1 >> 6 & 0x3f | 0x80;
target[strPosition++] = c1 & 0x3f | 0x80;
}
}
length = strPosition - position - headerSize;
} else {
length = encodeUtf8(value, position + headerSize);
}
if (length < 0x20) {
target[position++] = 0xa0 | length;
} else if (length < 0x100) {
if (headerSize < 2) {
target.copyWithin(position + 2, position + 1, position + 1 + length);
}
target[position++] = 0xd9;
target[position++] = length;
} else if (length < 0x10000) {
if (headerSize < 3) {
target.copyWithin(position + 3, position + 2, position + 2 + length);
}
target[position++] = 0xda;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
if (headerSize < 5) {
target.copyWithin(position + 5, position + 3, position + 3 + length);
}
target[position++] = 0xdb;
targetView.setUint32(position, length);
position += 4;
}
position += length;
} else if (type === 'number') {
if (value >>> 0 === value) {// positive integer, 32-bit or less
// positive uint
if (value < 0x20 || (value < 0x80 && this.useRecords === false) || (value < 0x40 && !this.randomAccessStructure)) {
target[position++] = value;
} else if (value < 0x100) {
target[position++] = 0xcc;
target[position++] = value;
} else if (value < 0x10000) {
target[position++] = 0xcd;
target[position++] = value >> 8;
target[position++] = value & 0xff;
} else {
target[position++] = 0xce;
targetView.setUint32(position, value);
position += 4;
}
} else if (value >> 0 === value) { // negative integer
if (value >= -0x20) {
target[position++] = 0x100 + value;
} else if (value >= -0x80) {
target[position++] = 0xd0;
target[position++] = value + 0x100;
} else if (value >= -0x8000) {
target[position++] = 0xd1;
targetView.setInt16(position, value);
position += 2;
} else {
target[position++] = 0xd2;
targetView.setInt32(position, value);
position += 4;
}
} else {
let useFloat32;
if ((useFloat32 = this.useFloat32) > 0 && value < 0x100000000 && value >= -0x80000000) {
target[position++] = 0xca;
targetView.setFloat32(position, value);
let xShifted;
if (useFloat32 < 4 ||
// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
((xShifted = value * mult10[((target[position] & 0x7f) << 1) | (target[position + 1] >> 7)]) >> 0) === xShifted) {
position += 4;
return
} else
position--; // move back into position for writing a double
}
target[position++] = 0xcb;
targetView.setFloat64(position, value);
position += 8;
}
} else if (type === 'object' || type === 'function') {
if (!value)
target[position++] = 0xc0;
else {
if (referenceMap) {
let referee = referenceMap.get(value);
if (referee) {
if (!referee.id) {
let idsToInsert = referenceMap.idsToInsert || (referenceMap.idsToInsert = []);
referee.id = idsToInsert.push(referee);
}
target[position++] = 0xd6; // fixext 4
target[position++] = 0x70; // "p" for pointer
targetView.setUint32(position, referee.id);
position += 4;
return
} else
referenceMap.set(value, { offset: position - start });
}
let constructor = value.constructor;
if (constructor === Object) {
writeObject(value);
} else if (constructor === Array) {
packArray(value);
} else if (constructor === Map) {
if (this.mapAsEmptyObject) target[position++] = 0x80;
else {
length = value.size;
if (length < 0x10) {
target[position++] = 0x80 | length;
} else if (length < 0x10000) {
target[position++] = 0xde;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdf;
targetView.setUint32(position, length);
position += 4;
}
for (let [key, entryValue] of value) {
pack(key);
pack(entryValue);
}
}
} else {
for (let i = 0, l = extensions.length; i < l; i++) {
let extensionClass = extensionClasses[i];
if (value instanceof extensionClass) {
let extension = extensions[i];
if (extension.write) {
if (extension.type) {
target[position++] = 0xd4; // one byte "tag" extension
target[position++] = extension.type;
target[position++] = 0;
}
let writeResult = extension.write.call(this, value);
if (writeResult === value) { // avoid infinite recursion
if (Array.isArray(value)) {
packArray(value);
} else {
writeObject(value);
}
} else {
pack(writeResult);
}
return
}
let currentTarget = target;
let currentTargetView = targetView;
let currentPosition = position;
target = null;
let result;
try {
result = extension.pack.call(this, value, (size) => {
// restore target and use it
target = currentTarget;
currentTarget = null;
position += size;
if (position > safeEnd)
makeRoom(position);
return {
target, targetView, position: position - size
}
}, pack);
} finally {
// restore current target information (unless already restored)
if (currentTarget) {
target = currentTarget;
targetView = currentTargetView;
position = currentPosition;
safeEnd = target.length - 10;
}
}
if (result) {
if (result.length + position > safeEnd)
makeRoom(result.length + position);
position = writeExtensionData(result, target, position, extension.type);
}
return
}
}
// check isArray after extensions, because extensions can extend Array
if (Array.isArray(value)) {
packArray(value);
} else {
// use this as an alternate mechanism for expressing how to serialize
if (value.toJSON) {
const json = value.toJSON();
// if for some reason value.toJSON returns itself it'll loop forever
if (json !== value)
return pack(json)
}
// if there is a writeFunction, use it, otherwise just encode as undefined
if (type === 'function')
return pack(this.writeFunction && this.writeFunction(value));
// no extension found, write as plain object
writeObject(value);
}
}
}
} else if (type === 'boolean') {
target[position++] = value ? 0xc3 : 0xc2;
} else if (type === 'bigint') {
if (value < 0x8000000000000000 && value >= -0x8000000000000000) {
// use a signed int as long as it fits
target[position++] = 0xd3;
targetView.setBigInt64(position, value);
} else if (value < 0x10000000000000000 && value > 0) {
// if we can fit an unsigned int, use that
target[position++] = 0xcf;
targetView.setBigUint64(position, value);
} else {
// overflow
if (this.largeBigIntToFloat) {
target[position++] = 0xcb;
targetView.setFloat64(position, Number(value));
} else if (this.largeBigIntToString) {
return pack(value.toString());
} else if (this.useBigIntExtension || this.moreTypes) {
let empty = value < 0 ? BigInt(-1) : BigInt(0);
let array;
if (value >> BigInt(0x10000) === empty) {
let mask = BigInt(0x10000000000000000) - BigInt(1); // literal would overflow
let chunks = [];
while (true) {
chunks.push(value & mask);
if ((value >> BigInt(63)) === empty) break
value >>= BigInt(64);
}
array = new Uint8Array(new BigUint64Array(chunks).buffer);
array.reverse();
} else {
let invert = value < 0;
let string = (invert ? ~value : value).toString(16);
if (string.length % 2) {
string = '0' + string;
} else if (parseInt(string.charAt(0), 16) >= 8) {
string = '00' + string;
}
if (hasNodeBuffer$1) {
array = Buffer.from(string, 'hex');
} else {
array = new Uint8Array(string.length / 2);
for (let i = 0; i < array.length; i++) {
array[i] = parseInt(string.slice(i * 2, i * 2 + 2), 16);
}
}
if (invert) {
for (let i = 0; i < array.length; i++) array[i] = ~array[i];
}
}
if (array.length + position > safeEnd)
makeRoom(array.length + position);
position = writeExtensionData(array, target, position, 0x42);
return
} else {
throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, use' +
' useBigIntExtension, or set largeBigIntToFloat to convert to float-64, or set' +
' largeBigIntToString to convert to string')
}
}
position += 8;
} else if (type === 'undefined') {
if (this.encodeUndefinedAsNil)
target[position++] = 0xc0;
else {
target[position++] = 0xd4; // a number of implementations use fixext1 with type 0, data 0 to denote undefined, so we follow suite
target[position++] = 0;
target[position++] = 0;
}
} else {
throw new Error('Unknown type: ' + type)
}
};
const writePlainObject = (this.variableMapSize || this.coercibleKeyAsNumber || this.skipValues) ? (object) => {
// this method is slightly slower, but generates "preferred serialization" (optimally small for smaller objects)
let keys;
if (this.skipValues) {
keys = [];
for (let key in object) {
if ((typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) &&
!this.skipValues.includes(object[key]))
keys.push(key);
}
} else {
keys = Object.keys(object);
}
let length = keys.length;
if (length < 0x10) {
target[position++] = 0x80 | length;
} else if (length < 0x10000) {
target[position++] = 0xde;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xdf;
targetView.setUint32(position, length);
position += 4;
}
let key;
if (this.coercibleKeyAsNumber) {
for (let i = 0; i < length; i++) {
key = keys[i];
let num = Number(key);
pack(isNaN(num) ? key : num);
pack(object[key]);
}
} else {
for (let i = 0; i < length; i++) {
pack(key = keys[i]);
pack(object[key]);
}
}
} :
(object) => {
target[position++] = 0xde; // always using map 16, so we can preallocate and set the length afterwards
let objectOffset = position - start;
position += 2;
let size = 0;
for (let key in object) {
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
pack(key);
pack(object[key]);
size++;
}
}
if (size > 0xffff) {
throw new Error('Object is too large to serialize with fast 16-bit map size,' +
' use the "variableMapSize" option to serialize this object');
}
target[objectOffset++ + start] = size >> 8;
target[objectOffset + start] = size & 0xff;
};
const writeRecord = this.useRecords === false ? writePlainObject :
(options.progressiveRecords && !useTwoByteRecords) ? // this is about 2% faster for highly stable structures, since it only requires one for-in loop (but much more expensive when new structure needs to be written)
(object) => {
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
let objectOffset = position++ - start;
let wroteKeys;
for (let key in object) {
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
nextTransition = transition[key];
if (nextTransition)
transition = nextTransition;
else {
// record doesn't exist, create full new record and insert it
let keys = Object.keys(object);
let lastTransition = transition;
transition = structures.transitions;
let newTransitions = 0;
for (let i = 0, l = keys.length; i < l; i++) {
let key = keys[i];
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
newTransitions++;
}
transition = nextTransition;
}
if (objectOffset + start + 1 == position) {
// first key, so we don't need to insert, we can just write record directly
position--;
newRecord(transition, keys, newTransitions);
} else // otherwise we need to insert the record, moving existing data after the record
insertNewRecord(transition, keys, objectOffset, newTransitions);
wroteKeys = true;
transition = lastTransition[key];
}
pack(object[key]);
}
}
if (!wroteKeys) {
let recordId = transition[RECORD_SYMBOL];
if (recordId)
target[objectOffset + start] = recordId;
else
insertNewRecord(transition, Object.keys(object), objectOffset, 0);
}
} :
(object) => {
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
let newTransitions = 0;
for (let key in object) if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
nextTransition = transition[key];
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null);
newTransitions++;
}
transition = nextTransition;
}
let recordId = transition[RECORD_SYMBOL];
if (recordId) {
if (recordId >= 0x60 && useTwoByteRecords) {
target[position++] = ((recordId -= 0x60) & 0x1f) + 0x60;
target[position++] = recordId >> 5;
} else
target[position++] = recordId;
} else {
newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions);
}
// now write the values
for (let key in object)
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
pack(object[key]);
}
};
// create reference to useRecords if useRecords is a function
const checkUseRecords = typeof this.useRecords == 'function' && this.useRecords;
const writeObject = checkUseRecords ? (object) => {
checkUseRecords(object) ? writeRecord(object) : writePlainObject(object);
} : writeRecord;
const makeRoom = (end) => {
let newSize;
if (end > 0x1000000) {
// special handling for really large buffers
if ((end - start) > MAX_BUFFER_SIZE)
throw new Error('Packed buffer would be larger than maximum buffer size')
newSize = Math.min(MAX_BUFFER_SIZE,
Math.round(Math.max((end - start) * (end > 0x4000000 ? 1.25 : 2), 0x400000) / 0x1000) * 0x1000);
} else // faster handling for smaller buffers
newSize = ((Math.max((end - start) << 2, target.length - 1) >> 12) + 1) << 12;
let newBuffer = new ByteArrayAllocate(newSize);
targetView = newBuffer.dataView || (newBuffer.dataView = new DataView(newBuffer.buffer, 0, newSize));
end = Math.min(end, target.length);
if (target.copy)
target.copy(newBuffer, 0, start, end);
else
newBuffer.set(target.slice(start, end));
position -= start;
start = 0;
safeEnd = newBuffer.length - 10;
return target = newBuffer
};
const newRecord = (transition, keys, newTransitions) => {
let recordId = structures.nextId;
if (!recordId)
recordId = 0x40;
if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
recordId = structures.nextOwnId;
if (!(recordId < maxStructureId))
recordId = sharedLimitId;
structures.nextOwnId = recordId + 1;
} else {
if (recordId >= maxStructureId)// cycle back around
recordId = sharedLimitId;
structures.nextId = recordId + 1;
}
let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1;
transition[RECORD_SYMBOL] = recordId;
transition.__keys__ = keys;
structures[recordId - 0x40] = keys;
if (recordId < sharedLimitId) {
keys.isShared = true;
structures.sharedLength = recordId - 0x3f;
hasSharedUpdate = true;
if (highByte >= 0) {
target[position++] = (recordId & 0x1f) + 0x60;
target[position++] = highByte;
} else {
target[position++] = recordId;
}
} else {
if (highByte >= 0) {
target[position++] = 0xd5; // fixext 2
target[position++] = 0x72; // "r" record defintion extension type
target[position++] = (recordId & 0x1f) + 0x60;
target[position++] = highByte;
} else {
target[position++] = 0xd4; // fixext 1
target[position++] = 0x72; // "r" record defintion extension type
target[position++] = recordId;
}
if (newTransitions)
transitionsCount += serializationsSinceTransitionRebuild * newTransitions;
// record the removal of the id, we can maintain our shared structure
if (recordIdsToRemove.length >= maxOwnStructures)
recordIdsToRemove.shift()[RECORD_SYMBOL] = 0; // we are cycling back through, and have to remove old ones
recordIdsToRemove.push(transition);
pack(keys);
}
};
const insertNewRecord = (transition, keys, insertionOffset, newTransitions) => {
let mainTarget = target;
let mainPosition = position;
let mainSafeEnd = safeEnd;
let mainStart = start;
target = keysTarget;
position = 0;
start = 0;
if (!target)
keysTarget = target = new ByteArrayAllocate(8192);
safeEnd = target.length - 10;
newRecord(transition, keys, newTransitions);
keysTarget = target;
let keysPosition = position;
target = mainTarget;
position = mainPosition;
safeEnd = mainSafeEnd;
start = mainStart;
if (keysPosition > 1) {
let newEnd = position + keysPosition - 1;
if (newEnd > safeEnd)
makeRoom(newEnd);
let insertionPosition = insertionOffset + start;
target.copyWithin(insertionPosition + keysPosition, insertionPosition + 1, position);
target.set(keysTarget.slice(0, keysPosition), insertionPosition);
position = newEnd;
} else {
target[insertionOffset + start] = keysTarget[0];
}
};
const writeStruct = (object) => {
let newPosition = writeStructSlots(object, target, start, position, structures, makeRoom, (value, newPosition, notifySharedUpdate) => {
if (notifySharedUpdate)
return hasSharedUpdate = true;
position = newPosition;
let startTarget = target;
pack(value);
resetStructures();
if (startTarget !== target) {
return { position, targetView, target }; // indicate the buffer was re-allocated
}
return position;
}, this);
if (newPosition === 0) // bail and go to a msgpack object
return writeObject(object);
position = newPosition;
};
}
useBuffer(buffer) {
// this means we are finished using our own buffer and we can write over it safely
target = buffer;
target.dataView || (target.dataView = new DataView(target.buffer, target.byteOffset, target.byteLength));
targetView = target.dataView;
position = 0;
}
set position (value) {
position = value;
}
get position() {
return position;
}
clearSharedData() {
if (this.structures)
this.structures = [];
if (this.typedStructs)
this.typedStructs = [];
}
};
extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, DataView, C1Type ];
extensions = [{
pack(date, allocateForWrite, pack) {
let seconds = date.getTime() / 1000;
if ((this.useTimestamp32 || date.getMilliseconds() === 0) && seconds >= 0 && seconds < 0x100000000) {
// Timestamp 32
let { target, targetView, position} = allocateForWrite(6);
target[position++] = 0xd6;
target[position++] = 0xff;
targetView.setUint32(position, seconds);
} else if (seconds > 0 && seconds < 0x100000000) {
// Timestamp 64
let { target, targetView, position} = allocateForWrite(10);
target[position++] = 0xd7;
target[position++] = 0xff;
targetView.setUint32(position, date.getMilliseconds() * 4000000 + ((seconds / 1000 / 0x100000000) >> 0));
targetView.setUint32(position + 4, seconds);
} else if (isNaN(seconds)) {
if (this.onInvalidDate) {
allocateForWrite(0);
return pack(this.onInvalidDate())
}
// Intentionally invalid timestamp
let { target, targetView, position} = allocateForWrite(3);
target[position++] = 0xd4;
target[position++] = 0xff;
target[position++] = 0xff;
} else {
// Timestamp 96
let { target, targetView, position} = allocateForWrite(15);
target[position++] = 0xc7;
target[position++] = 12;
target[position++] = 0xff;
targetView.setUint32(position, date.getMilliseconds() * 1000000);
targetView.setBigInt64(position + 4, BigInt(Math.floor(seconds)));
}
}
}, {
pack(set, allocateForWrite, pack) {
if (this.setAsEmptyObject) {
allocateForWrite(0);
return pack({})
}
let array = Array.from(set);
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x73; // 's' for Set
target[position++] = 0;
}
pack(array);
}
}, {
pack(error, allocateForWrite, pack) {
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x65; // 'e' for error
target[position++] = 0;
}
pack([ error.name, error.message, error.cause ]);
}
}, {
pack(regex, allocateForWrite, pack) {
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
if (this.moreTypes) {
target[position++] = 0xd4;
target[position++] = 0x78; // 'x' for regeXp
target[position++] = 0;
}
pack([ regex.source, regex.flags ]);
}
}, {
pack(arrayBuffer, allocateForWrite) {
if (this.moreTypes)
writeExtBuffer(arrayBuffer, 0x10, allocateForWrite);
else
writeBuffer(hasNodeBuffer$1 ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
}
}, {
pack(typedArray, allocateForWrite) {
let constructor = typedArray.constructor;
if (constructor !== ByteArray && this.moreTypes)
writeExtBuffer(typedArray, typedArrays.indexOf(constructor.name), allocateForWrite);
else
writeBuffer(typedArray, allocateForWrite);
}
}, {
pack(arrayBuffer, allocateForWrite) {
if (this.moreTypes)
writeExtBuffer(arrayBuffer, 0x11, allocateForWrite);
else
writeBuffer(hasNodeBuffer$1 ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
}
}, {
pack(c1, allocateForWrite) { // specific 0xC1 object
let { target, position} = allocateForWrite(1);
target[position] = 0xc1;
}
}];
function writeExtBuffer(typedArray, type, allocateForWrite, encode) {
let length = typedArray.byteLength;
if (length + 1 < 0x100) {
var { target, position } = allocateForWrite(4 + length);
target[position++] = 0xc7;
target[position++] = length + 1;
} else if (length + 1 < 0x10000) {
var { target, position } = allocateForWrite(5 + length);
target[position++] = 0xc8;
target[position++] = (length + 1) >> 8;
target[position++] = (length + 1) & 0xff;
} else {
var { target, position, targetView } = allocateForWrite(7 + length);
target[position++] = 0xc9;
targetView.setUint32(position, length + 1); // plus one for the type byte
position += 4;
}
target[position++] = 0x74; // "t" for typed array
target[position++] = type;
if (!typedArray.buffer) typedArray = new Uint8Array(typedArray);
target.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength), position);
}
function writeBuffer(buffer, allocateForWrite) {
let length = buffer.byteLength;
var target, position;
if (length < 0x100) {
var { target, position } = allocateForWrite(length + 2);
target[position++] = 0xc4;
target[position++] = length;
} else if (length < 0x10000) {
var { target, position } = allocateForWrite(length + 3);
target[position++] = 0xc5;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
var { target, position, targetView } = allocateForWrite(length + 5);
target[position++] = 0xc6;
targetView.setUint32(position, length);
position += 4;
}
target.set(buffer, position);
}
function writeExtensionData(result, target, position, type) {
let length = result.length;
switch (length) {
case 1:
target[position++] = 0xd4;
break
case 2:
target[position++] = 0xd5;
break
case 4:
target[position++] = 0xd6;
break
case 8:
target[position++] = 0xd7;
break
case 16:
target[position++] = 0xd8;
break
default:
if (length < 0x100) {
target[position++] = 0xc7;
target[position++] = length;
} else if (length < 0x10000) {
target[position++] = 0xc8;
target[position++] = length >> 8;
target[position++] = length & 0xff;
} else {
target[position++] = 0xc9;
target[position++] = length >> 24;
target[position++] = (length >> 16) & 0xff;
target[position++] = (length >> 8) & 0xff;
target[position++] = length & 0xff;
}
}
target[position++] = type;
target.set(result, position);
position += length;
return position
}
function insertIds(serialized, idsToInsert) {
// insert the ids that need to be referenced for structured clones
let nextId;
let distanceToMove = idsToInsert.length * 6;
let lastEnd = serialized.length - distanceToMove;
while (nextId = idsToInsert.pop()) {
let offset = nextId.offset;
let id = nextId.id;
serialized.copyWithin(offset + distanceToMove, offset, lastEnd);
distanceToMove -= 6;
let position = offset + distanceToMove;
serialized[position++] = 0xd6;
serialized[position++] = 0x69; // 'i'
serialized[position++] = id >> 24;
serialized[position++] = (id >> 16) & 0xff;
serialized[position++] = (id >> 8) & 0xff;
serialized[position++] = id & 0xff;
lastEnd = offset;
}
return serialized
}
function writeBundles(start, pack, incrementPosition) {
if (bundledStrings.length > 0) {
targetView.setUint32(bundledStrings.position + start, position + incrementPosition - bundledStrings.position - start);
bundledStrings.stringsPosition = position - start;
let writeStrings = bundledStrings;
bundledStrings = null;
pack(writeStrings[0]);
pack(writeStrings[1]);
}
}
function addExtension$1(extension) {
if (extension.Class) {
if (!extension.pack && !extension.write)
throw new Error('Extension has no pack or write function')
if (extension.pack && !extension.type)
throw new Error('Extension has no type (numeric code to identify the extension)')
extensionClasses.unshift(extension.Class);
extensions.unshift(extension);
}
addExtension$2(extension);
}
function prepareStructures$1(structures, packr) {
structures.isCompatible = (existingStructures) => {
let compatible = !existingStructures || ((packr.lastNamedStructuresLength || 0) === existingStructures.length);
if (!compatible) // we want to merge these existing structures immediately since we already have it and we are in the right transaction
packr._mergeStructures(existingStructures);
return compatible;
};
return structures
}
function setWriteStructSlots(writeSlots, makeStructures) {
writeStructSlots = writeSlots;
prepareStructures$1 = makeStructures;
}
let defaultPackr = new Packr$1({ useRecords: false });
const pack$1 = defaultPackr.pack;
defaultPackr.pack;
const REUSE_BUFFER_MODE = 512;
const RESET_BUFFER_MODE = 1024;
const RESERVE_START_SPACE = 2048;
const ASCII = 3; // the MIBenum from https://www.iana.org/assignments/character-sets/character-sets.xhtml (and other character encodings could be referenced by MIBenum)
const NUMBER = 0;
const UTF8 = 2;
const OBJECT_DATA = 1;
const DATE = 16;
const TYPE_NAMES = ['num', 'object', 'string', 'ascii'];
TYPE_NAMES[DATE] = 'date';
const float32Headers = [false, true, true, false, false, true, true, false];
let evalSupported;
try {
new Function('');
evalSupported = true;
} catch(error) {
// if eval variants are not supported, do not create inline object readers ever
}
let updatedPosition;
const hasNodeBuffer = typeof Buffer !== 'undefined';
let textEncoder, currentSource;
try {
textEncoder = new TextEncoder();
} catch (error) {}
const encodeUtf8 = hasNodeBuffer ? function(target, string, position) {
return target.utf8Write(string, position, target.byteLength - position)
} : (textEncoder && textEncoder.encodeInto) ?
function(target, string, position) {
return textEncoder.encodeInto(string, target.subarray(position)).written
} : false;
setWriteStructSlots(writeStruct, prepareStructures);
function writeStruct(object, target, encodingStart, position, structures, makeRoom, pack, packr, structureKnown) {
let typedStructs = packr.typedStructs || (packr.typedStructs = []);
// note that we rely on pack.js to load stored structures before we get to this point
// structureKnown is set only on the internal layout-retry below: attempt 1 already minted
// this record's structure, so the retry re-encodes a known shape and must not re-apply the
// cap (which could otherwise bail after attempt 1 already packed refs → corrupt fallback).
// `frozen` is a local (from this instance's typedStructs) — never a shared global — so a
// re-entrant encode on another instance (e.g. via an enumerable getter) can't flip it.
const cap = packr.maxOwnStructures ?? Infinity;
const frozen = !structureKnown && typedStructs.length >= cap;
let targetView = target.dataView;
let refsStartPosition = (typedStructs.lastStringStart || 100) + position;
let safeEnd = target.length - 10;
let start = position;
if (position > safeEnd) {
target = makeRoom(position);
targetView = target.dataView;
position -= encodingStart;
start -= encodingStart;
refsStartPosition -= encodingStart;
encodingStart = 0;
safeEnd = target.length - 10;
}
let refOffset, refPosition = refsStartPosition;
let transition = typedStructs.transitions || (typedStructs.transitions = Object.create(null));
let nextId = typedStructs.nextId || typedStructs.length;
let headerSize =
nextId < 0xf ? 1 :
nextId < 0xf0 ? 2 :
nextId < 0xf000 ? 3 :
nextId < 0xf00000 ? 4 : 0;
if (headerSize === 0)
return 0;
position += headerSize;
let queuedReferences = [];
let usedAscii0;
let keyIndex = 0;
for (let key in object) {
let nextTransition = transition[key];
// Resolve the key transition BEFORE reading the value: when frozen and the key is new we
// bail here, so an enumerable getter isn't invoked during this (failed) struct attempt and
// then again by the plain fallback (which would double-read a side-effecting accessor).
if (!nextTransition) {
if (frozen) return 0;
transition[key] = nextTransition = {
key,
parent: transition,
enumerationOffset: 0,
ascii0: null,
ascii8: null,
num8: null,
string16: null,
object16: null,
num32: null,
float64: null,
date64: null
};
}
let value = object[key];
if (position > safeEnd) {
target = makeRoom(position);
targetView = target.dataView;
position -= encodingStart;
start -= encodingStart;
refsStartPosition -= encodingStart;
refPosition -= encodingStart;
encodingStart = 0;
safeEnd = target.length - 10;
}
switch (typeof value) {
case 'number':
let number = value;
// first check to see if we are using a lot of ids and should default to wide/common format
if (nextId < 200 || !nextTransition.num64) {
if (number >> 0 === number && number < 0x20000000 && number > -0x1f000000) {
if (number < 0xf6 && number >= 0 && (nextTransition.num8 && !(nextId > 200 && nextTransition.num32) || number < 0x20 && !nextTransition.num32)) {
transition = nextTransition.num8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
target[position++] = number;
} else {
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
targetView.setUint32(position, number, true);
position += 4;
}
break;
} else if (number < 0x100000000 && number >= -0x80000000) {
targetView.setFloat32(position, number, true);
if (float32Headers[target[position + 3] >>> 5]) {
let xShifted;
// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
if (((xShifted = number * mult10[((target[position + 3] & 0x7f) << 1) | (target[position + 2] >> 7)]) >> 0) === xShifted) {
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
position += 4;
break;
}
}
}
}
transition = nextTransition.num64 || createTypeTransition(nextTransition, NUMBER, 8, frozen);
targetView.setFloat64(position, number, true);
position += 8;
break;
case 'string':
let strLength = value.length;
refOffset = refPosition - refsStartPosition;
if ((strLength << 2) + refPosition > safeEnd) {
target = makeRoom((strLength << 2) + refPosition);
targetView = target.dataView;
position -= encodingStart;
start -= encodingStart;
refsStartPosition -= encodingStart;
refPosition -= encodingStart;
encodingStart = 0;
safeEnd = target.length - 10;
}
if (strLength > ((0xff00 + refOffset) >> 2)) {
queuedReferences.push(key, value, position - start);
break;
}
let isNotAscii;
let strStart = refPosition;
if (strLength < 0x40) {
let i, c1, c2;
for (i = 0; i < strLength; i++) {
c1 = value.charCodeAt(i);
if (c1 < 0x80) {
target[refPosition++] = c1;
} else if (c1 < 0x800) {
isNotAscii = true;
target[refPosition++] = c1 >> 6 | 0xc0;
target[refPosition++] = c1 & 0x3f | 0x80;
} else if (
(c1 & 0xfc00) === 0xd800 &&
((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
) {
isNotAscii = true;
c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
i++;
target[refPosition++] = c1 >> 18 | 0xf0;
target[refPosition++] = c1 >> 12 & 0x3f | 0x80;
target[refPosition++] = c1 >> 6 & 0x3f | 0x80;
target[refPosition++] = c1 & 0x3f | 0x80;
} else {
isNotAscii = true;
target[refPosition++] = c1 >> 12 | 0xe0;
target[refPosition++] = c1 >> 6 & 0x3f | 0x80;
target[refPosition++] = c1 & 0x3f | 0x80;
}
}
} else {
refPosition += encodeUtf8(target, value, refPosition);
isNotAscii = refPosition - strStart > strLength;
}
if (refOffset < 0xa0 || (refOffset < 0xf6 && (nextTransition.ascii8 || nextTransition.string8))) {
// short strings
if (isNotAscii) {
if (!(transition = nextTransition.string8)) {
if (typedStructs.length > 10 && (transition = nextTransition.ascii8)) {
// we can safely change ascii to utf8 in place since they are compatible
transition.__type = UTF8;
nextTransition.ascii8 = null;
nextTransition.string8 = transition;
pack(null, 0, true); // special call to notify that structures have been updated
} else {
transition = createTypeTransition(nextTransition, UTF8, 1, frozen);
}
}
} else if (refOffset === 0 && !usedAscii0) {
usedAscii0 = true;
transition = nextTransition.ascii0 || createTypeTransition(nextTransition, ASCII, 0, frozen);
break; // don't increment position
}// else ascii:
else if (!(transition = nextTransition.ascii8) && !(typedStructs.length > 10 && (transition = nextTransition.string8)))
transition = createTypeTransition(nextTransition, ASCII, 1, frozen);
target[position++] = refOffset;
} else {
// TODO: Enable ascii16 at some point, but get the logic right
//if (isNotAscii)
transition = nextTransition.string16 || createTypeTransition(nextTransition, UTF8, 2, frozen);
//else
//transition = nextTransition.ascii16 || createTypeTransition(nextTransition, ASCII, 2);
targetView.setUint16(position, refOffset, true);
position += 2;
}
break;
case 'object':
if (value) {
if (value.constructor === Date) {
transition = nextTransition.date64 || createTypeTransition(nextTransition, DATE, 8, frozen);
targetView.setFloat64(position, value.getTime(), true);
position += 8;
} else {
queuedReferences.push(key, value, keyIndex);
}
break;
} else { // null
nextTransition = anyType(nextTransition, position, targetView, -10); // match CBOR with this
if (nextTransition) {
transition = nextTransition;
position = updatedPosition;
} else queuedReferences.push(key, value, keyIndex);
}
break;
case 'boolean':
transition = nextTransition.num8 || nextTransition.ascii8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
target[position++] = value ? 0xf9 : 0xf8; // match CBOR with these
break;
case 'undefined':
nextTransition = anyType(nextTransition, position, targetView, -9); // match CBOR with this
if (nextTransition) {
transition = nextTransition;
position = updatedPosition;
} else queuedReferences.push(key, value, keyIndex);
break;
default:
queuedReferences.push(key, value, keyIndex);
}
if (transition === undefined) return 0; // frozen: structure cap reached
keyIndex++;
}
// Cap enforcement for queued (nested-object / null) references. pack() advances msgpackr's
// shared write position and we cannot cleanly bail afterward, so preflight the whole queued
// chain through EXISTING transitions first: if the cap is reached and any field would need a
// new structure, fall back to plain encoding now (return 0) — before touching the shared
// position. Uses a FRESH length read (not the entry-time `frozen`): a getter invoked while
// reading values above may have minted on this same instance since entry.
if (!structureKnown && queuedReferences.length > 0 && typedStructs.length >= cap) {
let t = transition;
for (let i = 0, l = queuedReferences.length; i < l; i += 3) {
// A non-null (object/Date) ref is pack()ed into the shared buffer, advancing
// msgpackr's write position. Its structure variant (object16 vs object32) depends on
// the runtime ref-section offset (inline strings + earlier refs), which we can't know
// before packing — and we can't bail after a pack without corrupting the fallback. So
// under the cap, any record with a packing ref falls back to plain encoding now,
// before any pack(). null/undefined refs don't pack, so they're walked normally.
if (queuedReferences[i + 1] != null) return 0;
const nt = t[queuedReferences[i]];
if (!nt) return 0;
const next = nt.object16; // null/undefined ref → OBJECT_DATA size 2
if (!next) return 0;
t = next;
}
if (t[RECORD_SYMBOL] == null) return 0; // exact structure not yet minted
}
// Past the preflight the chain is known, so no minting happens — except a rare offset
// divergence (a known shape whose ref section now crosses 0xff00 and needs object32 where
// the preflight matched object16). Once a ref is packed we can no longer bail, so we finish
// via the unfrozen forceTypeTransition: a bounded, self-converging overshoot for that one
// record. packedRef keeps the record-id mint from bailing after a pack.
let packedRef = false;
for (let i = 0, l = queuedReferences.length; i < l;) {
let key = queuedReferences[i++];
let value = queuedReferences[i++];
let propertyIndex = queuedReferences[i++];
let nextTransition = transition[key];
if (!nextTransition) {
transition[key] = nextTransition = {
key,
parent: transition,
enumerationOffset: propertyIndex - keyIndex,
ascii0: null,
ascii8: null,
num8: null,
string16: null,
object16: null,
num32: null,
float64: null
};
}
let newPosition;
if (value) {
let size;
refOffset = refPosition - refsStartPosition;
if (refOffset < 0xff00) {
transition = nextTransition.object16;
if (transition)
size = 2;
else if ((transition = nextTransition.object32))
size = 4;
else {
transition = forceTypeTransition(nextTransition, OBJECT_DATA, 2);
size = 2;
}
} else {
transition = nextTransition.object32 || forceTypeTransition(nextTransition, OBJECT_DATA, 4);
size = 4;
}
newPosition = pack(value, refPosition);
packedRef = true;
if (typeof newPosition === 'object') {
// re-allocated
refPosition = newPosition.position;
targetView = newPosition.targetView;
target = newPosition.target;
refsStartPosition -= encodingStart;
position -= encodingStart;
start -= encodingStart;
encodingStart = 0;
} else
refPosition = newPosition;
if (size === 2) {
targetView.setUint16(position, refOffset, true);
position += 2;
} else {
targetView.setUint32(position, refOffset, true);
position += 4;
}
} else { // null or undefined
transition = nextTransition.object16 || forceTypeTransition(nextTransition, OBJECT_DATA, 2);
targetView.setInt16(position, value === null ? -10 : -9, true);
position += 2;
}
keyIndex++;
}
let recordId = transition[RECORD_SYMBOL];
if (recordId == null) {
// Flat records (no queued refs) reach here without packing, so the cap is enforced
// cleanly. Records that packed nested refs already passed the preflight; either way
// bailing now after refs were packed would corrupt the fallback.
if (!packedRef && typedStructs.length >= cap) return 0;
recordId = packr.typedStructs.length;
let structure = [];
let nextTransition = transition;
let key, type;
while ((type = nextTransition.__type) !== undefined) {
let size = nextTransition.__size;
nextTransition = nextTransition.__parent;
key = nextTransition.key;
let property = [type, size, key];
if (nextTransition.enumerationOffset)
property.push(nextTransition.enumerationOffset);
structure.push(property);
nextTransition = nextTransition.parent;
}
structure.reverse();
transition[RECORD_SYMBOL] = recordId;
packr.typedStructs[recordId] = structure;
pack(null, 0, true); // special call to notify that structures have been updated
}
switch (headerSize) {
case 1:
if (recordId >= 0x10) return 0;
target[start] = recordId + 0x20;
break;
case 2:
if (recordId >= 0x100) return 0;
target[start] = 0x38;
target[start + 1] = recordId;
break;
case 3:
if (recordId >= 0x10000) return 0;
target[start] = 0x39;
targetView.setUint16(start + 1, recordId, true);
break;
case 4:
if (recordId >= 0x1000000) return 0;
targetView.setUint32(start, (recordId << 8) + 0x3a, true);
break;
}
if (position < refsStartPosition) {
if (refsStartPosition === refPosition)
return position; // no refs
// adjust positioning
target.copyWithin(position, refsStartPosition, refPosition);
refPosition += position - refsStartPosition;
typedStructs.lastStringStart = position - start;
} else if (position > refsStartPosition) {
if (refsStartPosition === refPosition)
return position; // no refs
typedStructs.lastStringStart = position - start;
// Fixed section overflowed our estimate — retry with the corrected size. The structure
// is already minted at this point, so pass structureKnown=true to skip the cap check
// (otherwise a record that became frozen during attempt 1 would bail mid-retry, after
// refs were already packed, and corrupt the fallback).
return writeStruct(object, target, encodingStart, start, structures, makeRoom, pack, packr, true);
}
return refPosition;
}
function anyType(transition, position, targetView, value) {
let nextTransition;
if ((nextTransition = transition.ascii8 || transition.num8)) {
targetView.setInt8(position, value, true);
updatedPosition = position + 1;
return nextTransition;
}
if ((nextTransition = transition.string16 || transition.object16)) {
targetView.setInt16(position, value, true);
updatedPosition = position + 2;
return nextTransition;
}
if (nextTransition = transition.num32) {
targetView.setUint32(position, 0xe0000100 + value, true);
updatedPosition = position + 4;
return nextTransition;
}
// transition.float64
if (nextTransition = transition.num64) {
targetView.setFloat64(position, NaN, true);
targetView.setInt8(position, value);
updatedPosition = position + 8;
return nextTransition;
}
updatedPosition = position;
// TODO: can we do an "any" type where we defer the decision?
return;
}
// When the typed-structure dictionary reaches maxOwnStructures we stop minting new
// structures/transitions. typedStructs is append-only and pinned on the long-lived
// encoder (records reference structures by recordId), so an unbounded shape space —
// e.g. a wide, sparsely/variably-populated schema — would otherwise grow the
// dictionary + transition trie without limit. `frozen` is passed in (derived from the
// encoding instance's own typedStructs.length, never a shared global) so a re-entrant
// encode on another instance can't flip it; while frozen, a missing transition returns
// undefined so the caller bails and the record falls back to plain encoding.
function createTypeTransition(transition, type, size, frozen) {
let typeName = TYPE_NAMES[type] + (size << 3);
let newTransition = transition[typeName];
if (newTransition) return newTransition;
if (frozen) return undefined;
newTransition = transition[typeName] = Object.create(null);
newTransition.__type = type;
newTransition.__size = size;
newTransition.__parent = transition;
return newTransition;
}
// Unfrozen variant: always mints. Used in the queued-ref loop once a nested value has
// already been pack()ed — at that point pack() has advanced msgpackr's shared write
// position, so bailing with `return 0` would corrupt the fallback. We must finish the
// encode instead, even if that means minting a (bounded) handful of structures past the
// cap. The cap is still enforced up front via the preflight, before the first pack().
function forceTypeTransition(transition, type, size) {
let typeName = TYPE_NAMES[type] + (size << 3);
let newTransition = transition[typeName];
if (newTransition) return newTransition;
newTransition = transition[typeName] = Object.create(null);
newTransition.__type = type;
newTransition.__size = size;
newTransition.__parent = transition;
return newTransition;
}
function onLoadedStructures(sharedData) {
if (!(sharedData instanceof Map))
return sharedData;
let typed = sharedData.get('typed') || [];
if (Object.isFrozen(typed))
typed = typed.map(structure => structure.slice(0));
let named = sharedData.get('named');
let transitions = Object.create(null);
for (let i = 0, l = typed.length; i < l; i++) {
let structure = typed[i];
let transition = transitions;
for (let [type, size, key] of structure) {
let nextTransition = transition[key];
if (!nextTransition) {
transition[key] = nextTransition = {
key,
parent: transition,
enumerationOffset: 0,
ascii0: null,
ascii8: null,
num8: null,
string16: null,
object16: null,
num32: null,
float64: null,
date64: null,
};
}
// Replaying persisted structures is never subject to the cap — always mint.
transition = createTypeTransition(nextTransition, type, size, false);
}
transition[RECORD_SYMBOL] = i;
}
typed.transitions = transitions;
this.typedStructs = typed;
this.lastTypedStructuresLength = typed.length;
return named;
}
var sourceSymbol = Symbol.for('source');
function readStruct(src, position, srcEnd, unpackr) {
let recordId = src[position++] - 0x20;
if (recordId >= 24) {
switch(recordId) {
case 24: recordId = src[position++]; break;
// little endian:
case 25: recordId = src[position++] + (src[position++] << 8); break;
case 26: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16); break;
case 27: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16) + (src[position++] << 24); break;
}
}
let structure = unpackr.typedStructs && unpackr.typedStructs[recordId];
if (!structure) {
// copy src buffer because getStructures will override it
src = Uint8Array.prototype.slice.call(src, position, srcEnd);
srcEnd -= position;
position = 0;
if (!unpackr.getStructures)
throw new Error(`Reference to shared structure ${recordId} without getStructures method`);
unpackr._mergeStructures(unpackr.getStructures());
if (!unpackr.typedStructs)
throw new Error('Could not find any shared typed structures');
unpackr.lastTypedStructuresLength = unpackr.typedStructs.length;
structure = unpackr.typedStructs[recordId];
if (!structure)
throw new Error('Could not find typed structure ' + recordId);
}
var construct = structure.construct;
var fullConstruct = structure.fullConstruct;
if (!construct) {
construct = structure.construct = function LazyObject() {
};
fullConstruct = structure.fullConstruct = function LoadedObject() {
};
fullConstruct.prototype = unpackr.structPrototype || {};
var prototype = construct.prototype = unpackr.structPrototype ? Object.create(unpackr.structPrototype) : {};
let properties = [];
let currentOffset = 0;
let lastRefProperty;
for (let i = 0, l = structure.length; i < l; i++) {
let definition = structure[i];
let [ type, size, key, enumerationOffset ] = definition;
if (key === '__proto__')
key = '__proto_';
let property = {
key,
offset: currentOffset,
};
if (enumerationOffset)
properties.splice(i + enumerationOffset, 0, property);
else
properties.push(property);
let getRef;
switch(size) { // TODO: Move into a separate function
case 0: getRef = () => 0; break;
case 1:
getRef = (source, position) => {
let ref = source.bytes[position + property.offset];
return ref >= 0xf6 ? toConstant(ref) : ref;
};
break;
case 2:
getRef = (source, position) => {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let ref = dataView.getUint16(position + property.offset, true);
return ref >= 0xff00 ? toConstant(ref & 0xff) : ref;
};
break;
case 4:
getRef = (source, position) => {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let ref = dataView.getUint32(position + property.offset, true);
return ref >= 0xffffff00 ? toConstant(ref & 0xff) : ref;
};
break;
}
property.getRef = getRef;
currentOffset += size;
let get;
switch(type) {
case ASCII:
if (lastRefProperty && !lastRefProperty.next)
lastRefProperty.next = property;
lastRefProperty = property;
property.multiGetCount = 0;
get = function(source) {
let src = source.bytes;
let position = source.position;
let refStart = currentOffset + position;
let ref = getRef(source, position);
if (typeof ref !== 'number') return ref;
let end, next = property.next;
while(next) {
end = next.getRef(source, position);
if (typeof end === 'number')
break;
else
end = null;
next = next.next;
}
if (end == null)
end = source.bytesEnd - refStart;
if (source.srcString) {
return source.srcString.slice(ref, end);
}
/*if (property.multiGetCount > 0) {
let asciiEnd;
next = firstRefProperty;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
do {
asciiEnd = dataView.getUint16(source.position + next.offset, true);
if (asciiEnd < 0xff00)
break;
else
asciiEnd = null;
} while((next = next.next));
if (asciiEnd == null)
asciiEnd = source.bytesEnd - refStart
source.srcString = src.toString('latin1', refStart, refStart + asciiEnd);
return source.srcString.slice(ref, end);
}
if (source.prevStringGet) {
source.prevStringGet.multiGetCount += 2;
} else {
source.prevStringGet = property;
property.multiGetCount--;
}*/
return readString(src, ref + refStart, end - ref);
//return src.toString('latin1', ref + refStart, end + refStart);
};
break;
case UTF8: case OBJECT_DATA:
if (lastRefProperty && !lastRefProperty.next)
lastRefProperty.next = property;
lastRefProperty = property;
get = function(source) {
let position = source.position;
let refStart = currentOffset + position;
let ref = getRef(source, position);
if (typeof ref !== 'number') return ref;
let src = source.bytes;
let end, next = property.next;
while(next) {
end = next.getRef(source, position);
if (typeof end === 'number')
break;
else
end = null;
next = next.next;
}
if (end == null)
end = source.bytesEnd - refStart;
if (type === UTF8) {
return src.toString('utf8', ref + refStart, end + refStart);
} else {
currentSource = source;
try {
return unpackr.unpack(src, { start: ref + refStart, end: end + refStart });
} finally {
currentSource = null;
}
}
};
break;
case NUMBER:
switch(size) {
case 4:
get = function (source) {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let position = source.position + property.offset;
let value = dataView.getInt32(position, true);
if (value < 0x20000000) {
if (value > -0x1f000000)
return value;
if (value > -0x20000000)
return toConstant(value & 0xff);
}
let fValue = dataView.getFloat32(position, true);
// this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
let multiplier = mult10[((src[position + 3] & 0x7f) << 1) | (src[position + 2] >> 7)];
return ((multiplier * fValue + (fValue > 0 ? 0.5 : -0.5)) >> 0) / multiplier;
};
break;
case 8:
get = function (source) {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let value = dataView.getFloat64(source.position + property.offset, true);
if (isNaN(value)) {
let byte = src[source.position + property.offset];
if (byte >= 0xf6)
return toConstant(byte);
}
return value;
};
break;
case 1:
get = function (source) {
let src = source.bytes;
let value = src[source.position + property.offset];
return value < 0xf6 ? value : toConstant(value);
};
break;
}
break;
case DATE:
get = function (source) {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
return new Date(dataView.getFloat64(source.position + property.offset, true));
};
break;
}
property.get = get;
}
// TODO: load the srcString for faster string decoding on toJSON
if (evalSupported) {
let objectLiteralProperties = [];
let args = [];
let i = 0;
let hasInheritedProperties;
for (let property of properties) { // assign in enumeration order
if (unpackr.alwaysLazyProperty && unpackr.alwaysLazyProperty(property.key)) {
// these properties are not eagerly evaluated and this can be used for creating properties
// that are not serialized as JSON
hasInheritedProperties = true;
continue;
}
Object.defineProperty(prototype, property.key, { get: withSource(property.get), enumerable: true });
let valueFunction = 'v' + i++;
args.push(valueFunction);
objectLiteralProperties.push('o[' + JSON.stringify(property.key) + ']=' + valueFunction + '(s)');
}
if (hasInheritedProperties) {
objectLiteralProperties.push('__proto__:this');
}
let toObject = (new Function(...args, 'var c=this;return function(s){var o=new c();' + objectLiteralProperties.join(';') + ';return o;}')).apply(fullConstruct, properties.map(prop => prop.get));
Object.defineProperty(prototype, 'toJSON', {
value(omitUnderscoredProperties) {
return toObject.call(this, this[sourceSymbol]);
}
});
} else {
Object.defineProperty(prototype, 'toJSON', {
value(omitUnderscoredProperties) {
// return an enumerable object with own properties to JSON stringify
let resolved = {};
for (let i = 0, l = properties.length; i < l; i++) {
// TODO: check alwaysLazyProperty
let key = properties[i].key;
resolved[key] = this[key];
}
return resolved;
},
// not enumerable or anything
});
}
}
var instance = new construct();
instance[sourceSymbol] = {
bytes: src,
position,
srcString: '',
bytesEnd: srcEnd
};
return instance;
}
function toConstant(code) {
switch(code) {
case 0xf6: return null;
case 0xf7: return undefined;
case 0xf8: return false;
case 0xf9: return true;
}
throw new Error('Unknown constant');
}
function withSource(get) {
return function() {
return get(this[sourceSymbol]);
}
}
function saveState() {
if (currentSource) {
currentSource.bytes = Uint8Array.prototype.slice.call(currentSource.bytes, currentSource.position, currentSource.bytesEnd);
currentSource.position = 0;
currentSource.bytesEnd = currentSource.bytes.length;
}
}
function prepareStructures(structures, packr) {
if (packr.typedStructs) {
let structMap = new Map();
structMap.set('named', structures);
structMap.set('typed', packr.typedStructs);
structures = structMap;
}
let lastTypedStructuresLength = packr.lastTypedStructuresLength || 0;
structures.isCompatible = existing => {
let compatible = true;
if (existing instanceof Map) {
let named = existing.get('named') || [];
if (named.length !== (packr.lastNamedStructuresLength || 0))
compatible = false;
let typed = existing.get('typed') || [];
if (typed.length !== lastTypedStructuresLength)
compatible = false;
} else if (existing instanceof Array || Array.isArray(existing)) {
if (existing.length !== (packr.lastNamedStructuresLength || 0))
compatible = false;
}
if (!compatible)
packr._mergeStructures(existing);
return compatible;
};
packr.lastTypedStructuresLength = packr.typedStructs && packr.typedStructs.length;
return structures;
}
setReadStruct(readStruct, onLoadedStructures, saveState);
const nativeAccelerationDisabled = process.env.MSGPACKR_NATIVE_ACCELERATION_DISABLED !== undefined && process.env.MSGPACKR_NATIVE_ACCELERATION_DISABLED.toLowerCase() === 'true';
if (!nativeAccelerationDisabled) {
let extractor;
try {
if (typeof require == 'function')
extractor = require('msgpackr-extract');
else
extractor = module.createRequire((document.currentScript && document.currentScript.src || new URL('test.js', document.baseURI).href))('msgpackr-extract');
if (extractor)
setExtractor(extractor.extractStrings);
} catch (error) {
// native module is optional
}
}
let allSampleData = [];
for (let i = 1; i < 6; i++) {
allSampleData.push(JSON.parse(fs.readFileSync(new URL(`./example${i > 1 ? i : ''}.json`, (document.currentScript && document.currentScript.src || new URL('test.js', document.baseURI).href)))));
}
allSampleData.push({
name: 'some other types',
date: new Date(),
empty: '',
});
const sampleData = allSampleData[3];
function tryRequire(module) {
try {
return require(module)
} catch(error) {
return {}
}
}
let seed = 0;
function random() {
seed++;
let a = seed * 15485863;
return (a * a * a % 2038074743) / 2038074743;
}
//if (typeof chai === 'undefined') { chai = require('chai') }
var assert = chai.assert;
//if (typeof msgpackr === 'undefined') { msgpackr = require('..') }
var Packr = Packr$1;
var Unpackr = Unpackr$1;
var unpack = unpack$1;
var unpackMultiple = unpackMultiple$1;
var roundFloat32 = roundFloat32$1;
var pack = pack$1;
var DECIMAL_FIT = FLOAT32_OPTIONS.DECIMAL_FIT;
var addExtension = addExtension$1;
var zlib = tryRequire('zlib');
zlib.deflateSync;
zlib.inflateSync;
zlib.brotliCompressSync;
zlib.brotliDecompressSync;
zlib.constants;
var ITERATIONS = 4000;
class ExtendArray extends Array {
}
class ExtendArray2 extends Array {
}
class ExtendArray3 extends Array {
}
class ExtendObject {
}
suite('msgpackr basic tests', function() {
test('pack/unpack data', function () {
var data = {
data: [
{a: 1, name: 'one', type: 'odd', isOdd: true},
{a: 2, name: 'two', type: 'even'},
{a: 3, name: 'three', type: 'odd', isOdd: true},
{a: 4, name: 'four', type: 'even'},
{a: 5, name: 'five', type: 'odd', isOdd: true},
{a: 6, name: 'six', type: 'even', isOdd: null}
],
description: 'some names',
types: ['odd', 'even'],
convertEnumToNum: [
{prop: 'test'},
{prop: 'test'},
{prop: 'test'},
{prop: 1},
{prop: 2},
{prop: [undefined]},
{prop: null}
]
};
let structures = [];
let packr = new Packr({structures});
var serialized = packr.pack(data);
serialized = packr.pack(data);
serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('mixed structures', function () {
let data1 = {a: 1, b: 2, c: 3};
let data2 = {a: 1, b: 2, d: 4};
let data3 = {a: 1, b: 2, e: 5};
let structures = [];
let packr = new Packr({structures});
var serialized = packr.pack(data1);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data1);
var serialized = packr.pack(data2);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data2);
var serialized = packr.pack(data3);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data3);
});
test('mixed array', function () {
var data = [
'one',
'two',
'one',
10,
11,
null,
true,
'three',
'three',
'one', [
3, -5, -50, -400, 1.3, -5.3, true
]
];
let structures = [];
let packr = new Packr({structures});
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('255 chars', function () {
const data = 'RRZG9A6I7xupPeOZhxcOcioFsuhszGOdyDUcbRf4Zef2kdPIfC9RaLO4jTM5JhuZvTsF09fbRHMGtqk7YAgu3vespeTe9l61ziZ6VrMnYu2CamK96wCkmz0VUXyqaiUoTPgzk414LS9yYrd5uh7w18ksJF5SlC2e91rukWvNqAZJjYN3jpkqHNOFchCwFrhbxq2Lrv1kSJPYCx9blRg2hGmYqTbElLTZHv20iNqwZeQbRMgSBPT6vnbCBPnOh1W';
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.equal(deserialized, data);
});
test('overlong UTF-8 string', function () {
const payload = Buffer.concat([
Buffer.from([0xa2]), // msgpack fixstr, 2 bytes
Buffer.from([0xc0, 0xaf]), // overlong "/"
]);
const result = unpack(payload);
assert.notEqual(result, '/');
});
test('use ArrayBuffer', function () {
const data = {prop: 'a test'};
var serialized = pack(data);
let ab = new ArrayBuffer(serialized.length);
let u8 = new Uint8Array(ab);
u8.set(serialized);
var deserialized = unpack(ab);
assert.deepEqual(deserialized, data);
});
test('pack/unpack varying data with random access structures', function () {
let structures = [];
let packr = new Packr({
structures, useRecords: true, randomAccessStructure: true, freezeData: true, saveStructures(structures) {
}, getStructures() {
console.log('getStructures');
}
});
for (let i = 0; i < 2000; i++) {
let data = {};
let props = ['foo', 'bar', 'a', 'b', 'c', 'name', 'age', 'd'];
function makeString() {
let str = '';
while (random() < 0.9) {
str += random() < 0.8 ? 'hello world' : String.fromCharCode(300);
}
return str;
}
for (let i = 0; i < random() * 20; i++) {
data[props[Math.floor(random() * 8)]] =
random() < 0.3 ? Math.floor(random() * 400) / 2 :
random() < 0.3 ? makeString() : random() < 0.3 ? true : random() < 0.3 ? sampleData : null;
}
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
for (let key in deserialized) {
deserialized[key];
}
assert.deepEqual(deserialized, data);
}
});
for (let sampleData of allSampleData) {
let snippet = JSON.stringify(sampleData).slice(0, 20) + '...';
test('pack/unpack sample data ' + snippet, function () {
var data = sampleData;
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('pack/unpack sample data with Uint8Array encoding' + snippet, function () {
var data = sampleData;
var serialized = pack(data);
serialized = new Uint8Array(serialized);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('pack/unpack sample data with random access structures ' + snippet, function () {
var data = sampleData;
let structures = [];
let packr = new Packr({
structures, useRecords: true, randomAccessStructure: true, freezeData: true, saveStructures(structures) {
}, getStructures() {
console.log('getStructures');
}
});
for (let i = 0; i < 20; i++) {
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized, {lazy: true});
var copied = {};
for (let key in deserialized) {
copied[key] = deserialized[key];
}
assert.deepEqual(copied, data);
}
});
test('pack/unpack sample data with bundled strings ' + snippet, function () {
var data = sampleData;
let packr = new Packr({ /*structures,*/ useRecords: false, bundleStrings: true});
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data);
});
}
test('pack/unpack sample data with useRecords function', function () {
var data = [
{id: 1, type: 1, labels: {a: 1, b: 2}},
{id: 2, type: 1, labels: {b: 1, c: 2}},
{id: 3, type: 1, labels: {d: 1, e: 2}}
];
var alternatives = [
{useRecords: false}, // 88 bytes
{useRecords: true}, // 58 bytes
{mapsAsObjects: true, useRecords: (v)=>!!v.id}, // 55 bytes
{mapsAsObjects: true, variableMapSize: true, useRecords: (v)=>!!v.id} // 49 bytes
];
for(let o of alternatives) {
let packr = new Packr(o);
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data);
}
});
test('mapAsEmptyObject combination', function () {
const msgpackr = new Packr({ useRecords: false, encodeUndefinedAsNil: true, variableMapSize: true, mapAsEmptyObject: true, setAsEmptyObject: true });
const map = new Map();
map.set('a', 1);
map.set('b', 2);
const set = new Set();
set.add('a');
set.add('b');
const input = { map, set };
const packed = msgpackr.pack(input);
const unpacked = msgpackr.unpack(packed);
assert.deepEqual(unpacked.map, {});
assert.deepEqual(unpacked.set, {});
});
test('pack/unpack numeric coercible keys', function () {
var data = { a: 1, 2: 'test', '-3.45': 'test2'};
let packr = new Packr({variableMapSize: true, coercibleKeyAsNumber: true, useRecords: false});
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('pack/unpack empty data with bundled strings', function () {
var data = {};
let packr = new Packr({bundleStrings: true});
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('pack/unpack large amount of chinese characters', function() {
const MSGPACK_OPTIONS = {bundleStrings: true};
const item = {
message: '你好你好你好你好你好你好你好你好你好', // some Chinese characters
};
testSize(100);
testSize(1000);
testSize(10000);
function testSize(size) {
const list = [];
for (let i = 0; i < size; i++) {
list.push({...item});
}
const packer = new Packr(MSGPACK_OPTIONS);
const unpacker = new Unpackr(MSGPACK_OPTIONS);
const encoded = packer.pack(list);
const decoded = unpacker.unpack(encoded);
assert.deepEqual(list, decoded);
}
});
test('pack/unpack sequential data', function () {
var data = {foo: 1, bar: 2};
let packr = new Packr({sequential: true});
let unpackr = new Unpackr({sequential: true});
var serialized = packr.pack(data);
var deserialized = unpackr.unpack(serialized);
assert.deepEqual(deserialized, data);
var serialized = packr.pack(data);
var deserialized = unpackr.unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('pack/unpack with bundled strings and sequential', function () {
const options = {
bundleStrings: true,
sequential: true,
};
const packer = new Packr(options);
const unpacker = new Packr(options);
const data = {data: 42}; // key length >= 4
unpacker.unpackMultiple(Buffer.concat([
packer.pack(data),
packer.pack(data)
]));
});
if (typeof Buffer != 'undefined')
test('replace data', function(){
var data1 = {
data: [
{ a: 1, name: 'one', type: 'odd', isOdd: true, a: '13 characters' },
{ a: 2, name: 'two', type: 'even', a: '11 characte' },
{ a: 3, name: 'three', type: 'odd', isOdd: true, a: '12 character' },
{ a: 4, name: 'four', type: 'even', a: '9 charact'},
{ a: 5, name: 'five', type: 'odd', isOdd: true, a: '14 characters!' },
{ a: 6, name: 'six', type: 'even', isOdd: null }
],
};
var data2 = {
data: [
{ foo: 7, name: 'one', type: 'odd', isOdd: true },
{ foo: 8, name: 'two', type: 'even'},
{ foo: 9, name: 'three', type: 'odd', isOdd: true },
{ foo: 10, name: 'four', type: 'even'},
{ foo: 11, name: 'five', type: 'odd', isOdd: true },
{ foo: 12, name: 'six', type: 'even', isOdd: null }
],
};
var serialized1 = pack(data1);
var serialized2 = pack(data2);
var b = Buffer.alloc(8000);
serialized1.copy(b);
var deserialized1 = unpack(b, serialized1.length);
serialized2.copy(b);
var deserialized2 = unpack(b, serialized2.length);
assert.deepEqual(deserialized1, data1);
assert.deepEqual(deserialized2, data2);
});
test('compact 123', function() {
assert.equal(pack(123).length, 1);
});
test('BigInt', function() {
let packr = new Packr({ useBigIntExtension: true });
let data = {
a: 3333333333333333333333333333n,
b: 1234567890123456789012345678901234567890n,
c: -3333333333333333333333333333n,
d: -352523523642364364364264264264264264262642642n,
e: 0xffffffffffffffffffffffffffn,
f: -0xffffffffffffffffffffffffffn,
g: (1234n << 123n) ^ (5678n << 56n) ^ 890n,
h: (-1234n << 123n) ^ (5678n << 56n) ^ 890n,
i: (1234n << 1234n) ^ (5678n << 567n) ^ 890n,
j: (-1234n << 1234n) ^ (5678n << 567n) ^ 890n,
k: 0xdeadn << 0xbeefn,
l: -0xdeadn << 0xbeefn,
m: 11n << 0x11111n ^ 111n,
n: -11n << 0x11111n ^ 111n,
o: 12345678901234567890n,
p: -12345678901234567890n,
exp: [],
expexp: [],
};
for (let n = 1n; n.toString(16).length * 4 < 1500; n <<= 1n, n |= BigInt(Math.floor(Math.random() * 2))) {
data.exp.push(n, -n);
}
for (let n = 7n; n.toString(16).length * 4 < 150000; n *= n) {
data.expexp.push(n, -n);
}
let serialized = packr.pack(data);
let deserialized = packr.unpack(serialized);
assert.deepEqual(data, deserialized);
});
test('extended class pack/unpack', function(){
function Extended() {
}
Extended.prototype.getDouble = function() {
return this.value * 2
};
var instance = new Extended();
instance.value = 4;
instance.string = 'decode this: ᾜ';
var data = {
prop1: 'has multi-byte: ᾜ',
extendedInstance: instance,
prop2: 'more string',
num: 3,
};
let packr = new Packr();
addExtension({
Class: Extended,
type: 11,
unpack: function(buffer) {
let e = new Extended();
let data = packr.unpack(buffer);
e.value = data[0];
e.string = data[1];
return e
},
pack: function(instance) {
return packr.pack([instance.value, instance.string])
}
});
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(data, deserialized);
assert.equal(deserialized.extendedInstance.getDouble(), 8);
});
test('extended Array class read/write', function(){
var instance = new ExtendArray();
instance.push(0);
instance.push(1);
instance.push(2);
var data = {
prop1: 'has multi-byte: ᾜ',
extendedInstance: instance,
prop2: 'more string',
num: 3,
};
new Packr();
addExtension({
Class: ExtendArray,
type: 12,
read: function(data) {
Object.setPrototypeOf(data, ExtendArray.prototype);
return data
},
write: function(instance) {
return [...instance]
}
});
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), ExtendArray.prototype);
assert.deepEqual(data, deserialized);
});
test('unregistered extended Array class read/write', function(){
var instance = new ExtendArray2();
instance.push(0);
instance.push(1);
instance.push(2);
var data = {
prop1: 'has multi-byte: ᾜ',
extendedInstance: instance,
prop2: 'more string',
num: 3,
};
new Packr();
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), Array.prototype);
assert.deepEqual(data, deserialized);
});
test('unregistered extended Object class read/write', function(){
var instance = new ExtendObject();
instance.test1 = "string";
instance.test2 = 3421321;
var data = {
prop1: 'has multi-byte: ᾜ',
extendedInstance: instance,
prop2: 'more string',
num: 3,
};
new Packr();
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), Object.prototype);
assert.deepEqual(data, deserialized);
});
test('extended class pack/unpack custom size', function(){
function TestClass() {
}
addExtension({
Class: TestClass,
type: 0x01,
pack() {
return typeof Buffer != 'undefined' ? Buffer.alloc(256) : new Uint8Array(256)
},
unpack(data) {
return data.length
}
});
let result = unpack(pack(new TestClass()));
assert.equal(result, 256);
});
test('extended class read/write', function(){
function Extended() {
}
Extended.prototype.getDouble = function() {
return this.value * 2
};
var instance = new Extended();
instance.value = 4;
instance.string = 'decode this: ᾜ';
var data = {
prop1: 'has multi-byte: ᾜ',
extendedInstance: instance,
prop2: 'more string',
num: 3,
};
new Packr();
addExtension({
Class: Extended,
type: 12,
read: function(data) {
let e = new Extended();
e.value = data[0];
e.string = data[1];
return e
},
write: function(instance) {
return [instance.value, instance.string]
}
});
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(data, deserialized);
assert.equal(deserialized.extendedInstance.getDouble(), 8);
});
test('extended class return self', function(){
function Extended() {
}
Extended.prototype.getDouble = function() {
return this.value * 2
};
var instance = new Extended();
instance.value = 4;
instance.string = 'decode this: ᾜ';
var data = {
prop1: 'has multi-byte: ᾜ',
extendedInstance: instance,
prop2: 'more string',
num: 3,
};
new Packr();
addExtension({
Class: Extended,
type: 13,
read: function(data) {
Object.setPrototypeOf(data, Extended.prototype);
return data
},
write: function(data) {
return data
}
});
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(data, deserialized);
assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), Extended.prototype);
assert.equal(deserialized.extendedInstance.getDouble(), 8);
});
test('extended Array class return self', function(){
var instance = new ExtendArray3();
instance.push(0);
instance.push('has multi-byte: ᾜ');
var data = {
prop1: 'has multi-byte: ᾜ',
extendedInstance: instance,
prop2: 'more string',
num: 3,
};
new Packr();
addExtension({
Class: ExtendArray3,
type: 14,
read: function(data) {
Object.setPrototypeOf(data, ExtendArray3.prototype);
return data
},
write: function(data) {
return data
}
});
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(data, deserialized);
assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), ExtendArray3.prototype);
assert.equal(deserialized.extendedInstance[0], 0);
});
test('extended class pack/unpack proxied', function(){
function Extended() {
}
Extended.prototype.__call__ = function(){
return this.value * 4
};
Extended.prototype.getDouble = function() {
return this.value * 2
};
var instance = function() { instance.__call__();/* callable stuff */ };
Object.setPrototypeOf(instance,Extended.prototype);
instance.value = 4;
var data = instance;
let packr = new Packr();
addExtension({
Class: Extended,
type: 15,
unpack: function(buffer) {
var e = function() { e.__call__(); };
Object.setPrototypeOf(e,Extended.prototype);
e.value = packr.unpack(buffer);
return e
},
pack: function(instance) {
return packr.pack(instance.value)
}
});
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.equal(deserialized.getDouble(), 8);
});
test.skip('convert Date to string', function(){
var data = {
aDate: new Date(),
};
new Packr();
addExtension({
Class: Date,
write(date) {
return date.toString()
}
});
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.equal(deserialized.aDate, data.aDate.toString());
});
test('standard pack fails on circular reference with shared structures', function () {
var data = {};
data.self = data;
let structures = [];
let packr = new Packr({
structures,
saveStructures(structures) {
}
});
assert.throws(function () {
packr.pack(data);
});
});
test('proto handling', function() {
var objectWithProto = JSON.parse('{"__proto__":{"foo":3}}');
var decoded = unpack(pack(objectWithProto));
assert(!decoded.foo);
var objectsWithProto = [objectWithProto, objectWithProto, objectWithProto, objectWithProto, objectWithProto, objectWithProto];
let packr = new Packr();
var decoded = packr.unpack(packr.pack(objectsWithProto));
for (let object of decoded) {
assert(!decoded.foo);
}
});
test.skip('text decoder', function() {
let td = new TextDecoder('ISO-8859-15');
let b = Buffer.alloc(3);
for (var i = 0; i < 256; i++) {
b[0] = i;
b[1] = 0;
b[2] = 0;
let s = td.decode(b);
if (!require('msgpackr-extract').isOneByte(s)) {
console.log(i.toString(16), s.length);
}
}
});
test('moreTypes: Error with causes', function() {
const object = {
error: new Error('test'),
errorWithCause: new Error('test-1', { cause: new Error('test-2') }),
type: new TypeError(),
range: new RangeError('test', { cause: [1, 2] }),
};
const packr = new Packr({
moreTypes: true,
});
const serialized = packr.pack(object);
const deserialized = packr.unpack(serialized);
assert.equal(deserialized.error.message, object.error.message);
assert.equal(deserialized.error.cause, object.error.cause);
assert.equal(deserialized.errorWithCause.message, object.errorWithCause.message);
assert.equal(deserialized.errorWithCause.cause.message, object.errorWithCause.cause.message);
assert.equal(deserialized.errorWithCause.cause.cause, object.errorWithCause.cause.cause);
assert.equal(deserialized.type.message, object.type.message);
assert.equal(deserialized.range.message, object.range.message);
assert.deepEqual(deserialized.range.cause, object.range.cause);
assert(deserialized.error instanceof Error);
assert(deserialized.type instanceof TypeError);
assert(deserialized.range instanceof RangeError);
});
test('structured cloning: self reference', function() {
let object = {
test: 'string',
children: [
{ name: 'child' }
],
value: new ArrayBuffer(10)
};
let u8 = new Uint8Array(object.value);
u8[0] = 1;
u8[1] = 2;
object.self = object;
object.children[1] = object;
object.children[2] = object.children[0];
object.childrenAgain = object.children;
let packr = new Packr({
moreTypes: true,
structuredClone: true,
});
var serialized = packr.pack(object);
var deserialized = packr.unpack(serialized);
assert.equal(deserialized.self, deserialized);
assert.equal(deserialized.children[0].name, 'child');
assert.equal(deserialized.children[1], deserialized);
assert.equal(deserialized.children[0], deserialized.children[2]);
assert.equal(deserialized.children, deserialized.childrenAgain);
assert.equal(deserialized.value.constructor.name, 'ArrayBuffer');
u8 = new Uint8Array(deserialized.value);
assert.equal(u8[0], 1);
assert.equal(u8[1], 2);
});
test('structured cloning: self reference with more types', function() {
let set = new Set();
set.add(['hello', 1, 2, { map: new Map([[set, set], ['a', 'b']]) }]);
let packr = new Packr({
moreTypes: true,
structuredClone: true,
});
let serialized = packr.pack(set);
let deserialized = packr.unpack(serialized);
assert.equal(deserialized.constructor.name, 'Set');
let map = Array.from(deserialized)[0][3].map;
assert.equal(map.get(deserialized), deserialized);
let sizeTestMap = new Map();
for (let i = 0; i < 50; i++) {
sizeTestMap.set(i || sizeTestMap, sizeTestMap);
let deserialized = packr.unpack(packr.pack(sizeTestMap));
assert.equal(deserialized.size, i + 1);
assert(deserialized.has(deserialized));
assert(deserialized.has(i || deserialized));
}
let sizeTestSet = new Set();
for (let i = 0; i < 50; i++) {
sizeTestSet.add(i || sizeTestSet);
let deserialized = packr.unpack(packr.pack(sizeTestSet));
assert.equal(deserialized.size, i + 1);
assert(deserialized.has(deserialized));
assert(deserialized.has(i || deserialized));
}
});
test('structured cloning: types', function() {
let b = typeof Buffer != 'undefined' ? Buffer.alloc(20) : new Uint8Array(20);
let fa = new Float32Array(b.buffer, 8, 2);
fa[0] = 2.25;
fa[1] = 6;
let object = {
error: new Error('test'),
set: new Set(['a', 'b']),
regexp: /test/gi,
float32Array: fa,
uint16Array: new Uint16Array([3, 4]),
arrayBuffer: new Uint8Array([0xde, 0xad]).buffer,
dataView: new DataView(new Uint8Array([0xbe, 0xef]).buffer),
};
let packr = new Packr({
moreTypes: true,
structuredClone: true,
});
var serialized = packr.pack(object);
var deserialized = packr.unpack(serialized);
assert.deepEqual(Array.from(deserialized.set), Array.from(object.set));
assert.equal(deserialized.error.message, object.error.message);
assert.equal(deserialized.regexp.test('TEST'), true);
assert.equal(deserialized.float32Array.constructor.name, 'Float32Array');
assert.equal(deserialized.float32Array[0], 2.25);
assert.equal(deserialized.float32Array[1], 6);
assert.equal(deserialized.uint16Array.constructor.name, 'Uint16Array');
assert.equal(deserialized.uint16Array[0], 3);
assert.equal(deserialized.uint16Array[1], 4);
assert.equal(deserialized.arrayBuffer.constructor.name, 'ArrayBuffer');
assert.equal(new DataView(deserialized.arrayBuffer).getUint16(), 0xdead);
assert.equal(deserialized.dataView.constructor.name, 'DataView');
assert.equal(deserialized.dataView.getUint16(), 0xbeef);
});
test('big bundledStrings', function() {
const MSGPACK_OPTIONS = {bundleStrings: true};
const packer = new Packr(MSGPACK_OPTIONS);
const unpacker = new Unpackr(MSGPACK_OPTIONS);
const payload = {
output: [
{
url: 'https://www.example.com/',
},
],
};
for (let i = 0; i < 10000; i++) {
payload.output.push(payload.output[0]);
}
let deserialized = unpacker.unpack(packer.pack(payload));
assert.equal(deserialized.output[0].url, payload.output[0].url);
});
test('structured clone with bundled strings', function() {
const packer = new Packr({
structuredClone: true, // both options must be enabled
bundleStrings: true,
});
const v = {};
let shared = {
name1: v,
name2: v,
};
let deserialized = packer.unpack(packer.pack(shared));
assert.equal(deserialized.name1, deserialized.name2);
shared = {};
shared.aaaa = shared; // key length >= 4
deserialized = packer.unpack(packer.pack(shared));
assert.equal(deserialized.aaaa, deserialized);
});
test('object without prototype', function(){
var data = Object.create(null);
data.test = 3;
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('object with __proto__', function(){
const data = { foo: 'bar', __proto__: { isAdmin: true } };
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, { foo: 'bar' });
});
test('separate instances', function() {
const packr = new Packr({
structures: [['m', 'e'], ['action', 'share']]
});
const packr2 = new Packr({
structures: [['m', 'e'], ['action', 'share']]
});
let packed = packr.pack([{m: 1, e: 2}, {action: 3, share: 4}]);
// also tried directly decoding this without the first Packr instance packed = new Uint8Array([0x92, 0x40, 0x01, 0x02, 0x41, 0x03, 0x04]);
console.log(packr2.unpack(packed));
});
test('many shared structures', function() {
let data = [];
for (let i = 0; i < 200; i++) {
data.push({['a' + i]: i});
}
let structures = [];
let savedStructures;
let packr = new Packr({
structures,
saveStructures(structures) {
savedStructures = structures;
}
});
var serializedWith32 = packr.pack(data);
assert.equal(savedStructures.length, 32);
var deserialized = packr.unpack(serializedWith32);
assert.deepEqual(deserialized, data);
structures = structures.slice(0, 32);
packr = new Packr({
structures,
maxSharedStructures: 100,
saveStructures(structures) {
savedStructures = structures;
}
});
deserialized = packr.unpack(serializedWith32);
assert.deepEqual(deserialized, data);
structures = structures.slice(0, 32);
packr = new Packr({
structures,
maxSharedStructures: 100,
saveStructures(structures) {
savedStructures = structures;
}
});
let serialized = packr.pack(data);
assert.equal(savedStructures.length, 100);
deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data);
deserialized = packr.unpack(serializedWith32);
assert.deepEqual(deserialized, data);
assert.equal(savedStructures.length, 100);
deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data);
assert.equal(packr.structures.sharedLength, 100);
});
test('more shared structures', function() {
const structures = [];
for (let i = 0; i < 40; i++) {
structures.push(['a' + i]);
}
const structures2 = [...structures];
const packr = new Packr({
getStructures() {
return structures
},
saveStructures(structures) {
},
maxSharedStructures: 100
});
const packr2 = new Packr({
getStructures() {
return structures2
},
saveStructures(structures) {
},
maxSharedStructures: 100
});
const inputData = {a35: 35};
const buffer = packr.pack(inputData);
const outputData = packr2.decode(buffer);
assert.deepEqual(inputData, outputData);
});
test('declined structure save re-packs against durable structures (no dangling record)', function() {
// Regression: when saveStructures declines a save (a concurrent writer updated the shared
// structures, or the store txn did not durably commit), the in-memory structures/transition
// trie reference a record id that was never persisted. The re-pack must reload the durable
// structures and re-mint/re-save — otherwise it re-emits the same record pointing at an
// unsaved structure, and reads throw "Record id is not defined".
const meta = new Packr();
let store = null;
let declinedOnce = false;
const packr = new Packr({
useRecords: true,
getStructures() { return store ? meta.unpack(store) : undefined },
saveStructures(structures) {
if (!declinedOnce) { declinedOnce = true; return false } // decline the first save
store = meta.pack(structures); return true
},
});
const a = packr.pack({ x: 9, y: 8 }); // first mint is declined -> must re-pack + re-save
const b = packr.pack({ x: 7, y: 6 }); // same shape -> must still reference a saved structure
// A fresh reader sees only the durably-saved structures (another thread / post-restart):
const reader = new Packr({ getStructures() { return store ? meta.unpack(store) : undefined } });
assert.deepEqual(reader.unpack(a), { x: 9, y: 8 });
assert.deepEqual(reader.unpack(b), { x: 7, y: 6 });
});
test('big buffer', function() {
var size = 100000000;
var data = new Uint8Array(size).fill(1);
var packed = pack(data);
var unpacked = unpack(packed);
assert.equal(unpacked.length, size);
});
test('random strings', function(){
var data = [];
for (var i = 0; i < 2000; i++) {
var str = 'test';
while (Math.random() < 0.7 && str.length < 0x100000) {
str = str + String.fromCharCode(90/(Math.random() + 0.01)) + str;
}
data.push(str);
}
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('map/date', function(){
var map = new Map();
map.set(4, 'four');
map.set('three', 3);
var data = {
map: map,
date: new Date(1532219539733),
farFutureDate: new Date(3532219539133),
fartherFutureDate: new Date('2106-08-05T18:48:20.323Z'),
ancient: new Date(-3532219539133),
invalidDate: new Date('invalid')
};
let packr = new Packr();
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.equal(deserialized.map.get(4), 'four');
assert.equal(deserialized.map.get('three'), 3);
assert.equal(deserialized.date.getTime(), 1532219539733);
assert.equal(deserialized.farFutureDate.getTime(), 3532219539133);
assert.equal(deserialized.fartherFutureDate.toISOString(), '2106-08-05T18:48:20.323Z');
assert.equal(deserialized.ancient.getTime(), -3532219539133);
assert.equal(deserialized.invalidDate.toString(), 'Invalid Date');
});
test('map/date with options', function(){
var map = new Map();
map.set(4, 'four');
map.set('three', 3);
var data = {
map: map,
date: new Date(1532219539011),
invalidDate: new Date('invalid')
};
let packr = new Packr({
mapsAsObjects: true,
useTimestamp32: true,
onInvalidDate: () => 'Custom invalid date'
});
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.equal(deserialized.map[4], 'four');
assert.equal(deserialized.map.three, 3);
assert.equal(deserialized.date.getTime(), 1532219539000);
assert.equal(deserialized.invalidDate, 'Custom invalid date');
});
test('key caching', function() {
var data = {
foo: 2,
bar: 'test',
four: 4,
seven: 7,
foz: 3,
};
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
// do multiple times to test caching
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('strings', function() {
var data = [''];
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
// do multiple times
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
data = 'decode this: ᾜ';
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
data = 'decode this that is longer but without any non-latin characters';
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('decimal float32', function() {
var data = {
a: 2.526,
b: 0.0035235,
c: 0.00000000000352501,
d: 3252.77,
};
let packr = new Packr({
useFloat32: DECIMAL_FIT
});
var serialized = packr.pack(data);
assert.equal(serialized.length, 32);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('int64/uint64 should be bigints by default', function() {
var data = {
a: 325283295382932843n
};
let packr = new Packr();
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized.a, 325283295382932843n);
});
test('bigint to float', function() {
var data = {
a: 325283295382932843n
};
let packr = new Packr({
int64AsType: 'number'
});
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized.a, 325283295382932843);
});
test('int64AsNumber compatibility', function() {
// https://github.com/kriszyp/msgpackr/pull/85
var data = {
a: 325283295382932843n
};
let packr = new Packr({
int64AsNumber: true
});
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized.a, 325283295382932843);
});
test('bigint to auto (float or bigint)', function() {
var data = {
a: -9007199254740993n,
b: -9007199254740992n,
c: 0n,
d: 9007199254740992n,
e: 9007199254740993n,
};
let packr = new Packr({
int64AsType: 'auto'
});
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized.a, -9007199254740993n);
assert.deepEqual(deserialized.b, -9007199254740992);
assert.deepEqual(deserialized.c, 0);
assert.deepEqual(deserialized.d, 9007199254740992);
assert.deepEqual(deserialized.e, 9007199254740993n);
});
test('bigint to string', function() {
var data = {
a: 325283295382932843n,
};
let packr = new Packr({
int64AsType: 'string'
});
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized.a, '325283295382932843');
});
test('fixint should be one byte', function(){
let encoded = pack(123);
assert.equal(encoded.length, 1);
});
test('numbers', function(){
var data = {
bigEncodable: 48978578104322,
dateEpoch: 1530886513200,
realBig: 3432235352353255323,
decimal: 32.55234,
negative: -34.11,
exponential: 0.234e123,
tiny: 3.233e-120,
zero: 0,
//negativeZero: -0,
Infinity: Infinity
};
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('bigint', function(){
var data = {
bigintSmall: 352n,
bigintSmallNegative: -333335252n,
bigintBig: 2n**64n - 1n, // biggest possible
bigintBigNegative: -(2n**63n), // largest negative
mixedWithNormal: 44,
};
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
var tooBigInt = {
tooBig: 2n**66n
};
assert.throws(function(){ serialized = pack(tooBigInt); });
let packr = new Packr({
largeBigIntToFloat: true
});
serialized = packr.pack(tooBigInt);
deserialized = unpack(serialized);
assert.isTrue(deserialized.tooBig > 2n**65n);
packr = new Packr({
largeBigIntToString: true
});
serialized = packr.pack(tooBigInt);
deserialized = unpack(serialized);
assert.equal(deserialized.tooBig, (2n**66n).toString());
});
test('roundFloat32', function() {
assert.equal(roundFloat32(0.00333000003), 0.00333);
assert.equal(roundFloat32(43.29999999993), 43.3);
});
test('buffers', function(){
var data = {
buffer1: new Uint8Array([2,3,4]),
buffer2: new Uint8Array(pack(sampleData))
};
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('notepack test', function() {
const data = {
foo: 1,
bar: [1, 2, 3, 4, 'abc', 'def'],
foobar: {
foo: true,
bar: -2147483649,
foobar: {
foo: new Uint8Array([1, 2, 3, 4, 5]),
bar: 1.5,
foobar: [true, false, 'abcdefghijkmonpqrstuvwxyz']
}
}
};
var serialized = pack(data);
var deserialized = unpack(serialized);
var deserialized = unpack(serialized);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('arrays in map keys', function() {
const msgpackr = new Packr({ mapsAsObjects: true, allowArraysInMapKeys: true });
const map = new Map();
map.set([1, 2, 3], 1);
map.set([1, 2, ['foo', 3.14]], 2);
const packed = msgpackr.pack(map);
const unpacked = msgpackr.unpack(packed);
assert.deepEqual(unpacked, { '1,2,3': 1, '1,2,foo,3.14': 2 });
});
test('utf16 causing expansion', function() {
this.timeout(10000);
let data = {fixstr: 'ᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝ', str8:'ᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝ'};
var serialized = pack(data);
var deserialized = unpack(serialized);
assert.deepEqual(deserialized, data);
});
test('unpackMultiple', () => {
let values = unpackMultiple(new Uint8Array([1, 2, 3, 4]));
assert.deepEqual(values, [1, 2, 3, 4]);
values = [];
unpackMultiple(new Uint8Array([1, 2, 3, 4]), value => values.push(value));
assert.deepEqual(values, [1, 2, 3, 4]);
});
test('unpackMultiple with positions', () => {
let values = unpackMultiple(new Uint8Array([1, 2, 3, 4]));
assert.deepEqual(values, [1, 2, 3, 4]);
values = [];
unpackMultiple(new Uint8Array([1, 2, 3, 4]), (value,start,end) => values.push([value,start,end]));
assert.deepEqual(values, [[1,0,1], [2,1,2], [3,2,3], [4,3,4]]);
});
test('pack toJSON returning this', () => {
class Serializable {
someData = [1, 2, 3, 4]
toJSON() {
return this
}
}
const serialized = pack(new Serializable);
const deserialized = unpack(serialized);
assert.deepStrictEqual(deserialized, { someData: [1, 2, 3, 4] });
});
test('skip values', function () {
var data = {
data: [
{ a: 1, name: 'one', type: 'odd', isOdd: true },
{ a: 2, name: 'two', type: 'even', isOdd: undefined },
{ a: 3, name: 'three', type: 'odd', isOdd: true },
{ a: 4, name: 'four', type: 'even', isOdd: null},
{ a: 5, name: 'five', type: 'odd', isOdd: true },
{ a: 6, name: 'six', type: 'even', isOdd: null }
],
description: 'some names',
types: ['odd', 'even'],
convertEnumToNum: [
{ prop: 'test' },
{ prop: 'test' },
{ prop: 'test' },
{ prop: 1 },
{ prop: 2 },
{ prop: [undefined, null] },
{ prop: null }
]
};
var expected = {
data: [
{ a: 1, name: 'one', type: 'odd', isOdd: true },
{ a: 2, name: 'two', type: 'even' },
{ a: 3, name: 'three', type: 'odd', isOdd: true },
{ a: 4, name: 'four', type: 'even', },
{ a: 5, name: 'five', type: 'odd', isOdd: true },
{ a: 6, name: 'six', type: 'even' }
],
description: 'some names',
types: ['odd', 'even'],
convertEnumToNum: [
{ prop: 'test' },
{ prop: 'test' },
{ prop: 'test' },
{ prop: 1 },
{ prop: 2 },
{ prop: [undefined, null] },
{}
]
};
let packr = new Packr({ useRecords: false, skipValues: [undefined, null] });
var serialized = packr.pack(data);
var deserialized = packr.unpack(serialized);
assert.deepEqual(deserialized, expected);
});
});
suite('msgpackr performance tests', function(){
test('performance JSON.parse', function() {
var data = sampleData;
this.timeout(10000);
var serialized = JSON.stringify(data);
console.log('JSON size', serialized.length);
for (var i = 0; i < ITERATIONS; i++) {
JSON.parse(serialized);
}
});
test('performance JSON.stringify', function() {
var data = sampleData;
this.timeout(10000);
for (var i = 0; i < ITERATIONS; i++) {
JSON.stringify(data);
}
});
test('performance unpack', function() {
var data = sampleData;
this.timeout(10000);
let structures = [];
var serialized = pack(data);
console.log('MessagePack size', serialized.length);
let packr = new Packr({ structures, bundleStrings: false });
var serialized = packr.pack(data);
console.log('msgpackr w/ record ext size', serialized.length);
for (var i = 0; i < ITERATIONS; i++) {
packr.unpack(serialized);
}
});
test('performance pack', function() {
var data = sampleData;
this.timeout(10000);
let structures = [];
let packr = new Packr({ structures, bundleStrings: false });
let buffer = typeof Buffer != 'undefined' ? Buffer.alloc(0x10000) : new Uint8Array(0x10000);
for (var i = 0; i < ITERATIONS; i++) {
//serialized = pack(data, { shared: sharedStructure })
packr.useBuffer(buffer);
packr.pack(data);
//var serializedGzip = deflateSync(serialized)
}
//console.log('serialized', serialized.length, global.propertyComparisons)
});
});
suite('msgpackr maxOwnStructures cap (randomAccessStructure)', function () {
// Helper: create a width-heterogeneous record generator using a deterministic PRNG.
// Objects have up to 6 fields drawn from a sparse set, each value is an integer whose
// width (num8 / num32 / num64) varies per value, producing many distinct typed structures.
// useRecords: false keeps the classic named-record encoder out of the way so all structure
// creation goes through the typed-struct path and the cap is exercised in isolation.
function makeCapRunner(cap) {
const fields = ['a','b','c','d','e','f','g','h'];
let seed = 42;
const rnd = () => { seed = (seed * 1664525 + 1013904223) >>> 0; return seed / 0xffffffff; };
const packr = new Packr({
structures: [],
useRecords: false,
randomAccessStructure: true,
maxOwnStructures: cap,
});
const norm = r => packr.unpack(packr.pack(r));
for (let i = 0; i < 4000; i++) {
const r = {};
for (let j = 0; j < Math.ceil(rnd() * 6); j++) {
// values cycle across num8 / num32 / float64 ranges to force distinct structures
const mag = rnd() < 0.33 ? 10 : rnd() < 0.5 ? 400000 : 1e13;
r[fields[Math.floor(rnd() * 8)]] = Math.floor(rnd() * mag);
}
assert.deepEqual(norm(r), r);
}
return packr.typedStructs ? packr.typedStructs.length : 0;
}
test('uncapped (default) grows well past 256 for width-heterogeneous records', function () {
assert.ok(makeCapRunner(undefined) > 256, 'expected uncapped typedStructs to exceed 256');
});
test('cap=64 bounds typedStructs.length and preserves round-trips', function () {
assert.ok(makeCapRunner(64) <= 64, 'typedStructs should not exceed cap of 64');
});
test('cap=256 bounds typedStructs.length and preserves round-trips', function () {
assert.ok(makeCapRunner(256) <= 256, 'typedStructs should not exceed cap of 256');
});
test('flat-record streams stay a strict hard bound', function () {
// With maxOwnStructures=16, pack 2000 flat records; typedStructs must never exceed 16.
const packr = new Packr({
structures: [],
useRecords: false,
randomAccessStructure: true,
maxOwnStructures: 16,
});
let seed = 99;
const rnd = () => { seed = (seed * 1664525 + 1013904223) >>> 0; return seed / 0xffffffff; };
for (let i = 0; i < 2000; i++) {
const r = { x: Math.floor(rnd() * 1e6), y: Math.floor(rnd() * 200), z: Math.floor(rnd() * 1e12) };
assert.deepEqual(packr.unpack(packr.pack(r)), r);
}
assert.ok(packr.typedStructs.length <= 16, 'flat records must stay within cap, got ' + packr.typedStructs.length);
});
test('capped-out records fall back to plain encoding and still round-trip', function () {
// Once the cap is hit, novel shapes must decode correctly via plain msgpack fallback.
const packr = new Packr({
structures: [],
useRecords: false,
randomAccessStructure: true,
maxOwnStructures: 2,
});
const norm = r => packr.unpack(packr.pack(r));
assert.deepEqual(norm({ a: 1 }), { a: 1 }); // mints structure 0
assert.deepEqual(norm({ b: 'hello' }), { b: 'hello' }); // mints structure 1, cap hit
// These novel shapes fall back to plain msgpack — must still decode correctly:
assert.deepEqual(norm({ c: 42 }), { c: 42 });
assert.deepEqual(norm({ a: 1, b: 'hi', c: 99 }), { a: 1, b: 'hi', c: 99 });
assert.strictEqual(packr.typedStructs.length, 2, 'cap must be exact');
});
test('a known key later seen as a nested object falls back cleanly', function () {
// Once frozen, a previously-learned scalar key carrying an object must bail BEFORE pack()
// advances the shared encoder position — otherwise the plain fallback gets corrupt bytes.
const packr = new Packr({
structures: [],
useRecords: false,
randomAccessStructure: true,
maxOwnStructures: 1,
});
const norm = r => packr.unpack(packr.pack(r));
assert.deepEqual(norm({ a: 1 }), { a: 1 }); // mints structure 0, cap hit
const r2 = { a: { x: 1 } };
assert.deepEqual(norm(r2), r2); // 'a' known but now carries object — must fall back cleanly
});
test('nested records do not overshoot the cap and still round-trip', function () {
// A nested object mints its own structure before the outer record, so a stale frozen flag
// could push the outer record past the cap. The record-id mint guard re-checks the live
// length, keeping typedStructs.length a strict bound.
const packr = new Packr({
structures: [],
useRecords: false,
randomAccessStructure: true,
maxOwnStructures: 4,
});
let seed = 7;
const rnd = () => { seed = (seed * 1103515245 + 12345) & 0x7fffffff; return seed / 0x7fffffff; };
for (let i = 0; i < 1000; i++) {
const r = { outer: { inner: (rnd() * 1e7 | 0) * 1000 }, tag: 't' + (i % 20), n: (rnd() * 300 | 0) };
assert.deepEqual(packr.unpack(packr.pack(r)), r);
}
assert.ok(packr.typedStructs.length <= 4, 'nested encodes must not push typedStructs past the cap, got ' + packr.typedStructs.length);
});
test('persisted typed structures still load after a capped encoder froze the dictionary', function () {
// Replaying persisted structures in onLoadedStructures must always succeed, regardless of
// maxOwnStructures — the cap only limits minting NEW structures during encode.
let saved = null;
const writer = new Packr({
structures: [],
useRecords: false,
randomAccessStructure: true,
saveStructures(s) { saved = s; return true; },
getStructures() { return saved; },
});
const buf = writer.pack({ name: 'Alice', age: 30 });
// Warm up a capped encoder so the module-global-if-any freeze state is set.
const capped = new Packr({
structures: [],
useRecords: false,
randomAccessStructure: true,
maxOwnStructures: 1,
});
capped.pack({ x: 1 });
capped.pack({ y: 2, z: 3 }); // cap reached
// A fresh reader must still rebuild the transition trie from saved structures.
const reader = new Packr({
structures: [],
useRecords: false,
randomAccessStructure: true,
getStructures() { return saved; },
});
const result = reader.unpack(buf);
assert.equal(result.name, 'Alice');
assert.equal(result.age, 30);
});
test('the cap is per-instance: an uncapped sibling cannot lift this instance\'s cap', function () {
// frozen is derived from each encoder's own typedStructs.length — not a shared global —
// so an uncapped sibling churning out structures cannot lift the cap on the bounded one.
const uncapped = new Packr({
structures: [],
useRecords: false,
randomAccessStructure: true,
});
const capped = new Packr({
structures: [],
useRecords: false,
randomAccessStructure: true,
maxOwnStructures: 2,
});
let seed = 1;
const rnd = () => { seed = (seed * 1664525 + 1013904223) >>> 0; return seed / 0xffffffff; };
const mk = () => { const o = {}; for (let f = 0; f < 20; f++) if (rnd() < 0.5) o['f' + f] = Math.floor(rnd() * 1e7); return o; };
for (let i = 0; i < 500; i++) {
uncapped.pack(mk()); // grows the sibling's dictionary freely
const r = mk();
assert.deepEqual(capped.unpack(capped.pack(r)), r);
}
assert.ok(capped.typedStructs.length <= 2, 'capped must stay bounded, got ' + capped.typedStructs.length);
assert.ok(uncapped.typedStructs.length > 2, 'uncapped sibling should grow freely');
});
test('a layout-retry record (large fixed section + nested refs) does not corrupt', function () {
// A large fixed section overflows the ref-start estimate and triggers the internal retry
// (which re-invokes writeStruct after refs were packed). The retry passes structureKnown=true
// to re-encode the already-minted structure rather than bailing under the now-reached cap —
// bailing there would write the fallback at an advanced position and corrupt the bytes.
const packr = new Packr({ structures: [], useRecords: false, randomAccessStructure: true, maxOwnStructures: 1 });
const norm = r => packr.unpack(packr.pack(r));
const mk = base => { const r = {}; for (let i = 0; i < 40; i++) r['n' + i] = base + i; r.a = { x: base }; r.b = { y: base + 1 }; return r; };
assert.deepEqual(norm(mk(1000000)), mk(1000000));
assert.deepEqual(norm(mk(2000000)), mk(2000000));
assert.ok(packr.typedStructs.length <= 1, 'retry-path records must not exceed the cap, got ' + packr.typedStructs.length);
});
});
suite('msgpackr readOnlyStructures (write-disable, read-compatible)', function () {
// readOnlyStructures keeps randomAccessStructure decode semantics — existing random-access
// struct data still decodes and the struct-safe integer boundary is preserved — but never mints
// a new random-access structure on write. Objects fall through to the classic shared-structure
// record path (bytes 0x40-0x7f, disjoint from struct headers at 0x20-0x3f), the bounded,
// width-agnostic encoding used before struct mode. This is the mechanism for disabling typed
// structures on an existing store without breaking reads.
const sharedStore = () => {
let saved;
return { getStructures: () => saved, saveStructures: (s) => { saved = s; return true; } };
};
test('decodes pre-existing random-access structs (read stays enabled)', function () {
const full = new Packr({ randomAccessStructure: true, useRecords: true });
const rec = { a: 1, b: 'hello', c: true, n: 42 };
const buf = full.pack(rec);
assert.ok(buf[0] >= 0x20 && buf[0] < 0x40, 'control: full encoder emits a random-access struct');
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true });
ro.typedStructs = full.typedStructs; // share the existing random-access dictionary
assert.deepEqual(ro.unpack(buf), rec);
});
test('writes classic shared-structure records (0x40-0x7f), never a random-access struct', function () {
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...sharedStore() });
const rec = { x: 9, y: 'world', z: [1, 2, 3], nested: { deep: 50, k: 42 } };
const b1 = ro.pack(rec);
assert.ok(b1[0] >= 0x40 && b1[0] < 0x80, 'expected a classic record (0x40-0x7f), got 0x' + b1[0].toString(16));
assert.strictEqual(ro.typedStructs ? ro.typedStructs.length : 0, 0, 'no random-access struct minted');
assert.deepEqual(ro.unpack(b1), rec);
// a second record of the same shape reuses the shared classic structure (still classic)
const b2 = ro.pack({ x: 1, y: 'a', z: [], nested: { deep: 0, k: 0 } });
assert.ok(b2[0] >= 0x40 && b2[0] < 0x80, 'second record also classic');
});
test('preserves the struct-safe integer boundary (nested 0x20-0x3f ints do not collide)', function () {
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...sharedStore() });
for (const v of [0x1f, 0x20, 0x2a, 0x3f, 0x40, 0x7f]) {
assert.deepEqual(ro.unpack(ro.pack({ v, nested: { w: v } })), { v, nested: { w: v } });
}
});
test('a normal randomAccessStructure reader decodes readOnly classic records (replication compat)', function () {
let saved;
const store = { getStructures: () => saved, saveStructures: (s) => { saved = s; return true; } };
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...store });
const roBuf = ro.pack({ p: 'q', n: 42 });
assert.ok(roBuf[0] >= 0x40 && roBuf[0] < 0x80, 'readOnly writes a classic record');
// A peer that is NOT readOnly (a normal struct-writing node) shares the classic-structure
// store and decodes the classic record correctly — no mapsAsObjects needed, since classic
// records decode to objects natively.
const peer = new Packr({ randomAccessStructure: true, useRecords: true, ...store });
assert.deepEqual(peer.unpack(roBuf), { p: 'q', n: 42 });
});
test('every object shape uses the classic path (null-proto, shadowed ctor) — no random-access struct', function () {
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...sharedStore() });
const np = Object.create(null); np.a = 1; np.b = 'x';
assert.deepEqual(ro.unpack(ro.pack(np)), { a: 1, b: 'x' });
const cs = { constructor: 'shadow', a: 1 };
assert.deepEqual(ro.unpack(ro.pack(cs)), { constructor: 'shadow', a: 1 });
assert.strictEqual(ro.typedStructs ? ro.typedStructs.length : 0, 0, 'no random-access struct minted for any shape');
});
});
suite('msgpackr two-byte record definitions (over-cap own records with a shared store)', function () {
// Regression for a data-corruption bug: with a shared-structures store (getStructures/
// saveStructures) and maxOwnStructures > 32, msgpackr uses two-byte record ids
// (useTwoByteRecords). Over-cap "own" record shapes fall back to the classic record encoder,
// which emits a SELF-CONTAINED record definition (0xd5 0x72 <firstByte> <highByte> <keys>
// <values>). On read, recordDefinition() consumed the high byte itself, then invoked
// structure.read() — which, for a highByte === 0 structure, was a second-byte reader that
// consumed the FIRST VALUE BYTE as a phantom high byte, mis-resolving to a never-defined id
// ("Record id is not defined for N"). The one-byte path (maxOwnStructures <= 32, fixext 1
// 0xd4 0x72) was unaffected because no high byte / second-byte reader is involved.
// Fix: recordDefinition reads the body via the un-wrapped reader (structure.read0).
const sharedStore = () => {
let saved = [];
return { getStructures: () => saved, saveStructures: (s) => { saved = s; return true; }, peek: () => saved };
};
// Width-heterogeneous record generator: many distinct shapes, exceeding any modest cap, so
// the over-cap classic-record fallback path is exercised heavily.
const makeRecords = (count) => {
const out = [];
for (let i = 0; i < count; i++) {
const rec = { id: i };
const n = 1 + (i % 6);
for (let a = 0; a < n; a++) rec['attr_' + ((i * 7 + a) % 400)] = i % 2 ? 's' + i : i;
out.push(rec);
}
return out;
};
test('cap=256 + shared store round-trips every over-cap shape (two-byte mode)', function () {
const store = sharedStore();
const o = { randomAccessStructure: true, useRecords: true, useBigIntExtension: true, maxOwnStructures: 256, ...store };
const w = new Packr(o), r = new Packr(o);
const records = makeRecords(3000);
let sawTwoByteDef = false;
for (const rec of records) {
const buf = w.pack(rec);
if (buf[0] === 0xd5 && buf[1] === 0x72) sawTwoByteDef = true;
assert.deepEqual(r.unpack(buf), rec);
}
assert.ok(sawTwoByteDef, 'expected at least one two-byte (0xd5 0x72) record definition to be exercised');
});
test('readOnlyStructures + cap=256 + shared store round-trips (two-byte mode)', function () {
const store = sharedStore();
const o = { randomAccessStructure: true, useRecords: true, useBigIntExtension: true, readOnlyStructures: true, maxOwnStructures: 256, ...store };
const w = new Packr(o), r = new Packr(o);
for (const rec of makeRecords(3000)) {
assert.deepEqual(r.unpack(w.pack(rec)), rec);
}
});
test('over-cap own records do not grow the shared dictionary without bound', function () {
const store = sharedStore();
const o = { randomAccessStructure: true, useRecords: true, useBigIntExtension: true, maxOwnStructures: 256, ...store };
const w = new Packr(o);
for (const rec of makeRecords(3000)) w.pack(rec);
const saved = store.peek();
const named = saved instanceof Map ? (saved.get('named') || []) : (saved || []);
// The classic named-structures store must stay tiny (bounded by the few genuinely shared
// shapes); over-cap shapes are inlined as self-contained definitions, not minted as
// ever-growing shared ids.
assert.ok(named.length < 100, 'named structures should stay bounded, got ' + named.length);
});
test('two-byte record REFERENCES (shared, maxShared=64) round-trip with stable shapes', function () {
// maxSharedStructures > 32 forces two-byte mode; stable repeated shapes get a definition
// once then are emitted as two-byte references (0x60-0x7f + high byte) — the path that
// relies on structure.read remaining a second-byte reader after the fix.
const store = sharedStore();
const o = { useRecords: true, maxSharedStructures: 64, maxOwnStructures: 64, ...store };
const w = new Packr(o), r = new Packr(o);
const shapes = [];
for (let s = 0; s < 100; s++) { const rec = {}; for (let f = 0; f <= s % 8; f++) rec['k' + s + '_' + f] = s * 10 + f; shapes.push(rec); }
let sawRef = false;
for (let round = 0; round < 4; round++) for (const shp of shapes) {
const buf = w.pack(shp);
if (buf[0] >= 0x60 && buf[0] < 0x80) sawRef = true;
assert.deepEqual(r.unpack(buf), shp);
}
assert.ok(sawRef, 'expected at least one two-byte record reference (0x60-0x7f) to be exercised');
});
test('one-byte path (maxOwn=32) keeps working with a shared store', function () {
const store = sharedStore();
const o = { randomAccessStructure: true, useRecords: true, useBigIntExtension: true, maxOwnStructures: 32, ...store };
const w = new Packr(o), r = new Packr(o);
for (const rec of makeRecords(3000)) {
assert.deepEqual(r.unpack(w.pack(rec)), rec);
}
});
});
})(chai, null, module, fs);
//# sourceMappingURL=test.js.map
File diff suppressed because one or more lines are too long
+1281
View File
@@ -0,0 +1,1281 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.msgpackr = {}));
})(this, (function (exports) { 'use strict';
var decoder;
try {
decoder = new TextDecoder();
} catch(error) {}
var src;
var srcEnd;
var position = 0;
const EMPTY_ARRAY = [];
var strings = EMPTY_ARRAY;
var stringPosition = 0;
var currentUnpackr = {};
var currentStructures;
var srcString;
var srcStringStart = 0;
var srcStringEnd = 0;
var bundledStrings;
var referenceMap;
var currentExtensions = [];
var dataView;
var defaultOptions = {
useRecords: false,
mapsAsObjects: true
};
class C1Type {}
const C1 = new C1Type();
C1.name = 'MessagePack 0xC1';
var sequentialMode = false;
var inlineObjectReadThreshold = 2;
var readStruct, onLoadedStructures, onSaveState;
var BlockedFunction; // we use search and replace to change the next call to BlockedFunction to avoid CSP issues for
class Unpackr {
constructor(options) {
if (options) {
if (options.useRecords === false && options.mapsAsObjects === undefined)
options.mapsAsObjects = true;
if (options.sequential && options.trusted !== false) {
options.trusted = true;
if (!options.structures && options.useRecords != false) {
options.structures = [];
if (!options.maxSharedStructures)
options.maxSharedStructures = 0;
}
}
if (options.structures)
options.structures.sharedLength = options.structures.length;
else if (options.getStructures) {
(options.structures = []).uninitialized = true; // this is what we use to denote an uninitialized structures
options.structures.sharedLength = 0;
}
if (options.int64AsNumber) {
options.int64AsType = 'number';
}
}
Object.assign(this, options);
}
unpack(source, options) {
if (src) {
// re-entrant execution, save the state and restore it after we do this unpack
return saveState(() => {
clearSource();
return this ? this.unpack(source, options) : Unpackr.prototype.unpack.call(defaultOptions, source, options)
})
}
if (!source.buffer && source.constructor === ArrayBuffer)
source = typeof Buffer !== 'undefined' ? Buffer.from(source) : new Uint8Array(source);
if (typeof options === 'object') {
srcEnd = options.end || source.length;
position = options.start || 0;
} else {
position = 0;
srcEnd = options > -1 ? options : source.length;
}
stringPosition = 0;
srcStringEnd = 0;
srcString = null;
strings = EMPTY_ARRAY;
bundledStrings = null;
src = source;
// this provides cached access to the data view for a buffer if it is getting reused, which is a recommend
// technique for getting data from a database where it can be copied into an existing buffer instead of creating
// new ones
try {
dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength));
} catch(error) {
// if it doesn't have a buffer, maybe it is the wrong type of object
src = null;
if (source instanceof Uint8Array)
throw error
throw new Error('Source must be a Uint8Array or Buffer but was a ' + ((source && typeof source == 'object') ? source.constructor.name : typeof source))
}
if (this instanceof Unpackr) {
currentUnpackr = this;
if (this.structures) {
currentStructures = this.structures;
return checkedRead(options)
} else if (!currentStructures || currentStructures.length > 0) {
currentStructures = [];
}
} else {
currentUnpackr = defaultOptions;
if (!currentStructures || currentStructures.length > 0)
currentStructures = [];
}
return checkedRead(options)
}
unpackMultiple(source, forEach) {
let values, lastPosition = 0;
try {
sequentialMode = true;
let size = source.length;
let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size);
if (forEach) {
if (forEach(value, lastPosition, position) === false) return;
while(position < size) {
lastPosition = position;
if (forEach(checkedRead(), lastPosition, position) === false) {
return
}
}
}
else {
values = [ value ];
while(position < size) {
lastPosition = position;
values.push(checkedRead());
}
return values
}
} catch(error) {
error.lastPosition = lastPosition;
error.values = values;
throw error
} finally {
sequentialMode = false;
clearSource();
}
}
_mergeStructures(loadedStructures, existingStructures) {
if (onLoadedStructures)
loadedStructures = onLoadedStructures.call(this, loadedStructures);
loadedStructures = loadedStructures || [];
if (Object.isFrozen(loadedStructures))
loadedStructures = loadedStructures.map(structure => structure.slice(0));
for (let i = 0, l = loadedStructures.length; i < l; i++) {
let structure = loadedStructures[i];
if (structure) {
structure.isShared = true;
if (i >= 32)
structure.highByte = (i - 32) >> 5;
}
}
loadedStructures.sharedLength = loadedStructures.length;
for (let id in existingStructures || []) {
if (id >= 0) {
let structure = loadedStructures[id];
let existing = existingStructures[id];
if (existing) {
if (structure)
(loadedStructures.restoreStructures || (loadedStructures.restoreStructures = []))[id] = structure;
loadedStructures[id] = existing;
}
}
}
return this.structures = loadedStructures
}
decode(source, options) {
return this.unpack(source, options)
}
}
function getPosition() {
return position
}
function checkedRead(options) {
try {
if (!currentUnpackr.trusted && !sequentialMode) {
let sharedLength = currentStructures.sharedLength || 0;
if (sharedLength < currentStructures.length)
currentStructures.length = sharedLength;
}
let result;
if (currentUnpackr.randomAccessStructure && src[position] < 0x40 && src[position] >= 0x20 && readStruct) {
result = readStruct(src, position, srcEnd, currentUnpackr);
src = null; // dispose of this so that recursive unpack calls don't save state
if (!(options && options.lazy) && result)
result = result.toJSON();
position = srcEnd;
} else
result = read();
if (bundledStrings) { // bundled strings to skip past
position = bundledStrings.postBundlePosition;
bundledStrings = null;
}
if (sequentialMode)
// we only need to restore the structures if there was an error, but if we completed a read,
// we can clear this out and keep the structures we read
currentStructures.restoreStructures = null;
if (position == srcEnd) {
// finished reading this source, cleanup references
if (currentStructures && currentStructures.restoreStructures)
restoreStructures();
currentStructures = null;
src = null;
if (referenceMap)
referenceMap = null;
} else if (position > srcEnd) {
// over read
throw new Error('Unexpected end of MessagePack data')
} else if (!sequentialMode) {
let jsonView;
try {
jsonView = JSON.stringify(result, (_, value) => typeof value === "bigint" ? `${value}n` : value).slice(0, 100);
} catch(error) {
jsonView = '(JSON view not available ' + error + ')';
}
throw new Error('Data read, but end of buffer not reached ' + jsonView)
}
// else more to read, but we are reading sequentially, so don't clear source yet
return result
} catch(error) {
if (currentStructures && currentStructures.restoreStructures)
restoreStructures();
clearSource();
if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer') || position > srcEnd) {
error.incomplete = true;
}
throw error
}
}
function restoreStructures() {
for (let id in currentStructures.restoreStructures) {
currentStructures[id] = currentStructures.restoreStructures[id];
}
currentStructures.restoreStructures = null;
}
function read() {
let token = src[position++];
if (token < 0xa0) {
if (token < 0x80) {
if (token < 0x40)
return token
else {
let structure = currentStructures[token & 0x3f] ||
currentUnpackr.getStructures && loadStructures()[token & 0x3f];
if (structure) {
if (!structure.read) {
structure.read = createStructureReader(structure, token & 0x3f);
}
return structure.read()
} else
return token
}
} else if (token < 0x90) {
// map
token -= 0x80;
if (currentUnpackr.mapsAsObjects) {
let object = {};
for (let i = 0; i < token; i++) {
let key = readKey();
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
return object
} else {
let map = new Map();
for (let i = 0; i < token; i++) {
map.set(read(), read());
}
return map
}
} else {
token -= 0x90;
let array = new Array(token);
for (let i = 0; i < token; i++) {
array[i] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
} else if (token < 0xc0) {
// fixstr
let length = token - 0xa0;
if (srcStringEnd >= position) {
return srcString.slice(position - srcStringStart, (position += length) - srcStringStart)
}
if (srcStringEnd == 0 && srcEnd < 140) {
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length);
if (string != null)
return string
}
return readFixedString(length)
} else {
let value;
switch (token) {
case 0xc0: return null
case 0xc1:
if (bundledStrings) {
value = read(); // followed by the length of the string in characters (not bytes!)
if (value > 0)
return bundledStrings[1].slice(bundledStrings.position1, bundledStrings.position1 += value)
else
return bundledStrings[0].slice(bundledStrings.position0, bundledStrings.position0 -= value)
}
return C1; // "never-used", return special object to denote that
case 0xc2: return false
case 0xc3: return true
case 0xc4:
// bin 8
value = src[position++];
if (value === undefined)
throw new Error('Unexpected end of buffer')
return readBin(value)
case 0xc5:
// bin 16
value = dataView.getUint16(position);
position += 2;
return readBin(value)
case 0xc6:
// bin 32
value = dataView.getUint32(position);
position += 4;
return readBin(value)
case 0xc7:
// ext 8
return readExt(src[position++])
case 0xc8:
// ext 16
value = dataView.getUint16(position);
position += 2;
return readExt(value)
case 0xc9:
// ext 32
value = dataView.getUint32(position);
position += 4;
return readExt(value)
case 0xca:
value = dataView.getFloat32(position);
if (currentUnpackr.useFloat32 > 2) {
// this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
let multiplier = mult10[((src[position] & 0x7f) << 1) | (src[position + 1] >> 7)];
position += 4;
return ((multiplier * value + (value > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
position += 4;
return value
case 0xcb:
value = dataView.getFloat64(position);
position += 8;
return value
// uint handlers
case 0xcc:
return src[position++]
case 0xcd:
value = dataView.getUint16(position);
position += 2;
return value
case 0xce:
value = dataView.getUint32(position);
position += 4;
return value
case 0xcf:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getUint32(position) * 0x100000000;
value += dataView.getUint32(position + 4);
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigUint64(position).toString();
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigUint64(position);
if (value<=BigInt(2)<<BigInt(52)) value=Number(value);
} else
value = dataView.getBigUint64(position);
position += 8;
return value
// int handlers
case 0xd0:
return dataView.getInt8(position++)
case 0xd1:
value = dataView.getInt16(position);
position += 2;
return value
case 0xd2:
value = dataView.getInt32(position);
position += 4;
return value
case 0xd3:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getInt32(position) * 0x100000000;
value += dataView.getUint32(position + 4);
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigInt64(position).toString();
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigInt64(position);
if (value>=BigInt(-2)<<BigInt(52)&&value<=BigInt(2)<<BigInt(52)) value=Number(value);
} else
value = dataView.getBigInt64(position);
position += 8;
return value
case 0xd4:
// fixext 1
value = src[position++];
if (value == 0x72) {
return recordDefinition(src[position++] & 0x3f)
} else {
let extension = currentExtensions[value];
if (extension) {
if (extension.read) {
position++; // skip filler byte
return extension.read(read())
} else if (extension.noBuffer) {
position++; // skip filler byte
return extension()
} else
return extension(src.subarray(position, ++position))
} else
throw new Error('Unknown extension ' + value)
}
case 0xd5:
// fixext 2
value = src[position];
if (value == 0x72) {
position++;
return recordDefinition(src[position++] & 0x3f, src[position++])
} else
return readExt(2)
case 0xd6:
// fixext 4
return readExt(4)
case 0xd7:
// fixext 8
return readExt(8)
case 0xd8:
// fixext 16
return readExt(16)
case 0xd9:
// str 8
value = src[position++];
if (srcStringEnd >= position) {
return srcString.slice(position - srcStringStart, (position += value) - srcStringStart)
}
return readString8(value)
case 0xda:
// str 16
value = dataView.getUint16(position);
position += 2;
if (srcStringEnd >= position) {
return srcString.slice(position - srcStringStart, (position += value) - srcStringStart)
}
return readString16(value)
case 0xdb:
// str 32
value = dataView.getUint32(position);
position += 4;
if (srcStringEnd >= position) {
return srcString.slice(position - srcStringStart, (position += value) - srcStringStart)
}
return readString32(value)
case 0xdc:
// array 16
value = dataView.getUint16(position);
position += 2;
return readArray(value)
case 0xdd:
// array 32
value = dataView.getUint32(position);
position += 4;
return readArray(value)
case 0xde:
// map 16
value = dataView.getUint16(position);
position += 2;
return readMap(value)
case 0xdf:
// map 32
value = dataView.getUint32(position);
position += 4;
return readMap(value)
default: // negative int
if (token >= 0xe0)
return token - 0x100
if (token === undefined) {
let error = new Error('Unexpected end of MessagePack data');
error.incomplete = true;
throw error
}
throw new Error('Unknown MessagePack token ' + token)
}
}
}
const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/;
function createStructureReader(structure, firstId) {
function readObject() {
// This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function
if (readObject.count++ > inlineObjectReadThreshold) {
let optimizedReadObject;
try {
optimizedReadObject = structure.read = (new BlockedFunction ('r', 'return function(){return ' + (currentUnpackr.freezeData ? 'Object.freeze' : '') +
'({' + structure.map(key => key === '__proto__' ? '__proto_:r()' : validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '})}'))(read);
} catch(error) {
// in CF workers, the new BlockedFunction call could begin to fail at any point in time
inlineObjectReadThreshold = Infinity; // disable going forward
return readObject(); // recursively try again
}
structure.read0 = optimizedReadObject; // keep the un-wrapped body reader in sync
if (structure.highByte === 0)
structure.read = createSecondByteReader(firstId, structure.read);
return optimizedReadObject() // second byte is already read, if there is one so immediately read object
}
let object = {};
for (let i = 0, l = structure.length; i < l; i++) {
let key = structure[i];
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(object);
return object
}
readObject.count = 0;
// read0 is the un-wrapped body reader: it reads the record's values directly without
// consuming a leading high byte. recordDefinition uses it for the immediate read that follows
// a record definition (the high byte, if present, was already consumed). For highByte === 0
// structures the public reader is a second-byte reader (used by later references), but the
// definition read itself must not consume that byte.
structure.read0 = readObject;
if (structure.highByte === 0) {
return createSecondByteReader(firstId, readObject)
}
return readObject
}
const createSecondByteReader = (firstId, read0) => {
return function() {
let highByte = src[position++];
if (highByte === 0)
return read0()
let id = firstId < 32 ? -(firstId + (highByte << 5)) : firstId + (highByte << 5);
let structure = currentStructures[id] || loadStructures()[id];
if (!structure) {
throw new Error('Record id is not defined for ' + id)
}
if (!structure.read)
structure.read = createStructureReader(structure, firstId);
return structure.read()
}
};
function loadStructures() {
let loadedStructures = saveState(() => {
// save the state in case getStructures modifies our buffer
src = null;
return currentUnpackr.getStructures()
});
return currentStructures = currentUnpackr._mergeStructures(loadedStructures, currentStructures)
}
var readFixedString = readStringJS;
var readString8 = readStringJS;
var readString16 = readStringJS;
var readString32 = readStringJS;
exports.isNativeAccelerationEnabled = false;
function setExtractor(extractStrings) {
exports.isNativeAccelerationEnabled = true;
readFixedString = readString(1);
readString8 = readString(2);
readString16 = readString(3);
readString32 = readString(5);
function readString(headerLength) {
return function readString(length) {
let string = strings[stringPosition++];
if (string == null) {
if (bundledStrings)
return readStringJS(length)
let byteOffset = src.byteOffset;
let extraction = extractStrings(position - headerLength + byteOffset, srcEnd + byteOffset, src.buffer);
if (typeof extraction == 'string') {
string = extraction;
strings = EMPTY_ARRAY;
} else {
strings = extraction;
stringPosition = 1;
srcStringEnd = 1; // even if a utf-8 string was decoded, must indicate we are in the midst of extracted strings and can't skip strings
string = strings[0];
if (string === undefined)
throw new Error('Unexpected end of buffer')
}
}
let srcStringLength = string.length;
if (srcStringLength <= length) {
position += length;
return string
}
srcString = string;
srcStringStart = position;
srcStringEnd = position + srcStringLength;
position += length;
return string.slice(0, length) // we know we just want the beginning
}
}
}
function readStringJS(length) {
let result;
if (length < 16) {
if (result = shortStringInJS(length))
return result
}
if (length > 64 && decoder)
return decoder.decode(src.subarray(position, position += length))
const end = position + length;
const units = [];
result = '';
while (position < end) {
const byte1 = src[position++];
if ((byte1 & 0x80) === 0) {
// 1 byte
units.push(byte1);
} else if ((byte1 & 0xe0) === 0xc0) {
// 2 bytes
const byte2 = src[position++] & 0x3f;
const codePoint = ((byte1 & 0x1f) << 6) | byte2;
// Reject overlong encoding: 2-byte sequences must encode values >= 0x80
if (codePoint < 0x80) {
units.push(0xFFFD); // replacement character
} else {
units.push(codePoint);
}
} else if ((byte1 & 0xf0) === 0xe0) {
// 3 bytes
const byte2 = src[position++] & 0x3f;
const byte3 = src[position++] & 0x3f;
const codePoint = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3;
// Reject overlong encoding: 3-byte sequences must encode values >= 0x800
// Also reject surrogates (0xD800-0xDFFF)
if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
units.push(0xFFFD); // replacement character
} else {
units.push(codePoint);
}
} else if ((byte1 & 0xf8) === 0xf0) {
// 4 bytes
const byte2 = src[position++] & 0x3f;
const byte3 = src[position++] & 0x3f;
const byte4 = src[position++] & 0x3f;
let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
// Reject overlong encoding: 4-byte sequences must encode values >= 0x10000
// Also reject values > 0x10FFFF (maximum valid Unicode)
if (unit < 0x10000 || unit > 0x10FFFF) {
units.push(0xFFFD); // replacement character
} else if (unit > 0xffff) {
unit -= 0x10000;
units.push(((unit >>> 10) & 0x3ff) | 0xd800);
unit = 0xdc00 | (unit & 0x3ff);
units.push(unit);
} else {
units.push(unit);
}
} else {
units.push(0xFFFD); // replacement character for invalid lead byte
}
if (units.length >= 0x1000) {
result += fromCharCode.apply(String, units);
units.length = 0;
}
}
if (units.length > 0) {
result += fromCharCode.apply(String, units);
}
return result
}
function readString(source, start, length) {
let existingSrc = src;
src = source;
position = start;
try {
return readStringJS(length);
} finally {
src = existingSrc;
}
}
function readArray(length) {
let array = new Array(length);
for (let i = 0; i < length; i++) {
array[i] = read();
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
function readMap(length) {
if (currentUnpackr.mapsAsObjects) {
let object = {};
for (let i = 0; i < length; i++) {
let key = readKey();
if (key === '__proto__')
key = '__proto_';
object[key] = read();
}
return object
} else {
let map = new Map();
for (let i = 0; i < length; i++) {
map.set(read(), read());
}
return map
}
}
var fromCharCode = String.fromCharCode;
function longStringInJS(length) {
let start = position;
let bytes = new Array(length);
for (let i = 0; i < length; i++) {
const byte = src[position++];
if ((byte & 0x80) > 0) {
position = start;
return
}
bytes[i] = byte;
}
return fromCharCode.apply(String, bytes)
}
function shortStringInJS(length) {
if (length < 4) {
if (length < 2) {
if (length === 0)
return ''
else {
let a = src[position++];
if ((a & 0x80) > 1) {
position -= 1;
return
}
return fromCharCode(a)
}
} else {
let a = src[position++];
let b = src[position++];
if ((a & 0x80) > 0 || (b & 0x80) > 0) {
position -= 2;
return
}
if (length < 3)
return fromCharCode(a, b)
let c = src[position++];
if ((c & 0x80) > 0) {
position -= 3;
return
}
return fromCharCode(a, b, c)
}
} else {
let a = src[position++];
let b = src[position++];
let c = src[position++];
let d = src[position++];
if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
position -= 4;
return
}
if (length < 6) {
if (length === 4)
return fromCharCode(a, b, c, d)
else {
let e = src[position++];
if ((e & 0x80) > 0) {
position -= 5;
return
}
return fromCharCode(a, b, c, d, e)
}
} else if (length < 8) {
let e = src[position++];
let f = src[position++];
if ((e & 0x80) > 0 || (f & 0x80) > 0) {
position -= 6;
return
}
if (length < 7)
return fromCharCode(a, b, c, d, e, f)
let g = src[position++];
if ((g & 0x80) > 0) {
position -= 7;
return
}
return fromCharCode(a, b, c, d, e, f, g)
} else {
let e = src[position++];
let f = src[position++];
let g = src[position++];
let h = src[position++];
if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) {
position -= 8;
return
}
if (length < 10) {
if (length === 8)
return fromCharCode(a, b, c, d, e, f, g, h)
else {
let i = src[position++];
if ((i & 0x80) > 0) {
position -= 9;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i)
}
} else if (length < 12) {
let i = src[position++];
let j = src[position++];
if ((i & 0x80) > 0 || (j & 0x80) > 0) {
position -= 10;
return
}
if (length < 11)
return fromCharCode(a, b, c, d, e, f, g, h, i, j)
let k = src[position++];
if ((k & 0x80) > 0) {
position -= 11;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k)
} else {
let i = src[position++];
let j = src[position++];
let k = src[position++];
let l = src[position++];
if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) {
position -= 12;
return
}
if (length < 14) {
if (length === 12)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l)
else {
let m = src[position++];
if ((m & 0x80) > 0) {
position -= 13;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m)
}
} else {
let m = src[position++];
let n = src[position++];
if ((m & 0x80) > 0 || (n & 0x80) > 0) {
position -= 14;
return
}
if (length < 15)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n)
let o = src[position++];
if ((o & 0x80) > 0) {
position -= 15;
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
}
}
}
}
}
function readOnlyJSString() {
let token = src[position++];
let length;
if (token < 0xc0) {
// fixstr
length = token - 0xa0;
} else {
switch(token) {
case 0xd9:
// str 8
length = src[position++];
break
case 0xda:
// str 16
length = dataView.getUint16(position);
position += 2;
break
case 0xdb:
// str 32
length = dataView.getUint32(position);
position += 4;
break
default:
throw new Error('Expected string')
}
}
return readStringJS(length)
}
function readBin(length) {
return currentUnpackr.copyBuffers ?
// specifically use the copying slice (not the node one)
Uint8Array.prototype.slice.call(src, position, position += length) :
src.subarray(position, position += length)
}
function readExt(length) {
let type = src[position++];
if (currentExtensions[type]) {
let end;
return currentExtensions[type](src.subarray(position, end = (position += length)), (readPosition) => {
position = readPosition;
try {
return read();
} finally {
position = end;
}
})
}
else
throw new Error('Unknown extension type ' + type)
}
var keyCache = new Array(4096);
function readKey() {
let length = src[position++];
if (length >= 0xa0 && length < 0xc0) {
// fixstr, potentially use key cache
length = length - 0xa0;
if (srcStringEnd >= position) // if it has been extracted, must use it (and faster anyway)
return srcString.slice(position - srcStringStart, (position += length) - srcStringStart)
else if (!(srcStringEnd == 0 && srcEnd < 180))
return readFixedString(length)
} else { // not cacheable, go back and do a standard read
position--;
return asSafeString(read())
}
let key = ((length << 5) ^ (length > 1 ? dataView.getUint16(position) : length > 0 ? src[position] : 0)) & 0xfff;
let entry = keyCache[key];
let checkPosition = position;
let end = position + length - 3;
let chunk;
let i = 0;
if (entry && entry.bytes == length) {
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition);
if (chunk != entry[i++]) {
checkPosition = 0x70000000;
break
}
checkPosition += 4;
}
end += 3;
while (checkPosition < end) {
chunk = src[checkPosition++];
if (chunk != entry[i++]) {
checkPosition = 0x70000000;
break
}
}
if (checkPosition === end) {
position = checkPosition;
return entry.string
}
end -= 3;
checkPosition = position;
}
entry = [];
keyCache[key] = entry;
entry.bytes = length;
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition);
entry.push(chunk);
checkPosition += 4;
}
end += 3;
while (checkPosition < end) {
chunk = src[checkPosition++];
entry.push(chunk);
}
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length);
if (string != null)
return entry.string = string
return entry.string = readFixedString(length)
}
function asSafeString(property) {
// protect against expensive (DoS) string conversions
if (typeof property === 'string') return property;
if (typeof property === 'number' || typeof property === 'boolean' || typeof property === 'bigint') return property.toString();
if (property == null) return property + '';
if (currentUnpackr.allowArraysInMapKeys && Array.isArray(property) && property.flat().every(item => ['string', 'number', 'boolean', 'bigint'].includes(typeof item))) {
return property.flat().toString();
}
throw new Error(`Invalid property type for record: ${typeof property}`);
}
// the registration of the record definition extension (as "r")
const recordDefinition = (id, highByte) => {
let structure = read().map(asSafeString); // ensure that all keys are strings and
// that the array is mutable
let firstByte = id;
if (highByte !== undefined) {
id = id < 32 ? -((highByte << 5) + id) : ((highByte << 5) + id);
structure.highByte = highByte;
}
let existingStructure = currentStructures[id];
// If it is a shared structure, we need to restore any changes after reading.
// Also in sequential mode, we may get incomplete reads and thus errors, and we need to restore
// to the state prior to an incomplete read in order to properly resume.
if (existingStructure && (existingStructure.isShared || sequentialMode)) {
(currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure;
}
currentStructures[id] = structure;
structure.read = createStructureReader(structure, firstByte);
// The high byte (if any) was already consumed as the `highByte` argument above, so read the
// record body directly. Going through structure.read (a second-byte reader when highByte === 0)
// would misinterpret the first value byte as a high byte — corrupting two-byte own-record
// definitions (0xd5 0x72 ...). createStructureReader stashes the un-wrapped body reader on
// structure.read0 precisely for this immediate post-definition read.
return (structure.read0 || structure.read)()
};
currentExtensions[0] = () => {}; // notepack defines extension 0 to mean undefined, so use that as the default here
currentExtensions[0].noBuffer = true;
currentExtensions[0x42] = data => {
let headLength = (data.byteLength % 8) || 8;
let head = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0]);
for (let i = 1; i < headLength; i++) {
head <<= BigInt(8);
head += BigInt(data[i]);
}
if (data.byteLength !== headLength) {
let view = new DataView(data.buffer, data.byteOffset, data.byteLength);
let decode = (start, end) => {
let length = end - start;
if (length <= 40) {
let out = view.getBigUint64(start);
for (let i = start + 8; i < end; i += 8) {
out <<= BigInt(64);
out |= view.getBigUint64(i);
}
return out
}
// if (length === 8) return view.getBigUint64(start)
let middle = start + (length >> 4 << 3);
let left = decode(start, middle);
let right = decode(middle, end);
return (left << BigInt((end - middle) * 8)) | right
};
head = (head << BigInt((view.byteLength - headLength) * 8)) | decode(headLength, view.byteLength);
}
return head
};
let errors = {
Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError: typeof AggregateError === 'function' ? AggregateError : null,
};
currentExtensions[0x65] = () => {
let data = read();
if (!errors[data[0]]) {
let error = Error(data[1], { cause: data[2] });
error.name = data[0];
return error
}
return errors[data[0]](data[1], { cause: data[2] })
};
currentExtensions[0x69] = (data) => {
// id extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position - 4);
if (!referenceMap)
referenceMap = new Map();
let token = src[position];
let target;
// TODO: handle any other types that can cycle and make the code more robust if there are other extensions
if (token >= 0x90 && token < 0xa0 || token == 0xdc || token == 0xdd)
target = [];
else if (token >= 0x80 && token < 0x90 || token == 0xde || token == 0xdf)
target = new Map();
else if ((token >= 0xc7 && token <= 0xc9 || token >= 0xd4 && token <= 0xd8) && src[position + 1] === 0x73)
target = new Set();
else
target = {};
let refEntry = { target }; // a placeholder object
referenceMap.set(id, refEntry);
let targetProperties = read(); // read the next value as the target object to id
if (!refEntry.used) {
// no cycle, can just use the returned read object
return refEntry.target = targetProperties // replace the placeholder with the real one
} else {
// there is a cycle, so we have to assign properties to original target
Object.assign(target, targetProperties);
}
// copy over map/set entries if we're able to
if (target instanceof Map)
for (let [k, v] of targetProperties.entries()) target.set(k, v);
if (target instanceof Set)
for (let i of Array.from(targetProperties)) target.add(i);
return target
};
currentExtensions[0x70] = (data) => {
// pointer extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position - 4);
let refEntry = referenceMap.get(id);
refEntry.used = true;
return refEntry.target
};
currentExtensions[0x73] = () => new Set(read());
const typedArrays = ['Int8','Uint8','Uint8Clamped','Int16','Uint16','Int32','Uint32','Float32','Float64','BigInt64','BigUint64'].map(type => type + 'Array');
let glbl = typeof globalThis === 'object' ? globalThis : window;
currentExtensions[0x74] = (data) => {
let typeCode = data[0];
// we always have to slice to get a new ArrayBuffer that is aligned
let buffer = Uint8Array.prototype.slice.call(data, 1).buffer;
let typedArrayName = typedArrays[typeCode];
if (!typedArrayName) {
if (typeCode === 16) return buffer
if (typeCode === 17) return new DataView(buffer)
throw new Error('Could not find typed array for code ' + typeCode)
}
return new glbl[typedArrayName](buffer)
};
currentExtensions[0x78] = () => {
let data = read();
return new RegExp(data[0], data[1])
};
const TEMP_BUNDLE = [];
currentExtensions[0x62] = (data) => {
let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3];
let dataPosition = position;
position += dataSize - data.length;
bundledStrings = TEMP_BUNDLE;
bundledStrings = [readOnlyJSString(), readOnlyJSString()];
bundledStrings.position0 = 0;
bundledStrings.position1 = 0;
bundledStrings.postBundlePosition = position;
position = dataPosition;
return read()
};
currentExtensions[0xff] = (data) => {
// 32-bit date extension
if (data.length == 4)
return new Date((data[0] * 0x1000000 + (data[1] << 16) + (data[2] << 8) + data[3]) * 1000)
else if (data.length == 8)
return new Date(
((data[0] << 22) + (data[1] << 14) + (data[2] << 6) + (data[3] >> 2)) / 1000000 +
((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000)
else if (data.length == 12)
return new Date(
((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
(((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000)
else
return new Date('invalid')
};
// registration of bulk record definition?
// currentExtensions[0x52] = () =>
function saveState(callback) {
if (onSaveState)
onSaveState();
let savedSrcEnd = srcEnd;
let savedPosition = position;
let savedStringPosition = stringPosition;
let savedSrcStringStart = srcStringStart;
let savedSrcStringEnd = srcStringEnd;
let savedSrcString = srcString;
let savedStrings = strings;
let savedReferenceMap = referenceMap;
let savedBundledStrings = bundledStrings;
// TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
let savedStructures = currentStructures;
let savedStructuresContents = currentStructures.slice(0, currentStructures.length);
let savedPackr = currentUnpackr;
let savedSequentialMode = sequentialMode;
let value = callback();
srcEnd = savedSrcEnd;
position = savedPosition;
stringPosition = savedStringPosition;
srcStringStart = savedSrcStringStart;
srcStringEnd = savedSrcStringEnd;
srcString = savedSrcString;
strings = savedStrings;
referenceMap = savedReferenceMap;
bundledStrings = savedBundledStrings;
src = savedSrc;
sequentialMode = savedSequentialMode;
currentStructures = savedStructures;
currentStructures.splice(0, currentStructures.length, ...savedStructuresContents);
currentUnpackr = savedPackr;
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
return value
}
function clearSource() {
src = null;
referenceMap = null;
currentStructures = null;
}
function addExtension(extension) {
if (extension.unpack)
currentExtensions[extension.type] = extension.unpack;
else
currentExtensions[extension.type] = extension;
}
const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding
for (let i = 0; i < 256; i++) {
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103));
}
const Decoder = Unpackr;
var defaultUnpackr = new Unpackr({ useRecords: false });
const unpack = defaultUnpackr.unpack;
const unpackMultiple = defaultUnpackr.unpackMultiple;
const decode = defaultUnpackr.unpack;
const FLOAT32_OPTIONS = {
NEVER: 0,
ALWAYS: 1,
DECIMAL_ROUND: 3,
DECIMAL_FIT: 4
};
let f32Array = new Float32Array(1);
let u8Array = new Uint8Array(f32Array.buffer, 0, 4);
function roundFloat32(float32Number) {
f32Array[0] = float32Number;
let multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)];
return ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
function setReadStruct(updatedReadStruct, loadedStructs, saveState) {
readStruct = updatedReadStruct;
onLoadedStructures = loadedStructs;
onSaveState = saveState;
}
exports.C1 = C1;
exports.C1Type = C1Type;
exports.Decoder = Decoder;
exports.FLOAT32_OPTIONS = FLOAT32_OPTIONS;
exports.Unpackr = Unpackr;
exports.addExtension = addExtension;
exports.checkedRead = checkedRead;
exports.clearSource = clearSource;
exports.decode = decode;
exports.getPosition = getPosition;
exports.loadStructures = loadStructures;
exports.mult10 = mult10;
exports.read = read;
exports.readString = readString;
exports.roundFloat32 = roundFloat32;
exports.setExtractor = setExtractor;
exports.setReadStruct = setReadStruct;
exports.typedArrays = typedArrays;
exports.unpack = unpack;
exports.unpackMultiple = unpackMultiple;
}));
//# sourceMappingURL=unpack-no-eval.cjs.map
File diff suppressed because one or more lines are too long
+93
View File
@@ -0,0 +1,93 @@
export enum FLOAT32_OPTIONS {
NEVER = 0,
ALWAYS = 1,
DECIMAL_ROUND = 3,
DECIMAL_FIT = 4
}
export interface Options {
useFloat32?: FLOAT32_OPTIONS
useRecords?: boolean | ((value:any)=> boolean)
structures?: {}[]
moreTypes?: boolean
sequential?: boolean
structuredClone?: boolean
mapsAsObjects?: boolean
variableMapSize?: boolean
coercibleKeyAsNumber?: boolean
copyBuffers?: boolean
bundleStrings?: boolean
useTimestamp32?: boolean
largeBigIntToFloat?: boolean
largeBigIntToString?: boolean
useBigIntExtension?: boolean
encodeUndefinedAsNil?: boolean
maxSharedStructures?: number
maxOwnStructures?: number
mapAsEmptyObject?: boolean
setAsEmptyObject?: boolean
allowArraysInMapKeys?: boolean
writeFunction?: () => any
/** @deprecated use int64AsType: 'number' */
int64AsNumber?: boolean
int64AsType?: 'bigint' | 'number' | 'string'
shouldShareStructure?: (keys: string[]) => boolean
getStructures?(): {}[]
saveStructures?(structures: {}[]): boolean | void
onInvalidDate?: () => any
}
interface Extension {
Class?: Function
type?: number
pack?(value: any): Buffer | Uint8Array
unpack?(messagePack: Buffer | Uint8Array): any
read?(datum: any): any
write?(instance: any): any
}
export type UnpackOptions = { start?: number; end?: number; lazy?: boolean; } | number;
export class Unpackr {
constructor(options?: Options)
unpack(messagePack: Buffer | Uint8Array, options?: UnpackOptions): any
decode(messagePack: Buffer | Uint8Array, options?: UnpackOptions): any
unpackMultiple(messagePack: Buffer | Uint8Array): any[]
unpackMultiple(messagePack: Buffer | Uint8Array, forEach: (value: any, start?: number, end?: number) => any): void
}
export class Decoder extends Unpackr {}
export function unpack(messagePack: Buffer | Uint8Array, options?: UnpackOptions): any
export function unpackMultiple(messagePack: Buffer | Uint8Array): any[]
export function unpackMultiple(messagePack: Buffer | Uint8Array, forEach: (value: any, start?: number, end?: number) => any): void
export function decode(messagePack: Buffer | Uint8Array, options?: UnpackOptions): any
export function addExtension(extension: Extension): void
export function clearSource(): void
export function roundFloat32(float32Number: number): number
export function decodeIter(bufferIterator: Iterable<Buffer | Uint8Array> | Iterator<Buffer | Uint8Array> | AsyncIterable<Buffer | Uint8Array> | AsyncIterator<Buffer | Uint8Array>, options?: Options): AsyncGenerator<any>
export function encodeIter(objectIterator: Iterable<Buffer | Uint8Array> | Iterator<Buffer | Uint8Array> | AsyncIterable<Buffer | Uint8Array> | AsyncIterator<Buffer | Uint8Array>, options?: Options): IterableIterator<Buffer> | Promise<AsyncIterableIterator<Buffer>>
export const C1: {}
export let isNativeAccelerationEnabled: boolean
export class Packr extends Unpackr {
offset: number;
position: number;
pack(value: any, encodeOptions?: number): Buffer
encode(value: any, encodeOptions?: number): Buffer
useBuffer(buffer: Buffer | Uint8Array): void;
clearSharedData(): void;
}
export class Encoder extends Packr {}
export function pack(value: any, encodeOptions?: number): Buffer
export function encode(value: any, encodeOptions?: number): Buffer
export const REUSE_BUFFER_MODE: number;
export const RESET_BUFFER_MODE: number;
export const RESERVE_START_SPACE: number;
import { Transform, Readable } from 'stream'
export as namespace msgpackr;
export class UnpackrStream extends Transform {
constructor(options?: Options | { highWaterMark: number, emitClose: boolean, allowHalfOpen: boolean })
}
export class PackrStream extends Transform {
constructor(options?: Options | { highWaterMark: number, emitClose: boolean, allowHalfOpen: boolean })
}
export { PackrStream as EncoderStream, UnpackrStream as DecoderStream };
+93
View File
@@ -0,0 +1,93 @@
export enum FLOAT32_OPTIONS {
NEVER = 0,
ALWAYS = 1,
DECIMAL_ROUND = 3,
DECIMAL_FIT = 4
}
export interface Options {
useFloat32?: FLOAT32_OPTIONS
useRecords?: boolean | ((value:any)=> boolean)
structures?: {}[]
moreTypes?: boolean
sequential?: boolean
structuredClone?: boolean
mapsAsObjects?: boolean
variableMapSize?: boolean
coercibleKeyAsNumber?: boolean
copyBuffers?: boolean
bundleStrings?: boolean
useTimestamp32?: boolean
largeBigIntToFloat?: boolean
largeBigIntToString?: boolean
useBigIntExtension?: boolean
encodeUndefinedAsNil?: boolean
maxSharedStructures?: number
maxOwnStructures?: number
mapAsEmptyObject?: boolean
setAsEmptyObject?: boolean
allowArraysInMapKeys?: boolean
writeFunction?: () => any
/** @deprecated use int64AsType: 'number' */
int64AsNumber?: boolean
int64AsType?: 'bigint' | 'number' | 'string'
shouldShareStructure?: (keys: string[]) => boolean
getStructures?(): {}[]
saveStructures?(structures: {}[]): boolean | void
onInvalidDate?: () => any
}
interface Extension {
Class?: Function
type?: number
pack?(value: any): Buffer | Uint8Array
unpack?(messagePack: Buffer | Uint8Array): any
read?(datum: any): any
write?(instance: any): any
}
export type UnpackOptions = { start?: number; end?: number; lazy?: boolean; } | number;
export class Unpackr {
constructor(options?: Options)
unpack(messagePack: Buffer | Uint8Array, options?: UnpackOptions): any
decode(messagePack: Buffer | Uint8Array, options?: UnpackOptions): any
unpackMultiple(messagePack: Buffer | Uint8Array): any[]
unpackMultiple(messagePack: Buffer | Uint8Array, forEach: (value: any, start?: number, end?: number) => any): void
}
export class Decoder extends Unpackr {}
export function unpack(messagePack: Buffer | Uint8Array, options?: UnpackOptions): any
export function unpackMultiple(messagePack: Buffer | Uint8Array): any[]
export function unpackMultiple(messagePack: Buffer | Uint8Array, forEach: (value: any, start?: number, end?: number) => any): void
export function decode(messagePack: Buffer | Uint8Array, options?: UnpackOptions): any
export function addExtension(extension: Extension): void
export function clearSource(): void
export function roundFloat32(float32Number: number): number
export function decodeIter(bufferIterator: Iterable<Buffer | Uint8Array> | Iterator<Buffer | Uint8Array> | AsyncIterable<Buffer | Uint8Array> | AsyncIterator<Buffer | Uint8Array>, options?: Options): AsyncGenerator<any>
export function encodeIter(objectIterator: Iterable<Buffer | Uint8Array> | Iterator<Buffer | Uint8Array> | AsyncIterable<Buffer | Uint8Array> | AsyncIterator<Buffer | Uint8Array>, options?: Options): IterableIterator<Buffer> | Promise<AsyncIterableIterator<Buffer>>
export const C1: {}
export let isNativeAccelerationEnabled: boolean
export class Packr extends Unpackr {
offset: number;
position: number;
pack(value: any, encodeOptions?: number): Buffer
encode(value: any, encodeOptions?: number): Buffer
useBuffer(buffer: Buffer | Uint8Array): void;
clearSharedData(): void;
}
export class Encoder extends Packr {}
export function pack(value: any, encodeOptions?: number): Buffer
export function encode(value: any, encodeOptions?: number): Buffer
export const REUSE_BUFFER_MODE: number;
export const RESET_BUFFER_MODE: number;
export const RESERVE_START_SPACE: number;
import { Transform, Readable } from 'stream'
export as namespace msgpackr;
export class UnpackrStream extends Transform {
constructor(options?: Options | { highWaterMark: number, emitClose: boolean, allowHalfOpen: boolean })
}
export class PackrStream extends Transform {
constructor(options?: Options | { highWaterMark: number, emitClose: boolean, allowHalfOpen: boolean })
}
export { PackrStream as EncoderStream, UnpackrStream as DecoderStream };
+5
View File
@@ -0,0 +1,5 @@
export { Packr, Encoder, addExtension, pack, encode, NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT, REUSE_BUFFER_MODE, RESET_BUFFER_MODE, RESERVE_START_SPACE } from './pack.js'
export { Unpackr, Decoder, C1, unpack, unpackMultiple, decode, FLOAT32_OPTIONS, clearSource, roundFloat32, isNativeAccelerationEnabled } from './unpack.js'
export { decodeIter, encodeIter } from './iterators.js'
export const useRecords = false
export const mapsAsObjects = true
+87
View File
@@ -0,0 +1,87 @@
import { Packr } from './pack.js'
import { Unpackr } from './unpack.js'
/**
* Given an Iterable first argument, returns an Iterable where each value is packed as a Buffer
* If the argument is only Async Iterable, the return value will be an Async Iterable.
* @param {Iterable|Iterator|AsyncIterable|AsyncIterator} objectIterator - iterable source, like a Readable object stream, an array, Set, or custom object
* @param {options} [options] - msgpackr pack options
* @returns {IterableIterator|Promise.<AsyncIterableIterator>}
*/
export function packIter (objectIterator, options = {}) {
if (!objectIterator || typeof objectIterator !== 'object') {
throw new Error('first argument must be an Iterable, Async Iterable, or a Promise for an Async Iterable')
} else if (typeof objectIterator[Symbol.iterator] === 'function') {
return packIterSync(objectIterator, options)
} else if (typeof objectIterator.then === 'function' || typeof objectIterator[Symbol.asyncIterator] === 'function') {
return packIterAsync(objectIterator, options)
} else {
throw new Error('first argument must be an Iterable, Async Iterable, Iterator, Async Iterator, or a Promise')
}
}
function * packIterSync (objectIterator, options) {
const packr = new Packr(options)
for (const value of objectIterator) {
yield packr.pack(value)
}
}
async function * packIterAsync (objectIterator, options) {
const packr = new Packr(options)
for await (const value of objectIterator) {
yield packr.pack(value)
}
}
/**
* Given an Iterable/Iterator input which yields buffers, returns an IterableIterator which yields sync decoded objects
* Or, given an Async Iterable/Iterator which yields promises resolving in buffers, returns an AsyncIterableIterator.
* @param {Iterable|Iterator|AsyncIterable|AsyncIterableIterator} bufferIterator
* @param {object} [options] - unpackr options
* @returns {IterableIterator|Promise.<AsyncIterableIterator}
*/
export function unpackIter (bufferIterator, options = {}) {
if (!bufferIterator || typeof bufferIterator !== 'object') {
throw new Error('first argument must be an Iterable, Async Iterable, Iterator, Async Iterator, or a promise')
}
const unpackr = new Unpackr(options)
let incomplete
const parser = (chunk) => {
let yields
// if there's incomplete data from previous chunk, concatinate and try again
if (incomplete) {
chunk = Buffer.concat([incomplete, chunk])
incomplete = undefined
}
try {
yields = unpackr.unpackMultiple(chunk)
} catch (err) {
if (err.incomplete) {
incomplete = chunk.slice(err.lastPosition)
yields = err.values
} else {
throw err
}
}
return yields
}
if (typeof bufferIterator[Symbol.iterator] === 'function') {
return (function * iter () {
for (const value of bufferIterator) {
yield * parser(value)
}
})()
} else if (typeof bufferIterator[Symbol.asyncIterator] === 'function') {
return (async function * iter () {
for await (const value of bufferIterator) {
yield * parser(value)
}
})()
}
}
export const decodeIter = unpackIter
export const encodeIter = packIter
+25
View File
@@ -0,0 +1,25 @@
export { Packr, Encoder, addExtension, pack, encode, NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT, REUSE_BUFFER_MODE, RESET_BUFFER_MODE, RESERVE_START_SPACE } from './pack.js'
export { Unpackr, Decoder, C1, unpack, unpackMultiple, decode, FLOAT32_OPTIONS, clearSource, roundFloat32, isNativeAccelerationEnabled } from './unpack.js'
import './struct.js'
export { PackrStream, UnpackrStream, PackrStream as EncoderStream, UnpackrStream as DecoderStream } from './stream.js'
export { decodeIter, encodeIter } from './iterators.js'
export const useRecords = false
export const mapsAsObjects = true
import { setExtractor } from './unpack.js'
import { createRequire } from 'module'
const nativeAccelerationDisabled = process.env.MSGPACKR_NATIVE_ACCELERATION_DISABLED !== undefined && process.env.MSGPACKR_NATIVE_ACCELERATION_DISABLED.toLowerCase() === 'true';
if (!nativeAccelerationDisabled) {
let extractor
try {
if (typeof require == 'function')
extractor = require('msgpackr-extract')
else
extractor = createRequire(import.meta.url)('msgpackr-extract')
if (extractor)
setExtractor(extractor.extractStrings)
} catch (error) {
// native module is optional
}
}
+1
View File
@@ -0,0 +1 @@
export { Unpackr, Decoder, Packr, Encoder, pack, encode, unpack, decode, addExtension, FLOAT32_OPTIONS, REUSE_BUFFER_MODE, RESET_BUFFER_MODE, RESERVE_START_SPACE } from '.'
+1
View File
@@ -0,0 +1 @@
export { Unpackr, Decoder, Packr, Encoder, pack, encode, unpack, decode, addExtension, FLOAT32_OPTIONS, REUSE_BUFFER_MODE, RESET_BUFFER_MODE, RESERVE_START_SPACE } from '.'
+1154
View File
@@ -0,0 +1,1154 @@
import { Unpackr, mult10, C1Type, typedArrays, addExtension as unpackAddExtension } from './unpack.js'
let textEncoder
try {
textEncoder = new TextEncoder()
} catch (error) {}
let extensions, extensionClasses
const hasNodeBuffer = typeof Buffer !== 'undefined'
const ByteArrayAllocate = hasNodeBuffer ?
function(length) { return Buffer.allocUnsafeSlow(length) } : Uint8Array
const ByteArray = hasNodeBuffer ? Buffer : Uint8Array
const MAX_BUFFER_SIZE = hasNodeBuffer ? 0x100000000 : 0x7fd00000
let target, keysTarget
let targetView
let position = 0
let safeEnd
let bundledStrings = null
let writeStructSlots
const MAX_BUNDLE_SIZE = 0x5500 // maximum characters such that the encoded bytes fits in 16 bits.
const hasNonLatin = /[\u0080-\uFFFF]/
export const RECORD_SYMBOL = Symbol('record-id')
export class Packr extends Unpackr {
constructor(options) {
super(options)
this.offset = 0
let typeBuffer
let start
let hasSharedUpdate
let structures
let referenceMap
let encodeUtf8 = ByteArray.prototype.utf8Write ? function(string, position) {
return target.utf8Write(string, position, target.byteLength - position)
} : (textEncoder && textEncoder.encodeInto) ?
function(string, position) {
return textEncoder.encodeInto(string, target.subarray(position)).written
} : false
let packr = this
if (!options)
options = {}
let isSequential = options && options.sequential
let hasSharedStructures = options.structures || options.saveStructures
let maxSharedStructures = options.maxSharedStructures
if (maxSharedStructures == null)
maxSharedStructures = hasSharedStructures ? 32 : 0
if (maxSharedStructures > 8160)
throw new Error('Maximum maxSharedStructure is 8160')
if (options.structuredClone && options.moreTypes == undefined) {
this.moreTypes = true
}
let maxOwnStructures = options.maxOwnStructures
if (maxOwnStructures == null)
maxOwnStructures = hasSharedStructures ? 32 : 64
if (!this.structures && options.useRecords != false)
this.structures = []
// two byte record ids for shared structures
let useTwoByteRecords = maxSharedStructures > 32 || (maxOwnStructures + maxSharedStructures > 64)
let sharedLimitId = maxSharedStructures + 0x40
let maxStructureId = maxSharedStructures + maxOwnStructures + 0x40
if (maxStructureId > 8256) {
throw new Error('Maximum maxSharedStructure + maxOwnStructure is 8192')
}
let recordIdsToRemove = []
let transitionsCount = 0
let serializationsSinceTransitionRebuild = 0
this.pack = this.encode = function(value, encodeOptions) {
if (!target) {
target = new ByteArrayAllocate(8192)
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, 8192))
position = 0
}
safeEnd = target.length - 10
if (safeEnd - position < 0x800) {
// don't start too close to the end,
target = new ByteArrayAllocate(target.length)
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, target.length))
safeEnd = target.length - 10
position = 0
} else
position = (position + 7) & 0x7ffffff8 // Word align to make any future copying of this buffer faster
start = position
if (encodeOptions & RESERVE_START_SPACE) position += (encodeOptions & 0xff)
referenceMap = packr.structuredClone ? new Map() : null
if (packr.bundleStrings && typeof value !== 'string') {
bundledStrings = []
bundledStrings.size = Infinity // force a new bundle start on first string
} else
bundledStrings = null
structures = packr.structures
if (structures) {
if (structures.uninitialized)
structures = packr._mergeStructures(packr.getStructures())
let sharedLength = structures.sharedLength || 0
if (sharedLength > maxSharedStructures) {
//if (maxSharedStructures <= 32 && structures.sharedLength > 32) // TODO: could support this, but would need to update the limit ids
throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' + structures.sharedLength)
}
if (!structures.transitions) {
// rebuild our structure transitions
structures.transitions = Object.create(null)
for (let i = 0; i < sharedLength; i++) {
let keys = structures[i]
if (!keys)
continue
let nextTransition, transition = structures.transitions
for (let j = 0, l = keys.length; j < l; j++) {
let key = keys[j]
nextTransition = transition[key]
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null)
}
transition = nextTransition
}
transition[RECORD_SYMBOL] = i + 0x40
}
this.lastNamedStructuresLength = sharedLength
}
if (!isSequential) {
structures.nextId = sharedLength + 0x40
}
}
if (hasSharedUpdate)
hasSharedUpdate = false
let encodingError;
try {
// readOnlyStructures: skip the random-access struct write path so NO new struct is
// minted. randomAccessStructure stays true (the struct READ path and the struct-safe
// integer boundary are preserved, so existing struct data still decodes), but objects
// fall through to the normal pack()->writeObject->writeRecord path and are written as
// classic shared-structure records (byte range 0x40-0x7f, disjoint from struct headers
// at 0x20-0x3f) — the bounded, width-agnostic encoding used before struct mode.
if (packr.randomAccessStructure && !packr.readOnlyStructures && value && typeof value === 'object') {
if (value.constructor === Object) writeStruct(value); // simple object
else if (value.constructor !== Map && !Array.isArray(value) && !extensionClasses.some(extClass => value instanceof extClass)) {
// allow user classes, if they don't need special handling (but do use toJSON if available)
writeStruct(value.toJSON ? value.toJSON() : value);
} else pack(value)
} else
pack(value)
let lastBundle = bundledStrings;
if (bundledStrings)
writeBundles(start, pack, 0)
if (referenceMap && referenceMap.idsToInsert) {
let idsToInsert = referenceMap.idsToInsert.sort((a, b) => a.offset > b.offset ? 1 : -1);
let i = idsToInsert.length;
let incrementPosition = -1;
while (lastBundle && i > 0) {
let insertionPoint = idsToInsert[--i].offset + start;
if (insertionPoint < (lastBundle.stringsPosition + start) && incrementPosition === -1)
incrementPosition = 0;
if (insertionPoint > (lastBundle.position + start)) {
if (incrementPosition >= 0)
incrementPosition += 6;
} else {
if (incrementPosition >= 0) {
// update the bundle reference now
targetView.setUint32(lastBundle.position + start,
targetView.getUint32(lastBundle.position + start) + incrementPosition)
incrementPosition = -1; // reset
}
lastBundle = lastBundle.previous;
i++;
}
}
if (incrementPosition >= 0 && lastBundle) {
// update the bundle reference now
targetView.setUint32(lastBundle.position + start,
targetView.getUint32(lastBundle.position + start) + incrementPosition)
}
position += idsToInsert.length * 6;
if (position > safeEnd)
makeRoom(position)
packr.offset = position
let serialized = insertIds(target.subarray(start, position), idsToInsert)
referenceMap = null
return serialized
}
packr.offset = position // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially
if (encodeOptions & REUSE_BUFFER_MODE) {
target.start = start
target.end = position
return target
}
return target.subarray(start, position) // position can change if we call pack again in saveStructures, so we get the buffer now
} catch(error) {
encodingError = error;
throw error;
} finally {
if (structures) {
resetStructures();
if (hasSharedUpdate && packr.saveStructures) {
let sharedLength = structures.sharedLength || 0
// we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save
let returnBuffer = target.subarray(start, position)
let newSharedData = prepareStructures(structures, packr);
if (!encodingError) { // TODO: If there is an encoding error, should make the structures as uninitialized so they get rebuilt next time
if (packr.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
// The save was declined (a concurrent writer updated the shared structures,
// or the store transaction did not durably commit). Our in-memory
// structures + transition trie may now reference record ids that were
// never persisted; re-packing as-is would re-emit the same record pointing
// at an unpersisted structure (-> "Record id is not defined" on decode).
// Mark structures uninitialized so the re-pack reloads durable structures
// via getStructures, rebuilds the transition trie, and re-mints + re-saves.
structures.uninitialized = true
return packr.pack(value, encodeOptions)
}
packr.lastNamedStructuresLength = sharedLength
// don't keep large buffers around
if (target.length > 0x40000000) target = null
return returnBuffer
}
}
}
// don't keep large buffers around, they take too much memory and cause problems (limit at 1GB)
if (target.length > 0x40000000) target = null
if (encodeOptions & RESET_BUFFER_MODE)
position = start
}
}
const resetStructures = () => {
if (serializationsSinceTransitionRebuild < 10)
serializationsSinceTransitionRebuild++
let sharedLength = structures.sharedLength || 0
if (structures.length > sharedLength && !isSequential)
structures.length = sharedLength
if (transitionsCount > 10000) {
// force a rebuild occasionally after a lot of transitions so it can get cleaned up
structures.transitions = null
serializationsSinceTransitionRebuild = 0
transitionsCount = 0
if (recordIdsToRemove.length > 0)
recordIdsToRemove = []
} else if (recordIdsToRemove.length > 0 && !isSequential) {
for (let i = 0, l = recordIdsToRemove.length; i < l; i++) {
recordIdsToRemove[i][RECORD_SYMBOL] = 0
}
recordIdsToRemove = []
}
}
const packArray = (value) => {
var length = value.length
if (length < 0x10) {
target[position++] = 0x90 | length
} else if (length < 0x10000) {
target[position++] = 0xdc
target[position++] = length >> 8
target[position++] = length & 0xff
} else {
target[position++] = 0xdd
targetView.setUint32(position, length)
position += 4
}
for (let i = 0; i < length; i++) {
pack(value[i])
}
}
const pack = (value) => {
if (position > safeEnd)
target = makeRoom(position)
var type = typeof value
var length
if (type === 'string') {
let strLength = value.length
if (bundledStrings && strLength >= 4 && strLength < 0x1000) {
if ((bundledStrings.size += strLength) > MAX_BUNDLE_SIZE) {
let extStart
let maxBytes = (bundledStrings[0] ? bundledStrings[0].length * 3 + bundledStrings[1].length : 0) + 10
if (position + maxBytes > safeEnd)
target = makeRoom(position + maxBytes)
let lastBundle
if (bundledStrings.position) { // here we use the 0x62 extension to write the last bundle and reserve space for the reference pointer to the next/current bundle
lastBundle = bundledStrings
target[position] = 0xc8 // ext 16
position += 3 // reserve for the writing bundle size
target[position++] = 0x62 // 'b'
extStart = position - start
position += 4 // reserve for writing bundle reference
writeBundles(start, pack, 0) // write the last bundles
targetView.setUint16(extStart + start - 3, position - start - extStart)
} else { // here we use the 0x62 extension just to reserve the space for the reference pointer to the bundle (will be updated once the bundle is written)
target[position++] = 0xd6 // fixext 4
target[position++] = 0x62 // 'b'
extStart = position - start
position += 4 // reserve for writing bundle reference
}
bundledStrings = ['', ''] // create new ones
bundledStrings.previous = lastBundle;
bundledStrings.size = 0
bundledStrings.position = extStart
}
let twoByte = hasNonLatin.test(value)
bundledStrings[twoByte ? 0 : 1] += value
target[position++] = 0xc1
pack(twoByte ? -strLength : strLength);
return
}
let headerSize
// first we estimate the header size, so we can write to the correct location
if (strLength < 0x20) {
headerSize = 1
} else if (strLength < 0x100) {
headerSize = 2
} else if (strLength < 0x10000) {
headerSize = 3
} else {
headerSize = 5
}
let maxBytes = strLength * 3
if (position + maxBytes > safeEnd)
target = makeRoom(position + maxBytes)
if (strLength < 0x40 || !encodeUtf8) {
let i, c1, c2, strPosition = position + headerSize
for (i = 0; i < strLength; i++) {
c1 = value.charCodeAt(i)
if (c1 < 0x80) {
target[strPosition++] = c1
} else if (c1 < 0x800) {
target[strPosition++] = c1 >> 6 | 0xc0
target[strPosition++] = c1 & 0x3f | 0x80
} else if (
(c1 & 0xfc00) === 0xd800 &&
((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
) {
c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff)
i++
target[strPosition++] = c1 >> 18 | 0xf0
target[strPosition++] = c1 >> 12 & 0x3f | 0x80
target[strPosition++] = c1 >> 6 & 0x3f | 0x80
target[strPosition++] = c1 & 0x3f | 0x80
} else {
target[strPosition++] = c1 >> 12 | 0xe0
target[strPosition++] = c1 >> 6 & 0x3f | 0x80
target[strPosition++] = c1 & 0x3f | 0x80
}
}
length = strPosition - position - headerSize
} else {
length = encodeUtf8(value, position + headerSize)
}
if (length < 0x20) {
target[position++] = 0xa0 | length
} else if (length < 0x100) {
if (headerSize < 2) {
target.copyWithin(position + 2, position + 1, position + 1 + length)
}
target[position++] = 0xd9
target[position++] = length
} else if (length < 0x10000) {
if (headerSize < 3) {
target.copyWithin(position + 3, position + 2, position + 2 + length)
}
target[position++] = 0xda
target[position++] = length >> 8
target[position++] = length & 0xff
} else {
if (headerSize < 5) {
target.copyWithin(position + 5, position + 3, position + 3 + length)
}
target[position++] = 0xdb
targetView.setUint32(position, length)
position += 4
}
position += length
} else if (type === 'number') {
if (value >>> 0 === value) {// positive integer, 32-bit or less
// positive uint
if (value < 0x20 || (value < 0x80 && this.useRecords === false) || (value < 0x40 && !this.randomAccessStructure)) {
target[position++] = value
} else if (value < 0x100) {
target[position++] = 0xcc
target[position++] = value
} else if (value < 0x10000) {
target[position++] = 0xcd
target[position++] = value >> 8
target[position++] = value & 0xff
} else {
target[position++] = 0xce
targetView.setUint32(position, value)
position += 4
}
} else if (value >> 0 === value) { // negative integer
if (value >= -0x20) {
target[position++] = 0x100 + value
} else if (value >= -0x80) {
target[position++] = 0xd0
target[position++] = value + 0x100
} else if (value >= -0x8000) {
target[position++] = 0xd1
targetView.setInt16(position, value)
position += 2
} else {
target[position++] = 0xd2
targetView.setInt32(position, value)
position += 4
}
} else {
let useFloat32
if ((useFloat32 = this.useFloat32) > 0 && value < 0x100000000 && value >= -0x80000000) {
target[position++] = 0xca
targetView.setFloat32(position, value)
let xShifted
if (useFloat32 < 4 ||
// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
((xShifted = value * mult10[((target[position] & 0x7f) << 1) | (target[position + 1] >> 7)]) >> 0) === xShifted) {
position += 4
return
} else
position-- // move back into position for writing a double
}
target[position++] = 0xcb
targetView.setFloat64(position, value)
position += 8
}
} else if (type === 'object' || type === 'function') {
if (!value)
target[position++] = 0xc0
else {
if (referenceMap) {
let referee = referenceMap.get(value)
if (referee) {
if (!referee.id) {
let idsToInsert = referenceMap.idsToInsert || (referenceMap.idsToInsert = [])
referee.id = idsToInsert.push(referee)
}
target[position++] = 0xd6 // fixext 4
target[position++] = 0x70 // "p" for pointer
targetView.setUint32(position, referee.id)
position += 4
return
} else
referenceMap.set(value, { offset: position - start })
}
let constructor = value.constructor
if (constructor === Object) {
writeObject(value)
} else if (constructor === Array) {
packArray(value)
} else if (constructor === Map) {
if (this.mapAsEmptyObject) target[position++] = 0x80
else {
length = value.size
if (length < 0x10) {
target[position++] = 0x80 | length
} else if (length < 0x10000) {
target[position++] = 0xde
target[position++] = length >> 8
target[position++] = length & 0xff
} else {
target[position++] = 0xdf
targetView.setUint32(position, length)
position += 4
}
for (let [key, entryValue] of value) {
pack(key)
pack(entryValue)
}
}
} else {
for (let i = 0, l = extensions.length; i < l; i++) {
let extensionClass = extensionClasses[i]
if (value instanceof extensionClass) {
let extension = extensions[i]
if (extension.write) {
if (extension.type) {
target[position++] = 0xd4 // one byte "tag" extension
target[position++] = extension.type
target[position++] = 0
}
let writeResult = extension.write.call(this, value)
if (writeResult === value) { // avoid infinite recursion
if (Array.isArray(value)) {
packArray(value)
} else {
writeObject(value)
}
} else {
pack(writeResult)
}
return
}
let currentTarget = target
let currentTargetView = targetView
let currentPosition = position
target = null
let result
try {
result = extension.pack.call(this, value, (size) => {
// restore target and use it
target = currentTarget
currentTarget = null
position += size
if (position > safeEnd)
makeRoom(position)
return {
target, targetView, position: position - size
}
}, pack)
} finally {
// restore current target information (unless already restored)
if (currentTarget) {
target = currentTarget
targetView = currentTargetView
position = currentPosition
safeEnd = target.length - 10
}
}
if (result) {
if (result.length + position > safeEnd)
makeRoom(result.length + position)
position = writeExtensionData(result, target, position, extension.type)
}
return
}
}
// check isArray after extensions, because extensions can extend Array
if (Array.isArray(value)) {
packArray(value)
} else {
// use this as an alternate mechanism for expressing how to serialize
if (value.toJSON) {
const json = value.toJSON()
// if for some reason value.toJSON returns itself it'll loop forever
if (json !== value)
return pack(json)
}
// if there is a writeFunction, use it, otherwise just encode as undefined
if (type === 'function')
return pack(this.writeFunction && this.writeFunction(value));
// no extension found, write as plain object
writeObject(value)
}
}
}
} else if (type === 'boolean') {
target[position++] = value ? 0xc3 : 0xc2
} else if (type === 'bigint') {
if (value < 0x8000000000000000 && value >= -0x8000000000000000) {
// use a signed int as long as it fits
target[position++] = 0xd3
targetView.setBigInt64(position, value)
} else if (value < 0x10000000000000000 && value > 0) {
// if we can fit an unsigned int, use that
target[position++] = 0xcf
targetView.setBigUint64(position, value)
} else {
// overflow
if (this.largeBigIntToFloat) {
target[position++] = 0xcb
targetView.setFloat64(position, Number(value))
} else if (this.largeBigIntToString) {
return pack(value.toString());
} else if (this.useBigIntExtension || this.moreTypes) {
let empty = value < 0 ? BigInt(-1) : BigInt(0)
let array
if (value >> BigInt(0x10000) === empty) {
let mask = BigInt(0x10000000000000000) - BigInt(1) // literal would overflow
let chunks = []
while (true) {
chunks.push(value & mask)
if ((value >> BigInt(63)) === empty) break
value >>= BigInt(64)
}
array = new Uint8Array(new BigUint64Array(chunks).buffer)
array.reverse()
} else {
let invert = value < 0
let string = (invert ? ~value : value).toString(16)
if (string.length % 2) {
string = '0' + string
} else if (parseInt(string.charAt(0), 16) >= 8) {
string = '00' + string
}
if (hasNodeBuffer) {
array = Buffer.from(string, 'hex')
} else {
array = new Uint8Array(string.length / 2)
for (let i = 0; i < array.length; i++) {
array[i] = parseInt(string.slice(i * 2, i * 2 + 2), 16)
}
}
if (invert) {
for (let i = 0; i < array.length; i++) array[i] = ~array[i]
}
}
if (array.length + position > safeEnd)
makeRoom(array.length + position)
position = writeExtensionData(array, target, position, 0x42)
return
} else {
throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, use' +
' useBigIntExtension, or set largeBigIntToFloat to convert to float-64, or set' +
' largeBigIntToString to convert to string')
}
}
position += 8
} else if (type === 'undefined') {
if (this.encodeUndefinedAsNil)
target[position++] = 0xc0
else {
target[position++] = 0xd4 // a number of implementations use fixext1 with type 0, data 0 to denote undefined, so we follow suite
target[position++] = 0
target[position++] = 0
}
} else {
throw new Error('Unknown type: ' + type)
}
}
const writePlainObject = (this.variableMapSize || this.coercibleKeyAsNumber || this.skipValues) ? (object) => {
// this method is slightly slower, but generates "preferred serialization" (optimally small for smaller objects)
let keys;
if (this.skipValues) {
keys = [];
for (let key in object) {
if ((typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) &&
!this.skipValues.includes(object[key]))
keys.push(key);
}
} else {
keys = Object.keys(object)
}
let length = keys.length
if (length < 0x10) {
target[position++] = 0x80 | length
} else if (length < 0x10000) {
target[position++] = 0xde
target[position++] = length >> 8
target[position++] = length & 0xff
} else {
target[position++] = 0xdf
targetView.setUint32(position, length)
position += 4
}
let key
if (this.coercibleKeyAsNumber) {
for (let i = 0; i < length; i++) {
key = keys[i]
let num = Number(key)
pack(isNaN(num) ? key : num)
pack(object[key])
}
} else {
for (let i = 0; i < length; i++) {
pack(key = keys[i])
pack(object[key])
}
}
} :
(object) => {
target[position++] = 0xde // always using map 16, so we can preallocate and set the length afterwards
let objectOffset = position - start
position += 2
let size = 0
for (let key in object) {
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
pack(key)
pack(object[key])
size++
}
}
if (size > 0xffff) {
throw new Error('Object is too large to serialize with fast 16-bit map size,' +
' use the "variableMapSize" option to serialize this object');
}
target[objectOffset++ + start] = size >> 8
target[objectOffset + start] = size & 0xff
}
const writeRecord = this.useRecords === false ? writePlainObject :
(options.progressiveRecords && !useTwoByteRecords) ? // this is about 2% faster for highly stable structures, since it only requires one for-in loop (but much more expensive when new structure needs to be written)
(object) => {
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
let objectOffset = position++ - start
let wroteKeys
for (let key in object) {
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
nextTransition = transition[key]
if (nextTransition)
transition = nextTransition
else {
// record doesn't exist, create full new record and insert it
let keys = Object.keys(object)
let lastTransition = transition
transition = structures.transitions
let newTransitions = 0
for (let i = 0, l = keys.length; i < l; i++) {
let key = keys[i]
nextTransition = transition[key]
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null)
newTransitions++
}
transition = nextTransition
}
if (objectOffset + start + 1 == position) {
// first key, so we don't need to insert, we can just write record directly
position--
newRecord(transition, keys, newTransitions)
} else // otherwise we need to insert the record, moving existing data after the record
insertNewRecord(transition, keys, objectOffset, newTransitions)
wroteKeys = true
transition = lastTransition[key]
}
pack(object[key])
}
}
if (!wroteKeys) {
let recordId = transition[RECORD_SYMBOL]
if (recordId)
target[objectOffset + start] = recordId
else
insertNewRecord(transition, Object.keys(object), objectOffset, 0)
}
} :
(object) => {
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
let newTransitions = 0
for (let key in object) if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
nextTransition = transition[key]
if (!nextTransition) {
nextTransition = transition[key] = Object.create(null)
newTransitions++
}
transition = nextTransition
}
let recordId = transition[RECORD_SYMBOL]
if (recordId) {
if (recordId >= 0x60 && useTwoByteRecords) {
target[position++] = ((recordId -= 0x60) & 0x1f) + 0x60
target[position++] = recordId >> 5
} else
target[position++] = recordId
} else {
newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions)
}
// now write the values
for (let key in object)
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
pack(object[key])
}
}
// create reference to useRecords if useRecords is a function
const checkUseRecords = typeof this.useRecords == 'function' && this.useRecords;
const writeObject = checkUseRecords ? (object) => {
checkUseRecords(object) ? writeRecord(object) : writePlainObject(object)
} : writeRecord
const makeRoom = (end) => {
let newSize
if (end > 0x1000000) {
// special handling for really large buffers
if ((end - start) > MAX_BUFFER_SIZE)
throw new Error('Packed buffer would be larger than maximum buffer size')
newSize = Math.min(MAX_BUFFER_SIZE,
Math.round(Math.max((end - start) * (end > 0x4000000 ? 1.25 : 2), 0x400000) / 0x1000) * 0x1000)
} else // faster handling for smaller buffers
newSize = ((Math.max((end - start) << 2, target.length - 1) >> 12) + 1) << 12
let newBuffer = new ByteArrayAllocate(newSize)
targetView = newBuffer.dataView || (newBuffer.dataView = new DataView(newBuffer.buffer, 0, newSize))
end = Math.min(end, target.length)
if (target.copy)
target.copy(newBuffer, 0, start, end)
else
newBuffer.set(target.slice(start, end))
position -= start
start = 0
safeEnd = newBuffer.length - 10
return target = newBuffer
}
const newRecord = (transition, keys, newTransitions) => {
let recordId = structures.nextId
if (!recordId)
recordId = 0x40
if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
recordId = structures.nextOwnId
if (!(recordId < maxStructureId))
recordId = sharedLimitId
structures.nextOwnId = recordId + 1
} else {
if (recordId >= maxStructureId)// cycle back around
recordId = sharedLimitId
structures.nextId = recordId + 1
}
let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1
transition[RECORD_SYMBOL] = recordId
transition.__keys__ = keys
structures[recordId - 0x40] = keys
if (recordId < sharedLimitId) {
keys.isShared = true
structures.sharedLength = recordId - 0x3f
hasSharedUpdate = true
if (highByte >= 0) {
target[position++] = (recordId & 0x1f) + 0x60
target[position++] = highByte
} else {
target[position++] = recordId
}
} else {
if (highByte >= 0) {
target[position++] = 0xd5 // fixext 2
target[position++] = 0x72 // "r" record defintion extension type
target[position++] = (recordId & 0x1f) + 0x60
target[position++] = highByte
} else {
target[position++] = 0xd4 // fixext 1
target[position++] = 0x72 // "r" record defintion extension type
target[position++] = recordId
}
if (newTransitions)
transitionsCount += serializationsSinceTransitionRebuild * newTransitions
// record the removal of the id, we can maintain our shared structure
if (recordIdsToRemove.length >= maxOwnStructures)
recordIdsToRemove.shift()[RECORD_SYMBOL] = 0 // we are cycling back through, and have to remove old ones
recordIdsToRemove.push(transition)
pack(keys)
}
}
const insertNewRecord = (transition, keys, insertionOffset, newTransitions) => {
let mainTarget = target
let mainPosition = position
let mainSafeEnd = safeEnd
let mainStart = start
target = keysTarget
position = 0
start = 0
if (!target)
keysTarget = target = new ByteArrayAllocate(8192)
safeEnd = target.length - 10
newRecord(transition, keys, newTransitions)
keysTarget = target
let keysPosition = position
target = mainTarget
position = mainPosition
safeEnd = mainSafeEnd
start = mainStart
if (keysPosition > 1) {
let newEnd = position + keysPosition - 1
if (newEnd > safeEnd)
makeRoom(newEnd)
let insertionPosition = insertionOffset + start
target.copyWithin(insertionPosition + keysPosition, insertionPosition + 1, position)
target.set(keysTarget.slice(0, keysPosition), insertionPosition)
position = newEnd
} else {
target[insertionOffset + start] = keysTarget[0]
}
}
const writeStruct = (object) => {
let newPosition = writeStructSlots(object, target, start, position, structures, makeRoom, (value, newPosition, notifySharedUpdate) => {
if (notifySharedUpdate)
return hasSharedUpdate = true;
position = newPosition;
let startTarget = target;
pack(value);
resetStructures();
if (startTarget !== target) {
return { position, targetView, target }; // indicate the buffer was re-allocated
}
return position;
}, this);
if (newPosition === 0) // bail and go to a msgpack object
return writeObject(object);
position = newPosition;
}
}
useBuffer(buffer) {
// this means we are finished using our own buffer and we can write over it safely
target = buffer
target.dataView || (target.dataView = new DataView(target.buffer, target.byteOffset, target.byteLength))
targetView = target.dataView;
position = 0
}
set position (value) {
position = value;
}
get position() {
return position;
}
clearSharedData() {
if (this.structures)
this.structures = []
if (this.typedStructs)
this.typedStructs = []
}
}
extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, DataView, C1Type ]
extensions = [{
pack(date, allocateForWrite, pack) {
let seconds = date.getTime() / 1000
if ((this.useTimestamp32 || date.getMilliseconds() === 0) && seconds >= 0 && seconds < 0x100000000) {
// Timestamp 32
let { target, targetView, position} = allocateForWrite(6)
target[position++] = 0xd6
target[position++] = 0xff
targetView.setUint32(position, seconds)
} else if (seconds > 0 && seconds < 0x100000000) {
// Timestamp 64
let { target, targetView, position} = allocateForWrite(10)
target[position++] = 0xd7
target[position++] = 0xff
targetView.setUint32(position, date.getMilliseconds() * 4000000 + ((seconds / 1000 / 0x100000000) >> 0))
targetView.setUint32(position + 4, seconds)
} else if (isNaN(seconds)) {
if (this.onInvalidDate) {
allocateForWrite(0)
return pack(this.onInvalidDate())
}
// Intentionally invalid timestamp
let { target, targetView, position} = allocateForWrite(3)
target[position++] = 0xd4
target[position++] = 0xff
target[position++] = 0xff
} else {
// Timestamp 96
let { target, targetView, position} = allocateForWrite(15)
target[position++] = 0xc7
target[position++] = 12
target[position++] = 0xff
targetView.setUint32(position, date.getMilliseconds() * 1000000)
targetView.setBigInt64(position + 4, BigInt(Math.floor(seconds)))
}
}
}, {
pack(set, allocateForWrite, pack) {
if (this.setAsEmptyObject) {
allocateForWrite(0);
return pack({})
}
let array = Array.from(set)
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0)
if (this.moreTypes) {
target[position++] = 0xd4
target[position++] = 0x73 // 's' for Set
target[position++] = 0
}
pack(array)
}
}, {
pack(error, allocateForWrite, pack) {
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0)
if (this.moreTypes) {
target[position++] = 0xd4
target[position++] = 0x65 // 'e' for error
target[position++] = 0
}
pack([ error.name, error.message, error.cause ])
}
}, {
pack(regex, allocateForWrite, pack) {
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0)
if (this.moreTypes) {
target[position++] = 0xd4
target[position++] = 0x78 // 'x' for regeXp
target[position++] = 0
}
pack([ regex.source, regex.flags ])
}
}, {
pack(arrayBuffer, allocateForWrite) {
if (this.moreTypes)
writeExtBuffer(arrayBuffer, 0x10, allocateForWrite)
else
writeBuffer(hasNodeBuffer ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite)
}
}, {
pack(typedArray, allocateForWrite) {
let constructor = typedArray.constructor
if (constructor !== ByteArray && this.moreTypes)
writeExtBuffer(typedArray, typedArrays.indexOf(constructor.name), allocateForWrite)
else
writeBuffer(typedArray, allocateForWrite)
}
}, {
pack(arrayBuffer, allocateForWrite) {
if (this.moreTypes)
writeExtBuffer(arrayBuffer, 0x11, allocateForWrite)
else
writeBuffer(hasNodeBuffer ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite)
}
}, {
pack(c1, allocateForWrite) { // specific 0xC1 object
let { target, position} = allocateForWrite(1)
target[position] = 0xc1
}
}]
function writeExtBuffer(typedArray, type, allocateForWrite, encode) {
let length = typedArray.byteLength
if (length + 1 < 0x100) {
var { target, position } = allocateForWrite(4 + length)
target[position++] = 0xc7
target[position++] = length + 1
} else if (length + 1 < 0x10000) {
var { target, position } = allocateForWrite(5 + length)
target[position++] = 0xc8
target[position++] = (length + 1) >> 8
target[position++] = (length + 1) & 0xff
} else {
var { target, position, targetView } = allocateForWrite(7 + length)
target[position++] = 0xc9
targetView.setUint32(position, length + 1) // plus one for the type byte
position += 4
}
target[position++] = 0x74 // "t" for typed array
target[position++] = type
if (!typedArray.buffer) typedArray = new Uint8Array(typedArray)
target.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength), position)
}
function writeBuffer(buffer, allocateForWrite) {
let length = buffer.byteLength
var target, position
if (length < 0x100) {
var { target, position } = allocateForWrite(length + 2)
target[position++] = 0xc4
target[position++] = length
} else if (length < 0x10000) {
var { target, position } = allocateForWrite(length + 3)
target[position++] = 0xc5
target[position++] = length >> 8
target[position++] = length & 0xff
} else {
var { target, position, targetView } = allocateForWrite(length + 5)
target[position++] = 0xc6
targetView.setUint32(position, length)
position += 4
}
target.set(buffer, position)
}
function writeExtensionData(result, target, position, type) {
let length = result.length
switch (length) {
case 1:
target[position++] = 0xd4
break
case 2:
target[position++] = 0xd5
break
case 4:
target[position++] = 0xd6
break
case 8:
target[position++] = 0xd7
break
case 16:
target[position++] = 0xd8
break
default:
if (length < 0x100) {
target[position++] = 0xc7
target[position++] = length
} else if (length < 0x10000) {
target[position++] = 0xc8
target[position++] = length >> 8
target[position++] = length & 0xff
} else {
target[position++] = 0xc9
target[position++] = length >> 24
target[position++] = (length >> 16) & 0xff
target[position++] = (length >> 8) & 0xff
target[position++] = length & 0xff
}
}
target[position++] = type
target.set(result, position)
position += length
return position
}
function insertIds(serialized, idsToInsert) {
// insert the ids that need to be referenced for structured clones
let nextId
let distanceToMove = idsToInsert.length * 6
let lastEnd = serialized.length - distanceToMove
while (nextId = idsToInsert.pop()) {
let offset = nextId.offset
let id = nextId.id
serialized.copyWithin(offset + distanceToMove, offset, lastEnd)
distanceToMove -= 6
let position = offset + distanceToMove
serialized[position++] = 0xd6
serialized[position++] = 0x69 // 'i'
serialized[position++] = id >> 24
serialized[position++] = (id >> 16) & 0xff
serialized[position++] = (id >> 8) & 0xff
serialized[position++] = id & 0xff
lastEnd = offset
}
return serialized
}
function writeBundles(start, pack, incrementPosition) {
if (bundledStrings.length > 0) {
targetView.setUint32(bundledStrings.position + start, position + incrementPosition - bundledStrings.position - start)
bundledStrings.stringsPosition = position - start;
let writeStrings = bundledStrings
bundledStrings = null
pack(writeStrings[0])
pack(writeStrings[1])
}
}
export function addExtension(extension) {
if (extension.Class) {
if (!extension.pack && !extension.write)
throw new Error('Extension has no pack or write function')
if (extension.pack && !extension.type)
throw new Error('Extension has no type (numeric code to identify the extension)')
extensionClasses.unshift(extension.Class)
extensions.unshift(extension)
}
unpackAddExtension(extension)
}
function prepareStructures(structures, packr) {
structures.isCompatible = (existingStructures) => {
let compatible = !existingStructures || ((packr.lastNamedStructuresLength || 0) === existingStructures.length)
if (!compatible) // we want to merge these existing structures immediately since we already have it and we are in the right transaction
packr._mergeStructures(existingStructures);
return compatible;
}
return structures
}
export function setWriteStructSlots(writeSlots, makeStructures) {
writeStructSlots = writeSlots;
prepareStructures = makeStructures;
}
let defaultPackr = new Packr({ useRecords: false })
export const pack = defaultPackr.pack
export const encode = defaultPackr.pack
export const Encoder = Packr
export { FLOAT32_OPTIONS } from './unpack.js'
import { FLOAT32_OPTIONS } from './unpack.js'
export const { NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } = FLOAT32_OPTIONS
export const REUSE_BUFFER_MODE = 512
export const RESET_BUFFER_MODE = 1024
export const RESERVE_START_SPACE = 2048
+104
View File
@@ -0,0 +1,104 @@
{
"name": "msgpackr",
"author": "Kris Zyp",
"version": "1.12.1",
"description": "Ultra-fast MessagePack implementation with extensions for records and structured cloning",
"license": "MIT",
"types": "./index.d.ts",
"main": "./dist/node.cjs",
"module": "./index.js",
"react-native": "./index.js",
"keywords": [
"MessagePack",
"msgpack",
"performance",
"structured",
"clone"
],
"repository": {
"type": "git",
"url": "http://github.com/kriszyp/msgpackr"
},
"scripts": {
"benchmark": "node ./tests/benchmark.cjs",
"build": "rollup -c && cpy index.d.ts . --rename=index.d.cts && cpy pack.d.ts . --rename=pack.d.cts && cpy unpack.d.ts . --rename=unpack.d.cts",
"dry-run": "npm publish --dry-run",
"prepare": "npm run build",
"test": "mocha tests/test**.*js -u tdd --experimental-json-modules"
},
"type": "module",
"exports": {
".": {
"types": {
"require": "./index.d.cts",
"import": "./index.d.ts"
},
"browser": "./index.js",
"node": {
"require": "./dist/node.cjs",
"import": "./node-index.js"
},
"bun": {
"require": "./dist/node.cjs",
"import": "./node-index.js"
},
"default": "./index.js"
},
"./pack": {
"types": {
"require": "./pack.d.cts",
"import": "./pack.d.ts"
},
"browser": "./pack.js",
"node": {
"import": "./index.js",
"require": "./dist/node.cjs"
},
"bun": {
"import": "./index.js",
"require": "./dist/node.cjs"
},
"default": "./pack.js"
},
"./unpack": {
"types": {
"require": "./unpack.d.cts",
"import": "./unpack.d.ts"
},
"browser": "./unpack.js",
"node": {
"import": "./index.js",
"require": "./dist/node.cjs"
},
"bun": {
"import": "./index.js",
"require": "./dist/node.cjs"
},
"default": "./unpack.js"
},
"./unpack-no-eval": "./dist/unpack-no-eval.cjs",
"./index-no-eval": "./dist/index-no-eval.cjs"
},
"files": [
"/dist",
"*.md",
"/*.js",
"/*.ts",
"/*.cts"
],
"optionalDependencies": {
"msgpackr-extract": "^3.0.2"
},
"devDependencies": {
"@rollup/plugin-json": "^5.0.1",
"@rollup/plugin-replace": "^5.0.1",
"@types/node": "latest",
"async": "^3",
"chai": "^4.3.4",
"cpy-cli": "^4.1.0",
"esm": "^3.2.25",
"mocha": "^10.1.0",
"rollup": "^3.2.5",
"@rollup/plugin-terser": "^0.1.0"
}
}
+88
View File
@@ -0,0 +1,88 @@
import terser from '@rollup/plugin-terser';
import json from "@rollup/plugin-json";
import replace from "@rollup/plugin-replace";
export default [
{
input: "node-index.js",
output: [
{
file: "dist/node.cjs",
format: "cjs",
sourcemap: true
}
]
},
{
input: "index.js",
output: {
file: "dist/index.js",
format: "umd",
name: "msgpackr",
sourcemap: true
}
},
{
input: "index.js",
plugins: [
replace({ Function: 'BlockedFunction '})
],
output: {
file: "dist/index-no-eval.cjs",
format: "umd",
name: "msgpackr",
sourcemap: true
},
},
{
input: "unpack.js",
plugins: [
replace({ Function: 'BlockedFunction '})
],
output: {
file: "dist/unpack-no-eval.cjs",
format: "umd",
name: "msgpackr",
sourcemap: true
},
},
{
input: "index.js",
plugins: [
terser({})
],
output: {
file: "dist/index.min.js",
format: "umd",
name: "msgpackr",
sourcemap: true
}
},
{
input: "index.js",
plugins: [
replace({ Function: 'BlockedFunction '}),
terser({})
],
output: {
file: "dist/index-no-eval.min.js",
format: "umd",
name: "msgpackr",
sourcemap: true
}
},
{
input: "tests/test.js",
plugins: [json()],
external: ['chai', '../index.js'],
output: {
file: "dist/test.js",
format: "iife",
sourcemap: true,
globals: {
chai: 'chai',
'./index.js': 'msgpackr',
},
}
}
];
+62
View File
@@ -0,0 +1,62 @@
import { Transform } from 'stream'
import { Packr } from './pack.js'
import { Unpackr } from './unpack.js'
var DEFAULT_OPTIONS = {objectMode: true}
export class PackrStream extends Transform {
constructor(options) {
if (!options)
options = {}
options.writableObjectMode = true
super(options)
options.sequential = true
this.packr = options.packr || new Packr(options)
}
_transform(value, encoding, callback) {
this.push(this.packr.pack(value))
callback()
}
}
export class UnpackrStream extends Transform {
constructor(options) {
if (!options)
options = {}
options.objectMode = true
super(options)
options.structures = []
this.maxIncompleteBufferSize = options.maxIncompleteBufferSize !== undefined ? options.maxIncompleteBufferSize : 0x4000000
this.unpackr = options.unpackr || new Unpackr(options)
}
_transform(chunk, encoding, callback) {
if (this.incompleteBuffer) {
chunk = Buffer.concat([this.incompleteBuffer, chunk])
this.incompleteBuffer = null
}
let values
try {
values = this.unpackr.unpackMultiple(chunk)
} catch(error) {
if (error.incomplete) {
let incompleteBuffer = chunk.slice(error.lastPosition)
if (incompleteBuffer.length > this.maxIncompleteBufferSize) {
this.incompleteBuffer = null
return callback(new Error('Maximum incomplete buffer size exceeded'))
}
this.incompleteBuffer = incompleteBuffer
values = error.values
} else {
return callback(error)
}
}
for (let value of values || []) {
if (value === null)
value = this.getNullValue()
this.push(value)
}
callback()
}
getNullValue() {
return Symbol.for(null)
}
}
+884
View File
@@ -0,0 +1,884 @@
/*
For "any-data":
32-55 - record with record ids (-32)
56 - 8-bit record ids
57 - 16-bit record ids
58 - 24-bit record ids
59 - 32-bit record ids
250-255 - followed by typed fixed width values
64-250 msgpackr/cbor/paired data
arrays and strings within arrays are handled by paired encoding
Structure encoding:
(type - string (using paired encoding))+
Type encoding
encoding byte - fixed width byte - next reference+
Encoding byte:
first bit:
0 - inline
1 - reference
second bit:
0 - data or number
1 - string
remaining bits:
character encoding - ISO-8859-x
null (0xff)+ 0xf6
null (0xff)+ 0xf7
*/
import {setWriteStructSlots, RECORD_SYMBOL, addExtension} from './pack.js'
import {setReadStruct, mult10, readString} from './unpack.js';
const ASCII = 3; // the MIBenum from https://www.iana.org/assignments/character-sets/character-sets.xhtml (and other character encodings could be referenced by MIBenum)
const NUMBER = 0;
const UTF8 = 2;
const OBJECT_DATA = 1;
const DATE = 16;
const TYPE_NAMES = ['num', 'object', 'string', 'ascii'];
TYPE_NAMES[DATE] = 'date';
const float32Headers = [false, true, true, false, false, true, true, false];
let evalSupported;
try {
new Function('');
evalSupported = true;
} catch(error) {
// if eval variants are not supported, do not create inline object readers ever
}
let updatedPosition;
const hasNodeBuffer = typeof Buffer !== 'undefined'
let textEncoder, currentSource;
try {
textEncoder = new TextEncoder()
} catch (error) {}
const encodeUtf8 = hasNodeBuffer ? function(target, string, position) {
return target.utf8Write(string, position, target.byteLength - position)
} : (textEncoder && textEncoder.encodeInto) ?
function(target, string, position) {
return textEncoder.encodeInto(string, target.subarray(position)).written
} : false
const TYPE = Symbol('type');
const PARENT = Symbol('parent');
setWriteStructSlots(writeStruct, prepareStructures);
function writeStruct(object, target, encodingStart, position, structures, makeRoom, pack, packr, structureKnown) {
let typedStructs = packr.typedStructs || (packr.typedStructs = []);
// note that we rely on pack.js to load stored structures before we get to this point
// structureKnown is set only on the internal layout-retry below: attempt 1 already minted
// this record's structure, so the retry re-encodes a known shape and must not re-apply the
// cap (which could otherwise bail after attempt 1 already packed refs → corrupt fallback).
// `frozen` is a local (from this instance's typedStructs) — never a shared global — so a
// re-entrant encode on another instance (e.g. via an enumerable getter) can't flip it.
const cap = packr.maxOwnStructures ?? Infinity;
const frozen = !structureKnown && typedStructs.length >= cap;
let targetView = target.dataView;
let refsStartPosition = (typedStructs.lastStringStart || 100) + position;
let safeEnd = target.length - 10;
let start = position;
if (position > safeEnd) {
target = makeRoom(position);
targetView = target.dataView;
position -= encodingStart;
start -= encodingStart;
refsStartPosition -= encodingStart;
encodingStart = 0;
safeEnd = target.length - 10;
}
let refOffset, refPosition = refsStartPosition;
let transition = typedStructs.transitions || (typedStructs.transitions = Object.create(null));
let nextId = typedStructs.nextId || typedStructs.length;
let headerSize =
nextId < 0xf ? 1 :
nextId < 0xf0 ? 2 :
nextId < 0xf000 ? 3 :
nextId < 0xf00000 ? 4 : 0;
if (headerSize === 0)
return 0;
position += headerSize;
let queuedReferences = [];
let usedAscii0;
let keyIndex = 0;
for (let key in object) {
let nextTransition = transition[key];
// Resolve the key transition BEFORE reading the value: when frozen and the key is new we
// bail here, so an enumerable getter isn't invoked during this (failed) struct attempt and
// then again by the plain fallback (which would double-read a side-effecting accessor).
if (!nextTransition) {
if (frozen) return 0;
transition[key] = nextTransition = {
key,
parent: transition,
enumerationOffset: 0,
ascii0: null,
ascii8: null,
num8: null,
string16: null,
object16: null,
num32: null,
float64: null,
date64: null
};
}
let value = object[key];
if (position > safeEnd) {
target = makeRoom(position);
targetView = target.dataView;
position -= encodingStart;
start -= encodingStart;
refsStartPosition -= encodingStart;
refPosition -= encodingStart;
encodingStart = 0;
safeEnd = target.length - 10
}
switch (typeof value) {
case 'number':
let number = value;
// first check to see if we are using a lot of ids and should default to wide/common format
if (nextId < 200 || !nextTransition.num64) {
if (number >> 0 === number && number < 0x20000000 && number > -0x1f000000) {
if (number < 0xf6 && number >= 0 && (nextTransition.num8 && !(nextId > 200 && nextTransition.num32) || number < 0x20 && !nextTransition.num32)) {
transition = nextTransition.num8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
target[position++] = number;
} else {
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
targetView.setUint32(position, number, true);
position += 4;
}
break;
} else if (number < 0x100000000 && number >= -0x80000000) {
targetView.setFloat32(position, number, true);
if (float32Headers[target[position + 3] >>> 5]) {
let xShifted
// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
if (((xShifted = number * mult10[((target[position + 3] & 0x7f) << 1) | (target[position + 2] >> 7)]) >> 0) === xShifted) {
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
position += 4;
break;
}
}
}
}
transition = nextTransition.num64 || createTypeTransition(nextTransition, NUMBER, 8, frozen);
targetView.setFloat64(position, number, true);
position += 8;
break;
case 'string':
let strLength = value.length;
refOffset = refPosition - refsStartPosition;
if ((strLength << 2) + refPosition > safeEnd) {
target = makeRoom((strLength << 2) + refPosition);
targetView = target.dataView;
position -= encodingStart;
start -= encodingStart;
refsStartPosition -= encodingStart;
refPosition -= encodingStart;
encodingStart = 0;
safeEnd = target.length - 10
}
if (strLength > ((0xff00 + refOffset) >> 2)) {
queuedReferences.push(key, value, position - start);
break;
}
let isNotAscii
let strStart = refPosition;
if (strLength < 0x40) {
let i, c1, c2;
for (i = 0; i < strLength; i++) {
c1 = value.charCodeAt(i)
if (c1 < 0x80) {
target[refPosition++] = c1
} else if (c1 < 0x800) {
isNotAscii = true;
target[refPosition++] = c1 >> 6 | 0xc0
target[refPosition++] = c1 & 0x3f | 0x80
} else if (
(c1 & 0xfc00) === 0xd800 &&
((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
) {
isNotAscii = true;
c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff)
i++
target[refPosition++] = c1 >> 18 | 0xf0
target[refPosition++] = c1 >> 12 & 0x3f | 0x80
target[refPosition++] = c1 >> 6 & 0x3f | 0x80
target[refPosition++] = c1 & 0x3f | 0x80
} else {
isNotAscii = true;
target[refPosition++] = c1 >> 12 | 0xe0
target[refPosition++] = c1 >> 6 & 0x3f | 0x80
target[refPosition++] = c1 & 0x3f | 0x80
}
}
} else {
refPosition += encodeUtf8(target, value, refPosition);
isNotAscii = refPosition - strStart > strLength;
}
if (refOffset < 0xa0 || (refOffset < 0xf6 && (nextTransition.ascii8 || nextTransition.string8))) {
// short strings
if (isNotAscii) {
if (!(transition = nextTransition.string8)) {
if (typedStructs.length > 10 && (transition = nextTransition.ascii8)) {
// we can safely change ascii to utf8 in place since they are compatible
transition.__type = UTF8;
nextTransition.ascii8 = null;
nextTransition.string8 = transition;
pack(null, 0, true); // special call to notify that structures have been updated
} else {
transition = createTypeTransition(nextTransition, UTF8, 1, frozen);
}
}
} else if (refOffset === 0 && !usedAscii0) {
usedAscii0 = true;
transition = nextTransition.ascii0 || createTypeTransition(nextTransition, ASCII, 0, frozen);
break; // don't increment position
}// else ascii:
else if (!(transition = nextTransition.ascii8) && !(typedStructs.length > 10 && (transition = nextTransition.string8)))
transition = createTypeTransition(nextTransition, ASCII, 1, frozen);
target[position++] = refOffset;
} else {
// TODO: Enable ascii16 at some point, but get the logic right
//if (isNotAscii)
transition = nextTransition.string16 || createTypeTransition(nextTransition, UTF8, 2, frozen);
//else
//transition = nextTransition.ascii16 || createTypeTransition(nextTransition, ASCII, 2);
targetView.setUint16(position, refOffset, true);
position += 2;
}
break;
case 'object':
if (value) {
if (value.constructor === Date) {
transition = nextTransition.date64 || createTypeTransition(nextTransition, DATE, 8, frozen);
targetView.setFloat64(position, value.getTime(), true);
position += 8;
} else {
queuedReferences.push(key, value, keyIndex);
}
break;
} else { // null
nextTransition = anyType(nextTransition, position, targetView, -10); // match CBOR with this
if (nextTransition) {
transition = nextTransition;
position = updatedPosition;
} else queuedReferences.push(key, value, keyIndex);
}
break;
case 'boolean':
transition = nextTransition.num8 || nextTransition.ascii8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
target[position++] = value ? 0xf9 : 0xf8; // match CBOR with these
break;
case 'undefined':
nextTransition = anyType(nextTransition, position, targetView, -9); // match CBOR with this
if (nextTransition) {
transition = nextTransition;
position = updatedPosition;
} else queuedReferences.push(key, value, keyIndex);
break;
default:
queuedReferences.push(key, value, keyIndex);
}
if (transition === undefined) return 0; // frozen: structure cap reached
keyIndex++;
}
// Cap enforcement for queued (nested-object / null) references. pack() advances msgpackr's
// shared write position and we cannot cleanly bail afterward, so preflight the whole queued
// chain through EXISTING transitions first: if the cap is reached and any field would need a
// new structure, fall back to plain encoding now (return 0) — before touching the shared
// position. Uses a FRESH length read (not the entry-time `frozen`): a getter invoked while
// reading values above may have minted on this same instance since entry.
if (!structureKnown && queuedReferences.length > 0 && typedStructs.length >= cap) {
let t = transition;
for (let i = 0, l = queuedReferences.length; i < l; i += 3) {
// A non-null (object/Date) ref is pack()ed into the shared buffer, advancing
// msgpackr's write position. Its structure variant (object16 vs object32) depends on
// the runtime ref-section offset (inline strings + earlier refs), which we can't know
// before packing — and we can't bail after a pack without corrupting the fallback. So
// under the cap, any record with a packing ref falls back to plain encoding now,
// before any pack(). null/undefined refs don't pack, so they're walked normally.
if (queuedReferences[i + 1] != null) return 0;
const nt = t[queuedReferences[i]];
if (!nt) return 0;
const next = nt.object16; // null/undefined ref → OBJECT_DATA size 2
if (!next) return 0;
t = next;
}
if (t[RECORD_SYMBOL] == null) return 0; // exact structure not yet minted
}
// Past the preflight the chain is known, so no minting happens — except a rare offset
// divergence (a known shape whose ref section now crosses 0xff00 and needs object32 where
// the preflight matched object16). Once a ref is packed we can no longer bail, so we finish
// via the unfrozen forceTypeTransition: a bounded, self-converging overshoot for that one
// record. packedRef keeps the record-id mint from bailing after a pack.
let packedRef = false;
for (let i = 0, l = queuedReferences.length; i < l;) {
let key = queuedReferences[i++];
let value = queuedReferences[i++];
let propertyIndex = queuedReferences[i++];
let nextTransition = transition[key];
if (!nextTransition) {
transition[key] = nextTransition = {
key,
parent: transition,
enumerationOffset: propertyIndex - keyIndex,
ascii0: null,
ascii8: null,
num8: null,
string16: null,
object16: null,
num32: null,
float64: null
};
}
let newPosition;
if (value) {
let size;
refOffset = refPosition - refsStartPosition;
if (refOffset < 0xff00) {
transition = nextTransition.object16;
if (transition)
size = 2;
else if ((transition = nextTransition.object32))
size = 4;
else {
transition = forceTypeTransition(nextTransition, OBJECT_DATA, 2);
size = 2;
}
} else {
transition = nextTransition.object32 || forceTypeTransition(nextTransition, OBJECT_DATA, 4);
size = 4;
}
newPosition = pack(value, refPosition);
packedRef = true;
if (typeof newPosition === 'object') {
// re-allocated
refPosition = newPosition.position;
targetView = newPosition.targetView;
target = newPosition.target;
refsStartPosition -= encodingStart;
position -= encodingStart;
start -= encodingStart;
encodingStart = 0;
} else
refPosition = newPosition;
if (size === 2) {
targetView.setUint16(position, refOffset, true);
position += 2;
} else {
targetView.setUint32(position, refOffset, true);
position += 4;
}
} else { // null or undefined
transition = nextTransition.object16 || forceTypeTransition(nextTransition, OBJECT_DATA, 2);
targetView.setInt16(position, value === null ? -10 : -9, true);
position += 2;
}
keyIndex++;
}
let recordId = transition[RECORD_SYMBOL];
if (recordId == null) {
// Flat records (no queued refs) reach here without packing, so the cap is enforced
// cleanly. Records that packed nested refs already passed the preflight; either way
// bailing now after refs were packed would corrupt the fallback.
if (!packedRef && typedStructs.length >= cap) return 0;
recordId = packr.typedStructs.length;
let structure = [];
let nextTransition = transition;
let key, type;
while ((type = nextTransition.__type) !== undefined) {
let size = nextTransition.__size;
nextTransition = nextTransition.__parent;
key = nextTransition.key;
let property = [type, size, key];
if (nextTransition.enumerationOffset)
property.push(nextTransition.enumerationOffset);
structure.push(property);
nextTransition = nextTransition.parent;
}
structure.reverse();
transition[RECORD_SYMBOL] = recordId;
packr.typedStructs[recordId] = structure;
pack(null, 0, true); // special call to notify that structures have been updated
}
switch (headerSize) {
case 1:
if (recordId >= 0x10) return 0;
target[start] = recordId + 0x20;
break;
case 2:
if (recordId >= 0x100) return 0;
target[start] = 0x38;
target[start + 1] = recordId;
break;
case 3:
if (recordId >= 0x10000) return 0;
target[start] = 0x39;
targetView.setUint16(start + 1, recordId, true);
break;
case 4:
if (recordId >= 0x1000000) return 0;
targetView.setUint32(start, (recordId << 8) + 0x3a, true);
break;
}
if (position < refsStartPosition) {
if (refsStartPosition === refPosition)
return position; // no refs
// adjust positioning
target.copyWithin(position, refsStartPosition, refPosition);
refPosition += position - refsStartPosition;
typedStructs.lastStringStart = position - start;
} else if (position > refsStartPosition) {
if (refsStartPosition === refPosition)
return position; // no refs
typedStructs.lastStringStart = position - start;
// Fixed section overflowed our estimate — retry with the corrected size. The structure
// is already minted at this point, so pass structureKnown=true to skip the cap check
// (otherwise a record that became frozen during attempt 1 would bail mid-retry, after
// refs were already packed, and corrupt the fallback).
return writeStruct(object, target, encodingStart, start, structures, makeRoom, pack, packr, true);
}
return refPosition;
}
function anyType(transition, position, targetView, value) {
let nextTransition;
if ((nextTransition = transition.ascii8 || transition.num8)) {
targetView.setInt8(position, value, true);
updatedPosition = position + 1;
return nextTransition;
}
if ((nextTransition = transition.string16 || transition.object16)) {
targetView.setInt16(position, value, true);
updatedPosition = position + 2;
return nextTransition;
}
if (nextTransition = transition.num32) {
targetView.setUint32(position, 0xe0000100 + value, true);
updatedPosition = position + 4;
return nextTransition;
}
// transition.float64
if (nextTransition = transition.num64) {
targetView.setFloat64(position, NaN, true);
targetView.setInt8(position, value);
updatedPosition = position + 8;
return nextTransition;
}
updatedPosition = position;
// TODO: can we do an "any" type where we defer the decision?
return;
}
// When the typed-structure dictionary reaches maxOwnStructures we stop minting new
// structures/transitions. typedStructs is append-only and pinned on the long-lived
// encoder (records reference structures by recordId), so an unbounded shape space —
// e.g. a wide, sparsely/variably-populated schema — would otherwise grow the
// dictionary + transition trie without limit. `frozen` is passed in (derived from the
// encoding instance's own typedStructs.length, never a shared global) so a re-entrant
// encode on another instance can't flip it; while frozen, a missing transition returns
// undefined so the caller bails and the record falls back to plain encoding.
function createTypeTransition(transition, type, size, frozen) {
let typeName = TYPE_NAMES[type] + (size << 3);
let newTransition = transition[typeName];
if (newTransition) return newTransition;
if (frozen) return undefined;
newTransition = transition[typeName] = Object.create(null);
newTransition.__type = type;
newTransition.__size = size;
newTransition.__parent = transition;
return newTransition;
}
// Unfrozen variant: always mints. Used in the queued-ref loop once a nested value has
// already been pack()ed — at that point pack() has advanced msgpackr's shared write
// position, so bailing with `return 0` would corrupt the fallback. We must finish the
// encode instead, even if that means minting a (bounded) handful of structures past the
// cap. The cap is still enforced up front via the preflight, before the first pack().
function forceTypeTransition(transition, type, size) {
let typeName = TYPE_NAMES[type] + (size << 3);
let newTransition = transition[typeName];
if (newTransition) return newTransition;
newTransition = transition[typeName] = Object.create(null);
newTransition.__type = type;
newTransition.__size = size;
newTransition.__parent = transition;
return newTransition;
}
function onLoadedStructures(sharedData) {
if (!(sharedData instanceof Map))
return sharedData;
let typed = sharedData.get('typed') || [];
if (Object.isFrozen(typed))
typed = typed.map(structure => structure.slice(0));
let named = sharedData.get('named');
let transitions = Object.create(null);
for (let i = 0, l = typed.length; i < l; i++) {
let structure = typed[i];
let transition = transitions;
for (let [type, size, key] of structure) {
let nextTransition = transition[key];
if (!nextTransition) {
transition[key] = nextTransition = {
key,
parent: transition,
enumerationOffset: 0,
ascii0: null,
ascii8: null,
num8: null,
string16: null,
object16: null,
num32: null,
float64: null,
date64: null,
};
}
// Replaying persisted structures is never subject to the cap — always mint.
transition = createTypeTransition(nextTransition, type, size, false);
}
transition[RECORD_SYMBOL] = i;
}
typed.transitions = transitions;
this.typedStructs = typed;
this.lastTypedStructuresLength = typed.length;
return named;
}
var sourceSymbol = Symbol.for('source')
function readStruct(src, position, srcEnd, unpackr) {
let recordId = src[position++] - 0x20;
if (recordId >= 24) {
switch(recordId) {
case 24: recordId = src[position++]; break;
// little endian:
case 25: recordId = src[position++] + (src[position++] << 8); break;
case 26: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16); break;
case 27: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16) + (src[position++] << 24); break;
}
}
let structure = unpackr.typedStructs && unpackr.typedStructs[recordId];
if (!structure) {
// copy src buffer because getStructures will override it
src = Uint8Array.prototype.slice.call(src, position, srcEnd);
srcEnd -= position;
position = 0;
if (!unpackr.getStructures)
throw new Error(`Reference to shared structure ${recordId} without getStructures method`);
unpackr._mergeStructures(unpackr.getStructures());
if (!unpackr.typedStructs)
throw new Error('Could not find any shared typed structures');
unpackr.lastTypedStructuresLength = unpackr.typedStructs.length;
structure = unpackr.typedStructs[recordId];
if (!structure)
throw new Error('Could not find typed structure ' + recordId);
}
var construct = structure.construct;
var fullConstruct = structure.fullConstruct;
if (!construct) {
construct = structure.construct = function LazyObject() {
}
fullConstruct = structure.fullConstruct = function LoadedObject() {
}
fullConstruct.prototype = unpackr.structPrototype || {};
var prototype = construct.prototype = unpackr.structPrototype ? Object.create(unpackr.structPrototype) : {};
let properties = [];
let currentOffset = 0;
let lastRefProperty;
for (let i = 0, l = structure.length; i < l; i++) {
let definition = structure[i];
let [ type, size, key, enumerationOffset ] = definition;
if (key === '__proto__')
key = '__proto_';
let property = {
key,
offset: currentOffset,
}
if (enumerationOffset)
properties.splice(i + enumerationOffset, 0, property);
else
properties.push(property);
let getRef;
switch(size) { // TODO: Move into a separate function
case 0: getRef = () => 0; break;
case 1:
getRef = (source, position) => {
let ref = source.bytes[position + property.offset];
return ref >= 0xf6 ? toConstant(ref) : ref;
};
break;
case 2:
getRef = (source, position) => {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let ref = dataView.getUint16(position + property.offset, true);
return ref >= 0xff00 ? toConstant(ref & 0xff) : ref;
};
break;
case 4:
getRef = (source, position) => {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let ref = dataView.getUint32(position + property.offset, true);
return ref >= 0xffffff00 ? toConstant(ref & 0xff) : ref;
};
break;
}
property.getRef = getRef;
currentOffset += size;
let get;
switch(type) {
case ASCII:
if (lastRefProperty && !lastRefProperty.next)
lastRefProperty.next = property;
lastRefProperty = property;
property.multiGetCount = 0;
get = function(source) {
let src = source.bytes;
let position = source.position;
let refStart = currentOffset + position;
let ref = getRef(source, position);
if (typeof ref !== 'number') return ref;
let end, next = property.next;
while(next) {
end = next.getRef(source, position);
if (typeof end === 'number')
break;
else
end = null;
next = next.next;
}
if (end == null)
end = source.bytesEnd - refStart;
if (source.srcString) {
return source.srcString.slice(ref, end);
}
/*if (property.multiGetCount > 0) {
let asciiEnd;
next = firstRefProperty;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
do {
asciiEnd = dataView.getUint16(source.position + next.offset, true);
if (asciiEnd < 0xff00)
break;
else
asciiEnd = null;
} while((next = next.next));
if (asciiEnd == null)
asciiEnd = source.bytesEnd - refStart
source.srcString = src.toString('latin1', refStart, refStart + asciiEnd);
return source.srcString.slice(ref, end);
}
if (source.prevStringGet) {
source.prevStringGet.multiGetCount += 2;
} else {
source.prevStringGet = property;
property.multiGetCount--;
}*/
return readString(src, ref + refStart, end - ref);
//return src.toString('latin1', ref + refStart, end + refStart);
};
break;
case UTF8: case OBJECT_DATA:
if (lastRefProperty && !lastRefProperty.next)
lastRefProperty.next = property;
lastRefProperty = property;
get = function(source) {
let position = source.position;
let refStart = currentOffset + position;
let ref = getRef(source, position);
if (typeof ref !== 'number') return ref;
let src = source.bytes;
let end, next = property.next;
while(next) {
end = next.getRef(source, position);
if (typeof end === 'number')
break;
else
end = null;
next = next.next;
}
if (end == null)
end = source.bytesEnd - refStart;
if (type === UTF8) {
return src.toString('utf8', ref + refStart, end + refStart);
} else {
currentSource = source;
try {
return unpackr.unpack(src, { start: ref + refStart, end: end + refStart });
} finally {
currentSource = null;
}
}
};
break;
case NUMBER:
switch(size) {
case 4:
get = function (source) {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let position = source.position + property.offset;
let value = dataView.getInt32(position, true)
if (value < 0x20000000) {
if (value > -0x1f000000)
return value;
if (value > -0x20000000)
return toConstant(value & 0xff);
}
let fValue = dataView.getFloat32(position, true);
// this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
let multiplier = mult10[((src[position + 3] & 0x7f) << 1) | (src[position + 2] >> 7)]
return ((multiplier * fValue + (fValue > 0 ? 0.5 : -0.5)) >> 0) / multiplier;
};
break;
case 8:
get = function (source) {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
let value = dataView.getFloat64(source.position + property.offset, true);
if (isNaN(value)) {
let byte = src[source.position + property.offset];
if (byte >= 0xf6)
return toConstant(byte);
}
return value;
};
break;
case 1:
get = function (source) {
let src = source.bytes;
let value = src[source.position + property.offset];
return value < 0xf6 ? value : toConstant(value);
};
break;
}
break;
case DATE:
get = function (source) {
let src = source.bytes;
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
return new Date(dataView.getFloat64(source.position + property.offset, true));
};
break;
}
property.get = get;
}
// TODO: load the srcString for faster string decoding on toJSON
if (evalSupported) {
let objectLiteralProperties = [];
let args = [];
let i = 0;
let hasInheritedProperties;
for (let property of properties) { // assign in enumeration order
if (unpackr.alwaysLazyProperty && unpackr.alwaysLazyProperty(property.key)) {
// these properties are not eagerly evaluated and this can be used for creating properties
// that are not serialized as JSON
hasInheritedProperties = true;
continue;
}
Object.defineProperty(prototype, property.key, { get: withSource(property.get), enumerable: true });
let valueFunction = 'v' + i++;
args.push(valueFunction);
objectLiteralProperties.push('o[' + JSON.stringify(property.key) + ']=' + valueFunction + '(s)');
}
if (hasInheritedProperties) {
objectLiteralProperties.push('__proto__:this');
}
let toObject = (new Function(...args, 'var c=this;return function(s){var o=new c();' + objectLiteralProperties.join(';') + ';return o;}')).apply(fullConstruct, properties.map(prop => prop.get));
Object.defineProperty(prototype, 'toJSON', {
value(omitUnderscoredProperties) {
return toObject.call(this, this[sourceSymbol]);
}
});
} else {
Object.defineProperty(prototype, 'toJSON', {
value(omitUnderscoredProperties) {
// return an enumerable object with own properties to JSON stringify
let resolved = {};
for (let i = 0, l = properties.length; i < l; i++) {
// TODO: check alwaysLazyProperty
let key = properties[i].key;
resolved[key] = this[key];
}
return resolved;
},
// not enumerable or anything
});
}
}
var instance = new construct();
instance[sourceSymbol] = {
bytes: src,
position,
srcString: '',
bytesEnd: srcEnd
}
return instance;
}
function toConstant(code) {
switch(code) {
case 0xf6: return null;
case 0xf7: return undefined;
case 0xf8: return false;
case 0xf9: return true;
}
throw new Error('Unknown constant');
}
function withSource(get) {
return function() {
return get(this[sourceSymbol]);
}
}
function saveState() {
if (currentSource) {
currentSource.bytes = Uint8Array.prototype.slice.call(currentSource.bytes, currentSource.position, currentSource.bytesEnd);
currentSource.position = 0;
currentSource.bytesEnd = currentSource.bytes.length;
}
}
function prepareStructures(structures, packr) {
if (packr.typedStructs) {
let structMap = new Map();
structMap.set('named', structures);
structMap.set('typed', packr.typedStructs);
structures = structMap;
}
let lastTypedStructuresLength = packr.lastTypedStructuresLength || 0;
structures.isCompatible = existing => {
let compatible = true;
if (existing instanceof Map) {
let named = existing.get('named') || [];
if (named.length !== (packr.lastNamedStructuresLength || 0))
compatible = false;
let typed = existing.get('typed') || [];
if (typed.length !== lastTypedStructuresLength)
compatible = false;
} else if (existing instanceof Array || Array.isArray(existing)) {
if (existing.length !== (packr.lastNamedStructuresLength || 0))
compatible = false;
}
if (!compatible)
packr._mergeStructures(existing);
return compatible;
};
packr.lastTypedStructuresLength = packr.typedStructs && packr.typedStructs.length;
return structures;
}
setReadStruct(readStruct, onLoadedStructures, saveState);
+3
View File
@@ -0,0 +1,3 @@
setTimeout(() => {
console.log('done');
}, 10000);
+2
View File
@@ -0,0 +1,2 @@
export { Unpackr, Decoder, unpack, unpackMultiple, decode,
addExtension, FLOAT32_OPTIONS, Options, Extension, clearSource, roundFloat32 } from '.'
+2
View File
@@ -0,0 +1,2 @@
export { Unpackr, Decoder, unpack, unpackMultiple, decode,
addExtension, FLOAT32_OPTIONS, Options, Extension, clearSource, roundFloat32 } from '.'
+1252
View File
@@ -0,0 +1,1252 @@
var decoder
try {
decoder = new TextDecoder()
} catch(error) {}
var src
var srcEnd
var position = 0
var alreadySet
const EMPTY_ARRAY = []
var strings = EMPTY_ARRAY
var stringPosition = 0
var currentUnpackr = {}
var currentStructures
var srcString
var srcStringStart = 0
var srcStringEnd = 0
var bundledStrings
var referenceMap
var currentExtensions = []
var dataView
var defaultOptions = {
useRecords: false,
mapsAsObjects: true
}
export class C1Type {}
export const C1 = new C1Type()
C1.name = 'MessagePack 0xC1'
var sequentialMode = false
var inlineObjectReadThreshold = 2
var readStruct, onLoadedStructures, onSaveState
var BlockedFunction // we use search and replace to change the next call to BlockedFunction to avoid CSP issues for
export class Unpackr {
constructor(options) {
if (options) {
if (options.useRecords === false && options.mapsAsObjects === undefined)
options.mapsAsObjects = true
if (options.sequential && options.trusted !== false) {
options.trusted = true;
if (!options.structures && options.useRecords != false) {
options.structures = []
if (!options.maxSharedStructures)
options.maxSharedStructures = 0
}
}
if (options.structures)
options.structures.sharedLength = options.structures.length
else if (options.getStructures) {
(options.structures = []).uninitialized = true // this is what we use to denote an uninitialized structures
options.structures.sharedLength = 0
}
if (options.int64AsNumber) {
options.int64AsType = 'number'
}
}
Object.assign(this, options)
}
unpack(source, options) {
if (src) {
// re-entrant execution, save the state and restore it after we do this unpack
return saveState(() => {
clearSource()
return this ? this.unpack(source, options) : Unpackr.prototype.unpack.call(defaultOptions, source, options)
})
}
if (!source.buffer && source.constructor === ArrayBuffer)
source = typeof Buffer !== 'undefined' ? Buffer.from(source) : new Uint8Array(source);
if (typeof options === 'object') {
srcEnd = options.end || source.length
position = options.start || 0
} else {
position = 0
srcEnd = options > -1 ? options : source.length
}
stringPosition = 0
srcStringEnd = 0
srcString = null
strings = EMPTY_ARRAY
bundledStrings = null
src = source
// this provides cached access to the data view for a buffer if it is getting reused, which is a recommend
// technique for getting data from a database where it can be copied into an existing buffer instead of creating
// new ones
try {
dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength))
} catch(error) {
// if it doesn't have a buffer, maybe it is the wrong type of object
src = null
if (source instanceof Uint8Array)
throw error
throw new Error('Source must be a Uint8Array or Buffer but was a ' + ((source && typeof source == 'object') ? source.constructor.name : typeof source))
}
if (this instanceof Unpackr) {
currentUnpackr = this
if (this.structures) {
currentStructures = this.structures
return checkedRead(options)
} else if (!currentStructures || currentStructures.length > 0) {
currentStructures = []
}
} else {
currentUnpackr = defaultOptions
if (!currentStructures || currentStructures.length > 0)
currentStructures = []
}
return checkedRead(options)
}
unpackMultiple(source, forEach) {
let values, lastPosition = 0
try {
sequentialMode = true
let size = source.length
let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size)
if (forEach) {
if (forEach(value, lastPosition, position) === false) return;
while(position < size) {
lastPosition = position
if (forEach(checkedRead(), lastPosition, position) === false) {
return
}
}
}
else {
values = [ value ]
while(position < size) {
lastPosition = position
values.push(checkedRead())
}
return values
}
} catch(error) {
error.lastPosition = lastPosition
error.values = values
throw error
} finally {
sequentialMode = false
clearSource()
}
}
_mergeStructures(loadedStructures, existingStructures) {
if (onLoadedStructures)
loadedStructures = onLoadedStructures.call(this, loadedStructures);
loadedStructures = loadedStructures || []
if (Object.isFrozen(loadedStructures))
loadedStructures = loadedStructures.map(structure => structure.slice(0))
for (let i = 0, l = loadedStructures.length; i < l; i++) {
let structure = loadedStructures[i]
if (structure) {
structure.isShared = true
if (i >= 32)
structure.highByte = (i - 32) >> 5
}
}
loadedStructures.sharedLength = loadedStructures.length
for (let id in existingStructures || []) {
if (id >= 0) {
let structure = loadedStructures[id]
let existing = existingStructures[id]
if (existing) {
if (structure)
(loadedStructures.restoreStructures || (loadedStructures.restoreStructures = []))[id] = structure
loadedStructures[id] = existing
}
}
}
return this.structures = loadedStructures
}
decode(source, options) {
return this.unpack(source, options)
}
}
export function getPosition() {
return position
}
export function checkedRead(options) {
try {
if (!currentUnpackr.trusted && !sequentialMode) {
let sharedLength = currentStructures.sharedLength || 0
if (sharedLength < currentStructures.length)
currentStructures.length = sharedLength
}
let result
if (currentUnpackr.randomAccessStructure && src[position] < 0x40 && src[position] >= 0x20 && readStruct) {
result = readStruct(src, position, srcEnd, currentUnpackr)
src = null // dispose of this so that recursive unpack calls don't save state
if (!(options && options.lazy) && result)
result = result.toJSON()
position = srcEnd
} else
result = read()
if (bundledStrings) { // bundled strings to skip past
position = bundledStrings.postBundlePosition
bundledStrings = null
}
if (sequentialMode)
// we only need to restore the structures if there was an error, but if we completed a read,
// we can clear this out and keep the structures we read
currentStructures.restoreStructures = null
if (position == srcEnd) {
// finished reading this source, cleanup references
if (currentStructures && currentStructures.restoreStructures)
restoreStructures()
currentStructures = null
src = null
if (referenceMap)
referenceMap = null
} else if (position > srcEnd) {
// over read
throw new Error('Unexpected end of MessagePack data')
} else if (!sequentialMode) {
let jsonView;
try {
jsonView = JSON.stringify(result, (_, value) => typeof value === "bigint" ? `${value}n` : value).slice(0, 100)
} catch(error) {
jsonView = '(JSON view not available ' + error + ')'
}
throw new Error('Data read, but end of buffer not reached ' + jsonView)
}
// else more to read, but we are reading sequentially, so don't clear source yet
return result
} catch(error) {
if (currentStructures && currentStructures.restoreStructures)
restoreStructures()
clearSource()
if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer') || position > srcEnd) {
error.incomplete = true
}
throw error
}
}
function restoreStructures() {
for (let id in currentStructures.restoreStructures) {
currentStructures[id] = currentStructures.restoreStructures[id]
}
currentStructures.restoreStructures = null
}
export function read() {
let token = src[position++]
if (token < 0xa0) {
if (token < 0x80) {
if (token < 0x40)
return token
else {
let structure = currentStructures[token & 0x3f] ||
currentUnpackr.getStructures && loadStructures()[token & 0x3f]
if (structure) {
if (!structure.read) {
structure.read = createStructureReader(structure, token & 0x3f)
}
return structure.read()
} else
return token
}
} else if (token < 0x90) {
// map
token -= 0x80
if (currentUnpackr.mapsAsObjects) {
let object = {}
for (let i = 0; i < token; i++) {
let key = readKey()
if (key === '__proto__')
key = '__proto_'
object[key] = read()
}
return object
} else {
let map = new Map()
for (let i = 0; i < token; i++) {
map.set(read(), read())
}
return map
}
} else {
token -= 0x90
let array = new Array(token)
for (let i = 0; i < token; i++) {
array[i] = read()
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
} else if (token < 0xc0) {
// fixstr
let length = token - 0xa0
if (srcStringEnd >= position) {
return srcString.slice(position - srcStringStart, (position += length) - srcStringStart)
}
if (srcStringEnd == 0 && srcEnd < 140) {
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length)
if (string != null)
return string
}
return readFixedString(length)
} else {
let value
switch (token) {
case 0xc0: return null
case 0xc1:
if (bundledStrings) {
value = read() // followed by the length of the string in characters (not bytes!)
if (value > 0)
return bundledStrings[1].slice(bundledStrings.position1, bundledStrings.position1 += value)
else
return bundledStrings[0].slice(bundledStrings.position0, bundledStrings.position0 -= value)
}
return C1; // "never-used", return special object to denote that
case 0xc2: return false
case 0xc3: return true
case 0xc4:
// bin 8
value = src[position++]
if (value === undefined)
throw new Error('Unexpected end of buffer')
return readBin(value)
case 0xc5:
// bin 16
value = dataView.getUint16(position)
position += 2
return readBin(value)
case 0xc6:
// bin 32
value = dataView.getUint32(position)
position += 4
return readBin(value)
case 0xc7:
// ext 8
return readExt(src[position++])
case 0xc8:
// ext 16
value = dataView.getUint16(position)
position += 2
return readExt(value)
case 0xc9:
// ext 32
value = dataView.getUint32(position)
position += 4
return readExt(value)
case 0xca:
value = dataView.getFloat32(position)
if (currentUnpackr.useFloat32 > 2) {
// this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
let multiplier = mult10[((src[position] & 0x7f) << 1) | (src[position + 1] >> 7)]
position += 4
return ((multiplier * value + (value > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
position += 4
return value
case 0xcb:
value = dataView.getFloat64(position)
position += 8
return value
// uint handlers
case 0xcc:
return src[position++]
case 0xcd:
value = dataView.getUint16(position)
position += 2
return value
case 0xce:
value = dataView.getUint32(position)
position += 4
return value
case 0xcf:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getUint32(position) * 0x100000000
value += dataView.getUint32(position + 4)
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigUint64(position).toString()
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigUint64(position)
if (value<=BigInt(2)<<BigInt(52)) value=Number(value)
} else
value = dataView.getBigUint64(position)
position += 8
return value
// int handlers
case 0xd0:
return dataView.getInt8(position++)
case 0xd1:
value = dataView.getInt16(position)
position += 2
return value
case 0xd2:
value = dataView.getInt32(position)
position += 4
return value
case 0xd3:
if (currentUnpackr.int64AsType === 'number') {
value = dataView.getInt32(position) * 0x100000000
value += dataView.getUint32(position + 4)
} else if (currentUnpackr.int64AsType === 'string') {
value = dataView.getBigInt64(position).toString()
} else if (currentUnpackr.int64AsType === 'auto') {
value = dataView.getBigInt64(position)
if (value>=BigInt(-2)<<BigInt(52)&&value<=BigInt(2)<<BigInt(52)) value=Number(value)
} else
value = dataView.getBigInt64(position)
position += 8
return value
case 0xd4:
// fixext 1
value = src[position++]
if (value == 0x72) {
return recordDefinition(src[position++] & 0x3f)
} else {
let extension = currentExtensions[value]
if (extension) {
if (extension.read) {
position++ // skip filler byte
return extension.read(read())
} else if (extension.noBuffer) {
position++ // skip filler byte
return extension()
} else
return extension(src.subarray(position, ++position))
} else
throw new Error('Unknown extension ' + value)
}
case 0xd5:
// fixext 2
value = src[position]
if (value == 0x72) {
position++
return recordDefinition(src[position++] & 0x3f, src[position++])
} else
return readExt(2)
case 0xd6:
// fixext 4
return readExt(4)
case 0xd7:
// fixext 8
return readExt(8)
case 0xd8:
// fixext 16
return readExt(16)
case 0xd9:
// str 8
value = src[position++]
if (srcStringEnd >= position) {
return srcString.slice(position - srcStringStart, (position += value) - srcStringStart)
}
return readString8(value)
case 0xda:
// str 16
value = dataView.getUint16(position)
position += 2
if (srcStringEnd >= position) {
return srcString.slice(position - srcStringStart, (position += value) - srcStringStart)
}
return readString16(value)
case 0xdb:
// str 32
value = dataView.getUint32(position)
position += 4
if (srcStringEnd >= position) {
return srcString.slice(position - srcStringStart, (position += value) - srcStringStart)
}
return readString32(value)
case 0xdc:
// array 16
value = dataView.getUint16(position)
position += 2
return readArray(value)
case 0xdd:
// array 32
value = dataView.getUint32(position)
position += 4
return readArray(value)
case 0xde:
// map 16
value = dataView.getUint16(position)
position += 2
return readMap(value)
case 0xdf:
// map 32
value = dataView.getUint32(position)
position += 4
return readMap(value)
default: // negative int
if (token >= 0xe0)
return token - 0x100
if (token === undefined) {
let error = new Error('Unexpected end of MessagePack data')
error.incomplete = true
throw error
}
throw new Error('Unknown MessagePack token ' + token)
}
}
}
const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/
function createStructureReader(structure, firstId) {
function readObject() {
// This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function
if (readObject.count++ > inlineObjectReadThreshold) {
let optimizedReadObject
try {
optimizedReadObject = structure.read = (new Function('r', 'return function(){return ' + (currentUnpackr.freezeData ? 'Object.freeze' : '') +
'({' + structure.map(key => key === '__proto__' ? '__proto_:r()' : validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '})}'))(read)
} catch(error) {
// in CF workers, the new Function call could begin to fail at any point in time
inlineObjectReadThreshold = Infinity // disable going forward
return readObject(); // recursively try again
}
structure.read0 = optimizedReadObject // keep the un-wrapped body reader in sync
if (structure.highByte === 0)
structure.read = createSecondByteReader(firstId, structure.read)
return optimizedReadObject() // second byte is already read, if there is one so immediately read object
}
let object = {}
for (let i = 0, l = structure.length; i < l; i++) {
let key = structure[i]
if (key === '__proto__')
key = '__proto_'
object[key] = read()
}
if (currentUnpackr.freezeData)
return Object.freeze(object);
return object
}
readObject.count = 0
// read0 is the un-wrapped body reader: it reads the record's values directly without
// consuming a leading high byte. recordDefinition uses it for the immediate read that follows
// a record definition (the high byte, if present, was already consumed). For highByte === 0
// structures the public reader is a second-byte reader (used by later references), but the
// definition read itself must not consume that byte.
structure.read0 = readObject
if (structure.highByte === 0) {
return createSecondByteReader(firstId, readObject)
}
return readObject
}
const createSecondByteReader = (firstId, read0) => {
return function() {
let highByte = src[position++]
if (highByte === 0)
return read0()
let id = firstId < 32 ? -(firstId + (highByte << 5)) : firstId + (highByte << 5)
let structure = currentStructures[id] || loadStructures()[id]
if (!structure) {
throw new Error('Record id is not defined for ' + id)
}
if (!structure.read)
structure.read = createStructureReader(structure, firstId)
return structure.read()
}
}
export function loadStructures() {
let loadedStructures = saveState(() => {
// save the state in case getStructures modifies our buffer
src = null
return currentUnpackr.getStructures()
})
return currentStructures = currentUnpackr._mergeStructures(loadedStructures, currentStructures)
}
var readFixedString = readStringJS
var readString8 = readStringJS
var readString16 = readStringJS
var readString32 = readStringJS
export let isNativeAccelerationEnabled = false
export function setExtractor(extractStrings) {
isNativeAccelerationEnabled = true
readFixedString = readString(1)
readString8 = readString(2)
readString16 = readString(3)
readString32 = readString(5)
function readString(headerLength) {
return function readString(length) {
let string = strings[stringPosition++]
if (string == null) {
if (bundledStrings)
return readStringJS(length)
let byteOffset = src.byteOffset
let extraction = extractStrings(position - headerLength + byteOffset, srcEnd + byteOffset, src.buffer)
if (typeof extraction == 'string') {
string = extraction
strings = EMPTY_ARRAY
} else {
strings = extraction
stringPosition = 1
srcStringEnd = 1 // even if a utf-8 string was decoded, must indicate we are in the midst of extracted strings and can't skip strings
string = strings[0]
if (string === undefined)
throw new Error('Unexpected end of buffer')
}
}
let srcStringLength = string.length
if (srcStringLength <= length) {
position += length
return string
}
srcString = string
srcStringStart = position
srcStringEnd = position + srcStringLength
position += length
return string.slice(0, length) // we know we just want the beginning
}
}
}
function readStringJS(length) {
let result
if (length < 16) {
if (result = shortStringInJS(length))
return result
}
if (length > 64 && decoder)
return decoder.decode(src.subarray(position, position += length))
const end = position + length
const units = []
result = ''
while (position < end) {
const byte1 = src[position++]
if ((byte1 & 0x80) === 0) {
// 1 byte
units.push(byte1)
} else if ((byte1 & 0xe0) === 0xc0) {
// 2 bytes
const byte2 = src[position++] & 0x3f
const codePoint = ((byte1 & 0x1f) << 6) | byte2
// Reject overlong encoding: 2-byte sequences must encode values >= 0x80
if (codePoint < 0x80) {
units.push(0xFFFD) // replacement character
} else {
units.push(codePoint)
}
} else if ((byte1 & 0xf0) === 0xe0) {
// 3 bytes
const byte2 = src[position++] & 0x3f
const byte3 = src[position++] & 0x3f
const codePoint = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3
// Reject overlong encoding: 3-byte sequences must encode values >= 0x800
// Also reject surrogates (0xD800-0xDFFF)
if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
units.push(0xFFFD) // replacement character
} else {
units.push(codePoint)
}
} else if ((byte1 & 0xf8) === 0xf0) {
// 4 bytes
const byte2 = src[position++] & 0x3f
const byte3 = src[position++] & 0x3f
const byte4 = src[position++] & 0x3f
let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4
// Reject overlong encoding: 4-byte sequences must encode values >= 0x10000
// Also reject values > 0x10FFFF (maximum valid Unicode)
if (unit < 0x10000 || unit > 0x10FFFF) {
units.push(0xFFFD) // replacement character
} else if (unit > 0xffff) {
unit -= 0x10000
units.push(((unit >>> 10) & 0x3ff) | 0xd800)
unit = 0xdc00 | (unit & 0x3ff)
units.push(unit)
} else {
units.push(unit)
}
} else {
units.push(0xFFFD) // replacement character for invalid lead byte
}
if (units.length >= 0x1000) {
result += fromCharCode.apply(String, units)
units.length = 0
}
}
if (units.length > 0) {
result += fromCharCode.apply(String, units)
}
return result
}
export function readString(source, start, length) {
let existingSrc = src;
src = source;
position = start;
try {
return readStringJS(length);
} finally {
src = existingSrc;
}
}
function readArray(length) {
let array = new Array(length)
for (let i = 0; i < length; i++) {
array[i] = read()
}
if (currentUnpackr.freezeData)
return Object.freeze(array)
return array
}
function readMap(length) {
if (currentUnpackr.mapsAsObjects) {
let object = {}
for (let i = 0; i < length; i++) {
let key = readKey()
if (key === '__proto__')
key = '__proto_';
object[key] = read()
}
return object
} else {
let map = new Map()
for (let i = 0; i < length; i++) {
map.set(read(), read())
}
return map
}
}
var fromCharCode = String.fromCharCode
function longStringInJS(length) {
let start = position
let bytes = new Array(length)
for (let i = 0; i < length; i++) {
const byte = src[position++];
if ((byte & 0x80) > 0) {
position = start
return
}
bytes[i] = byte
}
return fromCharCode.apply(String, bytes)
}
function shortStringInJS(length) {
if (length < 4) {
if (length < 2) {
if (length === 0)
return ''
else {
let a = src[position++]
if ((a & 0x80) > 1) {
position -= 1
return
}
return fromCharCode(a)
}
} else {
let a = src[position++]
let b = src[position++]
if ((a & 0x80) > 0 || (b & 0x80) > 0) {
position -= 2
return
}
if (length < 3)
return fromCharCode(a, b)
let c = src[position++]
if ((c & 0x80) > 0) {
position -= 3
return
}
return fromCharCode(a, b, c)
}
} else {
let a = src[position++]
let b = src[position++]
let c = src[position++]
let d = src[position++]
if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
position -= 4
return
}
if (length < 6) {
if (length === 4)
return fromCharCode(a, b, c, d)
else {
let e = src[position++]
if ((e & 0x80) > 0) {
position -= 5
return
}
return fromCharCode(a, b, c, d, e)
}
} else if (length < 8) {
let e = src[position++]
let f = src[position++]
if ((e & 0x80) > 0 || (f & 0x80) > 0) {
position -= 6
return
}
if (length < 7)
return fromCharCode(a, b, c, d, e, f)
let g = src[position++]
if ((g & 0x80) > 0) {
position -= 7
return
}
return fromCharCode(a, b, c, d, e, f, g)
} else {
let e = src[position++]
let f = src[position++]
let g = src[position++]
let h = src[position++]
if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) {
position -= 8
return
}
if (length < 10) {
if (length === 8)
return fromCharCode(a, b, c, d, e, f, g, h)
else {
let i = src[position++]
if ((i & 0x80) > 0) {
position -= 9
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i)
}
} else if (length < 12) {
let i = src[position++]
let j = src[position++]
if ((i & 0x80) > 0 || (j & 0x80) > 0) {
position -= 10
return
}
if (length < 11)
return fromCharCode(a, b, c, d, e, f, g, h, i, j)
let k = src[position++]
if ((k & 0x80) > 0) {
position -= 11
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k)
} else {
let i = src[position++]
let j = src[position++]
let k = src[position++]
let l = src[position++]
if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) {
position -= 12
return
}
if (length < 14) {
if (length === 12)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l)
else {
let m = src[position++]
if ((m & 0x80) > 0) {
position -= 13
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m)
}
} else {
let m = src[position++]
let n = src[position++]
if ((m & 0x80) > 0 || (n & 0x80) > 0) {
position -= 14
return
}
if (length < 15)
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n)
let o = src[position++]
if ((o & 0x80) > 0) {
position -= 15
return
}
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
}
}
}
}
}
function readOnlyJSString() {
let token = src[position++]
let length
if (token < 0xc0) {
// fixstr
length = token - 0xa0
} else {
switch(token) {
case 0xd9:
// str 8
length = src[position++]
break
case 0xda:
// str 16
length = dataView.getUint16(position)
position += 2
break
case 0xdb:
// str 32
length = dataView.getUint32(position)
position += 4
break
default:
throw new Error('Expected string')
}
}
return readStringJS(length)
}
function readBin(length) {
return currentUnpackr.copyBuffers ?
// specifically use the copying slice (not the node one)
Uint8Array.prototype.slice.call(src, position, position += length) :
src.subarray(position, position += length)
}
function readExt(length) {
let type = src[position++]
if (currentExtensions[type]) {
let end
return currentExtensions[type](src.subarray(position, end = (position += length)), (readPosition) => {
position = readPosition;
try {
return read();
} finally {
position = end;
}
})
}
else
throw new Error('Unknown extension type ' + type)
}
var keyCache = new Array(4096)
function readKey() {
let length = src[position++]
if (length >= 0xa0 && length < 0xc0) {
// fixstr, potentially use key cache
length = length - 0xa0
if (srcStringEnd >= position) // if it has been extracted, must use it (and faster anyway)
return srcString.slice(position - srcStringStart, (position += length) - srcStringStart)
else if (!(srcStringEnd == 0 && srcEnd < 180))
return readFixedString(length)
} else { // not cacheable, go back and do a standard read
position--
return asSafeString(read())
}
let key = ((length << 5) ^ (length > 1 ? dataView.getUint16(position) : length > 0 ? src[position] : 0)) & 0xfff
let entry = keyCache[key]
let checkPosition = position
let end = position + length - 3
let chunk
let i = 0
if (entry && entry.bytes == length) {
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition)
if (chunk != entry[i++]) {
checkPosition = 0x70000000
break
}
checkPosition += 4
}
end += 3
while (checkPosition < end) {
chunk = src[checkPosition++]
if (chunk != entry[i++]) {
checkPosition = 0x70000000
break
}
}
if (checkPosition === end) {
position = checkPosition
return entry.string
}
end -= 3
checkPosition = position
}
entry = []
keyCache[key] = entry
entry.bytes = length
while (checkPosition < end) {
chunk = dataView.getUint32(checkPosition)
entry.push(chunk)
checkPosition += 4
}
end += 3
while (checkPosition < end) {
chunk = src[checkPosition++]
entry.push(chunk)
}
// for small blocks, avoiding the overhead of the extract call is helpful
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length)
if (string != null)
return entry.string = string
return entry.string = readFixedString(length)
}
function asSafeString(property) {
// protect against expensive (DoS) string conversions
if (typeof property === 'string') return property;
if (typeof property === 'number' || typeof property === 'boolean' || typeof property === 'bigint') return property.toString();
if (property == null) return property + '';
if (currentUnpackr.allowArraysInMapKeys && Array.isArray(property) && property.flat().every(item => ['string', 'number', 'boolean', 'bigint'].includes(typeof item))) {
return property.flat().toString();
}
throw new Error(`Invalid property type for record: ${typeof property}`);
}
// the registration of the record definition extension (as "r")
const recordDefinition = (id, highByte) => {
let structure = read().map(asSafeString) // ensure that all keys are strings and
// that the array is mutable
let firstByte = id
if (highByte !== undefined) {
id = id < 32 ? -((highByte << 5) + id) : ((highByte << 5) + id)
structure.highByte = highByte
}
let existingStructure = currentStructures[id]
// If it is a shared structure, we need to restore any changes after reading.
// Also in sequential mode, we may get incomplete reads and thus errors, and we need to restore
// to the state prior to an incomplete read in order to properly resume.
if (existingStructure && (existingStructure.isShared || sequentialMode)) {
(currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure
}
currentStructures[id] = structure
structure.read = createStructureReader(structure, firstByte)
// The high byte (if any) was already consumed as the `highByte` argument above, so read the
// record body directly. Going through structure.read (a second-byte reader when highByte === 0)
// would misinterpret the first value byte as a high byte — corrupting two-byte own-record
// definitions (0xd5 0x72 ...). createStructureReader stashes the un-wrapped body reader on
// structure.read0 precisely for this immediate post-definition read.
return (structure.read0 || structure.read)()
}
currentExtensions[0] = () => {} // notepack defines extension 0 to mean undefined, so use that as the default here
currentExtensions[0].noBuffer = true
currentExtensions[0x42] = data => {
let headLength = (data.byteLength % 8) || 8
let head = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0])
for (let i = 1; i < headLength; i++) {
head <<= BigInt(8)
head += BigInt(data[i])
}
if (data.byteLength !== headLength) {
let view = new DataView(data.buffer, data.byteOffset, data.byteLength)
let decode = (start, end) => {
let length = end - start
if (length <= 40) {
let out = view.getBigUint64(start)
for (let i = start + 8; i < end; i += 8) {
out <<= BigInt(64)
out |= view.getBigUint64(i)
}
return out
}
// if (length === 8) return view.getBigUint64(start)
let middle = start + (length >> 4 << 3)
let left = decode(start, middle)
let right = decode(middle, end)
return (left << BigInt((end - middle) * 8)) | right
}
head = (head << BigInt((view.byteLength - headLength) * 8)) | decode(headLength, view.byteLength)
}
return head
}
let errors = {
Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError: typeof AggregateError === 'function' ? AggregateError : null,
}
currentExtensions[0x65] = () => {
let data = read()
if (!errors[data[0]]) {
let error = Error(data[1], { cause: data[2] })
error.name = data[0]
return error
}
return errors[data[0]](data[1], { cause: data[2] })
}
currentExtensions[0x69] = (data) => {
// id extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position - 4)
if (!referenceMap)
referenceMap = new Map()
let token = src[position]
let target
// TODO: handle any other types that can cycle and make the code more robust if there are other extensions
if (token >= 0x90 && token < 0xa0 || token == 0xdc || token == 0xdd)
target = []
else if (token >= 0x80 && token < 0x90 || token == 0xde || token == 0xdf)
target = new Map()
else if ((token >= 0xc7 && token <= 0xc9 || token >= 0xd4 && token <= 0xd8) && src[position + 1] === 0x73)
target = new Set()
else
target = {}
let refEntry = { target } // a placeholder object
referenceMap.set(id, refEntry)
let targetProperties = read() // read the next value as the target object to id
if (!refEntry.used) {
// no cycle, can just use the returned read object
return refEntry.target = targetProperties // replace the placeholder with the real one
} else {
// there is a cycle, so we have to assign properties to original target
Object.assign(target, targetProperties)
}
// copy over map/set entries if we're able to
if (target instanceof Map)
for (let [k, v] of targetProperties.entries()) target.set(k, v)
if (target instanceof Set)
for (let i of Array.from(targetProperties)) target.add(i)
return target
}
currentExtensions[0x70] = (data) => {
// pointer extension (for structured clones)
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
let id = dataView.getUint32(position - 4)
let refEntry = referenceMap.get(id)
refEntry.used = true
return refEntry.target
}
currentExtensions[0x73] = () => new Set(read())
export const typedArrays = ['Int8','Uint8','Uint8Clamped','Int16','Uint16','Int32','Uint32','Float32','Float64','BigInt64','BigUint64'].map(type => type + 'Array')
let glbl = typeof globalThis === 'object' ? globalThis : window;
currentExtensions[0x74] = (data) => {
let typeCode = data[0]
// we always have to slice to get a new ArrayBuffer that is aligned
let buffer = Uint8Array.prototype.slice.call(data, 1).buffer
let typedArrayName = typedArrays[typeCode]
if (!typedArrayName) {
if (typeCode === 16) return buffer
if (typeCode === 17) return new DataView(buffer)
throw new Error('Could not find typed array for code ' + typeCode)
}
return new glbl[typedArrayName](buffer)
}
currentExtensions[0x78] = () => {
let data = read()
return new RegExp(data[0], data[1])
}
const TEMP_BUNDLE = []
currentExtensions[0x62] = (data) => {
let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]
let dataPosition = position
position += dataSize - data.length
bundledStrings = TEMP_BUNDLE
bundledStrings = [readOnlyJSString(), readOnlyJSString()]
bundledStrings.position0 = 0
bundledStrings.position1 = 0
bundledStrings.postBundlePosition = position
position = dataPosition
return read()
}
currentExtensions[0xff] = (data) => {
// 32-bit date extension
if (data.length == 4)
return new Date((data[0] * 0x1000000 + (data[1] << 16) + (data[2] << 8) + data[3]) * 1000)
else if (data.length == 8)
return new Date(
((data[0] << 22) + (data[1] << 14) + (data[2] << 6) + (data[3] >> 2)) / 1000000 +
((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000)
else if (data.length == 12)
return new Date(
((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
(((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000)
else
return new Date('invalid')
}
// registration of bulk record definition?
// currentExtensions[0x52] = () =>
function saveState(callback) {
if (onSaveState)
onSaveState();
let savedSrcEnd = srcEnd
let savedPosition = position
let savedStringPosition = stringPosition
let savedSrcStringStart = srcStringStart
let savedSrcStringEnd = srcStringEnd
let savedSrcString = srcString
let savedStrings = strings
let savedReferenceMap = referenceMap
let savedBundledStrings = bundledStrings
// TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
let savedSrc = new Uint8Array(src.slice(0, srcEnd)) // we copy the data in case it changes while external data is processed
let savedStructures = currentStructures
let savedStructuresContents = currentStructures.slice(0, currentStructures.length)
let savedPackr = currentUnpackr
let savedSequentialMode = sequentialMode
let value = callback()
srcEnd = savedSrcEnd
position = savedPosition
stringPosition = savedStringPosition
srcStringStart = savedSrcStringStart
srcStringEnd = savedSrcStringEnd
srcString = savedSrcString
strings = savedStrings
referenceMap = savedReferenceMap
bundledStrings = savedBundledStrings
src = savedSrc
sequentialMode = savedSequentialMode
currentStructures = savedStructures
currentStructures.splice(0, currentStructures.length, ...savedStructuresContents)
currentUnpackr = savedPackr
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength)
return value
}
export function clearSource() {
src = null
referenceMap = null
currentStructures = null
}
export function addExtension(extension) {
if (extension.unpack)
currentExtensions[extension.type] = extension.unpack
else
currentExtensions[extension.type] = extension
}
export const mult10 = new Array(147) // this is a table matching binary exponents to the multiplier to determine significant digit rounding
for (let i = 0; i < 256; i++) {
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103))
}
export const Decoder = Unpackr
var defaultUnpackr = new Unpackr({ useRecords: false })
export const unpack = defaultUnpackr.unpack
export const unpackMultiple = defaultUnpackr.unpackMultiple
export const decode = defaultUnpackr.unpack
export const FLOAT32_OPTIONS = {
NEVER: 0,
ALWAYS: 1,
DECIMAL_ROUND: 3,
DECIMAL_FIT: 4
}
let f32Array = new Float32Array(1)
let u8Array = new Uint8Array(f32Array.buffer, 0, 4)
export function roundFloat32(float32Number) {
f32Array[0] = float32Number
let multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)]
return ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / multiplier
}
export function setReadStruct(updatedReadStruct, loadedStructs, saveState) {
readStruct = updatedReadStruct;
onLoadedStructures = loadedStructs;
onSaveState = saveState;
}