@katis/weave
Version:
Simple functional dependency injection
219 lines (209 loc) • 6.32 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Provider: () => Provider,
weave: () => weave
});
module.exports = __toCommonJS(src_exports);
// src/Providers.ts
var isClassProvider = (provider) => "isClassProvider" in provider && provider.isClassProvider;
function Provider(base = Object) {
return class Injectable extends base {
static get isClassProvider() {
return true;
}
};
}
// src/mapValues.ts
var mapValues = (record, fn) => Object.fromEntries(
Object.entries(record).map(([key, value]) => [key, fn(value)])
);
// src/resolved.ts
var property = (provider) => {
let get;
if (isClassProvider(provider)) {
get = function() {
return new provider(this);
};
} else {
get = function() {
return provider(this);
};
}
return {
configurable: false,
enumerable: true,
get
};
};
var resolved = (providers) => Object.create(Object.prototype, mapValues(providers, property));
// src/assertNoCircularDeps.ts
var findCircularDependency = (graph) => {
const pathStack = [];
const nodeStates = new Map(
Object.keys(graph).map((key) => [key, 0 /* Unvisited */])
);
const dfs = (nodeName) => {
const nodeState = nodeStates.get(nodeName);
if (nodeState === 1 /* Visiting */) {
return pathStack.slice(pathStack.lastIndexOf(nodeName));
}
if (nodeState === 2 /* Visited */) {
return;
}
nodeStates.set(nodeName, 1 /* Visiting */);
pathStack.push(nodeName);
for (const dependency of graph[nodeName]) {
const cyclePath = dfs(dependency);
if (cyclePath) {
return cyclePath;
}
}
nodeStates.set(nodeName, 2 /* Visited */);
pathStack.pop();
};
for (const node of Object.keys(graph)) {
const cyclePath = dfs(node);
if (cyclePath) {
return cyclePath;
}
}
};
var assertNoCircularDeps = (graph) => {
const circular = findCircularDependency(graph);
if (circular) {
const message = [...circular, circular[0]].join(" -> ");
throw new Error(`Circular dependency detected: ${message}`);
}
};
// src/assertNoMissingProviders.ts
var assertNoMissingProviders = (providers) => {
for (const [provider, dependencies] of Object.entries(providers)) {
for (const dependency of dependencies) {
if (!(dependency in providers)) {
throw new Error(
`Provider "${dependency}" not defined, required by provider "${provider}"`
);
}
}
}
};
// src/parseDependencyNames.ts
var filterOutComments = (str) => {
const result = [];
let commentState = "none";
for (let i = 0; i < str.length; ++i) {
if (commentState === "singleline") {
if (str[i] === "\n")
commentState = "none";
} else if (commentState === "multiline") {
if (str[i - 1] === "*" && str[i] === "/")
commentState = "none";
} else if (commentState === "none") {
if (str[i] === "/" && str[i + 1] === "/") {
commentState = "singleline";
} else if (str[i] === "/" && str[i + 1] === "*") {
commentState = "multiline";
i += 2;
} else {
result.push(str[i]);
}
}
}
return result.join("");
};
var splitByComma = (str) => {
const result = [];
const stack = [];
let start = 0;
for (let i = 0; i < str.length; i++) {
if (str[i] === "{" || str[i] === "[") {
stack.push(str[i] === "{" ? "}" : "]");
} else if (str[i] === stack[stack.length - 1]) {
stack.pop();
} else if (!stack.length && str[i] === ",") {
const token = str.substring(start, i).trim();
if (token)
result.push(token);
start = i + 1;
}
}
const lastToken = str.substring(start).trim();
if (lastToken)
result.push(lastToken);
return result;
};
var extractConstructorParams = (classText) => {
const constructorMatch = classText.match(/constructor\s*\(([^)]*)\)/);
if (!constructorMatch)
throw Error("No constructor found in class");
return constructorMatch[1].trim();
};
var parseDependencyNames = (fn) => {
const isClass = isClassProvider(fn);
const text = filterOutComments(fn.toString());
let paramsText;
if (isClass) {
paramsText = extractConstructorParams(text);
} else {
const match = text.match(/(?:async)?(?:\s+function)?[^(]*\(([^)]*)/);
if (!match)
return [];
paramsText = match[1].trim();
}
if (!paramsText)
return [];
const [firstParam] = splitByComma(paramsText);
if (firstParam[0] !== "{" || firstParam[firstParam.length - 1] !== "}") {
throw Error(
"First argument must use the object destructuring pattern: " + firstParam
);
}
const props = splitByComma(
firstParam.substring(1, firstParam.length - 1)
).map((prop) => {
const colon = prop.indexOf(":");
return colon === -1 ? prop.trim() : prop.substring(0, colon).trim();
});
const restProperty = props.find((prop) => prop.startsWith("..."));
if (restProperty) {
throw Error(
`Rest property "${restProperty}" is not supported. List all used fixtures explicitly, separated by comma.`
);
}
return props;
};
// src/verifyDependencies.ts
var verifyDependencies = (providers) => {
const graph = mapValues(providers, parseDependencyNames);
assertNoCircularDeps(graph);
assertNoMissingProviders(graph);
};
// src/weave.ts
var weave = (providers) => {
verifyDependencies(providers);
return resolved(providers);
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Provider,
weave
});