123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- "use strict";
- module.exports = Root;
-
- // extends Namespace
- var Namespace = require("./namespace");
- ((Root.prototype = Object.create(Namespace.prototype)).constructor = Root).className = "Root";
-
- var Field = require("./field"),
- Enum = require("./enum"),
- OneOf = require("./oneof"),
- util = require("./util");
-
- var Type, // cyclic
- parse, // might be excluded
- common; // "
-
- /**
- * Constructs a new root namespace instance.
- * @classdesc Root namespace wrapping all types, enums, services, sub-namespaces etc. that belong together.
- * @extends NamespaceBase
- * @constructor
- * @param {Object.<string,*>} [options] Top level options
- */
- function Root(options) {
- Namespace.call(this, "", options);
-
- /**
- * Deferred extension fields.
- * @type {Field[]}
- */
- this.deferred = [];
-
- /**
- * Resolved file names of loaded files.
- * @type {string[]}
- */
- this.files = [];
- }
-
- /**
- * Loads a namespace descriptor into a root namespace.
- * @param {INamespace} json Nameespace descriptor
- * @param {Root} [root] Root namespace, defaults to create a new one if omitted
- * @returns {Root} Root namespace
- */
- Root.fromJSON = function fromJSON(json, root) {
- if (!root)
- root = new Root();
- if (json.options)
- root.setOptions(json.options);
- return root.addJSON(json.nested);
- };
-
- /**
- * Resolves the path of an imported file, relative to the importing origin.
- * This method exists so you can override it with your own logic in case your imports are scattered over multiple directories.
- * @function
- * @param {string} origin The file name of the importing file
- * @param {string} target The file name being imported
- * @returns {string|null} Resolved path to `target` or `null` to skip the file
- */
- Root.prototype.resolvePath = util.path.resolve;
-
- /**
- * Fetch content from file path or url
- * This method exists so you can override it with your own logic.
- * @function
- * @param {string} path File path or url
- * @param {FetchCallback} callback Callback function
- * @returns {undefined}
- */
- Root.prototype.fetch = util.fetch;
-
- // A symbol-like function to safely signal synchronous loading
- /* istanbul ignore next */
- function SYNC() {} // eslint-disable-line no-empty-function
-
- /**
- * Loads one or multiple .proto or preprocessed .json files into this root namespace and calls the callback.
- * @param {string|string[]} filename Names of one or multiple files to load
- * @param {IParseOptions} options Parse options
- * @param {LoadCallback} callback Callback function
- * @returns {undefined}
- */
- Root.prototype.load = function load(filename, options, callback) {
- if (typeof options === "function") {
- callback = options;
- options = undefined;
- }
- var self = this;
- if (!callback)
- return util.asPromise(load, self, filename, options);
-
- var sync = callback === SYNC; // undocumented
-
- // Finishes loading by calling the callback (exactly once)
- function finish(err, root) {
- /* istanbul ignore if */
- if (!callback)
- return;
- var cb = callback;
- callback = null;
- if (sync)
- throw err;
- cb(err, root);
- }
-
- // Bundled definition existence checking
- function getBundledFileName(filename) {
- var idx = filename.lastIndexOf("google/protobuf/");
- if (idx > -1) {
- var altname = filename.substring(idx);
- if (altname in common) return altname;
- }
- return null;
- }
-
- // Processes a single file
- function process(filename, source) {
- try {
- if (util.isString(source) && source.charAt(0) === "{")
- source = JSON.parse(source);
- if (!util.isString(source))
- self.setOptions(source.options).addJSON(source.nested);
- else {
- parse.filename = filename;
- var parsed = parse(source, self, options),
- resolved,
- i = 0;
- if (parsed.imports)
- for (; i < parsed.imports.length; ++i)
- if (resolved = getBundledFileName(parsed.imports[i]) || self.resolvePath(filename, parsed.imports[i]))
- fetch(resolved);
- if (parsed.weakImports)
- for (i = 0; i < parsed.weakImports.length; ++i)
- if (resolved = getBundledFileName(parsed.weakImports[i]) || self.resolvePath(filename, parsed.weakImports[i]))
- fetch(resolved, true);
- }
- } catch (err) {
- finish(err);
- }
- if (!sync && !queued)
- finish(null, self); // only once anyway
- }
-
- // Fetches a single file
- function fetch(filename, weak) {
-
- // Skip if already loaded / attempted
- if (self.files.indexOf(filename) > -1)
- return;
- self.files.push(filename);
-
- // Shortcut bundled definitions
- if (filename in common) {
- if (sync)
- process(filename, common[filename]);
- else {
- ++queued;
- setTimeout(function() {
- --queued;
- process(filename, common[filename]);
- });
- }
- return;
- }
-
- // Otherwise fetch from disk or network
- if (sync) {
- var source;
- try {
- source = util.fs.readFileSync(filename).toString("utf8");
- } catch (err) {
- if (!weak)
- finish(err);
- return;
- }
- process(filename, source);
- } else {
- ++queued;
- self.fetch(filename, function(err, source) {
- --queued;
- /* istanbul ignore if */
- if (!callback)
- return; // terminated meanwhile
- if (err) {
- /* istanbul ignore else */
- if (!weak)
- finish(err);
- else if (!queued) // can't be covered reliably
- finish(null, self);
- return;
- }
- process(filename, source);
- });
- }
- }
- var queued = 0;
-
- // Assembling the root namespace doesn't require working type
- // references anymore, so we can load everything in parallel
- if (util.isString(filename))
- filename = [ filename ];
- for (var i = 0, resolved; i < filename.length; ++i)
- if (resolved = self.resolvePath("", filename[i]))
- fetch(resolved);
-
- if (sync)
- return self;
- if (!queued)
- finish(null, self);
- return undefined;
- };
- // function load(filename:string, options:IParseOptions, callback:LoadCallback):undefined
-
- /**
- * Loads one or multiple .proto or preprocessed .json files into this root namespace and calls the callback.
- * @function Root#load
- * @param {string|string[]} filename Names of one or multiple files to load
- * @param {LoadCallback} callback Callback function
- * @returns {undefined}
- * @variation 2
- */
- // function load(filename:string, callback:LoadCallback):undefined
-
- /**
- * Loads one or multiple .proto or preprocessed .json files into this root namespace and returns a promise.
- * @function Root#load
- * @param {string|string[]} filename Names of one or multiple files to load
- * @param {IParseOptions} [options] Parse options. Defaults to {@link parse.defaults} when omitted.
- * @returns {Promise<Root>} Promise
- * @variation 3
- */
- // function load(filename:string, [options:IParseOptions]):Promise<Root>
-
- /**
- * Synchronously loads one or multiple .proto or preprocessed .json files into this root namespace (node only).
- * @function Root#loadSync
- * @param {string|string[]} filename Names of one or multiple files to load
- * @param {IParseOptions} [options] Parse options. Defaults to {@link parse.defaults} when omitted.
- * @returns {Root} Root namespace
- * @throws {Error} If synchronous fetching is not supported (i.e. in browsers) or if a file's syntax is invalid
- */
- Root.prototype.loadSync = function loadSync(filename, options) {
- if (!util.isNode)
- throw Error("not supported");
- return this.load(filename, options, SYNC);
- };
-
- /**
- * @override
- */
- Root.prototype.resolveAll = function resolveAll() {
- if (this.deferred.length)
- throw Error("unresolvable extensions: " + this.deferred.map(function(field) {
- return "'extend " + field.extend + "' in " + field.parent.fullName;
- }).join(", "));
- return Namespace.prototype.resolveAll.call(this);
- };
-
- // only uppercased (and thus conflict-free) children are exposed, see below
- var exposeRe = /^[A-Z]/;
-
- /**
- * Handles a deferred declaring extension field by creating a sister field to represent it within its extended type.
- * @param {Root} root Root instance
- * @param {Field} field Declaring extension field witin the declaring type
- * @returns {boolean} `true` if successfully added to the extended type, `false` otherwise
- * @inner
- * @ignore
- */
- function tryHandleExtension(root, field) {
- var extendedType = field.parent.lookup(field.extend);
- if (extendedType) {
- var sisterField = new Field(field.fullName, field.id, field.type, field.rule, undefined, field.options);
- sisterField.declaringField = field;
- field.extensionField = sisterField;
- extendedType.add(sisterField);
- return true;
- }
- return false;
- }
-
- /**
- * Called when any object is added to this root or its sub-namespaces.
- * @param {ReflectionObject} object Object added
- * @returns {undefined}
- * @private
- */
- Root.prototype._handleAdd = function _handleAdd(object) {
- if (object instanceof Field) {
-
- if (/* an extension field (implies not part of a oneof) */ object.extend !== undefined && /* not already handled */ !object.extensionField)
- if (!tryHandleExtension(this, object))
- this.deferred.push(object);
-
- } else if (object instanceof Enum) {
-
- if (exposeRe.test(object.name))
- object.parent[object.name] = object.values; // expose enum values as property of its parent
-
- } else if (!(object instanceof OneOf)) /* everything else is a namespace */ {
-
- if (object instanceof Type) // Try to handle any deferred extensions
- for (var i = 0; i < this.deferred.length;)
- if (tryHandleExtension(this, this.deferred[i]))
- this.deferred.splice(i, 1);
- else
- ++i;
- for (var j = 0; j < /* initializes */ object.nestedArray.length; ++j) // recurse into the namespace
- this._handleAdd(object._nestedArray[j]);
- if (exposeRe.test(object.name))
- object.parent[object.name] = object; // expose namespace as property of its parent
- }
-
- // The above also adds uppercased (and thus conflict-free) nested types, services and enums as
- // properties of namespaces just like static code does. This allows using a .d.ts generated for
- // a static module with reflection-based solutions where the condition is met.
- };
-
- /**
- * Called when any object is removed from this root or its sub-namespaces.
- * @param {ReflectionObject} object Object removed
- * @returns {undefined}
- * @private
- */
- Root.prototype._handleRemove = function _handleRemove(object) {
- if (object instanceof Field) {
-
- if (/* an extension field */ object.extend !== undefined) {
- if (/* already handled */ object.extensionField) { // remove its sister field
- object.extensionField.parent.remove(object.extensionField);
- object.extensionField = null;
- } else { // cancel the extension
- var index = this.deferred.indexOf(object);
- /* istanbul ignore else */
- if (index > -1)
- this.deferred.splice(index, 1);
- }
- }
-
- } else if (object instanceof Enum) {
-
- if (exposeRe.test(object.name))
- delete object.parent[object.name]; // unexpose enum values
-
- } else if (object instanceof Namespace) {
-
- for (var i = 0; i < /* initializes */ object.nestedArray.length; ++i) // recurse into the namespace
- this._handleRemove(object._nestedArray[i]);
-
- if (exposeRe.test(object.name))
- delete object.parent[object.name]; // unexpose namespaces
-
- }
- };
-
- // Sets up cyclic dependencies (called in index-light)
- Root._configure = function(Type_, parse_, common_) {
- Type = Type_;
- parse = parse_;
- common = common_;
- };
|