Comparing version 0.1.3 to 0.2.0
@@ -15,8 +15,2 @@ var wk = function (c, msg, transfer, cb) { | ||
var fnName = function (f) { | ||
if (f.name) | ||
return f.name; | ||
var ts = f.toString(); | ||
return ts.slice(0, 9) == 'function ' && ts.slice(9, ts.indexOf('(', 9)); | ||
}; | ||
var abvList = [ | ||
@@ -36,2 +30,3 @@ Int8Array, | ||
abvList.push(BigUint64Array); | ||
var rand = function () { return Math.ceil(Math.random() * 1073741823); }; | ||
var getAllPropertyKeys = function (o) { | ||
@@ -44,74 +39,139 @@ var keys = Object.getOwnPropertyNames(o); | ||
}; | ||
// optional chaining | ||
var chainWrap = function (name, expr, short) { | ||
return "(" + expr + "||(" + short + "={}))" + name; | ||
var bannedFunctionKeys = getAllPropertyKeys(Function.prototype).concat('prototype'); | ||
var toReplace = '/*r*/null'; | ||
var toReplaceRE = /\/\*r\*\/null/g; | ||
var renderCtx = function (ctx, ab, m) { | ||
var out = 'self'; | ||
for (var _i = 0, ctx_1 = ctx; _i < ctx_1.length; _i++) { | ||
var key = ctx_1[_i]; | ||
out += "[" + encoder[typeof key](key, ab, m) + "]"; | ||
} | ||
return out; | ||
}; | ||
var vDescriptors = function (v, keys, ab, m, g, s, ctx, dat) { | ||
var out = ''; | ||
var renderedCtx = renderCtx(ctx, ab, m); | ||
for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { | ||
var t = keys_1[_i]; | ||
var _a = Object.getOwnPropertyDescriptor(v, t), enumerable = _a.enumerable, configurable = _a.configurable, get = _a.get, set = _a.set, writable = _a.writable, value = _a.value; | ||
var keyEnc = encoder[typeof t](t, ab, m); | ||
var desc = '{', res = void 0; | ||
var enc = typeof writable == 'boolean' && | ||
encoder[typeof value](value, ab, m, g, s, ctx.concat(t), dat); | ||
var replaced = enc == toReplace; | ||
var obj = 'v'; | ||
if (replaced) { | ||
obj = renderedCtx; | ||
dat.pop(); | ||
} | ||
if (enc) { | ||
if (writable && configurable && enumerable) | ||
res = obj + "[" + keyEnc + "]=" + enc; | ||
else | ||
desc += "writable:" + writable + ",value:" + enc; | ||
} | ||
else | ||
desc += "get:" + (get ? encoder["function"](get, ab, m, g, s, ctx, dat) : 'void 0') + ",set:" + (set ? encoder["function"](set, ab, m, g, s, ctx, dat) : 'void 0'); | ||
if (!res) { | ||
desc += ",enumerable:" + enumerable + ",configurable:" + configurable + "}"; | ||
res = "Object.defineProperty(" + obj + ", " + encoder[typeof t](t, ab, m) + ", " + desc + ")"; | ||
} | ||
if (replaced) | ||
dat.push([res + ";", value]); | ||
else | ||
out += ";" + res; | ||
} | ||
if (Object.isSealed(v)) | ||
dat.push("Object.seal(" + renderedCtx + ");"); | ||
if (!Object.isExtensible(v)) | ||
dat.push("Object.preventExtensions(" + renderedCtx + ");"); | ||
if (Object.isFrozen(v)) | ||
dat.push("Object.freeze(" + renderedCtx + ");"); | ||
return out; | ||
}; | ||
var encoder = { | ||
undefined: function () { return 'void 0'; }, | ||
bigint: function (v) { return v.toString() + 'n'; }, | ||
symbol: function (v) { | ||
bigint: function (v) { return v + 'n'; }, | ||
string: function (v) { return JSON.stringify(v); }, | ||
boolean: function (v) { return v + ''; }, | ||
number: function (v) { return v + ''; }, | ||
symbol: function (v, _, m) { | ||
var key = Symbol.keyFor(v); | ||
return key | ||
? "Symbol.for(" + encoder.string(key) + ")" | ||
: "Symbol(" + encoder.string(v.toString().slice(7, -1)) + ")"; | ||
if (key) | ||
return "Symbol.for(" + encoder.string(key) + ")"; | ||
var gbn = m[v]; | ||
if (gbn) | ||
return "self[" + gbn + "]"; | ||
gbn = m[v] = rand(); | ||
return "(self[" + gbn + "]=Symbol(" + encoder.string(v.toString().slice(7, -1)) + "))"; | ||
}, | ||
string: function (v) { return JSON.stringify(v); }, | ||
"function": function (v, reg, ab) { | ||
"function": function (v, ab, m, g, s, ctx, dat) { | ||
var st = v.toString(); | ||
var proto = v.prototype; | ||
if (st.indexOf('[native code]', 12) != -1) | ||
st = fnName(v); | ||
else if (v.prototype) { | ||
var nm = fnName(v); | ||
if (nm) { | ||
if (nm in reg) | ||
return "self[" + encoder.string(nm) + "]"; | ||
reg[nm] = true; | ||
return v.name; | ||
if (st[0] != '(' && !proto) { | ||
var headMatch = st.match(/^(.+?)(?=\()/g); | ||
if (headMatch) | ||
st = 'function' + st.slice(headMatch[0].length); | ||
else | ||
throw new TypeError("failed to find function body in " + st); | ||
} | ||
var vd = vDescriptors(v, getAllPropertyKeys(v).filter(function (key) { return bannedFunctionKeys.indexOf(key) == -1; }), ab, m, g, s, ctx, dat); | ||
var gbn = g(v); | ||
if (gbn) | ||
return "(function(){var v=" + gbn + vd + ";return v})()"; | ||
if (proto) { | ||
var superCtr = Object.getPrototypeOf(proto).constructor; | ||
// TODO: Avoid duplicating methods for ES6 classes | ||
var base = '(function(){'; | ||
if (superCtr == Object) | ||
st = base + "var v=" + st; | ||
else { | ||
var superEnc = encoder["function"](superCtr, ab, m, g, s, ctx.concat('prototype'), dat); | ||
if (st[0] == 'c') { | ||
var superName = st.match(/(?<=^class(.*?)extends(.*?)(\s+))(.+?)(?=(\s*){)/g); | ||
if (!superName) | ||
throw new TypeError("failed to find superclass in " + st); | ||
st = base + "var " + superName[0] + "=" + superEnc + ";var v=" + st; | ||
} | ||
else | ||
st = base + "var v=" + st + ";v.prototype=Object.create(" + superEnc + ")"; | ||
} | ||
if (st[0] != 'c') { | ||
// Not an ES6 class; must iterate across the properties | ||
// Ignore superclass properties, assume superclass is handled elsewhere | ||
st = '(function(){var v=' + st; | ||
for (var _i = 0, _a = getAllPropertyKeys(v.prototype); _i < _a.length; _i++) { | ||
var t = _a[_i]; | ||
var val = v.prototype[t]; | ||
if (t != 'constructor') { | ||
st += ";v[" + encoder[typeof t](t) + "]=" + encoder[typeof val](val, reg, ab); | ||
} | ||
for (var _i = 0, _a = getAllPropertyKeys(proto); _i < _a.length; _i++) { | ||
var t = _a[_i]; | ||
var val = proto[t]; | ||
if (t != 'constructor') { | ||
var key = encoder[typeof t](t, ab, m); | ||
st += ";v.prototype[" + key + "]=" + encoder[typeof val](val, ab, m, g, s, ctx.concat('prototype', key), dat); | ||
} | ||
st += ';return v})()'; | ||
} | ||
st += vd + ";return v})()"; | ||
} | ||
return st; | ||
else if (vd.length) | ||
st = "(function(){var v=" + st + vd + ";return v})()"; | ||
return s(v, st); | ||
}, | ||
object: function (v, reg, ab) { | ||
object: function (v, ab, m, g, s, ctx, dat) { | ||
if (v == null) | ||
return 'null'; | ||
var proto = Object.getPrototypeOf(v); | ||
if (abvList.indexOf(proto.constructor) != -1) { | ||
ab.push(v.buffer); | ||
return v; | ||
var abv; | ||
if (abvList.indexOf(proto.constructor) != -1) | ||
abv = v.buffer; | ||
else if (wk.t.indexOf(proto.constructor) != -1) | ||
abv = v; | ||
if (abv) { | ||
ab.push(abv); | ||
dat.push([renderCtx(ctx, ab, m) + "=" + toReplace + ";", v]); | ||
return toReplace; | ||
} | ||
else if (wk.t.indexOf(proto.constructor) != -1) { | ||
ab.push(v); | ||
return v; | ||
} | ||
var out = ''; | ||
out += "(function(){"; | ||
var classDecl = ''; | ||
for (var i = 0, l = proto; l.constructor != Object; l = Object.getPrototypeOf(l), ++i) { | ||
var cls = l.constructor; | ||
var nm = fnName(cls) || '_cls' + i; | ||
if (nm in reg) | ||
continue; | ||
var enc = encoder["function"](cls, reg, ab); | ||
if (enc == nm) { | ||
break; | ||
} | ||
else { | ||
reg[nm] = true; | ||
classDecl = "self[" + encoder.string(nm) + "]=" + enc + ";" + classDecl; | ||
} | ||
} | ||
var gbn = g(v); | ||
if (gbn) | ||
return gbn; | ||
var out = '(function(){var v='; | ||
var keys = getAllPropertyKeys(v); | ||
if (proto.constructor == Array) { | ||
if (proto.constructor == Object) | ||
out += "{}"; | ||
else if (proto.constructor == Array) { | ||
var arrStr = ''; | ||
@@ -121,3 +181,3 @@ for (var i = 0; i < v.length; ++i) { | ||
var val = v[i]; | ||
arrStr += encoder[typeof val](val, reg, ab); | ||
arrStr += encoder[typeof val](val, ab, m, g, s, ctx.concat(i), dat); | ||
} | ||
@@ -129,26 +189,8 @@ arrStr += ','; | ||
}); | ||
out += "var v=[" + arrStr.slice(0, -1) + "]"; | ||
out += "[" + arrStr.slice(0, -1) + "]"; | ||
} | ||
else { | ||
out += | ||
classDecl + | ||
("var v=Object.create(self[" + encoder.string(fnName(proto.constructor) || '_cls0') + "].prototype);"); | ||
} | ||
for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { | ||
var t = keys_1[_i]; | ||
var _a = Object.getOwnPropertyDescriptor(v, t), enumerable = _a.enumerable, configurable = _a.configurable, get = _a.get, set = _a.set, writable = _a.writable, value = _a.value; | ||
var desc = '{'; | ||
if (typeof writable == 'boolean') { | ||
desc += "writable:" + writable + ",value:" + encoder[typeof value](value, reg, ab); | ||
} | ||
else { | ||
desc += "get:" + (get ? encoder["function"](get, reg, ab) : 'void 0') + "," + (set ? encoder["function"](set, reg, ab) : 'void 0'); | ||
} | ||
desc += ",enumerable:" + enumerable + ",configurable:" + configurable + "}"; | ||
out += "Object.defineProperty(v, " + encoder[typeof t](t) + ", " + desc + ");"; | ||
} | ||
return out + 'return v})()'; | ||
}, | ||
boolean: function (v) { return v.toString(); }, | ||
number: function (v) { return v.toString(); } | ||
else | ||
out += "Object.create(" + encoder["function"](proto.constructor, ab, m, g, s, ctx.concat('constructor'), dat) + ".prototype)"; | ||
return out + vDescriptors(v, keys, ab, m, g, s, ctx, dat) + ';return v})()'; | ||
} | ||
}; | ||
@@ -169,30 +211,33 @@ /** | ||
var out = ''; | ||
var reg = {}; | ||
var dat = {}; | ||
var dat = []; | ||
var ab = []; | ||
var symMap = {}; | ||
var gbnKey = typeof Symbol == 'undefined' ? "__iwgbn" + rand() + "__" : Symbol(); | ||
var getGBN = function (obj) { | ||
var gbn = obj[gbnKey]; | ||
if (gbn) | ||
return "self[" + gbn + "]"; | ||
}; | ||
var setGBN = function (obj, wrap) { | ||
var gbn = rand(); | ||
Object.defineProperty(obj, gbnKey, { | ||
value: gbn | ||
}); | ||
return "(self[" + gbn + "]=" + wrap + ")"; | ||
}; | ||
for (var i = 0; i < depValues.length; ++i) { | ||
var key = depNames[i], value = depValues[i]; | ||
var v = encoder[typeof value](value, ab, symMap, getGBN, setGBN, [key], dat); | ||
var parts = key | ||
.replace(/\\/, '') | ||
.match(/^(.*?)(?=(\.|\[|$))|\[(.*?)\]|(\.(.*?))(?=(\.|\[|$))/g); | ||
var v = encoder[typeof value](value, reg, ab); | ||
if (typeof v == 'string') { | ||
var pfx = 'self.' + parts[0]; | ||
var chain = pfx; | ||
for (var i_1 = 1; i_1 < parts.length; ++i_1) { | ||
chain = chainWrap(parts[i_1], chain, pfx); | ||
pfx += parts[i_1]; | ||
} | ||
out += chain + "=" + v + ";"; | ||
var pfx = 'self.' + parts[0]; | ||
var chain = pfx; | ||
for (var i_1 = 1; i_1 < parts.length; ++i_1) { | ||
chain = "(" + chain + "||(" + pfx + "={}))" + parts[i_1]; | ||
pfx += parts[i_1]; | ||
} | ||
else { | ||
// TODO: overwrite instead of assign | ||
var obj = dat; | ||
for (var i_2 = 0; i_2 < parts.length - 1; ++i_2) { | ||
obj = obj[parts[i_2]] = {}; | ||
} | ||
obj[parts[parts.length - 1]] = v; | ||
} | ||
out += chain + "=" + v + ";"; | ||
} | ||
return [out, dat, ab, reg]; | ||
return [out, dat, ab]; | ||
} | ||
@@ -211,2 +256,11 @@ var findTransferables = function (vals) { | ||
}; | ||
var globalEnv = typeof globalThis == 'undefined' | ||
? typeof window == 'undefined' | ||
? typeof self == 'undefined' | ||
? typeof global == 'undefined' | ||
? {} | ||
: global | ||
: self | ||
: window | ||
: globalThis; | ||
/** | ||
@@ -219,13 +273,57 @@ * Converts a function with dependencies into a worker | ||
* here as well. | ||
* @param replaceTransfer The list of objects to replace the default transfer | ||
* list with. If you provide an array of transferable | ||
* items, they will be used; if you provide the value | ||
* `true`, `isoworker` will refrain from the default | ||
* behavior of automatically transferring everything | ||
* it can. | ||
* @returns A function that accepts parameters and, as the last argument, a | ||
* callback to use when the worker returns. | ||
*/ | ||
export function workerize(fn, deps) { | ||
var _a = createContext(deps), str = _a[0], msg = _a[1], tfl = _a[2], reg = _a[3]; | ||
export function workerize(fn, deps, replaceTransfer) { | ||
var _a = createContext(deps), str = _a[0], exec = _a[1], tfl = _a[2]; | ||
var currentCb; | ||
var runCount = 0; | ||
var callCount = 0; | ||
var worker = wk(str + ";onmessage=function(e){for(var k in e.data){self[k]=e.data[k]}var h=" + encoder["function"](fn, reg, tfl) + ";var _p=function(d){d?typeof d.then=='function'?d.then(_p):postMessage(d,d.__transferList):postMessage(d)};onmessage=function(e){_p(h.apply(self,e.data))}}", msg, tfl, function (err, res) { | ||
var assignStr = ''; | ||
var transfer = (replaceTransfer | ||
? replaceTransfer instanceof Array | ||
? replaceTransfer | ||
: [] | ||
: tfl); | ||
var msg = []; | ||
for (var _i = 0, exec_1 = exec; _i < exec_1.length; _i++) { | ||
var cmd = exec_1[_i]; | ||
if (typeof cmd != 'string') { | ||
cmd = cmd[0].replace(toReplaceRE, "e.data[" + (msg.push(cmd[1]) - 1) + "]"); | ||
} | ||
assignStr += cmd; | ||
} | ||
if (!replaceTransfer) { | ||
var tfKey = typeof Symbol == 'undefined' ? "__iwtf" + rand() + "__" : Symbol(); | ||
for (var i = 0; i < transfer.length; ++i) { | ||
var buf = transfer[i]; | ||
if (buf[tfKey]) | ||
transfer.splice(i--, 1); | ||
buf[tfKey] = 1; | ||
} | ||
} | ||
var worker = wk(str + ";onmessage=function(e){" + assignStr + "var v=" + fn + ";var _p=function(d){typeof d.then=='function'?d.then(_p,_e):postMessage(d,d?d.__transfer:[])};var _e=function(e){!(e instanceof Error)&&(e=new Error(e));postMessage({__iwerr__:{s:e.stack,m:e.message,n:e.name}})};onmessage=function(e){try{_p(v.apply(self,e.data))}catch(e){_e(e)}}}", msg, transfer, function (err, res) { | ||
++runCount; | ||
currentCb(err, res); | ||
var rtErr = res && | ||
res.__iwerr__; | ||
if (rtErr) { | ||
err = new (globalEnv[rtErr.n] || Error)(); | ||
err.message = rtErr.m; | ||
err.name = rtErr.n; | ||
err.stack = rtErr.s; | ||
currentCb(err, null); | ||
} | ||
else if (err) { | ||
for (; runCount <= callCount; ++runCount) | ||
currentCb(err, res); | ||
wfn.close(); | ||
} | ||
else | ||
currentCb(err, res); | ||
}); | ||
@@ -232,0 +330,0 @@ var closed = false; |
@@ -1,9 +0,2 @@ | ||
import { WorkerTransfer } from './node-worker'; | ||
export declare type Registry = Record<string, boolean>; | ||
export declare type Context = [ | ||
string, | ||
Record<string, unknown>, | ||
WorkerTransfer[], | ||
Registry | ||
]; | ||
export declare type Context = [string, Array<string | [string, unknown]>, unknown[]]; | ||
export declare type DepList = () => unknown[]; | ||
@@ -34,5 +27,11 @@ /** | ||
* here as well. | ||
* @param replaceTransfer The list of objects to replace the default transfer | ||
* list with. If you provide an array of transferable | ||
* items, they will be used; if you provide the value | ||
* `true`, `isoworker` will refrain from the default | ||
* behavior of automatically transferring everything | ||
* it can. | ||
* @returns A function that accepts parameters and, as the last argument, a | ||
* callback to use when the worker returns. | ||
*/ | ||
export declare function workerize<TA extends unknown[], TR>(fn: (...args: TA) => TR, deps: DepList): Workerized<TA, TR>; | ||
export declare function workerize<TA extends unknown[], TR>(fn: (...args: TA) => TR, deps: DepList, replaceTransfer?: unknown[] | boolean): Workerized<TA, TR>; |
{ | ||
"name": "isoworker", | ||
"version": "0.1.3", | ||
"version": "0.2.0", | ||
"description": "Isomorphic workerization with dependencies", | ||
@@ -30,3 +30,4 @@ "main": "./lib/index.cjs", | ||
"scripts": { | ||
"build": "yarn lint && tsc && tsc --project tsconfig.esm.json && node -r ts-node/register scripts/rewriteBuilds.ts", | ||
"build": "yarn lint && yarn build:lib", | ||
"build:lib": "tsc && tsc --project tsconfig.esm.json && node -r ts-node/register scripts/rewriteBuilds.ts", | ||
"lint": "eslint --fix \"src/**/*.ts\"", | ||
@@ -33,0 +34,0 @@ "prepack": "yarn build" |
@@ -9,3 +9,3 @@ # isoworker | ||
If you're not experienced with build tools or are creating a library, however, using worker threads is virtually impossible. Nothing works on all platforms, with all build tools, and doesn't require embedding the codebase as a string. That's why most "asynchronous" packages such as [JSZip](https://github.com/Stuk/jszip) still run on the main thread and still hang the browser when doing a lot of work. | ||
If you're not experienced with build tools or are creating a library, however, using worker threads is virtually impossible. Nothing works on all platforms, with all build tools, and doesn't require embedding the codebase as a string. That's why most "asynchronous" packages such as [JSZip](https://github.com/Stuk/jszip) still run on the main thread in an event loop. While running in the event loop is fine, it doesn't allow your JS to take advantage of multiple CPU cores. | ||
@@ -65,5 +65,3 @@ This package abstracts all difficulties away by making your standard functions magically run in a separate thread in all environments. You don't even need a new file to run your code, and unlike other workerization packages, you can actually call other functions and use variables from your worker. | ||
// got 0 from worker | ||
asyncCount((err, res) => { | ||
asyncCount.close(); | ||
}); | ||
asyncCount(() => {}); | ||
// 1 | ||
@@ -74,2 +72,6 @@ | ||
console.log(number); // 0 | ||
// When you're finished using the function, call .close() to free the | ||
// resources used by the worker thread | ||
asyncCount.close(); | ||
``` | ||
@@ -83,3 +85,3 @@ | ||
// Promises are automatically resolved, so using async/await is fine | ||
const runWasmSync = async (wasmName, method, ...args) => { | ||
const runWasmMainThread = async (wasmName, method, ...args) => { | ||
if (!wasm[wasmName]) { | ||
@@ -92,3 +94,3 @@ wasm[wasmName] = (await WebAssembly.instantiateStreaming( | ||
} | ||
const runWasm = workerize(runWasmSync, () => [wasm]); | ||
const runWasm = workerize(runWasmMainThread, () => [wasm]); | ||
@@ -105,3 +107,82 @@ // If /wasm-files/hello.wasm exports a sayHelloTo method | ||
The workerizer supports complex types (including symbols, functions, classes, and instances of those classes) with infinite nesting thanks to a nifty recursive serializer. | ||
```js | ||
// ES5 style class works | ||
function Example1() { | ||
this.y = 2; | ||
} | ||
Example1.prototype.z = function() { | ||
return this.y * 2; | ||
} | ||
function Example2() { | ||
this.x = 3; | ||
Example1.call(this); | ||
} | ||
// Prototypal inheritance/extension works | ||
Example2.prototype = Object.create(Example1.prototype); | ||
// Normal extension works as well | ||
class OtherClass extends Example2 { | ||
constructor() { | ||
super(); | ||
console.log('Created an OtherClass'); | ||
} | ||
getResult() { | ||
return 'z() = ' + this.z(); | ||
} | ||
} | ||
const dat = new OtherClass(); // Created an OtherClass | ||
const getZ = workerize(() => { | ||
// On the worker thread, now dat.y is increased by dat.x | ||
dat.y += dat.x; | ||
return dat.getResult(); | ||
}, () => [dat]); | ||
// Note than when doing this, "Created an OtherClass" is not logged | ||
// Your classes and objects are created without construction or mutation | ||
getZ((err, result) => console.log(result)) // z() = 10 | ||
getZ((err, result) => console.log(result)) // z() = 16 | ||
// Nothing changed on the main thread | ||
console.log(dat.y) // 2 | ||
console.log(dat.getResult()); // z() = 4 | ||
``` | ||
If you need to maximize performance and know how to use [Transferables](https://developer.mozilla.org/en-US/docs/Web/API/Transferable), you can set a list of transferables by returning `__transfer` in your workerized function. | ||
```js | ||
// Since Uint8Array and Math.random() are in the global environment, | ||
// they don't need to be added to the dependency list | ||
const getRandomBuffer = workerize((bufLen) => { | ||
if (bufLen > 2 ** 30) { | ||
throw new TypeError('cannot create over 1GB random values'); | ||
} | ||
const buf = new Uint8Array(bufLen); | ||
for (let i = 0; i < bufLen; ++i) { | ||
// Uint8Array automatically takes the floor | ||
buf[i] = Math.random() * 256; | ||
} | ||
buf.__transfer = [buf.buffer]; | ||
return buf; | ||
}, () => []); | ||
getRandomBuffer(2 ** 28, (err, result) => { | ||
console.log(err); // null | ||
console.log(result); // Uint8Array(1073741824) [ ... ] | ||
}); | ||
getRandomBuffer(2 ** 31, (err, result) => { | ||
console.log(err); // TypeError: cannot process over 1GB | ||
console.log(result); // null | ||
}); | ||
``` | ||
If you're a library author, you may want to use the context creation API but don't need the workerization support. In that case, use `createContext(() => [dep1, dep2])` and use the return value `[code, initMessage, transferList]`. Take a look at the source code to understand how to use these. Effectively, `code` encodes most of the dependencies, `initMessage` contains code to be executed on the first message (and occasionally values that must be passed to that code), and `transferList` is the array of `Transferable`s that can optionally be transferred in the initialization message for much better initialization performance at the cost of breaking the implementation on the main thread if it depends on values that were transferred. | ||
## License | ||
MIT |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
80176
1820
182
0