type-dexie
Version:
class oriented schema building tool for dexie.js
516 lines (481 loc) • 19.3 kB
JavaScript
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
function __spreadArrays() {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
}
var TypeDexieError = /** @class */ (function (_super) {
__extends(TypeDexieError, _super);
function TypeDexieError() {
return _super !== null && _super.apply(this, arguments) || this;
}
return TypeDexieError;
}(Error));
var MustBeCamelCasedError = /** @class */ (function (_super) {
__extends(MustBeCamelCasedError, _super);
function MustBeCamelCasedError() {
return _super.call(this, 'Target string must be camel cased') || this;
}
return MustBeCamelCasedError;
}(TypeDexieError));
var Naming = /** @class */ (function () {
function Naming() {
}
Naming.prototype.camelCase = function (target) {
if (/^[A-Z]+$/.test(target.name)) {
return target.name.toLowerCase();
}
else {
return target.name.replace(/^[A-Z]/g, function (m) { return m.toLowerCase(); });
}
};
Naming.prototype.pluralize = function (input) {
for (var _i = 0, _a = Naming.PLURALIZE_RULES; _i < _a.length; _i++) {
var rule = _a[_i];
if (rule.pattern.test(input)) {
return input.replace(rule.pattern, rule.replace);
}
}
return input;
};
Naming.prototype.table = function (target) {
var camelCased = this.camelCase(target);
var lastWord = camelCased.match(Naming.LAST_WORD);
if (null == lastWord) {
throw new MustBeCamelCasedError();
}
return camelCased.replace(Naming.LAST_WORD, this.pluralize(lastWord[0]));
};
Naming.prototype.prop = function (target, propertyKey) {
return (target.name +
('symbol' === typeof propertyKey
? '[' + propertyKey.toString() + ']'
: '.' + propertyKey));
};
Naming.LAST_WORD = /([a-z]+|[A-Z][a-z]*)$/;
Naming.PLURALIZE_RULES = [
{ pattern: /(?<=s)$/i, replace: 'ses' },
{ pattern: /y$/i, replace: 'ies' },
{ pattern: /$/i, replace: 's' },
];
return Naming;
}());
var naming = new Naming();
var defineMetadata = function (metadataKey, metadataValue, target, propertyKey) {
return Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);
};
var getMetadata = function (metadataKey, target, propertyKey) {
return Reflect.getMetadata(metadataKey, target, propertyKey);
};
var hasMetadata = function (metadataKey, target, propertyKey) {
if (null == target ||
('object' !== typeof target && 'function' !== typeof target)) {
return false;
}
return Reflect.hasMetadata(metadataKey, target, propertyKey);
};
var wrapMetadata = function (metadataKey, metadataValue, target, propertyKey) {
if (!hasMetadata(metadataKey, target, propertyKey)) {
if ('function' === typeof metadataValue) {
metadataValue = metadataValue();
}
defineMetadata(metadataKey, metadataValue, target, propertyKey);
}
return getMetadata(metadataKey, target, propertyKey);
};
var appendMetadata = function (metadataKey, metadataValue, target, propertyKey) {
defineMetadata(metadataKey, wrapMetadata(metadataKey, [], target, propertyKey).concat(metadataValue), target, propertyKey);
};
function Entity() {
return function (target) {
defineMetadata(Entity, true, target);
};
}
Entity.isEntity = function (target) {
return hasMetadata(Entity, target);
};
var NotAnEntityError = /** @class */ (function (_super) {
__extends(NotAnEntityError, _super);
function NotAnEntityError(message) {
return _super.call(this, message || 'Target is not an entity') || this;
}
return NotAnEntityError;
}(TypeDexieError));
var NotAConstructorError = /** @class */ (function (_super) {
__extends(NotAConstructorError, _super);
function NotAConstructorError(message) {
return _super.call(this, message || 'Target is not a constructor') || this;
}
return NotAConstructorError;
}(TypeDexieError));
var Assert = /** @class */ (function () {
function Assert() {
}
Assert.prototype.isConstructor = function (target, message) {
if ('function' !== typeof target || !(target.prototype instanceof Object)) {
throw new NotAConstructorError(message);
}
};
Assert.prototype.isEntity = function (target, message) {
this.isConstructor(target, message);
if (!Entity.isEntity(target)) {
throw new NotAnEntityError(message);
}
};
return Assert;
}());
var assert = new Assert();
var MissingKeyPathError = /** @class */ (function (_super) {
__extends(MissingKeyPathError, _super);
function MissingKeyPathError() {
return _super.call(this, 'Key path must be supplied when property key is a symbol') || this;
}
return MissingKeyPathError;
}(TypeDexieError));
var EmptyEntityError = /** @class */ (function (_super) {
__extends(EmptyEntityError, _super);
function EmptyEntityError(target) {
return _super.call(this, target.name + ' does not have any column definition') || this;
}
return EmptyEntityError;
}(TypeDexieError));
function Column(keyPath, typeFn) {
if ('function' === typeof keyPath) {
return Column(null, keyPath);
}
return function (target, propertyKey) {
if (null == keyPath) {
if ('symbol' === typeof propertyKey) {
throw new MissingKeyPathError();
}
keyPath = propertyKey;
}
if (null == typeFn) {
typeFn = function () { return Reflect.getMetadata('design:type', target, propertyKey); };
}
appendMetadata(Column, { keyPath: keyPath, typeFn: typeFn, propertyKey: propertyKey }, target.constructor);
};
}
Column.getMetadata = function (target) {
assert.isEntity(target);
if (!hasMetadata(Column, target)) {
throw new EmptyEntityError(target);
}
return getMetadata(Column, target).map(function (metadata) {
var $type = metadata.typeFn();
if ($type instanceof Array) {
metadata.type = $type[0];
metadata.isArray = true;
}
else {
metadata.type = $type;
metadata.isArray = $type === Array;
}
return metadata;
});
};
Column.findMetadata = function (target, propertyKey) {
return Column.getMetadata(target).find(function (_) {
return propertyKey === _.propertyKey;
});
};
var InsufficientKeyPathError = /** @class */ (function (_super) {
__extends(InsufficientKeyPathError, _super);
function InsufficientKeyPathError() {
return _super.call(this, 'At least 2 key paths must be defined') || this;
}
return InsufficientKeyPathError;
}(TypeDexieError));
function Compound(unique) {
var keyPaths = [];
for (var _i = 1; _i < arguments.length; _i++) {
keyPaths[_i - 1] = arguments[_i];
}
if ('string' === typeof unique) {
return Compound.apply(void 0, __spreadArrays([false, unique], keyPaths));
}
if (keyPaths.length < 2) {
throw new InsufficientKeyPathError();
}
return function (target) {
appendMetadata(Compound, { unique: unique, keyPaths: keyPaths }, target);
};
}
Compound.getMetadata = function (target) {
assert.isEntity(target);
return wrapMetadata(Compound, [], target);
};
Compound.has = function (target, keyPath) {
return Compound.getMetadata(target).some(function (compound) {
return compound.keyPaths.includes(keyPath);
});
};
function JoinColumn() {
return function (target, propertyKey) {
return appendMetadata(JoinColumn, { propertyKey: propertyKey }, target.constructor);
};
}
JoinColumn.getMetadata = function (target) {
assert.isEntity(target);
return wrapMetadata(JoinColumn, [], target);
};
JoinColumn.has = function (target, propertyKey) {
return JoinColumn.getMetadata(target).some(function (_) {
return propertyKey === _.propertyKey;
});
};
function MultiValued() {
return function (target, propertyKey) {
return appendMetadata(MultiValued, { propertyKey: propertyKey }, target.constructor);
};
}
MultiValued.getMetadata = function (target) {
assert.isEntity(target);
return wrapMetadata(MultiValued, [], target);
};
MultiValued.has = function (target, propertyKey) {
return MultiValued.getMetadata(target).some(function (_) {
return propertyKey === _.propertyKey;
});
};
var MissingPrimaryKeyError = /** @class */ (function (_super) {
__extends(MissingPrimaryKeyError, _super);
function MissingPrimaryKeyError() {
return _super.call(this, 'Target does not have primary key') || this;
}
return MissingPrimaryKeyError;
}(TypeDexieError));
var PrimaryKeyAlreadyDefinedError = /** @class */ (function (_super) {
__extends(PrimaryKeyAlreadyDefinedError, _super);
function PrimaryKeyAlreadyDefinedError() {
return _super.call(this, 'Primary key is already defined') || this;
}
return PrimaryKeyAlreadyDefinedError;
}(TypeDexieError));
function Primary(autoIncrement) {
if (autoIncrement === void 0) { autoIncrement = true; }
return function (target, propertyKey) {
if ('object' === typeof target) {
target = target.constructor;
}
if (hasMetadata(Primary, target)) {
throw new PrimaryKeyAlreadyDefinedError();
}
defineMetadata(Primary, { propertyKey: propertyKey, autoIncrement: autoIncrement }, target);
};
}
Primary.getMetadata = function (target) {
assert.isEntity(target);
if (!hasMetadata(Primary, target)) {
throw new MissingPrimaryKeyError();
}
return getMetadata(Primary, target);
};
Primary.has = function (target, propertyKey) {
return propertyKey === Primary.getMetadata(target).propertyKey;
};
function Unique() {
return function (target, propertyKey) {
appendMetadata(Unique, { propertyKey: propertyKey }, target.constructor);
};
}
Unique.getMetadata = function (target) {
assert.isEntity(target);
return wrapMetadata(Unique, [], target);
};
Unique.has = function (target, propertyKey) {
return Unique.getMetadata(target).some(function (_) {
return propertyKey === _.propertyKey;
});
};
var PrimaryKeyNotAssociatedError = /** @class */ (function (_super) {
__extends(PrimaryKeyNotAssociatedError, _super);
function PrimaryKeyNotAssociatedError() {
return _super.call(this, 'Primary key is not associated with a column') || this;
}
return PrimaryKeyNotAssociatedError;
}(TypeDexieError));
var CompoundNotAssociatedError = /** @class */ (function (_super) {
__extends(CompoundNotAssociatedError, _super);
function CompoundNotAssociatedError() {
return _super.call(this, 'Compound key path is not associated with a column') || this;
}
return CompoundNotAssociatedError;
}(TypeDexieError));
var EntityCompiler = /** @class */ (function () {
function EntityCompiler(target, parent, propertyKey) {
this.target = target;
this.parent = parent;
this.propertyKey = propertyKey;
}
EntityCompiler.compile = function (target, parent, propertyKey) {
return new EntityCompiler(target, parent, propertyKey).compile();
};
EntityCompiler.prototype.compile = function () {
return this.primaryKeyDefinition()
.concat(this.columnDefinitions(), this.compoundDefinitions(), this.childrenDefinition(), this.parentDefinition())
.map(function (_a) {
var keyPath = _a.keyPath, unique = _a.unique, multiValued = _a.multiValued, autoIncrement = _a.autoIncrement;
return ((autoIncrement ? '++' : '') +
(unique ? '&' : '') +
(multiValued ? '*' : '') +
keyPath);
})
.join(', ');
};
EntityCompiler.prototype.primaryKeyDefinition = function () {
var primary = Primary.getMetadata(this.target);
if (null == primary.propertyKey) {
return [
{
keyPath: '',
autoIncrement: primary.autoIncrement,
},
];
}
var column = Column.findMetadata(this.target, primary.propertyKey);
if (null == column) {
throw new PrimaryKeyNotAssociatedError();
}
return [
{
keyPath: column.keyPath,
autoIncrement: primary.autoIncrement,
},
];
};
EntityCompiler.prototype.columnDefinitions = function () {
var _this = this;
return Column.getMetadata(this.target)
.filter(function (_a) {
var type = _a.type, keyPath = _a.keyPath, propertyKey = _a.propertyKey;
return !(Primary.has(_this.target, propertyKey) ||
Compound.has(_this.target, keyPath) ||
Entity.isEntity(type) ||
JoinColumn.has(_this.target, propertyKey));
})
.map(function (_a) {
var keyPath = _a.keyPath, propertyKey = _a.propertyKey;
return {
keyPath: keyPath,
unique: Unique.has(_this.target, propertyKey),
multiValued: MultiValued.has(_this.target, propertyKey),
};
});
};
EntityCompiler.prototype.compoundDefinitions = function () {
var _this = this;
return Compound.getMetadata(this.target).map(function (compound) {
var column = Column.getMetadata(_this.target);
var keyPaths = compound.keyPaths.map(function (keyPath) {
if (column.some(function (_) { return keyPath === _.keyPath; })) {
return keyPath;
}
throw new CompoundNotAssociatedError();
});
return {
unique: compound.unique,
keyPath: '[' + keyPaths.join('+') + ']',
};
});
};
EntityCompiler.prototype.childrenDefinition = function () {
var _this = this;
return Column.getMetadata(this.target)
.filter(function (_a) {
var type = _a.type, propertyKey = _a.propertyKey;
return (Entity.isEntity(type) && JoinColumn.has(_this.target, propertyKey));
})
.map(function (_a) {
var keyPath = _a.keyPath, propertyKey = _a.propertyKey;
return {
keyPath: keyPath,
unique: Unique.has(_this.target, propertyKey),
multiValued: MultiValued.has(_this.target, propertyKey),
};
});
};
EntityCompiler.prototype.parentDefinition = function () {
if (!this.parent || JoinColumn.has(this.parent, this.propertyKey)) {
return [];
}
return { keyPath: naming.camelCase(this.parent) };
};
return EntityCompiler;
}());
var compileEntity = EntityCompiler.compile;
var MultipleImplementationsError = /** @class */ (function (_super) {
__extends(MultipleImplementationsError, _super);
function MultipleImplementationsError(targetName) {
return _super.call(this, 'Multiple implementations on ' + targetName) || this;
}
return MultipleImplementationsError;
}(TypeDexieError));
var SchemaCompiler = /** @class */ (function () {
function SchemaCompiler(entities) {
this.schema = {};
this.entities = __spreadArrays(entities);
}
SchemaCompiler.compile = function () {
var entities = [];
for (var _i = 0; _i < arguments.length; _i++) {
entities[_i] = arguments[_i];
}
return new SchemaCompiler(entities).compile();
};
SchemaCompiler.prototype.compile = function () {
for (var _i = 0, _a = this.entities; _i < _a.length; _i++) {
var entity = _a[_i];
this._compile(entity);
}
return this.schema;
};
SchemaCompiler.prototype._compile = function (target, parent, propertyKey) {
var _this = this;
assert.isEntity(target);
var $name = naming.table(target);
var $def = compileEntity(target, parent, propertyKey);
if ($name in this.schema) {
if ($def !== this.schema[$name]) {
throw new MultipleImplementationsError($name);
}
}
else {
this.schema[$name] = $def;
}
Column.getMetadata(target)
.filter(function (column) { return Entity.isEntity(column.type); })
.forEach(function (column) {
_this._compile(column.type, target, column.propertyKey);
});
};
return SchemaCompiler;
}());
var compileSchema = SchemaCompiler.compile;
export { Column, Compound, CompoundNotAssociatedError, EmptyEntityError, Entity, InsufficientKeyPathError, JoinColumn, MissingKeyPathError, MissingPrimaryKeyError, MultiValued, MultipleImplementationsError, MustBeCamelCasedError, NotAConstructorError, NotAnEntityError, Primary, PrimaryKeyAlreadyDefinedError, PrimaryKeyNotAssociatedError, TypeDexieError, Unique, compileEntity, compileSchema, naming };
//# sourceMappingURL=type-dexie.es5.js.map