Comparing version 0.1.2 to 0.1.3
262
bookshelf.js
@@ -1,2 +0,2 @@ | ||
// Bookshelf.js 0.1.2 | ||
// Bookshelf.js 0.1.3 | ||
@@ -19,3 +19,3 @@ // (c) 2013 Tim Griesser | ||
var _ = require('underscore'); | ||
var When = require('when'); | ||
var when = require('when'); | ||
var Knex = require('knex'); | ||
@@ -26,6 +26,6 @@ var Inflection = require('inflection'); | ||
// so we can have event driven async validations, functions, etc. | ||
require('trigger-then')(Backbone, When); | ||
require('trigger-then')(Backbone, when); | ||
// Keep in sync with `package.json`. | ||
Bookshelf.VERSION = '0.1.2'; | ||
Bookshelf.VERSION = '0.1.3'; | ||
@@ -99,8 +99,3 @@ // We're using `Backbone.Events` rather than `EventEmitter`, | ||
// Returns the related item | ||
related: function(item) { | ||
return this.relations[item]; | ||
}, | ||
// Helper for attaching query constraints on related | ||
// Helper for attaching query constraints on related | ||
// `models` or `collections` as necessary. | ||
@@ -111,8 +106,8 @@ _addConstraints: function(resp) { | ||
if (!relation.fkValue && !resp) { | ||
return When.reject(new Error("The " + relation.otherKey + " must be specified.")); | ||
return when.reject(new Error("The " + relation.otherKey + " must be specified.")); | ||
} | ||
if (relation.type !== 'belongsToMany') { | ||
constraints.call(this, resp); | ||
constraints(this, resp); | ||
} else { | ||
belongsToMany.call(this, resp); | ||
belongsToMany(this, resp); | ||
} | ||
@@ -137,3 +132,4 @@ } | ||
options || (options = {}); | ||
this.attributes = {}; | ||
this.attributes = Object.create(null); | ||
this._reset(); | ||
this.relations = {}; | ||
@@ -146,3 +142,2 @@ this.cid = _.uniqueId('c'); | ||
this.set(attrs, options); | ||
this.changed = {}; | ||
this.initialize.apply(this, arguments); | ||
@@ -152,5 +147,5 @@ }; | ||
// A list of properties that are omitted from the `Backbone.Model.prototype`, since we're not | ||
// handling validations, or tracking changes in the same fashion as `Backbone`, we can drop all of | ||
// these related keys. | ||
var modelOmitted = ['isValid', 'hasChanged', 'changedAttributes', 'previous', 'previousAttributes']; | ||
// handling validations, or tracking changes in the same fashion as `Backbone`, we can drop these | ||
// specific methods. | ||
var modelOmitted = ['isValid', 'validationError', 'changedAttributes']; | ||
@@ -203,3 +198,3 @@ // List of attributes attached directly from the `options` passed to the constructor. | ||
joinTableName: joinTableName || [ | ||
_.result(this, 'tableName'), | ||
_.result(this, 'tableName'), | ||
_.result(Target.prototype, 'tableName') | ||
@@ -210,2 +205,43 @@ ].sort().join('_') | ||
// Similar to the standard `Backbone` set method, but without individual | ||
// change events, and adding different meaning to `changed` and `previousAttributes` | ||
// defined as the last "sync"'ed state of the model. | ||
set: function(key, val, options) { | ||
if (key == null) return this; | ||
var attr, attrs, changing; | ||
// Handle both `"key", value` and `{key: value}` -style arguments. | ||
if (typeof key === 'object') { | ||
attrs = key; | ||
options = val; | ||
} else { | ||
(attrs = {})[key] = val; | ||
} | ||
options || (options = {}); | ||
// Extract attributes and options. | ||
var hasChanged = false; | ||
var unset = options.unset; | ||
var current = this.attributes; | ||
var prev = this._previousAttributes; | ||
// Check for changes of `id`. | ||
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; | ||
// For each `set` attribute, update or delete the current value. | ||
for (attr in attrs) { | ||
val = attrs[attr]; | ||
if (!_.isEqual(prev[attr], val)) { | ||
this.changed[attr] = val; | ||
if (!_.isEqual(current[attr], val)) hasChanged = true; | ||
} else { | ||
delete this.changed[attr]; | ||
} | ||
unset ? delete current[attr] : current[attr] = val; | ||
} | ||
if (hasChanged && !options.silent) this.trigger('change', this, options); | ||
return this; | ||
}, | ||
// Fetch a model based on the currently set attributes, | ||
@@ -215,3 +251,6 @@ // returning a model to the callback, along with any options. | ||
fetch: function(options) { | ||
return this.sync(this, options).first(); | ||
return this.sync(this, options).first().then(function(model) { | ||
model._reset(); | ||
return model; | ||
}); | ||
}, | ||
@@ -224,3 +263,3 @@ | ||
save: function(key, val, options) { | ||
var id, attrs; | ||
var attrs; | ||
@@ -243,15 +282,21 @@ // Handle both `"key", value` and `{key: value}` -style arguments. | ||
// Merge any defaults here rather than during object creation. | ||
var defaults = _.result(this, 'defaults'); | ||
// Determine whether the model is new, typically based on whether the model has | ||
// an `idAttribute` or not. | ||
var method = options.method || (this.isNew(options) ? 'insert' : 'update'); | ||
var vals = attrs; | ||
if (defaults) { | ||
vals = _.extend({}, defaults, this.attributes, vals); | ||
// If the object is being created, we merge any defaults here | ||
// rather than during object creation. | ||
if (method === 'insert' || options.defaults) { | ||
var defaults = _.result(this, 'defaults'); | ||
if (defaults) { | ||
vals = _.extend({}, defaults, this.attributes, vals); | ||
} | ||
} | ||
// Set the attributes on the model, and maintain a reference to use below. | ||
var model = this.set(vals); | ||
var model = this.set(vals, {silent: true}); | ||
var sync = model.sync(model, options); | ||
var method = options.method || (model.isNew(options) ? 'insert' : 'update'); | ||
return When.all([ | ||
return when.all([ | ||
model.triggerThen((method === 'insert' ? 'creating' : 'updating'), model, attrs, options), | ||
@@ -264,5 +309,9 @@ model.triggerThen('saving', model, attrs, options) | ||
// After a successful database save, the id is updated if the model was created | ||
if (method === 'insert' && resp) model.set(model.idAttribute, resp[0]); | ||
if (method === 'insert' && resp) { | ||
model[model.idAttribute] = resp[0]; | ||
} | ||
model.trigger((method === 'insert' ? 'created' : 'updated'), model, resp, options); | ||
model.trigger('saved', model, resp, options); | ||
model._reset(); | ||
return model; | ||
@@ -283,3 +332,5 @@ }) | ||
.then(function(resp) { | ||
model.clear(); | ||
model.trigger('destroyed', model, resp, options); | ||
model._reset(); | ||
return resp; | ||
@@ -323,5 +374,6 @@ }).ensure(function() { | ||
var model = new this.constructor(this.attributes); | ||
model.relations = _.map(this.relations, function(relation) { | ||
return relation.clone(); | ||
}); | ||
var relations = this.relations; | ||
for (var key in relations) { | ||
model.relations[key] = relations[key].clone(); | ||
} | ||
return model; | ||
@@ -350,3 +402,3 @@ }, | ||
// If we're handling an eager loaded related model, | ||
// keep a reference to the original constructor to assemble | ||
// keep a reference to the original constructor to assemble | ||
// the correct object once the eager matching is finished. | ||
@@ -383,5 +435,18 @@ // Otherwise, just grab the `foreignKey` value for building the query. | ||
// Returns the related item, or creates a new | ||
// related item by creating a new model or collection. | ||
related: function(name) { | ||
return this.relations[name] || (this.relations[name] = this[name]()); | ||
}, | ||
// Called after a `sync` action (save, fetch, delete) - | ||
// resets the `_previousAttributes` and `changed` hash for the model. | ||
_reset: function() { | ||
this._previousAttributes = extendNull(this.attributes); | ||
this.changed = extendNull(); | ||
}, | ||
// Validation can be complicated, and is better handled | ||
// on its own and not mixed in with database logic. | ||
_validate: function() { | ||
_validate: function() { | ||
return true; | ||
@@ -395,3 +460,3 @@ } | ||
// A Bookshelf Collection contains a number of database rows, represented by | ||
// A Bookshelf Collection contains a number of database rows, represented by | ||
// models, so they can be easily sorted, serialized, and manipulated. | ||
@@ -429,3 +494,3 @@ var Collection = Bookshelf.Collection = function(models, options) { | ||
var collection = this; | ||
return model.save(null, options).then(function(resp) { | ||
return model.save(null, options).then(function() { | ||
collection.add(model, options); | ||
@@ -514,3 +579,3 @@ return model; | ||
pendingNames.push(name); | ||
pendingDeferred.push(eagerFetch.call(handled[name], { | ||
pendingDeferred.push(eagerFetch(handled[name], { | ||
transacting: options.transacting, | ||
@@ -523,3 +588,3 @@ withRelated: subRelated[name] | ||
// returning the original response when these syncs are complete. | ||
return When.all(pendingDeferred).spread(_.bind(this.matchResponses, this)); | ||
return when.all(pendingDeferred).spread(_.bind(this.matchResponses, this)); | ||
}, | ||
@@ -532,6 +597,6 @@ | ||
var handled = this.handled; | ||
// Pair each of the query responses with the parent models. | ||
for (var i = 0, l = args.length; i < l; i++) { | ||
// Get the current relation this response matches up with, based | ||
@@ -549,3 +614,3 @@ // on the pendingNames array. | ||
var models = parent.models; | ||
// Attach the appropriate related items onto the parent model. | ||
@@ -597,8 +662,8 @@ for (var i2 = 0, l2 = models.length; i2 < l2; i2++) { | ||
// Standard constraints for regular or eager loaded relations. | ||
var constraints = function(resp) { | ||
var relation = this._relation; | ||
var constraints = function(target, resp) { | ||
var relation = target._relation; | ||
if (resp) { | ||
this.query('whereIn', relation.foreignKey, _.pluck(resp, relation.parentIdAttr)); | ||
target.query('whereIn', relation.foreignKey, _.uniq(_.pluck(resp, relation.parentIdAttr))); | ||
} else { | ||
this.query('where', relation.foreignKey, '=', relation.fkValue); | ||
target.query('where', relation.foreignKey, '=', relation.fkValue); | ||
} | ||
@@ -608,10 +673,10 @@ }; | ||
// Helper function for adding the constraints needed on a eager load. | ||
var belongsToMany = function(resp) { | ||
var | ||
relation = this._relation, | ||
var belongsToMany = function(target, resp) { | ||
var | ||
relation = target._relation, | ||
columns = relation.columns || (relation.columns = []), | ||
builder = this.query(), | ||
builder = target.query(), | ||
tableName = _.result(this, 'tableName'), | ||
idAttribute = _.result(this, 'idAttribute'), | ||
tableName = _.result(target, 'tableName'), | ||
idAttribute = _.result(target, 'idAttribute'), | ||
@@ -643,14 +708,13 @@ otherKey = relation.otherKey, | ||
// Called from `EagerRelation.processRelated` with the context | ||
// Called from `EagerRelation.processRelated` with the context | ||
// of an eager-loading model or collection, this function | ||
// fetches the nested related items, and returns a deferred object, | ||
// fetches the nested related items, and returns a deferred object, | ||
// with the cumulative handling of multiple (potentially nested) relations. | ||
var eagerFetch = function(options) { | ||
var eagerFetch = function(related, options) { | ||
var current = this; | ||
var models = this.models = []; | ||
var relation = this._relation; | ||
var models = related.models = []; | ||
var relation = related._relation; | ||
return When(this._addConstraints(relation.parentResponse)).then(function() { | ||
return current.query().select(relation.columns); | ||
return when(related._addConstraints(relation.parentResponse)).then(function() { | ||
return related.query().select(relation.columns); | ||
}) | ||
@@ -670,3 +734,3 @@ .then(function(resp) { | ||
var model = new relation.modelCtor(); | ||
return new EagerRelation(current, model, resp).processRelated(options); | ||
return new EagerRelation(related, model, resp).processRelated(options); | ||
} | ||
@@ -678,3 +742,3 @@ } | ||
}).ensure(function() { | ||
current.resetQuery(); | ||
related.resetQuery(); | ||
}); | ||
@@ -690,5 +754,3 @@ }; | ||
Model.forge = Collection.forge = function() { | ||
var Ctor = function() {}; | ||
Ctor.prototype = this.prototype; | ||
var inst = new Ctor(); | ||
var inst = Object.create(this.prototype); | ||
var obj = this.apply(inst, arguments); | ||
@@ -711,3 +773,2 @@ return (Object(obj) === obj ? obj : inst); | ||
this.query = model.query(); | ||
if (options.transacting) this.query.transacting(options.transacting); | ||
@@ -718,5 +779,5 @@ }; | ||
// Select the first item from the database. | ||
// Select the first item from the database - only used by models. | ||
first: function() { | ||
this.query.where(_.extend({}, this.model.attributes)).limit(1); | ||
this.query.where(extendNull(this.model.attributes)).limit(1); | ||
return this.select(); | ||
@@ -729,3 +790,3 @@ }, | ||
// the promise is resolved. Any `success` handler passed in the | ||
// options will be called. | ||
// options will be called - used by both models & collections. | ||
select: function() { | ||
@@ -736,3 +797,3 @@ var sync = this; | ||
return When(model._addConstraints()).then(function() { | ||
return when(model._addConstraints()).then(function() { | ||
var columns = options.columns; | ||
@@ -746,6 +807,7 @@ | ||
return sync.query.select(columns); | ||
return model.triggerThen('fetching', model, columns, options).then(function() { | ||
return sync.query.select(columns); | ||
}); | ||
}) | ||
.then(function(resp) { | ||
var target; | ||
@@ -757,3 +819,4 @@ if (resp && resp.length > 0) { | ||
if (model instanceof Model) { | ||
model.set(model.parse(resp[0], options), options); | ||
model.set(model.parse(resp[0], options), _.extend({silent: true}, options)); | ||
model._previousAttributes = extendNull(model.attributes); | ||
} else { | ||
@@ -764,7 +827,7 @@ model.reset(resp, {silent: true, parse: true}); | ||
// If the `withRelated` property is specified on the options hash, we dive | ||
// into the `EagerRelation`. If the current querying object is a collection, | ||
// into the `EagerRelation`. If the current querying object is a collection, | ||
// we find the associated `model` to determine necessary eager relations. | ||
// Once the `EagerRelation` is complete, we return the original response from the query. | ||
if (options.withRelated) { | ||
target = (model instanceof Collection ? new model.model() : model); | ||
var target = (model instanceof Collection ? new model.model() : model); | ||
return new EagerRelation(model, target, resp) | ||
@@ -780,6 +843,6 @@ .processRelated(options) | ||
// a failure if the model comes up blank. | ||
if (options.require) return When.reject(new Error('EmptyResponse')); | ||
if (options.require) return when.reject(new Error('EmptyResponse')); | ||
if (model instanceof Model) { | ||
model.clear(); | ||
model.clear({silent: true}); | ||
return {}; | ||
@@ -801,15 +864,24 @@ } | ||
// Issues an `insert` command on the query. | ||
// Issues an `insert` command on the query - only used by models. | ||
insert: function() { | ||
var model = this.model; | ||
return this.query | ||
.idAttribute(_.result(this.model, 'idAttribute')) | ||
.insert(this.model.format(_.extend({}, this.model.attributes))); | ||
.idAttribute(model.idAttribute) | ||
.insert(model.format(extendNull(model.attributes))) | ||
.then(function(resp) { | ||
model._previousAttributes = extendNull(model.attributes); | ||
return resp; | ||
}); | ||
}, | ||
// Issues an `update` command on the query. | ||
// Issues an `update` command on the query - only used by models. | ||
update: function(attrs, options) { | ||
attrs = (attrs && options.patch ? attrs : this.model.attributes); | ||
var model = this.model; | ||
return this.query | ||
.where(this.model.idAttribute, this.model.id) | ||
.update(this.model.format(_.extend({}, attrs))); | ||
.where(model.idAttribute, model.id) | ||
.update(model.format(extendNull(model.attributes))) | ||
.then(function(resp) { | ||
model._previousAttributes = extendNull(model.attributes); | ||
return resp; | ||
}); | ||
}, | ||
@@ -819,3 +891,3 @@ | ||
del: function() { | ||
var wheres; | ||
var wheres, model = this.model; | ||
if (this.model.id != null) { | ||
@@ -826,3 +898,3 @@ wheres = {}; | ||
if (!wheres && this.query.wheres.length === 0) { | ||
return When.reject(new Error('A model cannot be destroyed without a "where" clause or an idAttribute.')); | ||
return when.reject(new Error('A model cannot be destroyed without a "where" clause or an idAttribute.')); | ||
} | ||
@@ -852,3 +924,3 @@ return this.query.where(wheres).del(); | ||
// it attempts to remove the item based on a where clause with | ||
// these parameters. If no parameters are specified, we assume we will | ||
// these parameters. If no parameters are specified, we assume we will | ||
// detach all related associations. | ||
@@ -865,3 +937,3 @@ detach: function(ids, options) { | ||
if (!_.isArray(columns)) columns = columns ? [columns] : []; | ||
var joinString, relation = this._relation; | ||
var relation = this._relation; | ||
relation.pivotColumns || (relation.pivotColumns = []); | ||
@@ -884,3 +956,3 @@ for (var i = 0, l = columns.length; i < l; i++) { | ||
_handler: function(method, ids, options) { | ||
if (ids == void 0 && method === 'insert') return When.resolve(); | ||
if (ids == void 0 && method === 'insert') return when.resolve(); | ||
if (!_.isArray(ids)) ids = ids ? [ids] : []; | ||
@@ -891,3 +963,3 @@ var pending = []; | ||
} | ||
return When.all(pending); | ||
return when.all(pending); | ||
}, | ||
@@ -925,5 +997,11 @@ | ||
// Creates a new object, extending an object that | ||
// does not inherit the `Object.prototype`. | ||
var extendNull = function(target) { | ||
return _.extend(Object.create(null), target); | ||
}; | ||
// Simple memoization of the singularize call. | ||
var singularMemo = (function(value) { | ||
var cache = {}; | ||
var singularMemo = (function() { | ||
var cache = Object.create(null); | ||
return function(arg) { | ||
@@ -991,3 +1069,3 @@ if (arg in cache) { | ||
// Named instances of Bookshelf, presumably with different `Knex` | ||
// options, to initialize different databases. | ||
// options, to initialize different databases. | ||
// The main instance being named "main"... | ||
@@ -994,0 +1072,0 @@ Bookshelf.Instances = {}; |
{ | ||
"name": "bookshelf", | ||
"author": "Tim Griesser", | ||
"version": "0.1.2", | ||
"description": "An ORM with some Backbone", | ||
"version": "0.1.3", | ||
"description": "A lightweight ORM for Postgres, MySQL, and SQLite3 with some Backbone", | ||
"main": "bookshelf.js", | ||
@@ -18,5 +17,13 @@ "scripts": { | ||
"sqlite", | ||
"sqlite3", | ||
"knex", | ||
"database", | ||
"object relational mapper", | ||
"datamapper", | ||
"active record" | ||
], | ||
"repository":{ | ||
"type": "git", | ||
"url": "https://github.com/tgriesser/knex.git" | ||
}, | ||
"dependencies": { | ||
@@ -38,4 +45,8 @@ "backbone": "1.0.x", | ||
}, | ||
"author": { | ||
"name": "Tim Griesser", | ||
"web": "https://github.com/tgriesser" | ||
}, | ||
"license": "MIT", | ||
"readmeFilename": "README.md" | ||
} |
@@ -0,1 +1,2 @@ | ||
/*global describe, it */ | ||
var _ = require('underscore'); | ||
@@ -30,3 +31,3 @@ _.str = require('underscore.string'); | ||
}); | ||
var SubUser = User.extend({ | ||
@@ -125,5 +126,5 @@ otherMethod: function() { return this.getData(); } | ||
describe('tableName', function() { | ||
var table = new Bookshelf.Model({}, {tableName: 'customers'}); | ||
it('can be passed in the initialize options', function() { | ||
@@ -219,7 +220,13 @@ equal(table.tableName, 'customers'); | ||
it('issues a first (get one) to Knex, returning a promise', function(ok) { | ||
it('issues a first (get one) to Knex, triggering a fetched event, returning a promise', function(ok) { | ||
var count = 0; | ||
var model = new Site({id: 1}); | ||
model.on('fetched', function() { | ||
count++; | ||
}); | ||
new Site({id: 1}).fetch().then(function(model) { | ||
model.fetch().then(function(model) { | ||
equal(model.get('id'), 1); | ||
equal(model.get('name'), 'knexjs.org'); | ||
equal(count, 1); | ||
ok(); | ||
@@ -230,2 +237,14 @@ }); | ||
it('has a fetching event, which will fail if an error is thrown or if a rejected promise is provided', function(ok) { | ||
var model = new Site({id: 1}); | ||
model.on('fetching', function() { | ||
throw new Error("This failed"); | ||
}); | ||
model.fetch().then(null, function() { | ||
ok(); | ||
}); | ||
}); | ||
}); | ||
@@ -383,3 +402,3 @@ | ||
}); | ||
}); | ||
}); | ||
@@ -400,14 +419,39 @@ describe('resetQuery', function() { | ||
var Item = Bookshelf.Model.extend({defaults: {item: 'test'}}); | ||
var item = new Item({id: 1}); | ||
deepEqual(item.toJSON(), {id: 1}); | ||
var item = new Item({newItem: 'test2'}); | ||
deepEqual(item.toJSON(), {newItem: 'test2'}); | ||
item.sync = function() { | ||
deepEqual(this.toJSON(), {id: 1, item: 'test'}); | ||
deepEqual(this.toJSON(), {id: 1, item: 'test', newItem: 'test2'}); | ||
ok(); | ||
return stubSync; | ||
}; | ||
item.save({id: 1}); | ||
}); | ||
it('only assigns defaults when creating a model, unless {defaults: true} is passed in the save options', function(ok) { | ||
var Item = Bookshelf.Model.extend({defaults: {item: 'test'}}); | ||
var item = new Item({id: 1, newItem: 'test2'}); | ||
deepEqual(item.toJSON(), {id: 1, newItem: 'test2'}); | ||
item.sync = function() { | ||
deepEqual(this.toJSON(), {id: 1, newItem: 'test2'}); | ||
return stubSync; | ||
}; | ||
item.save(); | ||
item.sync = function() { | ||
deepEqual(this.toJSON(), {id: 2, item: 'test', newItem: 'test2'}); | ||
ok(); | ||
return stubSync; | ||
}; | ||
item.save({id: 2}, {defaults: true}); | ||
}); | ||
}); | ||
describe('sync', function() { | ||
it('creates a new instance of Bookshelf.Sync', function(){ | ||
var model = new Bookshelf.Model(); | ||
equal((model.sync(model) instanceof Bookshelf.Sync), true); | ||
}); | ||
}); | ||
describe('isNew', function() { | ||
@@ -424,10 +468,37 @@ | ||
describe('sync', function() { | ||
describe('previous, previousAttributes', function() { | ||
it('creates a new instance of Bookshelf.Sync', function(){ | ||
var model = new Bookshelf.Model(); | ||
equal((model.sync(model) instanceof Bookshelf.Sync), true); | ||
it('will return the previous value of an attribute the last time it was synced', function(ok) { | ||
var count = 0; | ||
var model = new Models.Site({id: 1}); | ||
model.on('change', function() { | ||
count++; | ||
}); | ||
equal(model.previous('id'), void 0); | ||
model.fetch().then(function() { | ||
deepEqual(model.previousAttributes(), {id: 1, name: 'knexjs.org'}); | ||
deepEqual(model.changed, {}); | ||
model.set('id', 2); | ||
equal(model.previous('id'), 1); | ||
deepEqual(model.changed, {id: 2}); | ||
model.set('id', 1); | ||
equal(count, 1); | ||
ok(); | ||
}).then(null, ok); | ||
}); | ||
}); | ||
}); | ||
describe('hasChanged', function(ok) { | ||
var site = new Models.Site({name: 'Third Site'}); | ||
equal(site.hasChanged('name'), true); | ||
deepEqual(site.changed, {name: 'Third Site'}); | ||
site.fetch().then(function() { | ||
deepEqual(site.hasChanged, {}); | ||
}); | ||
}); | ||
}; |
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
Non-existent author
Supply chain riskThe package was published by an npm account that no longer exists.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
137430
3671
0
4