@homer0/jimple
Version:
An extended version of the Jimple lib, with extra features
507 lines (496 loc) • 18.7 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// ../../../node_modules/.pnpm/jimple@1.5.0/node_modules/jimple/src/Jimple.js
var require_Jimple = __commonJS({
"../../../node_modules/.pnpm/jimple@1.5.0/node_modules/jimple/src/Jimple.js"(exports2, module2) {
"use strict";
function assert(ok, message) {
if (!ok) {
throw new Error(message);
}
}
function isFunction(fn) {
return Object.prototype.toString.call(fn) === "[object Function]" && fn.constructor.name === "Function";
}
function isPlainObject(value) {
if (Object.prototype.toString.call(value) !== "[object Object]") {
return false;
}
let prototype = Object.getPrototypeOf(value);
return prototype === null || prototype === Object.prototype;
}
function checkDefined(container, key) {
assert(container.has(key), `Identifier "${key}" is not defined.`);
}
function addFunctionTo(set, fn) {
assert(isFunction(fn), "Service definition is not a Closure or invokable object");
set.add(fn);
return fn;
}
var Jimple3 = class {
static provider(register) {
return { register };
}
/**
* Create a Jimple Container.
* @param {Object?} [values] - An optional object whose keys and values will be associated in the container at initialization
*/
constructor(values) {
this._items = {};
this._instances = /* @__PURE__ */ new Map();
this._factories = /* @__PURE__ */ new Set();
this._protected = /* @__PURE__ */ new Set();
values = isPlainObject(values) ? values : {};
Object.keys(values).forEach(function(key) {
this.set(key, values[key]);
}, this);
}
/**
* Return the specified parameter or service. If the service is not built yet, this function will construct the service
* injecting all the dependencies needed in it's definition so the returned object is totally usable on the fly.
* @param {string} key - The key of the parameter or service to return
* @return {*} The object related to the service or the value of the parameter associated with the key informed
* @throws If the key does not exist
*/
get(key) {
checkDefined(this, key);
let item = this._items[key];
let obj;
if (isFunction(item)) {
if (this._protected.has(item)) {
obj = item;
} else if (this._instances.has(item)) {
obj = this._instances.get(item);
} else {
obj = item(this);
if (!this._factories.has(item)) {
this._instances.set(item, obj);
}
}
} else {
obj = item;
}
return obj;
}
/**
* Defines a new parameter or service.
* @param {string} key - The key of the parameter or service to be defined
* @param {*} value - The value of the parameter or a function that receives the container as parameter and constructs the service
*/
set(key, value) {
this._items[key] = value;
}
/**
* Returns if a service or parameter with the informed key is already defined in the container.
* @param {string} key - The key of the parameter or service to be checked.
* @return {boolean} If the key exists in the container or not
*/
has(key) {
return this._items.hasOwnProperty(key);
}
/**
* Defines a service as a factory, so the instances are not cached in the service and that function is always called
* @param {function(Jimple):*} fn - The function that constructs the service that is a factory
* @return {function(Jimple):*} The same function passed as parameter
*/
factory(fn) {
return addFunctionTo(this._factories, fn);
}
/**
* Defines a function as a parameter, so that function is not considered a service
* @param {*} fn - The function to not be considered a service
* @return {*} The same function passed as parameter
*/
protect(fn) {
return addFunctionTo(this._protected, fn);
}
/**
* Return all the keys registered in the container
* @returns {Array<string>}
*/
keys() {
return Object.keys(this._items);
}
/**
* Extends a service already registered in the container
* @param {string} key - The key of the service to be extended
* @param {function(*, Jimple):*} fn - The function that will be used to extend the service
* @throws If the key is not already defined
* @throws If the key saved in the container does not correspond to a service
* @throws If the function passed it not...well, a function
*/
extend(key, fn) {
checkDefined(this, key);
let originalItem = this._items[key];
assert(isFunction(originalItem) && this._protected.has(originalItem) === false, `Identifier '${key}' does not contain a service definition`);
assert(isFunction(fn), `The 'new' service definition for '${key}' is not a invokable object.`);
this._items[key] = function(app) {
return fn(originalItem(app), app);
};
if (this._factories.has(originalItem)) {
this._factories.delete(originalItem);
this._factories.add(this._items[key]);
}
}
/**
* Uses an provider to extend the service, so it's easy to split the service and parameter definitions across the system
* @param {{register: function(Jimple)}} provider - The provider to be used to register services and parameters in this container
*/
register(provider2) {
provider2.register(this);
}
/**
* Returns the raw value of a service or parameter. So, in the case of a service, for example, the value returned is the
* function used to construct the service.
* @param {string} key - The key of the service or parameter to return.
* @throws If the key does not exist in the container
* @return {*} The raw value of the service or parameter
*/
raw(key) {
checkDefined(this, key);
return this._items[key];
}
};
module2.exports = Jimple3;
}
});
// src/index.ts
var index_exports = {};
__export(index_exports, {
InjectHelper: () => InjectHelper,
Jimple: () => Jimple2,
createProvider: () => createProvider,
createProviderCreator: () => createProviderCreator,
createProviders: () => createProviders,
injectHelper: () => injectHelper,
jimple: () => jimple,
provider: () => provider,
providerCreator: () => providerCreator,
providers: () => providers,
resourceCreatorFactory: () => resourceCreatorFactory,
resourceFactory: () => resourceFactory,
resourcesCollectionFactory: () => resourcesCollectionFactory
});
module.exports = __toCommonJS(index_exports);
// src/jimplemod/index.ts
var import_jimple = __toESM(require_Jimple());
var Jimple = import_jimple.default;
// src/jimple/index.ts
var Jimple2 = class extends Jimple {
/**
* This is a version of the `get` method that doesn't throw if the key doesn't exist. It
* will return `undefined` instead.
*
* @param key The key of the parameter or service to return.
* @returns The object related to the service or the value of the parameter
* associated with the given key.
* @template T The type of the returned object.
*/
try(key) {
if (!this.has(key)) {
return void 0;
}
return this.get(key);
}
};
var jimple = (...args) => new Jimple2(...args);
// src/factories/resourceFactory.ts
var resourceFactory = () => (
/**
* Generates a new resource using the contraint defined in the factory.
*
* @param name The name of the resource.
* @param key The property key for the function.
* @param fn The resource function.
* @returns An object with the `name` set to `true`, and the `fn` as the `key`.
* @template Name The literal type of `name`, to be used in the return object.
* @template Key The literal type of `key`, to be used in the return object.
* @template Fn The type of `fn`, restricted by the factory constraint.
* @example
*
* const myAction = factory('action', 'fn', (c) => {
* c.set('foo', 'bar');
* });
*
*/
(name, key, fn) => ({
[name]: true,
[key]: fn
})
);
// src/factories/resourceCreatorFactory.ts
var resourceCreatorFactory = () => (
/**
* Generates a new resource creator using the contraint defined in the factory.
*
* @param name The name of the resource.
* @param key The property key for the function.
* @param creatorFn The curried function that returns the actual resource function.
* @returns A resource creator: an object that can be used as a resource, and as a
* function that returns a resource.
* @template Name The literal type of `name`, to be used in the return object.
* @template Key The literal type of `key`, to be used in the return object.
* @template ResFn The type of the resource function, the creator function needs
* to return. This is restricted by the factory constraint.
* @template CreatorFn The literal type of `creatorFn`, to be used in the return
* object.
* @example
*
* const myAction = factory('action', 'fn', (name = 'foo') => (c) => {
* c.set(name, 'bar');
* });
*
*/
(name, key, creatorFn) => {
const fnToProxy = (...args) => {
const actualResource = creatorFn(...args);
return resourceFactory()(name, key, actualResource);
};
const handler = {
name,
resource: null,
get(target, property) {
let result;
if (property === this.name) {
result = true;
} else if (property === key) {
if (this.resource === null) {
const newResource = creatorFn();
this.resource = newResource;
}
result = this.resource;
} else {
const targetKey = property;
result = target[targetKey];
}
return result;
},
has(target, property) {
if (property === this.name || property === key) {
return true;
}
const targetKey = property;
return targetKey in target;
}
};
return new Proxy(fnToProxy, handler);
}
);
// src/factories/resourcesCollectionFactory.ts
var resourcesCollectionFactory = () => (
/**
* Generates a function to create a collection of resources of an specified type. A
* collection is a dictionary of resources, and a resource itself, and when its
* "resource function" gets called, it calls the function of every resource (with the
* same args it recevied).
*
* @param name The resource name the items in the collection must have.
* @param key The key property the items in the collection must have.
* @param fn A custom function to process the items in the collection, when the
* collection function gets called.
* @returns A function that can be used to create a resources collection.
* @template Name The literal type of `name`, to be used in the return object.
* @template Key The literal type of `key`, to be used in the return object.
* @template ItemKey To capture the name of the resources in the collection.
* @template Items The kind of dictionary of resources the return function will
* expect.
* @template CustomFn The type of `fn`, restricted by the factory constraint.
* @example
*
* const myActions = factory('action', 'fn')({ myActionA, myActionB });
*
*/
(name, key, fn) => {
const collectionResourceFactory = resourceFactory();
return (items) => {
const invalidKeys = [name, key];
const itemsKeys = Object.keys(items);
const invalidKey = itemsKeys.some(
(itemKey) => invalidKeys.includes(String(itemKey))
);
if (invalidKey) {
throw new Error(
`No item on the collection can have the keys \`${name}\` nor \`${key}\``
);
}
const invalidItem = itemsKeys.find(
(itemKey) => typeof items[itemKey]?.[key] !== "function"
);
if (invalidItem) {
throw new Error(
`The item \`${String(
invalidItem
)}\` is invalid: it doesn't have a \`${key}\` function`
);
}
const useFn = fn ? (...args) => fn(items, ...args) : (...args) => {
itemsKeys.forEach((itemK) => {
const item = items[itemK];
const itemFn = item[key];
itemFn(...args);
});
};
return {
...collectionResourceFactory(name, key, useFn),
...items
};
};
}
);
// src/helpers/injectHelper.ts
var InjectHelper = class {
/**
* This method is meant to be used to validate the dependencies a service receives, and
* needs.
* It will check on the received dependencies, if a specific dependency exists, it will
* return it, otherwise, it will create a new instance.
*
* @param dependencies The dependencies received by the implementation.
* @param key The key of the dependency to validate.
* @param init A function to create a dependency in case it doesn't exist in
* the dictionary.
* @returns An instance of the dependency.
* @template DepKey The literal key of the dependency to validate.
* @template DepType The type of the dependency, obtained from the dictionary sent
* to the constructor.
* @example
*
* type Dependencies = {
* dep: string;
* };
* const helper = new InjectHelper<Dependencies>();
*
* type MyServiceOptions = {
* inject?: Partial<Dependencies>;
* };
* const myService = ({ inject = {} }: MyServiceOptions) => {
* const dep = helper.get(inject, 'dep', () => 'default');
* console.log('dep:', dep);
* };
*
*/
get(dependencies, key, init) {
const existing = dependencies[key];
if (typeof existing !== "undefined") {
return existing;
}
return init();
}
/**
* This is meant to be used in a provider creator to resolve dependencies' names sent as
* options. For each dependency, the method will first check if a new name was
* specified, and try to get it with that name, otherwise, it will try to get it with
* the default name, and if it doesn't exist, it will just keep it as `undefined` and
* expect the service implements {@link InjectHelper.get} to ensure the dependency.
*
* @param dependencies The dependencies needed by the service.
* @param container A reference to the Jimple container.
* @param inject A dictionary of dependencies names.
* @returns A dictionary of dependencies to send to the service.
* @template DepKey The literal key of the dependencies to validate.
* @template Container The type of the Jimple container.
* @example
*
* type Dependencies = {
* dep: string;
* };
* const helper = new InjectHelper<Dependencies>();
*
* type MyProviderOptions = {
* services?: {
* [key in keyof Dependencies]?: string;
* };
* };
*
* const myProvider = providerCreator(
* ({ services = {} }: MyProviderOptions) =>
* (container) => {
* container.set('myService', () => {
* const inject = helper.resolve(['dep'], container, services);
* return myService({ inject });
* });
* },
* );
*
*/
resolve(dependencies, container, inject) {
const result = dependencies.reduce((acc, key) => {
if (inject[key]) {
acc[key] = container.get(inject[key]);
return acc;
}
const keyStr = key;
if (container.has(keyStr)) {
acc[key] = container.get(keyStr);
return acc;
}
return acc;
}, {});
return result;
}
};
var injectHelper = () => new InjectHelper();
// src/helpers/provider.ts
var createProvider = () => {
const factory = resourceFactory();
return (register) => factory("provider", "register", register);
};
var provider = createProvider();
// src/helpers/providerCreator.ts
var createProviderCreator = () => {
const factory = resourceCreatorFactory();
return (creator) => factory("provider", "register", creator);
};
var providerCreator = createProviderCreator();
// src/helpers/providers.ts
var createProviders = () => {
const factory = resourcesCollectionFactory();
return factory("provider", "register");
};
var providers = createProviders();
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
InjectHelper,
Jimple,
createProvider,
createProviderCreator,
createProviders,
injectHelper,
jimple,
provider,
providerCreator,
providers,
resourceCreatorFactory,
resourceFactory,
resourcesCollectionFactory
});
//# sourceMappingURL=index.js.map