async-call-rpc
Advanced tools
Comparing version 2.0.2 to 3.0.0
@@ -5,2 +5,24 @@ # Changelog | ||
## [3.0.0](https://github.com/Jack-Works/async-call/compare/v2.0.2...v3.0.0) (2020-06-27) | ||
### ⚠ BREAKING CHANGES | ||
- Enable strict mode by default, if you need the non-strict behavior, switch "strict" to false. | ||
- Move `strict.noUndefined` to 3rd parameter of JSONSerialization since only JSON needs it. | ||
- The export version changed from `full` to `base`. If you need full version, please import from `async-call-rpc/full`. | ||
### Features | ||
* Add hook for custom Error data close [#8](https://github.com/Jack-Works/async-call/issues/8) ([2ab36bb](https://github.com/Jack-Works/async-call/commit/2ab36bb06c259ca7161a79f4ea649e15939f0966)) | ||
* change to strict by default, move undefined keeping to JSONSerialization ([d860da5](https://github.com/Jack-Works/async-call/commit/d860da52a88279fbadbb44982009ffc947426437)) | ||
* export base version by default ([6a11550](https://github.com/Jack-Works/async-call/commit/6a115507197a79694cd94a3dab6a517f913ff8ab)) | ||
* id generator, close [#13](https://github.com/Jack-Works/async-call/issues/13) ([d3c51b5](https://github.com/Jack-Works/async-call/commit/d3c51b59a7876bd0f14a76d8ee40a8dade5c65f2)) | ||
* preserve this binding, close [#16](https://github.com/Jack-Works/async-call/issues/16) ([69f1077](https://github.com/Jack-Works/async-call/commit/69f1077b6308e36aa99870c0257f2ed33897aef8)) | ||
### Bug Fixes | ||
* server ignore sendLocalStack. close [#18](https://github.com/Jack-Works/async-call/issues/18) ([25629d3](https://github.com/Jack-Works/async-call/commit/25629d3f8ad74d23fb8a23184927117abf1ff725)) | ||
### [2.0.2](https://github.com/Jack-Works/async-call/compare/v2.0.1...v2.0.2) (2020-06-10) | ||
@@ -7,0 +29,0 @@ |
@@ -28,6 +28,14 @@ /** | ||
* @param space - Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. | ||
* @param undefinedKeepingBehavior - How to keep "undefined" in result of SuccessResponse? | ||
* | ||
* If it is not handled properly, JSON.stringify will emit an invalid JSON RPC object. | ||
* | ||
* Options: | ||
* - `"null"`(**default**): convert it to null. | ||
* - `"keep"`: try to keep it by additional property "undef". | ||
* - `false`: Don't keep it, let it break. | ||
* @remarks {@link Serialization} | ||
* @public | ||
*/ | ||
declare const JSONSerialization: (replacerAndReceiver?: [Parameters<JSON['stringify']>[1], Parameters<JSON['parse']>[1]], space?: string | number | undefined) => Serialization; | ||
declare const JSONSerialization: (replacerAndReceiver?: [(((key: string, value: any) => any) | undefined)?, (((key: string, value: any) => any) | undefined)?], space?: string | number | undefined, undefinedKeepingBehavior?: 'keep' | 'null' | false) => Serialization; | ||
@@ -39,7 +47,7 @@ /** | ||
interface Console { | ||
debug(...args: unknown[]): void; | ||
debug?(...args: unknown[]): void; | ||
log(...args: unknown[]): void; | ||
groupCollapsed(...args: unknown[]): void; | ||
groupEnd(...args: unknown[]): void; | ||
error(...args: unknown[]): void; | ||
groupCollapsed?(...args: unknown[]): void; | ||
groupEnd?(...args: unknown[]): void; | ||
error?(...args: unknown[]): void; | ||
} | ||
@@ -84,14 +92,9 @@ | ||
/** | ||
* Return an error when the requested method is not defined | ||
* @defaultValue false | ||
* Return an error when the requested method is not defined, otherwise, ignore the request. | ||
* @defaultValue true | ||
*/ | ||
methodNotFound?: boolean; | ||
/** | ||
* don't try to keep `undefined` result (then it will be `null`) | ||
* @defaultValue false | ||
*/ | ||
noUndefined?: boolean; | ||
/** | ||
* send an error when receive invalid JSON RPC payload | ||
* @defaultValue false | ||
* @defaultValue true | ||
*/ | ||
@@ -101,2 +104,10 @@ unknownMessage?: boolean; | ||
/** | ||
* The message channel interface that allows | ||
* @public | ||
*/ | ||
interface MessageChannel { | ||
on(event: string, eventListener: (data: unknown) => void): void; | ||
emit(event: string, data: unknown): void; | ||
} | ||
/** | ||
* Options for {@link AsyncCall} | ||
@@ -123,3 +134,3 @@ * @public | ||
*/ | ||
key: string; | ||
key?: string; | ||
/** | ||
@@ -138,3 +149,3 @@ * How to serialization and deserialization JSON RPC payload | ||
*/ | ||
serializer: Serialization; | ||
serializer?: Serialization; | ||
/** | ||
@@ -146,3 +157,3 @@ * The logger of AsyncCall | ||
*/ | ||
logger: Console; | ||
logger?: Console; | ||
/** | ||
@@ -162,6 +173,3 @@ * The message channel can let you transport messages between server and client | ||
*/ | ||
messageChannel: { | ||
on(event: string, callback: (data: unknown) => void): void; | ||
emit(event: string, data: unknown): void; | ||
}; | ||
messageChannel: MessageChannel; | ||
/** | ||
@@ -171,3 +179,3 @@ * Choose log level. See {@link AsyncCallLogLevel} | ||
*/ | ||
log: AsyncCallLogLevel | boolean; | ||
log?: AsyncCallLogLevel | boolean; | ||
/** | ||
@@ -177,3 +185,3 @@ * Strict options. See {@link AsyncCallStrictJSONRPC} | ||
*/ | ||
strict: AsyncCallStrictJSONRPC | boolean; | ||
strict?: AsyncCallStrictJSONRPC | boolean; | ||
/** | ||
@@ -185,3 +193,3 @@ * How parameters passed to remote | ||
*/ | ||
parameterStructures: 'by-position' | 'by-name'; | ||
parameterStructures?: 'by-position' | 'by-name'; | ||
/** | ||
@@ -193,3 +201,3 @@ * Prefer local implementation than remote. | ||
*/ | ||
preferLocalImplementation: boolean; | ||
preferLocalImplementation?: boolean; | ||
/** | ||
@@ -206,5 +214,29 @@ * (Browser) Try to preserve the browser "pause on uncaught exception". | ||
*/ | ||
preservePauseOnException: boolean; | ||
preservePauseOnException?: boolean; | ||
/** | ||
* The ID generator of each JSON RPC request | ||
* @defaultValue () => Math.random().toString(36).slice(2) | ||
*/ | ||
idGenerator?(): string | number; | ||
/** | ||
* Control the error response data | ||
* @param error The happened Error | ||
* @param request The request object | ||
*/ | ||
mapError?: ErrorMapFunction<unknown>; | ||
} | ||
/** | ||
* @public | ||
*/ | ||
declare type ErrorMapFunction<T = unknown> = (error: unknown, request: Readonly<{ | ||
jsonrpc: '2.0'; | ||
id?: string | number | null; | ||
method: string; | ||
params: readonly unknown[] | object; | ||
}>) => { | ||
code: number; | ||
message: string; | ||
data?: T; | ||
}; | ||
/** | ||
* Make all function in the type T Async | ||
@@ -234,4 +266,4 @@ * @internal | ||
*/ | ||
declare function AsyncCall<OtherSideImplementedFunctions = {}>(thisSideImplementation: object | Promise<object> | undefined, options: Partial<AsyncCallOptions> & Pick<AsyncCallOptions, 'messageChannel'>): _AsyncVersionOf<OtherSideImplementedFunctions>; | ||
declare function AsyncCall<OtherSideImplementedFunctions = {}>(thisSideImplementation: object | Promise<object> | undefined, options: AsyncCallOptions): _AsyncVersionOf<OtherSideImplementedFunctions>; | ||
export { AsyncCall, AsyncCallLogLevel, AsyncCallOptions, AsyncCallStrictJSONRPC, Console, JSONSerialization, NoSerialization, Serialization, _AsyncVersionOf }; | ||
export { AsyncCall, AsyncCallLogLevel, AsyncCallOptions, AsyncCallStrictJSONRPC, Console, ErrorMapFunction, JSONSerialization, MessageChannel, NoSerialization, Serialization, _AsyncVersionOf }; |
119
out/base.js
/// <reference types="./base.d.ts" /> | ||
((e,r)=>{"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r((e=e||self).AsyncCall={})})(this,(function(e){"use strict" | ||
const r={serialization:async e=>e,deserialization:async e=>e} | ||
class t extends Error{constructor(e,r,t,n){super(r),this.name=e,this.code=t,this.stack=n}}const n={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError} | ||
function o(e=""){return e.replace(/^.+\n.+\n/,"")}const i=(()=>{const e=Reflect.get(globalThis,"DOMException") | ||
class r extends Error{constructor(e,r,t,o){super(r),this.name=e,this.code=t,this.stack=o}}const t={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError} | ||
function o(e=""){return e.replace(/^.+\n.+\n/,"")}const n=(()=>{const e=Reflect.get(globalThis,"DOMException") | ||
if("function"==typeof e)return e})() | ||
function a(e,r,t,n,o){let a=((e,r)=>{let t="Error" | ||
try{t=null===(i=null===(n=o)||void 0===n?void 0:n.constructor)||void 0===i?void 0:i.name}catch(n){}var n,i | ||
return"string"!=typeof t?"Error":t})() | ||
return i&&o instanceof i&&(a="DOMException:"+o.name),"string"!=typeof o&&"number"!=typeof o&&"boolean"!=typeof o&&"bigint"!=typeof o||(a="Error",t=o+""),void 0===e&&(e=null),Number.isNaN(r=Math.floor(r))&&(r=-1),{error:{code:r,message:t,data:{stack:n,type:a}},id:e,jsonrpc:"2.0"}}function s(e){if(!l(e))return!1 | ||
if(!c(e,"jsonrpc"))return!1 | ||
function i(e,r,t,o){void 0===e&&(e=null),Number.isNaN(r=Math.floor(r))&&(r=-1) | ||
const n={jsonrpc:"2.0",id:e,error:{code:r,message:t,data:o}} | ||
return d(n.error,"data"),n}function a(e,r,t){const{id:o}=e,{code:n,message:a,data:s}=t(r,e) | ||
return i(o,n,a,s)}i.ParseError=(e,r)=>{const t=a({},e,r),o=t.error | ||
return o.code=-32700,o.message="Parse error",t},i.InvalidRequest=e=>i(e,-32600,"Invalid Request"),i.MethodNotFound=e=>i(e,-32601,"Method not found") | ||
const s=(e="",r=-1)=>t=>{let o="" | ||
l(t)&&u(t,"message")&&"string"==typeof t.message&&(o=t.message) | ||
let i=((e,r)=>{let o="Error" | ||
try{o=null===(i=null===(n=t)||void 0===n?void 0:n.constructor)||void 0===i?void 0:i.name}catch(n){}var n,i | ||
return"string"!=typeof o?"Error":o})() | ||
return n&&t instanceof n&&(i="DOMException:"+t.name),"string"!=typeof t&&"number"!=typeof t&&"boolean"!=typeof t&&"bigint"!=typeof t||(i="Error",o=t+""),{code:r,message:o,data:e?{stack:e,type:i}:{type:i}}} | ||
function c(e){if(!l(e))return!1 | ||
if(!u(e,"jsonrpc"))return!1 | ||
if("2.0"!==e.jsonrpc)return!1 | ||
if(c(e,"params")){const r=e.params | ||
if(!Array.isArray(r)&&!l(r))return!1}return!0}function l(e){return"object"==typeof e&&null!==e}function c(e,r){return r in e}a.ParseError=(e="")=>a(null,-32700,"Parse error",e),a.InvalidRequest=e=>a(e,-32600,"Invalid Request",""),a.MethodNotFound=e=>a(e,-32601,"Method not found",""),a.InvalidParams=e=>a(e,-32602,"Invalid params",""),a.InternalError=(e,r="")=>a(e,-32603,"Internal error"+r,"") | ||
const d=Symbol.for("AsyncCall: This response should be ignored.") | ||
async function u(e,r,t){const n=document.createElement("iframe"),i=new Promise((i,a)=>{try{n.style.display="none",document.body.appendChild(n) | ||
{const s=n.contentDocument,l=n.contentWindow,c=s.createElement("button") | ||
s.body.appendChild(c),c.onclick=()=>new l.Promise(n=>{(async()=>{e(o(Error().stack)),i(await r(...t))})().then(n)}),l.addEventListener("unhandledrejection",e=>a(e.reason)),c.click()}}catch(e){return console.error("Please close preservePauseOnException.",e),i(r(...t))}}) | ||
return i.finally(()=>n.remove()),i}const f={serializer:r,key:"default-jsonrpc",strict:!1,log:!0,parameterStructures:"by-position",preferLocalImplementation:!1,preservePauseOnException:!1} | ||
e.AsyncCall=(e={},r)=>{let y=void 0 | ||
Promise.resolve(e).then(e=>y=e) | ||
const{serializer:p,key:m,strict:v,log:E,parameterStructures:g,preferLocalImplementation:h,preservePauseOnException:b}={...f,...r},k=r.messageChannel,{methodNotFound:w=!1,noUndefined:$=!1,unknownMessage:P=!1}=(e=>"boolean"!=typeof e?e:{methodNotFound:e,unknownMessage:e,noUndefined:e})(v),{beCalled:S=!0,localError:j=!0,remoteError:A=!0,type:I="pretty",sendLocalStack:O=!1}=(e=>"boolean"!=typeof e?e:{beCalled:e,localError:e,remoteError:e,type:e?"pretty":"basic"})(E),x=(e=>{const r=e||globalThis.console,t=(...e)=>r.log(...e) | ||
return Object.assign({},{debug:t,error:t,groupCollapsed:t,groupEnd:t,log:t},r)})(r.logger),C=new Map | ||
async function R(e){var r,o,a | ||
let s="",l="",d=0,u="Error" | ||
if(c(e,"error")&&(s=e.error.message,d=e.error.code,l=null!==(o=null===(r=e.error.data)||void 0===r?void 0:r.stack)&&void 0!==o?o:"<remote stack not available>",u=(null===(a=e.error.data)||void 0===a?void 0:a.type)||"Error",A&&("basic"===I?x.error(`${u}: ${s}(${d}) @${e.id}\n${l}`):x.error(`${u}: ${s}(${d}) %c@${e.id}\n%c${l}`,"color: gray",""))),null===e.id||void 0===e.id)return | ||
const{f:[f,y],stack:p}=C.get(e.id)||{stack:"",f:[null,null]} | ||
f&&(C.delete(e.id),c(e,"error")?y(((e,r,o,a)=>{try{if(e.startsWith("DOMException:")&&i){const[,t]=e.split("DOMException:") | ||
return new i(r,t)}if(e in n){const t=new n[e](r) | ||
return t.stack=a,Object.assign(t,{code:o}),t}return new t(e,r,o,a)}catch(t){return Error(`E${o} ${e}: ${r}\n${a}`)}})(u,s,d,l+"\n аt AsyncCall (rpc) \n"+p)):f(e.result))}return k.on(m,async e=>{let r,t=void 0 | ||
try{if(r=await p.deserialization(e),s(r))t=await M(r),t&&await n(t) | ||
else if(Array.isArray(r)&&r.every(s)&&0!==r.length){const e=await Promise.all(r.map(M)) | ||
if(r.every(e=>void 0===e))return | ||
await n(e.filter(e=>e))}else P&&await n(a.InvalidRequest(r.id||null))}catch(e){x.error(e,r,t),n(a.ParseError(null==e?void 0:e.stack))}async function n(e){if(Array.isArray(e)){const r=e.map(e=>e).filter(e=>void 0!==e.id) | ||
if(u(e,"params")){const r=e.params | ||
if(!Array.isArray(r)&&!l(r))return!1}return!0}function l(e){return"object"==typeof e&&null!==e}function u(e,r){return r in e}function d(e,r){void 0===e[r]&&delete e[r]}const f={serialization:e=>e,deserialization:e=>e},p=Symbol.for("AsyncCall/ignored") | ||
async function y(e,r,t,n){return new Promise((i,a)=>{var s | ||
let c={} | ||
try{c=document.createElement("iframe"),c.style.display="none",document.body.appendChild(c) | ||
{const s=c.contentDocument,l=c.contentWindow,u=s.createElement("button") | ||
s.body.appendChild(u),u.onclick=()=>new l.Promise(a=>{(async()=>{e(o(Error().stack)),i(await r.apply(t,n))})().then(a)}),l.onunhandledrejection=e=>a(e.reason),u.click()}}catch(e){try{console.error("Please close preservePauseOnException.",e)}catch(e){}return i(r(...n))}finally{null===(s=null==c?void 0:c.remove)||void 0===s||s.call(c)}})}const m={serializer:f,key:"default-jsonrpc",strict:!0,log:!0,parameterStructures:"by-position",preferLocalImplementation:!1,preservePauseOnException:!1,idGenerator:()=>Math.random().toString(36).slice(2)} | ||
e.AsyncCall=(e={},f)=>{let g=void 0 | ||
e instanceof Promise||(g=e),Promise.resolve(e).then(e=>g=e) | ||
const{serializer:E,key:h,strict:v,log:b,parameterStructures:k,preferLocalImplementation:w,preservePauseOnException:$,idGenerator:P,mapError:S}={...m,...f},j=f.messageChannel,{methodNotFound:O=!1,unknownMessage:x=!1}=(e=>"boolean"!=typeof e?e:{methodNotFound:e,unknownMessage:e})(v),{beCalled:C=!0,localError:A=!0,remoteError:M=!0,type:z="pretty",sendLocalStack:N=!1}=(e=>"boolean"!=typeof e?e:{beCalled:e,localError:e,remoteError:e,type:e?"pretty":"basic"})(b),R=(e=>{const r=e||globalThis.console,t=(...e)=>r.log(...e) | ||
return Object.assign({},{debug:t,error:t,groupCollapsed:t,groupEnd:t,log:t},r)})(f.logger),I=new Map | ||
return j.on(h,async e=>{var r | ||
let t,o=void 0 | ||
try{if(t=await E.deserialization(e),c(t))o=await D(t),o&&await n(o) | ||
else if(Array.isArray(t)&&t.every(c)&&0!==t.length){const e=await Promise.all(t.map(D)) | ||
if(t.every(e=>void 0===e))return | ||
await n(e.filter(e=>e))}else x&&await n(i.InvalidRequest(null!==(r=t.id)&&void 0!==r?r:null))}catch(e){A&&R.error(e,t,o),n(i.ParseError(e,S||s(null==e?void 0:e.stack)))}async function n(e){if(Array.isArray(e)){const r=e.filter(e=>u(e,"id")) | ||
if(0===r.length)return | ||
k.emit(m,await p.serialization(r))}else{if(!e)return | ||
if(void 0===e.id)return | ||
k.emit(m,await p.serialization(e))}}}),new Proxy({},{get(e,r){let t=o(Error().stack) | ||
return(...e)=>{if("string"!=typeof r){if("symbol"!=typeof r)return Promise.reject(new TypeError("[AsyncCall] Only string can be the method name")) | ||
{const e=Symbol.keyFor(r) | ||
e&&(r=e)}}else if(r.startsWith("rpc."))return Promise.reject(new TypeError("[AsyncCall] You cannot call JSON RPC internal methods directly")) | ||
if(h&&y&&"string"==typeof r){const t=y[r] | ||
if(t&&"function"==typeof t)return new Promise(r=>r(t(...e)))}return new Promise((n,o)=>{const i=Math.random().toString(36).slice(2),[a]=e,s=O?t:"",c="by-name"===g&&1===e.length&&l(a)?a:e,d=((e,r,t,n)=>{const o={jsonrpc:"2.0",id:e,method:r,params:t,remoteStack:n} | ||
return 0===n.length&&delete o.remoteStack,o})(i,r,c,s) | ||
Promise.resolve(p.serialization(d)).then(e=>{k.emit(m,e),C.set(i,{f:[n,o],stack:t})},o)})}}}) | ||
async function M(t){if(c(t,"method"))return(async t=>{y||await e | ||
let n="" | ||
try{const e=t.method.startsWith("rpc.")?Symbol.for(t.method):t.method,i=y[e] | ||
if(!i||"function"!=typeof i)return w?a.MethodNotFound(t.id):void(j&&x.debug("Receive remote call, but not implemented.",e,t)) | ||
const s=t.params | ||
if(Array.isArray(s)||"object"==typeof s&&null!==s){const e=Array.isArray(s)?s:[s] | ||
n=o(Error().stack) | ||
const a=b?u(e=>n=e,i,e):new Promise(r=>r(i(...e))) | ||
if(S)if("basic"===I)x.log(`${r.key}.${t.method}(${""+[...e]}) @${t.id}`) | ||
else{const n=[`${r.key}.%c${t.method}%c(${e.map(()=>"%o").join(", ")}%c)\n%o %c@${t.id}`,"color: #d2c057","",...e,"",a,"color: gray; font-style: italic;"] | ||
t.remoteStack?(x.groupCollapsed(...n),x.log(t.remoteStack),x.groupEnd()):x.log(...n)}if(await a===d)return | ||
return((e,r,t)=>{const n={jsonrpc:"2.0",id:e,result:void 0===r?null:r} | ||
return t||void 0!==r||(n.resultIsUndefined=!0),n})(t.id,await a,!!$)}return a.InvalidRequest(t.id)}catch(e){return"object"==typeof e&&"stack"in e&&(e.stack=n.split("\n").reduce((e,r)=>e.replace(r+"\n",""),e.stack||"")),j&&x.error(e),a(t.id,-1,null==e?void 0:e.message,null==e?void 0:e.stack,e)}})(t) | ||
if("error"in t||"result"in t)R(t) | ||
else{if(!("resultIsUndefined"in t))return a.InvalidRequest(t.id) | ||
t.result=void 0,R(t)}}},e.JSONSerialization=(e=[void 0,void 0],r)=>({serialization:async t=>JSON.stringify(t,e[0],r),deserialization:async r=>JSON.parse(r,e[1])}),e.NoSerialization=r,Object.defineProperty(e,"__esModule",{value:!0})})) | ||
j.emit(h,await E.serialization(r))}else{if(!e)return | ||
if(!u(e,"id"))return | ||
j.emit(h,await E.serialization(e))}}}),new Proxy({},{get(e,r){let t=o(Error().stack) | ||
return(...e)=>{if("symbol"==typeof r){const e=Symbol.keyFor(r) | ||
e&&(r=e)}else if(r.startsWith("rpc."))return Promise.reject(new TypeError("[AsyncCall] Can't call JSON RPC internal methods directly")) | ||
if(w&&g&&"string"==typeof r){const t=g[r] | ||
if(t&&"function"==typeof t)return new Promise(r=>r(t(...e)))}return new Promise((o,n)=>{const i=P(),[a]=e,s=N?t:"",c="by-name"===k&&1===e.length&&l(a)?a:e,u=((e,r,t,o)=>{const n={jsonrpc:"2.0",id:e,method:r,params:t,remoteStack:o} | ||
return d(n,"id"),((e,r)=>{e[r]||delete e[r]})(n,"remoteStack"),n})(i,r,c,s) | ||
Promise.resolve(E.serialization(u)).then(e=>{j.emit(h,e),I.set(i,{f:[o,n],stack:t})},n)})}}}) | ||
async function D(c){return u(c,"method")?async function(r){g||await e | ||
let t="" | ||
try{const e=r.method.startsWith("rpc.")?Symbol.for(r.method):r.method,n=g[e] | ||
if("function"!=typeof n)return O?i.MethodNotFound(r.id):void(A&&R.debug("Receive remote call, but not implemented.",e,r)) | ||
const{params:a}=r,s=Array.isArray(a)?a:[a] | ||
t=o(Error().stack) | ||
const c=$?y(e=>t=e,n,g,s):new Promise(e=>e(n.apply(g,s))) | ||
if(C)if("basic"===z)R.log(`${f.key}.${r.method}(${""+[...s]}) @${r.id}`) | ||
else{const e=[`${f.key}.%c${r.method}%c(${s.map(()=>"%o").join(", ")}%c)\n%o %c@${r.id}`,"color: #d2c057","",...s,"",c,"color: gray; font-style: italic;"] | ||
r.remoteStack?(R.groupCollapsed(...e),R.log(r.remoteStack),R.groupEnd()):R.log(...e)}if(await c===p)return | ||
return((e,r)=>{const t={jsonrpc:"2.0",id:e,result:r} | ||
return d(t,"id"),t})(r.id,await c)}catch(e){return"object"==typeof e&&"stack"in e&&(e.stack=t.split("\n").reduce((e,r)=>e.replace(r+"\n",""),e.stack||"")),A&&R.error(e),a(r,e,S||s(N?e.stack:void 0))}}(c):async function(e){let o="",i="",a=0,s="Error" | ||
if(u(e,"error")){const r=e.error | ||
o=r.message,a=r.code | ||
const t=r.data | ||
i=l(t)&&u(t,"stack")&&"string"==typeof t.stack?t.stack:"<remote stack not available>",s=l(t)&&u(t,"type")&&"string"==typeof t.type?t.type:"Error",M&&("basic"===z?R.error(`${s}: ${o}(${a}) @${e.id}\n${i}`):R.error(`${s}: ${o}(${a}) %c@${e.id}\n%c${i}`,"color: gray",""))}if(null===e.id||void 0===e.id)return | ||
const{f:[c,d],stack:f}=I.get(e.id)||{stack:"",f:[null,null]} | ||
c&&(I.delete(e.id),u(e,"error")?d(((e,o,i,a)=>{try{if(e.startsWith("DOMException:")&&n){const[,r]=e.split("DOMException:") | ||
return new n(o,r)}if(e in t){const r=new t[e](o) | ||
return r.stack=a,Object.assign(r,{code:i}),r}return new r(e,o,i,a)}catch(r){return Error(`E${i} ${e}: ${o}\n${a}`)}})(s,o,a,i+"\n аt AsyncCall (rpc) \n"+f)):c(e.result))}(c)}},e.JSONSerialization=(e=[void 0,void 0],r,t="null")=>({serialization(o){if(t&&l(o)&&u(o,"result")&&void 0===o.result){const e=Object.assign({},o) | ||
e.result=null,"keep"===t&&(e.undef=!0),o=e}return JSON.stringify(o,e[0],r)},deserialization(r){const t=JSON.parse(r,e[1]) | ||
return l(t)&&u(t,"result")&&null===t.result&&u(t,"undef")&&!0===t.undef&&(t.result=void 0,delete t.undef),t}}),e.NoSerialization=f,Object.defineProperty(e,"__esModule",{value:!0})})) | ||
//# sourceMappingURL=base.js.map |
@@ -28,6 +28,14 @@ /** | ||
* @param space - Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. | ||
* @param undefinedKeepingBehavior - How to keep "undefined" in result of SuccessResponse? | ||
* | ||
* If it is not handled properly, JSON.stringify will emit an invalid JSON RPC object. | ||
* | ||
* Options: | ||
* - `"null"`(**default**): convert it to null. | ||
* - `"keep"`: try to keep it by additional property "undef". | ||
* - `false`: Don't keep it, let it break. | ||
* @remarks {@link Serialization} | ||
* @public | ||
*/ | ||
declare const JSONSerialization: (replacerAndReceiver?: [Parameters<JSON['stringify']>[1], Parameters<JSON['parse']>[1]], space?: string | number | undefined) => Serialization; | ||
declare const JSONSerialization: (replacerAndReceiver?: [(((key: string, value: any) => any) | undefined)?, (((key: string, value: any) => any) | undefined)?], space?: string | number | undefined, undefinedKeepingBehavior?: 'keep' | 'null' | false) => Serialization; | ||
@@ -39,7 +47,7 @@ /** | ||
interface Console { | ||
debug(...args: unknown[]): void; | ||
debug?(...args: unknown[]): void; | ||
log(...args: unknown[]): void; | ||
groupCollapsed(...args: unknown[]): void; | ||
groupEnd(...args: unknown[]): void; | ||
error(...args: unknown[]): void; | ||
groupCollapsed?(...args: unknown[]): void; | ||
groupEnd?(...args: unknown[]): void; | ||
error?(...args: unknown[]): void; | ||
} | ||
@@ -84,14 +92,9 @@ | ||
/** | ||
* Return an error when the requested method is not defined | ||
* @defaultValue false | ||
* Return an error when the requested method is not defined, otherwise, ignore the request. | ||
* @defaultValue true | ||
*/ | ||
methodNotFound?: boolean; | ||
/** | ||
* don't try to keep `undefined` result (then it will be `null`) | ||
* @defaultValue false | ||
*/ | ||
noUndefined?: boolean; | ||
/** | ||
* send an error when receive invalid JSON RPC payload | ||
* @defaultValue false | ||
* @defaultValue true | ||
*/ | ||
@@ -101,2 +104,10 @@ unknownMessage?: boolean; | ||
/** | ||
* The message channel interface that allows | ||
* @public | ||
*/ | ||
interface MessageChannel { | ||
on(event: string, eventListener: (data: unknown) => void): void; | ||
emit(event: string, data: unknown): void; | ||
} | ||
/** | ||
* Options for {@link AsyncCall} | ||
@@ -123,3 +134,3 @@ * @public | ||
*/ | ||
key: string; | ||
key?: string; | ||
/** | ||
@@ -138,3 +149,3 @@ * How to serialization and deserialization JSON RPC payload | ||
*/ | ||
serializer: Serialization; | ||
serializer?: Serialization; | ||
/** | ||
@@ -146,3 +157,3 @@ * The logger of AsyncCall | ||
*/ | ||
logger: Console; | ||
logger?: Console; | ||
/** | ||
@@ -162,6 +173,3 @@ * The message channel can let you transport messages between server and client | ||
*/ | ||
messageChannel: { | ||
on(event: string, callback: (data: unknown) => void): void; | ||
emit(event: string, data: unknown): void; | ||
}; | ||
messageChannel: MessageChannel; | ||
/** | ||
@@ -171,3 +179,3 @@ * Choose log level. See {@link AsyncCallLogLevel} | ||
*/ | ||
log: AsyncCallLogLevel | boolean; | ||
log?: AsyncCallLogLevel | boolean; | ||
/** | ||
@@ -177,3 +185,3 @@ * Strict options. See {@link AsyncCallStrictJSONRPC} | ||
*/ | ||
strict: AsyncCallStrictJSONRPC | boolean; | ||
strict?: AsyncCallStrictJSONRPC | boolean; | ||
/** | ||
@@ -185,3 +193,3 @@ * How parameters passed to remote | ||
*/ | ||
parameterStructures: 'by-position' | 'by-name'; | ||
parameterStructures?: 'by-position' | 'by-name'; | ||
/** | ||
@@ -193,3 +201,3 @@ * Prefer local implementation than remote. | ||
*/ | ||
preferLocalImplementation: boolean; | ||
preferLocalImplementation?: boolean; | ||
/** | ||
@@ -206,5 +214,29 @@ * (Browser) Try to preserve the browser "pause on uncaught exception". | ||
*/ | ||
preservePauseOnException: boolean; | ||
preservePauseOnException?: boolean; | ||
/** | ||
* The ID generator of each JSON RPC request | ||
* @defaultValue () => Math.random().toString(36).slice(2) | ||
*/ | ||
idGenerator?(): string | number; | ||
/** | ||
* Control the error response data | ||
* @param error The happened Error | ||
* @param request The request object | ||
*/ | ||
mapError?: ErrorMapFunction<unknown>; | ||
} | ||
/** | ||
* @public | ||
*/ | ||
declare type ErrorMapFunction<T = unknown> = (error: unknown, request: Readonly<{ | ||
jsonrpc: '2.0'; | ||
id?: string | number | null; | ||
method: string; | ||
params: readonly unknown[] | object; | ||
}>) => { | ||
code: number; | ||
message: string; | ||
data?: T; | ||
}; | ||
/** | ||
* Make all function in the type T Async | ||
@@ -234,3 +266,3 @@ * @internal | ||
*/ | ||
declare function AsyncCall<OtherSideImplementedFunctions = {}>(thisSideImplementation: object | Promise<object> | undefined, options: Partial<AsyncCallOptions> & Pick<AsyncCallOptions, 'messageChannel'>): _AsyncVersionOf<OtherSideImplementedFunctions>; | ||
declare function AsyncCall<OtherSideImplementedFunctions = {}>(thisSideImplementation: object | Promise<object> | undefined, options: AsyncCallOptions): _AsyncVersionOf<OtherSideImplementedFunctions>; | ||
@@ -289,4 +321,4 @@ /** | ||
*/ | ||
declare function AsyncGeneratorCall<OtherSideImplementedFunctions = {}>(thisSideImplementation: object | Promise<object> | undefined, options: Partial<AsyncCallOptions> & Pick<AsyncCallOptions, 'messageChannel'>): _AsyncGeneratorVersionOf<OtherSideImplementedFunctions>; | ||
declare function AsyncGeneratorCall<OtherSideImplementedFunctions = {}>(thisSideImplementation: object | Promise<object> | undefined, options: AsyncCallOptions): _AsyncGeneratorVersionOf<OtherSideImplementedFunctions>; | ||
export { AsyncCall, AsyncCallLogLevel, AsyncCallOptions, AsyncCallStrictJSONRPC, AsyncGeneratorCall, Console, JSONSerialization, NoSerialization, Serialization, _AsyncGeneratorVersionOf, _AsyncVersionOf, _UnboxPromise }; | ||
export { AsyncCall, AsyncCallLogLevel, AsyncCallOptions, AsyncCallStrictJSONRPC, AsyncGeneratorCall, Console, ErrorMapFunction, JSONSerialization, MessageChannel, NoSerialization, Serialization, _AsyncGeneratorVersionOf, _AsyncVersionOf, _UnboxPromise }; |
161
out/full.js
/// <reference types="./full.d.ts" /> | ||
((r,e)=>{"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((r=r||self).AsyncCall={})})(this,(function(r){"use strict" | ||
const e={serialization:async r=>r,deserialization:async r=>r} | ||
class t extends Error{constructor(r,e,t,n){super(e),this.name=r,this.code=t,this.stack=n}}const n={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError} | ||
function o(r=""){return r.replace(/^.+\n.+\n/,"")}const i=(()=>{const r=Reflect.get(globalThis,"DOMException") | ||
if("function"==typeof r)return r})() | ||
function a(r,e,t,n,o){let a=((r,e)=>{let t="Error" | ||
try{t=null===(i=null===(n=o)||void 0===n?void 0:n.constructor)||void 0===i?void 0:i.name}catch(n){}var n,i | ||
return"string"!=typeof t?"Error":t})() | ||
return i&&o instanceof i&&(a="DOMException:"+o.name),"string"!=typeof o&&"number"!=typeof o&&"boolean"!=typeof o&&"bigint"!=typeof o||(a="Error",t=o+""),void 0===r&&(r=null),Number.isNaN(e=Math.floor(e))&&(e=-1),{error:{code:e,message:t,data:{stack:n,type:a}},id:r,jsonrpc:"2.0"}}function s(r){if(!c(r))return!1 | ||
if(!l(r,"jsonrpc"))return!1 | ||
if("2.0"!==r.jsonrpc)return!1 | ||
if(l(r,"params")){const e=r.params | ||
if(!Array.isArray(e)&&!c(e))return!1}return!0}function c(r){return"object"==typeof r&&null!==r}function l(r,e){return e in r}function u(){return Math.random().toString(36).slice(2)}function d(r){return"boolean"!=typeof r?r:{methodNotFound:r,unknownMessage:r,noUndefined:r}}a.ParseError=(r="")=>a(null,-32700,"Parse error",r),a.InvalidRequest=r=>a(r,-32600,"Invalid Request",""),a.MethodNotFound=r=>a(r,-32601,"Method not found",""),a.InvalidParams=r=>a(r,-32602,"Invalid params",""),a.InternalError=(r,e="")=>a(r,-32603,"Internal error"+e,"") | ||
const f=Symbol.for("AsyncCall: This response should be ignored.") | ||
async function y(r,e,t){const n=document.createElement("iframe"),i=new Promise((i,a)=>{try{n.style.display="none",document.body.appendChild(n) | ||
{const s=n.contentDocument,c=n.contentWindow,l=s.createElement("button") | ||
s.body.appendChild(l),l.onclick=()=>new c.Promise(n=>{(async()=>{r(o(Error().stack)),i(await e(...t))})().then(n)}),c.addEventListener("unhandledrejection",r=>a(r.reason)),l.click()}}catch(r){return console.error("Please close preservePauseOnException.",r),i(e(...t))}}) | ||
return i.finally(()=>n.remove()),i}const p={serializer:e,key:"default-jsonrpc",strict:!1,log:!0,parameterStructures:"by-position",preferLocalImplementation:!1,preservePauseOnException:!1} | ||
function m(r={},e){let m=void 0 | ||
Promise.resolve(r).then(r=>m=r) | ||
const{serializer:h,key:v,strict:g,log:E,parameterStructures:w,preferLocalImplementation:b,preservePauseOnException:_}={...p,...e},k=e.messageChannel,{methodNotFound:S=!1,noUndefined:P=!1,unknownMessage:$=!1}=d(g),{beCalled:x=!0,localError:A=!0,remoteError:j=!0,type:C="pretty",sendLocalStack:I=!1}=(r=>"boolean"!=typeof r?r:{beCalled:r,localError:r,remoteError:r,type:r?"pretty":"basic"})(E),O=(r=>{const e=r||globalThis.console,t=(...r)=>e.log(...r) | ||
return Object.assign({},{debug:t,error:t,groupCollapsed:t,groupEnd:t,log:t},e)})(e.logger),R=new Map | ||
async function M(r){var e,o,a | ||
let s="",c="",u=0,d="Error" | ||
if(l(r,"error")&&(s=r.error.message,u=r.error.code,c=null!==(o=null===(e=r.error.data)||void 0===e?void 0:e.stack)&&void 0!==o?o:"<remote stack not available>",d=(null===(a=r.error.data)||void 0===a?void 0:a.type)||"Error",j&&("basic"===C?O.error(`${d}: ${s}(${u}) @${r.id}\n${c}`):O.error(`${d}: ${s}(${u}) %c@${r.id}\n%c${c}`,"color: gray",""))),null===r.id||void 0===r.id)return | ||
const{f:[f,y],stack:p}=R.get(r.id)||{stack:"",f:[null,null]} | ||
f&&(R.delete(r.id),l(r,"error")?y(((r,e,o,a)=>{try{if(r.startsWith("DOMException:")&&i){const[,t]=r.split("DOMException:") | ||
return new i(e,t)}if(r in n){const t=new n[r](e) | ||
return t.stack=a,Object.assign(t,{code:o}),t}return new t(r,e,o,a)}catch(t){return Error(`E${o} ${r}: ${e}\n${a}`)}})(d,s,u,c+"\n аt AsyncCall (rpc) \n"+p)):f(r.result))}return k.on(v,async r=>{let e,t=void 0 | ||
try{if(e=await h.deserialization(r),s(e))t=await N(e),t&&await n(t) | ||
else if(Array.isArray(e)&&e.every(s)&&0!==e.length){const r=await Promise.all(e.map(N)) | ||
if(e.every(r=>void 0===r))return | ||
await n(r.filter(r=>r))}else $&&await n(a.InvalidRequest(e.id||null))}catch(r){O.error(r,e,t),n(a.ParseError(null==r?void 0:r.stack))}async function n(r){if(Array.isArray(r)){const e=r.map(r=>r).filter(r=>void 0!==r.id) | ||
if(0===e.length)return | ||
k.emit(v,await h.serialization(e))}else{if(!r)return | ||
if(void 0===r.id)return | ||
k.emit(v,await h.serialization(r))}}}),new Proxy({},{get(r,e){let t=o(Error().stack) | ||
return(...r)=>{if("string"!=typeof e){if("symbol"!=typeof e)return Promise.reject(new TypeError("[AsyncCall] Only string can be the method name")) | ||
{const r=Symbol.keyFor(e) | ||
r&&(e=r)}}else if(e.startsWith("rpc."))return Promise.reject(new TypeError("[AsyncCall] You cannot call JSON RPC internal methods directly")) | ||
if(b&&m&&"string"==typeof e){const t=m[e] | ||
if(t&&"function"==typeof t)return new Promise(e=>e(t(...r)))}return new Promise((n,o)=>{const i=u(),[a]=r,s=I?t:"",l="by-name"===w&&1===r.length&&c(a)?a:r,d=((r,e,t,n)=>{const o={jsonrpc:"2.0",id:r,method:e,params:t,remoteStack:n} | ||
return 0===n.length&&delete o.remoteStack,o})(i,e,l,s) | ||
Promise.resolve(h.serialization(d)).then(r=>{k.emit(v,r),R.set(i,{f:[n,o],stack:t})},o)})}}}) | ||
async function N(t){if(l(t,"method"))return(async t=>{m||await r | ||
let n="" | ||
try{const r=t.method.startsWith("rpc.")?Symbol.for(t.method):t.method,i=m[r] | ||
if(!i||"function"!=typeof i)return S?a.MethodNotFound(t.id):void(A&&O.debug("Receive remote call, but not implemented.",r,t)) | ||
const s=t.params | ||
if(Array.isArray(s)||"object"==typeof s&&null!==s){const r=Array.isArray(s)?s:[s] | ||
n=o(Error().stack) | ||
const a=_?y(r=>n=r,i,r):new Promise(e=>e(i(...r))) | ||
if(x)if("basic"===C)O.log(`${e.key}.${t.method}(${""+[...r]}) @${t.id}`) | ||
else{const n=[`${e.key}.%c${t.method}%c(${r.map(()=>"%o").join(", ")}%c)\n%o %c@${t.id}`,"color: #d2c057","",...r,"",a,"color: gray; font-style: italic;"] | ||
t.remoteStack?(O.groupCollapsed(...n),O.log(t.remoteStack),O.groupEnd()):O.log(...n)}if(await a===f)return | ||
return((r,e,t)=>{const n={jsonrpc:"2.0",id:r,result:void 0===e?null:e} | ||
return t||void 0!==e||(n.resultIsUndefined=!0),n})(t.id,await a,!!P)}return a.InvalidRequest(t.id)}catch(r){return"object"==typeof r&&"stack"in r&&(r.stack=n.split("\n").reduce((r,e)=>r.replace(e+"\n",""),r.stack||"")),A&&O.error(r),a(t.id,-1,null==r?void 0:r.message,null==r?void 0:r.stack,r)}})(t) | ||
if("error"in t||"result"in t)M(t) | ||
else{if(!("resultIsUndefined"in t))return a.InvalidRequest(t.id) | ||
t.result=void 0,M(t)}}}const h=Symbol.for("rpc.async-iterator.start"),v=Symbol.for("rpc.async-iterator.next"),g=Symbol.for("rpc.async-iterator.return"),E=Symbol.for("rpc.async-iterator.throw") | ||
class w{constructor(r,e){this.__3=r=>(b(r),r),this.__0=r,this.__1=e,this.__2=!1}async return(r){return this.__2?_(!0,r):this.__3(this.__0[g](await this.__1,r))}async next(r){return this.__2?_(!0):this.__3(this.__0[v](await this.__1,r))}async throw(r){if(this.__2)throw r | ||
return this.__3(this.__0[E](await this.__1,r))}[Symbol.asyncIterator](){return this}}async function b(r,e){const t=await r | ||
null==t||t.done}function _(r,e){return{done:r,value:e}}r.AsyncCall=m,r.AsyncGeneratorCall=(r={},e)=>{const t=new Map,n=d(e.strict||!1) | ||
function o(r,e,o){const i=t.get(r) | ||
if(!i){if(n.methodNotFound)throw Error("Remote iterator not found while executing "+e) | ||
return f}const a=o(i) | ||
return b(a),a}const i=m({async[h](e,o){const i=Reflect.get(await r,e) | ||
if("function"!=typeof i){if(n.methodNotFound)throw Error(e+" is not a function") | ||
return f}const a=i(...o),s=u() | ||
return t.set(s,a),Promise.resolve(s)},[v]:(r,e)=>o(r,"next",r=>r.next(e)),[g]:(r,e)=>o(r,"return",r=>{var t | ||
return null===(t=r.return)||void 0===t?void 0:t.call(r,e)}),[E]:(r,e)=>o(r,"throw",r=>{var t | ||
return null===(t=r.throw)||void 0===t?void 0:t.call(r,e)})},e) | ||
return new Proxy({},{get:(r,e)=>{if("string"!=typeof e)throw new TypeError("[*AsyncCall] Only string can be the method name") | ||
return(...r)=>{const t=i[h](e,r) | ||
return new w(i,t)}}})},r.JSONSerialization=(r=[void 0,void 0],e)=>({serialization:async t=>JSON.stringify(t,r[0],e),deserialization:async e=>JSON.parse(e,r[1])}),r.NoSerialization=e,Object.defineProperty(r,"__esModule",{value:!0})})) | ||
((e,r)=>{"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r((e=e||self).AsyncCall={})})(this,(function(e){"use strict" | ||
class r extends Error{constructor(e,r,t,o){super(r),this.name=e,this.code=t,this.stack=o}}const t={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError} | ||
function o(e=""){return e.replace(/^.+\n.+\n/,"")}const n=(()=>{const e=Reflect.get(globalThis,"DOMException") | ||
if("function"==typeof e)return e})() | ||
function i(e,r,t,o){void 0===e&&(e=null),Number.isNaN(r=Math.floor(r))&&(r=-1) | ||
const n={jsonrpc:"2.0",id:e,error:{code:r,message:t,data:o}} | ||
return d(n.error,"data"),n}function a(e,r,t){const{id:o}=e,{code:n,message:a,data:s}=t(r,e) | ||
return i(o,n,a,s)}i.ParseError=(e,r)=>{const t=a({},e,r),o=t.error | ||
return o.code=-32700,o.message="Parse error",t},i.InvalidRequest=e=>i(e,-32600,"Invalid Request"),i.MethodNotFound=e=>i(e,-32601,"Method not found") | ||
const s=(e="",r=-1)=>t=>{let o="" | ||
l(t)&&u(t,"message")&&"string"==typeof t.message&&(o=t.message) | ||
let i=((e,r)=>{let o="Error" | ||
try{o=null===(i=null===(n=t)||void 0===n?void 0:n.constructor)||void 0===i?void 0:i.name}catch(n){}var n,i | ||
return"string"!=typeof o?"Error":o})() | ||
return n&&t instanceof n&&(i="DOMException:"+t.name),"string"!=typeof t&&"number"!=typeof t&&"boolean"!=typeof t&&"bigint"!=typeof t||(i="Error",o=t+""),{code:r,message:o,data:e?{stack:e,type:i}:{type:i}}} | ||
function c(e){if(!l(e))return!1 | ||
if(!u(e,"jsonrpc"))return!1 | ||
if("2.0"!==e.jsonrpc)return!1 | ||
if(u(e,"params")){const r=e.params | ||
if(!Array.isArray(r)&&!l(r))return!1}return!0}function l(e){return"object"==typeof e&&null!==e}function u(e,r){return r in e}function d(e,r){void 0===e[r]&&delete e[r]}const f={serialization:e=>e,deserialization:e=>e} | ||
function y(){return Math.random().toString(36).slice(2)}function p(e){return"boolean"!=typeof e?e:{methodNotFound:e,unknownMessage:e}}const m=Symbol.for("AsyncCall/ignored") | ||
async function h(e,r,t,n){return new Promise((i,a)=>{var s | ||
let c={} | ||
try{c=document.createElement("iframe"),c.style.display="none",document.body.appendChild(c) | ||
{const s=c.contentDocument,l=c.contentWindow,u=s.createElement("button") | ||
s.body.appendChild(u),u.onclick=()=>new l.Promise(a=>{(async()=>{e(o(Error().stack)),i(await r.apply(t,n))})().then(a)}),l.onunhandledrejection=e=>a(e.reason),u.click()}}catch(e){try{console.error("Please close preservePauseOnException.",e)}catch(e){}return i(r(...n))}finally{null===(s=null==c?void 0:c.remove)||void 0===s||s.call(c)}})}const g={serializer:f,key:"default-jsonrpc",strict:!0,log:!0,parameterStructures:"by-position",preferLocalImplementation:!1,preservePauseOnException:!1,idGenerator:y} | ||
function v(e={},f){let y=void 0 | ||
e instanceof Promise||(y=e),Promise.resolve(e).then(e=>y=e) | ||
const{serializer:v,key:E,strict:w,log:b,parameterStructures:k,preferLocalImplementation:P,preservePauseOnException:$,idGenerator:S,mapError:O}={...g,...f},j=f.messageChannel,{methodNotFound:x=!1,unknownMessage:C=!1}=p(w),{beCalled:A=!0,localError:M=!0,remoteError:N=!0,type:R="pretty",sendLocalStack:z=!1}=(e=>"boolean"!=typeof e?e:{beCalled:e,localError:e,remoteError:e,type:e?"pretty":"basic"})(b),I=(e=>{const r=e||globalThis.console,t=(...e)=>r.log(...e) | ||
return Object.assign({},{debug:t,error:t,groupCollapsed:t,groupEnd:t,log:t},r)})(f.logger),F=new Map | ||
return j.on(E,async e=>{var r | ||
let t,o=void 0 | ||
try{if(t=await v.deserialization(e),c(t))o=await T(t),o&&await n(o) | ||
else if(Array.isArray(t)&&t.every(c)&&0!==t.length){const e=await Promise.all(t.map(T)) | ||
if(t.every(e=>void 0===e))return | ||
await n(e.filter(e=>e))}else C&&await n(i.InvalidRequest(null!==(r=t.id)&&void 0!==r?r:null))}catch(e){M&&I.error(e,t,o),n(i.ParseError(e,O||s(null==e?void 0:e.stack)))}async function n(e){if(Array.isArray(e)){const r=e.filter(e=>u(e,"id")) | ||
if(0===r.length)return | ||
j.emit(E,await v.serialization(r))}else{if(!e)return | ||
if(!u(e,"id"))return | ||
j.emit(E,await v.serialization(e))}}}),new Proxy({},{get(e,r){let t=o(Error().stack) | ||
return(...e)=>{if("symbol"==typeof r){const e=Symbol.keyFor(r) | ||
e&&(r=e)}else if(r.startsWith("rpc."))return Promise.reject(new TypeError("[AsyncCall] Can't call JSON RPC internal methods directly")) | ||
if(P&&y&&"string"==typeof r){const t=y[r] | ||
if(t&&"function"==typeof t)return new Promise(r=>r(t(...e)))}return new Promise((o,n)=>{const i=S(),[a]=e,s=z?t:"",c="by-name"===k&&1===e.length&&l(a)?a:e,u=((e,r,t,o)=>{const n={jsonrpc:"2.0",id:e,method:r,params:t,remoteStack:o} | ||
return d(n,"id"),((e,r)=>{e[r]||delete e[r]})(n,"remoteStack"),n})(i,r,c,s) | ||
Promise.resolve(v.serialization(u)).then(e=>{j.emit(E,e),F.set(i,{f:[o,n],stack:t})},n)})}}}) | ||
async function T(c){return u(c,"method")?async function(r){y||await e | ||
let t="" | ||
try{const e=r.method.startsWith("rpc.")?Symbol.for(r.method):r.method,n=y[e] | ||
if("function"!=typeof n)return x?i.MethodNotFound(r.id):void(M&&I.debug("Receive remote call, but not implemented.",e,r)) | ||
const{params:a}=r,s=Array.isArray(a)?a:[a] | ||
t=o(Error().stack) | ||
const c=$?h(e=>t=e,n,y,s):new Promise(e=>e(n.apply(y,s))) | ||
if(A)if("basic"===R)I.log(`${f.key}.${r.method}(${""+[...s]}) @${r.id}`) | ||
else{const e=[`${f.key}.%c${r.method}%c(${s.map(()=>"%o").join(", ")}%c)\n%o %c@${r.id}`,"color: #d2c057","",...s,"",c,"color: gray; font-style: italic;"] | ||
r.remoteStack?(I.groupCollapsed(...e),I.log(r.remoteStack),I.groupEnd()):I.log(...e)}if(await c===m)return | ||
return((e,r)=>{const t={jsonrpc:"2.0",id:e,result:r} | ||
return d(t,"id"),t})(r.id,await c)}catch(e){return"object"==typeof e&&"stack"in e&&(e.stack=t.split("\n").reduce((e,r)=>e.replace(r+"\n",""),e.stack||"")),M&&I.error(e),a(r,e,O||s(z?e.stack:void 0))}}(c):async function(e){let o="",i="",a=0,s="Error" | ||
if(u(e,"error")){const r=e.error | ||
o=r.message,a=r.code | ||
const t=r.data | ||
i=l(t)&&u(t,"stack")&&"string"==typeof t.stack?t.stack:"<remote stack not available>",s=l(t)&&u(t,"type")&&"string"==typeof t.type?t.type:"Error",N&&("basic"===R?I.error(`${s}: ${o}(${a}) @${e.id}\n${i}`):I.error(`${s}: ${o}(${a}) %c@${e.id}\n%c${i}`,"color: gray",""))}if(null===e.id||void 0===e.id)return | ||
const{f:[c,d],stack:f}=F.get(e.id)||{stack:"",f:[null,null]} | ||
c&&(F.delete(e.id),u(e,"error")?d(((e,o,i,a)=>{try{if(e.startsWith("DOMException:")&&n){const[,r]=e.split("DOMException:") | ||
return new n(o,r)}if(e in t){const r=new t[e](o) | ||
return r.stack=a,Object.assign(r,{code:i}),r}return new r(e,o,i,a)}catch(r){return Error(`E${i} ${e}: ${o}\n${a}`)}})(s,o,a,i+"\n аt AsyncCall (rpc) \n"+f)):c(e.result))}(c)}}const E=Symbol.for("rpc.async-iterator.start"),w=Symbol.for("rpc.async-iterator.next"),b=Symbol.for("rpc.async-iterator.return"),k=Symbol.for("rpc.async-iterator.throw") | ||
class P{constructor(e,r){this.r=e,this.i=r,this.d=!1,this.c=async e=>(await O(e,()=>this.d=!0),e)}async return(e){return this.d||this.c(this.r[b](await this.i,e)).catch(()=>{}),this.d=!0,j(!0,e)}async next(e){return this.d?j(!0):await this.c(this.r[w](await this.i,e))}async throw(e){if(!this.d)return await this.c(this.r[k](await this.i,e)) | ||
throw e}}const $=async function*(){}.constructor.prototype | ||
Object.setPrototypeOf(P,$) | ||
const S=Object.getPrototypeOf(async function*(){}()) | ||
async function O(e,r){try{const t=await e;(null==t?void 0:t.done)&&r()}catch(e){}}function j(e,r){return{done:e,value:r}}Object.setPrototypeOf(P.prototype,S),e.AsyncCall=v,e.AsyncGeneratorCall=(e={},r)=>{var t | ||
const o=new Map,n=p(null===(t=r.strict)||void 0===t||t),{idGenerator:i=y}=r | ||
function a(e,r,t){const i=o.get(e) | ||
if(!i){if(n.methodNotFound)throw Error(`Iterator ${e} not found, ${r}() failed.`) | ||
return m}const a=t(i) | ||
return O(a,()=>o.delete(e)),a}const s=v({async[E](r,t){const a=Reflect.get(await e,r) | ||
if("function"!=typeof a){if(n.methodNotFound)throw Error(r+" is not a function") | ||
return m}const s=a(...t),c=i() | ||
return o.set(c,s),Promise.resolve(c)},[w]:(e,r)=>a(e,"next",e=>e.next(r)),[b]:(e,r)=>a(e,"return",e=>{var t | ||
return null===(t=e.return)||void 0===t?void 0:t.call(e,r)}),[k]:(e,r)=>a(e,"throw",e=>{var t | ||
return null===(t=e.throw)||void 0===t?void 0:t.call(e,r)})},r) | ||
return new Proxy({},{get:(e,r)=>{if("string"!=typeof r)throw new TypeError("[*AsyncCall] Only string can be the method name") | ||
return(...e)=>{const t=s[E](r,e) | ||
return new P(s,t)}}})},e.JSONSerialization=(e=[void 0,void 0],r,t="null")=>({serialization(o){if(t&&l(o)&&u(o,"result")&&void 0===o.result){const e=Object.assign({},o) | ||
e.result=null,"keep"===t&&(e.undef=!0),o=e}return JSON.stringify(o,e[0],r)},deserialization(r){const t=JSON.parse(r,e[1]) | ||
return l(t)&&u(t,"result")&&null===t.result&&u(t,"undef")&&!0===t.undef&&(t.result=void 0,delete t.undef),t}}),e.NoSerialization=f,Object.defineProperty(e,"__esModule",{value:!0})})) | ||
//# sourceMappingURL=full.js.map |
{ | ||
"name": "async-call-rpc", | ||
"version": "2.0.2", | ||
"version": "3.0.0", | ||
"description": "A lightweight JSON RPC server & client", | ||
"main": "out/full.js", | ||
"module": "out/full.mjs", | ||
"types": "./out/full.d.ts", | ||
"main": "out/base.js", | ||
"module": "out/base.mjs", | ||
"types": "./out/base.d.ts", | ||
"exports": { | ||
".": { | ||
"require": "./out/base.js", | ||
"import": "./out/base.mjs" | ||
}, | ||
"./full": { | ||
"require": "./out/full.js", | ||
@@ -19,11 +23,13 @@ "import": "./out/full.mjs" | ||
"scripts": { | ||
"start": "node ./watch.js", | ||
"prepublishOnly": "npm run build", | ||
"release": "rimraf ./out && standard-version", | ||
"start": "node ./watch.js", | ||
"build": "tsc --declaration --outDir ./es/", | ||
"prepublishOnly": "npm run build && npm run roll && npm run doc", | ||
"build:tsc": "tsc --declaration --outDir ./es/", | ||
"build:roll": "rollup -c", | ||
"build": "npm run build:tsc && npm run build:roll && npm run doc", | ||
"doc:api": "api-extractor run --local --verbose", | ||
"doc:md": "api-documenter markdown -o docs -i temp", | ||
"doc:preview": "docsify serve .", | ||
"doc": "npm run doc:api && npm run doc:md", | ||
"doc:preview": "docsify serve .", | ||
"roll": "rollup -c" | ||
"test": "jest --coverage --coverage-provider=v8" | ||
}, | ||
@@ -47,8 +53,13 @@ "repository": { | ||
"@rollup/plugin-typescript": "^4.1.2", | ||
"@types/jest": "^26.0.0", | ||
"@types/node": "^14.0.13", | ||
"docsify-cli": "^4.4.1", | ||
"jest": "^26.0.1", | ||
"prettier": "^2.0.5", | ||
"rimraf": "^3.0.2", | ||
"rollup": "^2.13.1", | ||
"rollup": "^2.16.1", | ||
"rollup-plugin-dts": "^1.4.7", | ||
"rollup-plugin-terser": "^6.1.0", | ||
"standard-version": "^8.0.0", | ||
"ts-jest": "^26.1.0", | ||
"tslib": "^2.0.0", | ||
@@ -55,0 +66,0 @@ "typescript": "^3.9.5" |
@@ -5,5 +5,10 @@ # Async Call | ||
[![Code coverage](https://codecov.io/gh/Jack-Works/async-call-rpc/branch/master/graph/badge.svg)](https://codecov.io/gh/Jack-Works/async-call-rpc) | ||
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/Jack-Works/async-call-rpc/build)](https://github.com/Jack-Works/async-call-rpc/actions) | ||
[![npm](https://img.shields.io/npm/v/async-call-rpc)](https://npmjs.org/async-call-rpc) | ||
![ES2015+](https://img.shields.io/badge/ECMAScript-2015%2B-brightgreen) | ||
## Links | ||
[CHANGELOG.md](./CHANGELOG.md) | [Document of AsyncCall](https://jack-works.github.io/async-call/async-call-rpc.asynccall.html) | [Document of AsyncGeneratorCall](https://jack-works.github.io/async-call/async-call-rpc.asyncgeneratorcall.html) | [Playground](https://jack-works.github.io/async-call/) | ||
[CHANGELOG.md](./CHANGELOG.md) | [Document of AsyncCall](https://jack-works.github.io/async-call-rpc/async-call-rpc.asynccall.html) | [Document of AsyncGeneratorCall](https://jack-works.github.io/async-call-rpc/async-call-rpc.asyncgeneratorcall.html) | [Playground](https://jack-works.github.io/async-call-rpc/) | ||
@@ -16,2 +21,3 @@ Chapters: | ||
- [Entries](#entries) | ||
- [Look this if both server and client are created by this library](#look-this-if-both-server-and-client-are-created-by-this-library) | ||
- [Implemented JSON RPC internal methods](#implemented-json-rpc-internal-methods) | ||
@@ -32,4 +38,3 @@ - [Non-standard extension to JSON RPC specification](#non-standard-extension-to-json-rpc-specification) | ||
- NOT support ECMAScript 5 (ES6 `Proxy` is the core of this library) | ||
- This package is shipping ECMAScript 2018 syntax (including `async function`). You need to use a transformer to transpile to ES6. | ||
- The default configuration is not standard JSON RPC (with a small extension to help easy using in JavaScript). But you can [switch on the "strict" mode](https://jack-works.github.io/async-call/async-call-rpc.asynccallstrictjsonrpc.html) | ||
- This package is shipping ECMAScript 2018 syntax (including `async function`). | ||
- The async generator mode might leak memory on the server. Use it by your caution. | ||
@@ -131,6 +136,4 @@ - NOT support JSON RPC 1.0 | ||
> Currently the default entry is `full` but in the next major version, it will be `base`. | ||
This library has 2 entry. `base` and `full`. `base` is the default entry point. The `full` version includes the `AsyncGeneratorCall` but the base version doesn't. | ||
This library has 2 entry. `base` and `full`. The difference is the `full` version includes the `AsyncGeneratorCall` but the base version doesn't. | ||
### Browser / Deno | ||
@@ -152,2 +155,9 @@ | ||
## Look this if both server and client are created by this library | ||
AsyncCall has some non-standard extensions to the JSON RPC specification that can help the library easier to use. Those features aren't enabled by default. | ||
- Send call stack of Error response or send call stack of caller's request. See [remoteStack on Request object](#remotestack-on-request-object) | ||
- Try to keep the "undefined" result when using JSONSerialization. See ["undef" on response object](#undef-on-response-object) | ||
## Implemented JSON RPC internal methods | ||
@@ -177,3 +187,3 @@ | ||
Controlled by [`option.log.sendLocalStack`](https://jack-works.github.io/async-call/async-call-rpc.asynccallloglevel.sendlocalstack.html). Default to `false`. | ||
Controlled by [`option.log.sendLocalStack`](https://jack-works.github.io/async-call-rpc/async-call-rpc.asynccallloglevel.sendlocalstack.html). Default to `false`. | ||
@@ -187,7 +197,7 @@ ```ts | ||
### resultIsUndefined on Response object | ||
### "undef" on Response object | ||
This is a non-standard property. It's a hint to the client, that the result is `undefined` (because in JSON there is no `undefined`, only `null`). If this is option is off, the `undefined` result will become `null`. | ||
This is a non-standard property appears when using JSONSerialization due to JSON doesn't support `undefined`. It's a hint to the client, that the result is `undefined`. | ||
Controlled by [`strict.noUndefined`](https://jack-works.github.io/async-call/async-call-rpc.asynccallstrictjsonrpc.noundefined.html). Default to `false`. | ||
This behavior is controlled by the 3rd parameter of [JSONSerialization(replacerAndReceiver?, space?, undefinedKeepingBehavior?: false | "keep" | "null" = "null")](https://jack-works.github.io/async-call-rpc/async-call-rpc.jsonserialization.html). Default to `"null"`. To turn on this feature to "keep" undefined values, change the 3rd option to "keep". | ||
@@ -198,3 +208,3 @@ ```ts | ||
// If the client is run in JavaScript, it should treat "result: null" as "result: undefined" | ||
resultIsUndefined?: boolean | ||
undef?: boolean | ||
} | ||
@@ -209,4 +219,2 @@ ``` | ||
Todo: [Disable this behavior](https://github.com/Jack-Works/async-call/issues/18) | ||
```ts | ||
@@ -213,0 +221,0 @@ interface JSONRPC_Error_object { |
/** | ||
* See the document at https://github.com/Jack-Works/async-call/ | ||
*/ | ||
import { AsyncCallOptions, AsyncCall } from './Async-Call.js' | ||
import { AsyncCallOptions, AsyncCall } from './Async-Call' | ||
import { AsyncCallIgnoreResponse } from './utils/internalSymbol' | ||
@@ -9,12 +9,12 @@ import { normalizeStrictOptions } from './utils/normalizeOptions' | ||
const _AsyncIteratorStart = Symbol.for('rpc.async-iterator.start') | ||
const _AsyncIteratorNext = Symbol.for('rpc.async-iterator.next') | ||
const _AsyncIteratorReturn = Symbol.for('rpc.async-iterator.return') | ||
const _AsyncIteratorThrow = Symbol.for('rpc.async-iterator.throw') | ||
const AsyncIteratorStart = Symbol.for('rpc.async-iterator.start') | ||
const AsyncIteratorNext = Symbol.for('rpc.async-iterator.next') | ||
const AsyncIteratorReturn = Symbol.for('rpc.async-iterator.return') | ||
const AsyncIteratorThrow = Symbol.for('rpc.async-iterator.throw') | ||
interface AsyncGeneratorInternalMethods { | ||
[_AsyncIteratorStart](method: string, params: unknown[]): Promise<string> | ||
[_AsyncIteratorNext](id: string, value: unknown): Promise<IteratorResult<unknown>> | ||
[_AsyncIteratorReturn](id: string, value: unknown): Promise<IteratorResult<unknown>> | ||
[_AsyncIteratorThrow](id: string, value: unknown): Promise<IteratorResult<unknown>> | ||
[AsyncIteratorStart](method: string, params: unknown[]): Promise<string> | ||
[AsyncIteratorNext](id: string, value: unknown): Promise<IteratorResult<unknown>> | ||
[AsyncIteratorReturn](id: string, value: unknown): Promise<IteratorResult<unknown>> | ||
[AsyncIteratorThrow](id: string, value: unknown): Promise<IteratorResult<unknown>> | ||
} | ||
@@ -85,6 +85,7 @@ /** | ||
thisSideImplementation: object | Promise<object> = {}, | ||
options: Partial<AsyncCallOptions> & Pick<AsyncCallOptions, 'messageChannel'>, | ||
options: AsyncCallOptions, | ||
): _AsyncGeneratorVersionOf<OtherSideImplementedFunctions> { | ||
const iterators = new Map<string, Iter>() | ||
const strict = normalizeStrictOptions(options.strict || false) | ||
const iterators = new Map<string | number, Iter>() | ||
const strict = normalizeStrictOptions(options.strict ?? true) | ||
const { idGenerator = generateRandomID } = options | ||
function findIterator( | ||
@@ -97,3 +98,3 @@ id: string, | ||
if (!it) { | ||
if (strict.methodNotFound) throw new Error(`Remote iterator not found while executing ${label}`) | ||
if (strict.methodNotFound) throw new Error(`Iterator ${id} not found, ${label}() failed.`) | ||
else return AsyncCallIgnoreResponse | ||
@@ -106,3 +107,3 @@ } | ||
const server = { | ||
async [_AsyncIteratorStart](method, args) { | ||
async [AsyncIteratorStart](method, args) { | ||
const iteratorGenerator: unknown = Reflect.get(await thisSideImplementation, method) | ||
@@ -114,13 +115,13 @@ if (typeof iteratorGenerator !== 'function') { | ||
const iterator = iteratorGenerator(...args) | ||
const id = generateRandomID() | ||
const id = idGenerator() | ||
iterators.set(id, iterator) | ||
return Promise.resolve(id) | ||
}, | ||
[_AsyncIteratorNext](id, val) { | ||
[AsyncIteratorNext](id, val) { | ||
return findIterator(id, 'next', (it) => it.next(val as any)) | ||
}, | ||
[_AsyncIteratorReturn](id, val) { | ||
[AsyncIteratorReturn](id, val) { | ||
return findIterator(id, 'return', (it) => it.return?.(val)) | ||
}, | ||
[_AsyncIteratorThrow](id, val) { | ||
[AsyncIteratorThrow](id, val) { | ||
return findIterator(id, 'throw', (it) => it.throw?.(val)) | ||
@@ -136,3 +137,3 @@ }, | ||
return function (...args: unknown[]) { | ||
const id = remote[_AsyncIteratorStart](key, args) | ||
const id = remote[AsyncIteratorStart](key, args) | ||
return new AsyncGenerator(remote, id) | ||
@@ -144,37 +145,41 @@ } | ||
class AsyncGenerator implements AsyncIterableIterator<unknown>, AsyncIterator<unknown, unknown, unknown> { | ||
// #remoteImpl | ||
private __0: AsyncGeneratorInternalMethods | ||
// #id | ||
private __1: Promise<string> | ||
// #done | ||
private __2: boolean | ||
// #check | ||
private __3 = (val: IterResult) => { | ||
isFinished(val, () => (this.__2 = true)) | ||
/** done? */ | ||
private d: boolean = false | ||
/** check */ | ||
private c = async (val: IterResult) => { | ||
await isFinished(val, () => (this.d = true)) | ||
return val | ||
} | ||
constructor(remoteImpl: AsyncGeneratorInternalMethods, id: Promise<string>) { | ||
this.__0 = remoteImpl | ||
this.__1 = id | ||
this.__2 = false | ||
} | ||
/** | ||
* @param r Remote Implementation | ||
* @param i id | ||
*/ | ||
constructor(private r: AsyncGeneratorInternalMethods, private i: Promise<string>) {} | ||
async return(val: unknown) { | ||
if (this.__2) return makeIteratorResult(true, val) | ||
return this.__3(this.__0[_AsyncIteratorReturn](await this.__1, val)) | ||
if (!this.d) this.c(this.r[AsyncIteratorReturn](await this.i, val)).catch(() => {}) | ||
this.d = true | ||
return makeIteratorResult(true, val) | ||
} | ||
async next(val?: unknown) { | ||
if (this.__2) return makeIteratorResult(true) | ||
return this.__3(this.__0[_AsyncIteratorNext](await this.__1, val)) | ||
if (this.d) return makeIteratorResult(true) | ||
return await this.c(this.r[AsyncIteratorNext](await this.i, val)) | ||
} | ||
async throw(val?: unknown) { | ||
if (this.__2) throw val | ||
return this.__3(this.__0[_AsyncIteratorThrow](await this.__1, val)) | ||
if (!this.d) return await this.c(this.r[AsyncIteratorThrow](await this.i, val)) | ||
throw val | ||
} | ||
[Symbol.asyncIterator]() { | ||
return this | ||
} | ||
// Inherited from AsyncGeneratorPrototype | ||
declare [Symbol.asyncIterator]: () => this | ||
} | ||
const AsyncGeneratorConstructor = async function* () {}.constructor | ||
const AsyncGeneratorConstructorPrototype = AsyncGeneratorConstructor.prototype | ||
Object.setPrototypeOf(AsyncGenerator, AsyncGeneratorConstructorPrototype) | ||
const AsyncGeneratorPrototype = Object.getPrototypeOf((async function* () {})()) | ||
Object.setPrototypeOf(AsyncGenerator.prototype, AsyncGeneratorPrototype) | ||
async function isFinished(result: IterResult | undefined, cb: () => void) { | ||
const x = await result | ||
!!x?.done && cb | ||
try { | ||
const x = await result | ||
!!x?.done && cb() | ||
} catch {} | ||
} | ||
@@ -181,0 +186,0 @@ |
@@ -9,3 +9,13 @@ /** | ||
export { Console } from './utils/console' | ||
import { Request, Response, ErrorResponse, SuccessResponse, hasKey, isJSONRPCObject, isObject } from './utils/jsonrpc' | ||
import { | ||
Request, | ||
Response, | ||
ErrorResponseMapped, | ||
SuccessResponse, | ||
hasKey, | ||
isJSONRPCObject, | ||
isObject, | ||
ErrorResponse, | ||
defaultErrorMapper, | ||
} from './utils/jsonrpc' | ||
import { removeStackHeader, RecoverError } from './utils/error' | ||
@@ -55,14 +65,9 @@ import { generateRandomID } from './utils/generateRandomID' | ||
/** | ||
* Return an error when the requested method is not defined | ||
* @defaultValue false | ||
* Return an error when the requested method is not defined, otherwise, ignore the request. | ||
* @defaultValue true | ||
*/ | ||
methodNotFound?: boolean | ||
/** | ||
* don't try to keep `undefined` result (then it will be `null`) | ||
* @defaultValue false | ||
*/ | ||
noUndefined?: boolean | ||
/** | ||
* send an error when receive invalid JSON RPC payload | ||
* @defaultValue false | ||
* @defaultValue true | ||
*/ | ||
@@ -73,2 +78,11 @@ unknownMessage?: boolean | ||
/** | ||
* The message channel interface that allows | ||
* @public | ||
*/ | ||
export interface MessageChannel { | ||
on(event: string, eventListener: (data: unknown) => void): void | ||
emit(event: string, data: unknown): void | ||
} | ||
/** | ||
* Options for {@link AsyncCall} | ||
@@ -95,3 +109,3 @@ * @public | ||
*/ | ||
key: string | ||
key?: string | ||
/** | ||
@@ -110,3 +124,3 @@ * How to serialization and deserialization JSON RPC payload | ||
*/ | ||
serializer: Serialization | ||
serializer?: Serialization | ||
/** | ||
@@ -118,3 +132,3 @@ * The logger of AsyncCall | ||
*/ | ||
logger: Console | ||
logger?: Console | ||
/** | ||
@@ -134,6 +148,3 @@ * The message channel can let you transport messages between server and client | ||
*/ | ||
messageChannel: { | ||
on(event: string, callback: (data: unknown) => void): void | ||
emit(event: string, data: unknown): void | ||
} | ||
messageChannel: MessageChannel | ||
/** | ||
@@ -143,3 +154,3 @@ * Choose log level. See {@link AsyncCallLogLevel} | ||
*/ | ||
log: AsyncCallLogLevel | boolean | ||
log?: AsyncCallLogLevel | boolean | ||
/** | ||
@@ -149,3 +160,3 @@ * Strict options. See {@link AsyncCallStrictJSONRPC} | ||
*/ | ||
strict: AsyncCallStrictJSONRPC | boolean | ||
strict?: AsyncCallStrictJSONRPC | boolean | ||
/** | ||
@@ -157,3 +168,3 @@ * How parameters passed to remote | ||
*/ | ||
parameterStructures: 'by-position' | 'by-name' | ||
parameterStructures?: 'by-position' | 'by-name' | ||
/** | ||
@@ -165,3 +176,3 @@ * Prefer local implementation than remote. | ||
*/ | ||
preferLocalImplementation: boolean | ||
preferLocalImplementation?: boolean | ||
/** | ||
@@ -178,6 +189,34 @@ * (Browser) Try to preserve the browser "pause on uncaught exception". | ||
*/ | ||
preservePauseOnException: boolean | ||
preservePauseOnException?: boolean | ||
/** | ||
* The ID generator of each JSON RPC request | ||
* @defaultValue () => Math.random().toString(36).slice(2) | ||
*/ | ||
idGenerator?(): string | number | ||
/** | ||
* Control the error response data | ||
* @param error The happened Error | ||
* @param request The request object | ||
*/ | ||
mapError?: ErrorMapFunction<unknown> | ||
} | ||
/** | ||
* @public | ||
*/ | ||
export type ErrorMapFunction<T = unknown> = ( | ||
error: unknown, | ||
request: Readonly<{ | ||
jsonrpc: '2.0' | ||
id?: string | number | null | ||
method: string | ||
params: readonly unknown[] | object | ||
}>, | ||
) => { | ||
code: number | ||
message: string | ||
data?: T | ||
} | ||
/** | ||
* Make all function in the type T Async | ||
@@ -194,6 +233,8 @@ * @internal | ||
const AsyncCallDefaultOptions = (<T extends Partial<AsyncCallOptions>>(a: T) => a)({ | ||
const AsyncCallDefaultOptions = (<T extends Omit<Required<AsyncCallOptions>, 'messageChannel' | 'logger' | 'mapError'>>( | ||
a: T, | ||
) => a)({ | ||
serializer: NoSerialization, | ||
key: 'default-jsonrpc', | ||
strict: false, | ||
strict: true, | ||
log: true, | ||
@@ -203,3 +244,4 @@ parameterStructures: 'by-position', | ||
preservePauseOnException: false, | ||
} as const) | ||
idGenerator: generateRandomID, | ||
}) | ||
@@ -226,7 +268,18 @@ /** | ||
thisSideImplementation: object | Promise<object> = {}, | ||
options: Partial<AsyncCallOptions> & Pick<AsyncCallOptions, 'messageChannel'>, | ||
options: AsyncCallOptions, | ||
): _AsyncVersionOf<OtherSideImplementedFunctions> { | ||
let resolvedThisSideImplementation: object | undefined = undefined | ||
if (!(thisSideImplementation instanceof Promise)) resolvedThisSideImplementation = thisSideImplementation | ||
Promise.resolve(thisSideImplementation).then((x) => (resolvedThisSideImplementation = x)) | ||
const { serializer, key, strict, log, parameterStructures, preferLocalImplementation, preservePauseOnException } = { | ||
const { | ||
serializer, | ||
key, | ||
strict, | ||
log, | ||
parameterStructures, | ||
preferLocalImplementation, | ||
preservePauseOnException, | ||
idGenerator, | ||
mapError, | ||
} = { | ||
...AsyncCallDefaultOptions, | ||
@@ -238,3 +291,2 @@ ...options, | ||
methodNotFound: banMethodNotFound = false, | ||
noUndefined: noUndefinedKeeping = false, | ||
unknownMessage: banUnknownMessage = false, | ||
@@ -259,3 +311,3 @@ } = normalizeStrictOptions(strict) | ||
const executor: unknown = resolvedThisSideImplementation![key] | ||
if (!executor || typeof executor !== 'function') { | ||
if (typeof executor !== 'function') { | ||
if (!banMethodNotFound) { | ||
@@ -266,35 +318,36 @@ if (logLocalError) console.debug('Receive remote call, but not implemented.', key, data) | ||
} | ||
const params: unknown = data.params | ||
if (Array.isArray(params) || (typeof params === 'object' && params !== null)) { | ||
const args = Array.isArray(params) ? params : [params] | ||
frameworkStack = removeStackHeader(new Error().stack) | ||
const promise = preservePauseOnException | ||
? preservePauseOnExceptionCaller((x) => (frameworkStack = x), executor, args) | ||
: new Promise((resolve) => resolve(executor(...args))) | ||
if (logBeCalled) { | ||
if (logType === 'basic') | ||
console.log(`${options.key}.${data.method}(${[...args].toString()}) @${data.id}`) | ||
else { | ||
const logArgs = [ | ||
`${options.key}.%c${data.method}%c(${args.map(() => '%o').join(', ')}%c)\n%o %c@${data.id}`, | ||
'color: #d2c057', | ||
'', | ||
...args, | ||
'', | ||
promise, | ||
'color: gray; font-style: italic;', | ||
] | ||
if (data.remoteStack) { | ||
console.groupCollapsed(...logArgs) | ||
console.log(data.remoteStack) | ||
console.groupEnd() | ||
} else console.log(...logArgs) | ||
} | ||
const { params } = data | ||
const args = Array.isArray(params) ? params : [params] | ||
frameworkStack = removeStackHeader(new Error().stack) | ||
const promise = preservePauseOnException | ||
? preservePauseOnExceptionCaller( | ||
(x) => (frameworkStack = x), | ||
executor, | ||
resolvedThisSideImplementation, | ||
args, | ||
) | ||
: new Promise((resolve) => resolve(executor.apply(resolvedThisSideImplementation, args))) | ||
if (logBeCalled) { | ||
if (logType === 'basic') | ||
console.log(`${options.key}.${data.method}(${[...args].toString()}) @${data.id}`) | ||
else { | ||
const logArgs = [ | ||
`${options.key}.%c${data.method}%c(${args.map(() => '%o').join(', ')}%c)\n%o %c@${data.id}`, | ||
'color: #d2c057', | ||
'', | ||
...args, | ||
'', | ||
promise, | ||
'color: gray; font-style: italic;', | ||
] | ||
if (data.remoteStack) { | ||
console.groupCollapsed(...logArgs) | ||
console.log(data.remoteStack) | ||
console.groupEnd() | ||
} else console.log(...logArgs) | ||
} | ||
const result = await promise | ||
if (result === AsyncCallIgnoreResponse) return | ||
return SuccessResponse(data.id, await promise, !!noUndefinedKeeping) | ||
} else { | ||
return ErrorResponse.InvalidRequest(data.id) | ||
} | ||
const result = await promise | ||
if (result === AsyncCallIgnoreResponse) return | ||
return SuccessResponse(data.id, await promise) | ||
} catch (e) { | ||
@@ -306,6 +359,6 @@ if (typeof e === 'object' && 'stack' in e) | ||
if (logLocalError) console.error(e) | ||
return ErrorResponse(data.id, -1, e?.message, e?.stack, e) | ||
return ErrorResponseMapped(data, e, mapError || defaultErrorMapper(sendLocalStack ? e.stack : void 0)) | ||
} | ||
} | ||
async function onResponse(data: Response): Promise<void> { | ||
async function onResponse(data: Response): Promise<undefined> { | ||
let errorMessage = '', | ||
@@ -316,6 +369,14 @@ remoteErrorStack = '', | ||
if (hasKey(data, 'error')) { | ||
errorMessage = data.error.message | ||
errorCode = data.error.code | ||
remoteErrorStack = data.error.data?.stack ?? '<remote stack not available>' | ||
errorType = data.error.data?.type || 'Error' | ||
const e = data.error | ||
errorMessage = e.message | ||
errorCode = e.code | ||
const detail = e.data | ||
if (isObject(detail) && hasKey(detail, 'stack') && typeof detail.stack === 'string') | ||
remoteErrorStack = detail.stack | ||
else remoteErrorStack = '<remote stack not available>' | ||
if (isObject(detail) && hasKey(detail, 'type') && typeof detail.type === 'string') errorType = detail.type | ||
else errorType = 'Error' | ||
if (logRemoteError) | ||
@@ -350,2 +411,3 @@ logType === 'basic' | ||
} | ||
return undefined | ||
} | ||
@@ -367,3 +429,3 @@ message.on(key, async (_: unknown) => { | ||
if (banUnknownMessage) { | ||
await send(ErrorResponse.InvalidRequest((data as any).id || null)) | ||
await send(ErrorResponse.InvalidRequest((data as any).id ?? null)) | ||
} else { | ||
@@ -374,8 +436,8 @@ // ? Ignore this message. The message channel maybe also used to transfer other message too. | ||
} catch (e) { | ||
console.error(e, data, result) | ||
send(ErrorResponse.ParseError(e?.stack)) | ||
if (logLocalError) console.error(e, data, result) | ||
send(ErrorResponse.ParseError(e, mapError || defaultErrorMapper(e?.stack))) | ||
} | ||
async function send(res?: Response | (Response | undefined)[]) { | ||
if (Array.isArray(res)) { | ||
const reply = res.map((x) => x).filter((x) => x!.id !== undefined) | ||
const reply = res.filter((x) => hasKey(x, 'id')) | ||
if (reply.length === 0) return | ||
@@ -386,3 +448,3 @@ message.emit(key, await serializer.serialization(reply)) | ||
// ? This is a Notification, we MUST not return it. | ||
if (res.id === undefined) return | ||
if (!hasKey(res, 'id')) return | ||
message.emit(key, await serializer.serialization(res)) | ||
@@ -395,13 +457,11 @@ } | ||
{ | ||
get(_target, method) { | ||
get(_target, method: string | symbol) { | ||
let stack = removeStackHeader(new Error().stack) | ||
return (...params: unknown[]) => { | ||
if (typeof method !== 'string') { | ||
if (typeof method === 'symbol') { | ||
const internalMethod = Symbol.keyFor(method) | ||
if (internalMethod) method = internalMethod | ||
} else return Promise.reject(new TypeError('[AsyncCall] Only string can be the method name')) | ||
if (typeof method === 'symbol') { | ||
const internalMethod = Symbol.keyFor(method) | ||
if (internalMethod) method = internalMethod | ||
} else if (method.startsWith('rpc.')) | ||
return Promise.reject( | ||
new TypeError('[AsyncCall] You cannot call JSON RPC internal methods directly'), | ||
new TypeError("[AsyncCall] Can't call JSON RPC internal methods directly"), | ||
) | ||
@@ -415,3 +475,3 @@ if (preferLocalImplementation && resolvedThisSideImplementation && typeof method === 'string') { | ||
return new Promise((resolve, reject) => { | ||
const id = generateRandomID() | ||
const id = idGenerator() | ||
const [param0] = params | ||
@@ -437,15 +497,8 @@ const sendingStack = sendLocalStack ? stack : '' | ||
async function handleSingleMessage(data: SuccessResponse | ErrorResponse | Request) { | ||
if (hasKey(data, 'method')) { | ||
return onRequest(data) | ||
} else if ('error' in data || 'result' in data) { | ||
onResponse(data) | ||
} else { | ||
if ('resultIsUndefined' in data) { | ||
;(data as any).result = undefined | ||
onResponse(data) | ||
} else return ErrorResponse.InvalidRequest((data as any).id) | ||
} | ||
return undefined | ||
async function handleSingleMessage( | ||
data: SuccessResponse | ErrorResponse | Request, | ||
): Promise<SuccessResponse | ErrorResponse | undefined> { | ||
if (hasKey(data, 'method')) return onRequest(data) | ||
return onResponse(data) | ||
} | ||
} |
/** | ||
* A light implementation of {@link https://www.jsonrpc.org/specification | JSON RPC 2.0} | ||
* @packageDocumentation | ||
* @see https://github.com/Jack-Works/async-call | ||
* @remarks | ||
@@ -6,0 +5,0 @@ * See the introduction at {@link https://github.com/Jack-Works/async-call | Github} |
@@ -7,9 +7,9 @@ //#region Console | ||
export interface Console { | ||
debug(...args: unknown[]): void | ||
debug?(...args: unknown[]): void | ||
log(...args: unknown[]): void | ||
groupCollapsed(...args: unknown[]): void | ||
groupEnd(...args: unknown[]): void | ||
error(...args: unknown[]): void | ||
groupCollapsed?(...args: unknown[]): void | ||
groupEnd?(...args: unknown[]): void | ||
error?(...args: unknown[]): void | ||
} | ||
export function getConsole(_console?: Console): Console { | ||
export function getConsole(_console?: Console): Required<Console> { | ||
const console: Console = _console || (globalThis as any).console | ||
@@ -16,0 +16,0 @@ const defaultLog = (...args: unknown[]) => console.log(...args) |
@@ -20,3 +20,3 @@ class CustomError extends Error { | ||
*/ | ||
export function RecoverError(type: string, message: string, code: number, stack: string) { | ||
export function RecoverError(type: string, message: string, code: number, stack: string): Error { | ||
try { | ||
@@ -23,0 +23,0 @@ if (type.startsWith(DOMExceptionHeader) && DOMException) { |
@@ -1,1 +0,1 @@ | ||
export const AsyncCallIgnoreResponse = Symbol.for('AsyncCall: This response should be ignored.') | ||
export const AsyncCallIgnoreResponse = Symbol.for('AsyncCall/ignored') |
import { DOMException, DOMExceptionHeader } from './error' | ||
import { ErrorMapFunction } from '../Async-Call' | ||
const jsonrpc = '2.0' | ||
type ID = string | number | null | undefined | ||
export const jsonrpc = '2.0' | ||
export type ID = string | number | null | undefined | ||
/** | ||
* JSONRPC Request object. | ||
*/ | ||
export interface Request | ||
extends Readonly<{ | ||
jsonrpc: typeof jsonrpc | ||
id?: ID | ||
method: string | ||
params: readonly unknown[] | object | ||
remoteStack?: string | ||
}> {} | ||
type JSONRPC = Readonly<{ | ||
jsonrpc: typeof jsonrpc | ||
id: ID | ||
}> | ||
export type Request = Readonly<{ | ||
method: string | ||
params: readonly unknown[] | object | ||
}> & { remoteStack?: string } & JSONRPC | ||
export function Request(id: ID, method: string, params: readonly unknown[] | object, remoteStack: string): Request { | ||
export function Request(id: ID, method: string, params: readonly unknown[] | object, remoteStack?: string): Request { | ||
const x: Request = { jsonrpc, id, method, params, remoteStack } | ||
if (remoteStack.length === 0) delete x.remoteStack | ||
deleteUndefined(x, 'id') | ||
deleteFalsy(x, 'remoteStack') | ||
return x | ||
} | ||
export type SuccessResponse = Readonly<{ result: unknown }> & { resultIsUndefined?: boolean } & JSONRPC | ||
export function SuccessResponse(id: ID, result: any, noUndefinedKeeping: boolean): SuccessResponse { | ||
const x: SuccessResponse = { jsonrpc, id, result: result === undefined ? null : result } | ||
if (!noUndefinedKeeping && result === undefined) { | ||
x.resultIsUndefined = true | ||
} | ||
/** | ||
* JSONRPC SuccessResponse object. | ||
*/ | ||
export interface SuccessResponse | ||
extends Readonly<{ | ||
jsonrpc: typeof jsonrpc | ||
id?: ID | ||
result: unknown | ||
}> {} | ||
export function SuccessResponse(id: ID, result: unknown): SuccessResponse { | ||
const x: SuccessResponse = { jsonrpc, id, result } | ||
deleteUndefined(x, 'id') | ||
return x | ||
} | ||
export type ErrorResponse = JSONRPC & | ||
Readonly<{ | ||
error: { code: number; message: string; data?: { stack?: string; type?: string } } | ||
}> | ||
/** | ||
* JSONRPC ErrorResponse object. | ||
* @public | ||
*/ | ||
export interface ErrorResponse<E = unknown> | ||
extends Readonly<{ | ||
jsonrpc: typeof jsonrpc | ||
id?: ID | ||
error: Readonly<{ code: number; message: string; data?: E }> | ||
}> {} | ||
export function ErrorResponse(id: ID, code: number, message: string, stack: string, e?: unknown): ErrorResponse { | ||
export function ErrorResponse<T>(id: ID, code: number, message: string, data?: T): ErrorResponse<T> { | ||
if (id === undefined) id = null | ||
code = Math.floor(code) | ||
if (Number.isNaN(code)) code = -1 | ||
const x: ErrorResponse<T> = { jsonrpc, id, error: { code, message, data } } | ||
deleteUndefined(x.error, 'data') | ||
return x | ||
} | ||
// Pre defined error in section 5.1 | ||
ErrorResponse.ParseError = <T>(e: unknown, mapper: ErrorMapFunction<T>): ErrorResponse<T> => { | ||
const obj = ErrorResponseMapped({} as any, e, mapper) | ||
const o = obj.error as Mutable<ErrorResponse['error']> | ||
o.code = -32700 | ||
o.message = 'Parse error' | ||
return obj | ||
} | ||
// Not using. | ||
// InvalidParams -32602 'Invalid params' | ||
// InternalError -32603 'Internal error' | ||
ErrorResponse.InvalidRequest = (id: ID) => ErrorResponse(id, -32600, 'Invalid Request') | ||
ErrorResponse.MethodNotFound = (id: ID) => ErrorResponse(id, -32601, 'Method not found') | ||
type AsyncCallErrorDetail = { | ||
stack?: string | ||
type?: string | ||
} | ||
export function ErrorResponseMapped<T>(request: Request, e: unknown, mapper: ErrorMapFunction<T>): ErrorResponse<T> { | ||
const { id } = request | ||
const { code, message, data } = mapper(e, request) | ||
return ErrorResponse(id, code, message, data) | ||
} | ||
export const defaultErrorMapper = (stack = '', code = -1): ErrorMapFunction<AsyncCallErrorDetail> => (e) => { | ||
let message = '' | ||
if (isObject(e) && hasKey(e, 'message') && typeof e.message === 'string') message = e.message | ||
let type = toString('Error', () => (e as any)?.constructor?.name) | ||
@@ -41,17 +93,9 @@ if (DOMException && e instanceof DOMException) type = DOMExceptionHeader + e.name | ||
} | ||
if (id === undefined) id = null | ||
code = Math.floor(code) | ||
if (Number.isNaN(code)) code = -1 | ||
const error: ErrorResponse['error'] = { code, message, data: { stack, type } } | ||
return { error, id, jsonrpc } | ||
const data: AsyncCallErrorDetail = stack ? { stack, type } : { type } | ||
return { code, message, data } | ||
} | ||
// Pre defined error in section 5.1 | ||
ErrorResponse.ParseError = (stack = '') => ErrorResponse(null, -32700, 'Parse error', stack) | ||
ErrorResponse.InvalidRequest = (id: ID) => ErrorResponse(id, -32600, 'Invalid Request', '') | ||
ErrorResponse.MethodNotFound = (id: ID) => ErrorResponse(id, -32601, 'Method not found', '') | ||
ErrorResponse.InvalidParams = (id: ID) => ErrorResponse(id, -32602, 'Invalid params', '') | ||
ErrorResponse.InternalError = (id: ID, message: string = '') => | ||
ErrorResponse(id, -32603, 'Internal error' + message, '') | ||
/** | ||
* A JSONRPC response object | ||
*/ | ||
export type Response = SuccessResponse | ErrorResponse | ||
@@ -92,1 +136,8 @@ | ||
} | ||
function deleteUndefined<O>(x: O, key: keyof O) { | ||
if (x[key] === undefined) delete x[key] | ||
} | ||
function deleteFalsy<T>(x: T, key: keyof T) { | ||
if (!x[key]) delete x[key] | ||
} | ||
type Mutable<T> = { -readonly [key in keyof T]: T[key] } |
import { AsyncCallOptions, AsyncCallLogLevel, AsyncCallStrictJSONRPC } from '../Async-Call' | ||
export function normalizeLogOptions(log: AsyncCallOptions['log']): AsyncCallLogLevel { | ||
export function normalizeLogOptions(log: NonNullable<AsyncCallOptions['log']>): AsyncCallLogLevel { | ||
if (typeof log !== 'boolean') return log | ||
@@ -11,3 +11,3 @@ return { | ||
} | ||
export function normalizeStrictOptions(strict: AsyncCallOptions['strict']): AsyncCallStrictJSONRPC { | ||
export function normalizeStrictOptions(strict: NonNullable<AsyncCallOptions['strict']>): AsyncCallStrictJSONRPC { | ||
if (typeof strict !== 'boolean') return strict | ||
@@ -17,4 +17,3 @@ return { | ||
unknownMessage: strict, | ||
noUndefined: strict, | ||
} | ||
} |
import { removeStackHeader } from './error' | ||
declare const document: any | ||
export async function preservePauseOnException(stackCallback: (x: string) => void, f: Function, args: any[]) { | ||
const iframe = document.createElement('iframe') | ||
export async function preservePauseOnException( | ||
stackCallback: (x: string) => void, | ||
f: Function, | ||
thisBinding: any, | ||
args: any[], | ||
) { | ||
const promise = new Promise((resolve, reject) => { | ||
let iframe: any = {} | ||
async function executor() { | ||
stackCallback(removeStackHeader(new Error().stack)) | ||
// receive the return value | ||
resolve(await f(...args)) | ||
resolve(await f.apply(thisBinding, args)) | ||
} | ||
try { | ||
iframe = document.createElement('iframe') | ||
iframe.style.display = 'none' | ||
@@ -25,12 +30,16 @@ document.body.appendChild(iframe) | ||
}) | ||
window.addEventListener('unhandledrejection', (e: any) => reject(e.reason)) | ||
window.onunhandledrejection = (e: any) => reject(e.reason) | ||
button.click() | ||
} | ||
} catch (e) { | ||
console.error('Please close preservePauseOnException.', e) | ||
try { | ||
// @ts-expect-error | ||
console.error('Please close preservePauseOnException.', e) | ||
} catch {} | ||
return resolve(f(...args)) | ||
} finally { | ||
iframe?.remove?.() | ||
} | ||
}) | ||
promise.finally(() => iframe.remove()) | ||
return promise | ||
} |
//#region Serialization | ||
import { isObject, hasKey } from './jsonrpc' | ||
/** | ||
@@ -25,6 +28,6 @@ * Serialization and deserialization of the JSON RPC payload | ||
export const NoSerialization: Serialization = { | ||
async serialization(from) { | ||
serialization(from) { | ||
return from | ||
}, | ||
async deserialization(serialized) { | ||
deserialization(serialized) { | ||
return serialized | ||
@@ -39,2 +42,10 @@ }, | ||
* @param space - Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. | ||
* @param undefinedKeepingBehavior - How to keep "undefined" in result of SuccessResponse? | ||
* | ||
* If it is not handled properly, JSON.stringify will emit an invalid JSON RPC object. | ||
* | ||
* Options: | ||
* - `"null"`(**default**): convert it to null. | ||
* - `"keep"`: try to keep it by additional property "undef". | ||
* - `false`: Don't keep it, let it break. | ||
* @remarks {@link Serialization} | ||
@@ -44,13 +55,33 @@ * @public | ||
export const JSONSerialization = ( | ||
replacerAndReceiver: [Parameters<JSON['stringify']>[1], Parameters<JSON['parse']>[1]] = [undefined, undefined], | ||
replacerAndReceiver: [((key: string, value: any) => any)?, ((key: string, value: any) => any)?] = [ | ||
undefined, | ||
undefined, | ||
], | ||
space?: string | number | undefined, | ||
) => | ||
({ | ||
async serialization(from) { | ||
return JSON.stringify(from, replacerAndReceiver[0], space) | ||
}, | ||
async deserialization(serialized) { | ||
return JSON.parse(serialized as string, replacerAndReceiver[1]) | ||
}, | ||
} as Serialization) | ||
undefinedKeepingBehavior: 'keep' | 'null' | false = 'null', | ||
): Serialization => ({ | ||
serialization(from) { | ||
if (undefinedKeepingBehavior && isObject(from) && hasKey(from, 'result') && from.result === undefined) { | ||
const alt = Object.assign({}, from) | ||
alt.result = null | ||
if (undefinedKeepingBehavior === 'keep') (alt as any).undef = true | ||
from = alt | ||
} | ||
return JSON.stringify(from, replacerAndReceiver[0], space) | ||
}, | ||
deserialization(serialized) { | ||
const result = JSON.parse(serialized as string, replacerAndReceiver[1]) | ||
if ( | ||
isObject(result) && | ||
hasKey(result, 'result') && | ||
result.result === null && | ||
hasKey(result, 'undef') && | ||
result.undef === true | ||
) { | ||
result.result = undefined | ||
delete result.undef | ||
} | ||
return result | ||
}, | ||
}) | ||
//#endregion |
{ | ||
"compilerOptions": { | ||
"plugins": [ | ||
{ | ||
"transform": "@magic-works/ttypescript-browser-like-import-transformer", | ||
"after": true, | ||
"extName": ".ts" | ||
// Configs go here. | ||
} | ||
], | ||
"target": "es2018" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, | ||
@@ -28,5 +20,9 @@ "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, | ||
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, | ||
"rootDir": "./src/" | ||
"rootDir": "./src/", | ||
// This lib does not require any env | ||
"typeRoots": [], | ||
"types": [], | ||
"esModuleInterop": true | ||
}, | ||
"include": ["./src/**/*.ts"] | ||
} |
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
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
288301
1909
231
16