Sharing of resources
As you are aware, libraries have shared and non-shared resources. PropertiesService
is listed under non-shared resources, meaning that the library has its own instance of the service that is accessed when you reference it in the library code.
const getStore = () => PropertiesService.getScriptProperties();
If the function above is declared in the library, it will use the library's resource, if in the calling script - its own instance.
V8 runtime solution
V8 runtime does not create a special context for your code and gives you access to built-in services directly. Because of this when using the runtime, the service can be injected by simply defining or replacing a property on a global this
:
//in the library;
var getProperty = ((ctxt) => (key) => {
var service = ctxt.injectedService;
var store = service.getScriptProperties();
return store.getProperty(key);
})(this);
var setProperty = ((ctxt) => (key, val) => {
var service = ctxt.injectedService;
var store = service.getScriptProperties();
return store.setProperty(key, val);
})(this);
var inject = ((ctxt) => (service) => ctxt.injectedService = service)(this);
var greet = ((ctxt) => () => {
var store = ctxt.injectedService.getScriptProperties();
return store.getProperty("greeting") || "Ola!";
})(this);
//in the calling script;
function testSharedResources() {
PropertiesService.getScriptProperties().setProperty("greeting", "Hello, lib!");
$.inject(PropertiesService);
Logger.log($.greet()); //Hello, lib!
$.setProperty("greeting", "Hello, world!");
Logger.log($.greet()); //Hello, world!
}
In some contexts global this
will be undefined
(I encountered this when adding a library to a bound script). In this case, simply define a private global namespace (to avoid leaking to the caller script):
//in the library;
var Dependencies_ = {
properties : PropertiesService
};
var use = (service) => {
if ("getScriptProperties" in service) {
Dependencies_.properties = service;
}
};
//in the calling script;
$.use(PropertiesService);
Rhino runtime solution
Older Rhino runtime, on the other hand, creates a special implicit context. This means that you have no access to built-in services or the global this
. Your only option is to bypass calling the service in the library (your approach #3 is perfect for doing so).
Questions
- Why did the first two ways not work, but the third way did?
All issues with your approaches boil down to:
- Resource sharing (libraries have their own service instances)
- Special implicit context (no external access to lib built-ins in Rhino)
But there is a catch: all 3 approaches do work as designed.
First, Approach one does work if you specifically reference the PropertiesService
on $
. This makes sense as the library is included as a namespace with members mapped to global declarations in the library. For example:
//in the caller script
PropertiesService.getScriptProperties().setProperty("test", "test");
$.PropertiesService = PropertiesService;
Logger.log( $.PropertiesService.getScriptProperties().getProperty("test") ); // "test"
Logger.log( $.getProperty("test") ); // "null"
//in the library
function getProperty(key) {
var store = PropertiesService.getScriptProperties();
return store.getProperty(key);
}
Approach two also works. Binding of the function in the caller script does not change the fact if called in the library it receives library context, but if you call the bound copy directly in the calling script, it works:
//in the caller script
PropertiesService.getScriptProperties().setProperty("test", "test");
var bound = $.PropertiesService.getScriptProperties.bind(PropertiesService);
var obj = { getScriptProperties : bound };
$.PropertiesService = obj;
Logger.log( bound().getProperty("test") ); // "test"
Logger.log( $.getProperty("test") ); // "null"
Now, why does the third approach work out of the box? Because of the closure resulting from the wrapped function capturing the PropertiesService
of the calling script and applying the getScriptProperties
method. To illustrate:
//in the caller script
var appl = {
getScriptProperties : (function(val) {
return function() {
return val.apply(PropertiesService);
};
})(PropertiesService.getScriptProperties)
};
$.PropertiesService = appl;
Logger.log( $.getProperty("test") ); // "test"
- Can I expect this to continue working?
Yes and no. Yes, because your _mock
function behavior exhibits the expected behavior in all cases. No, because apply
relies on the getScriptProperties
not being implemented as an arrow function where this
override will be ignored.
- Is there a better way to have library access the main script's properties?
For Rhino runtime - don't think so. For V8 - direct injection of the service will suffice.