Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescripting Transaction in Knex #44

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions lib/LoggerHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export namespace LoggerHelpers {
function getLogger(persistor, logger) {
return logger || persistor.logger;
}

export function debug(persistor, logger, logObj, message) {
getLogger(persistor, logger).debug(logObj, message);
}

export function error(persistor, logger, logObj, message) {
getLogger(persistor, logger).error(logObj, message);
}

export function info(persistor, logger, logObj, message) {
getLogger(persistor, logger).info(logObj, message);
}
}
13 changes: 7 additions & 6 deletions lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
*/

import { PersistorTransaction, RemoteDocConnectionOptions } from './types';
import {PersistorTransaction, RemoteDocConnectionOptions} from './types';


module.exports = function (PersistObjectTemplate, baseClassForPersist) {
Expand Down Expand Up @@ -56,7 +56,7 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) {
}
}
}
}
};

/**
* PUBLIC INTERFACE FOR TEMPLATES
Expand All @@ -67,7 +67,8 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) {
this._prepareSchema(template);
this._injectTemplateFunctions(template);
this._injectObjectFunctions(template);
}
};

PersistObjectTemplate._prepareSchema = function (template) {
if (!this.schemaVerified) {
this._verifySchema();
Expand Down Expand Up @@ -305,7 +306,7 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) {
let deleteQuery = dbType == PersistObjectTemplate.DB_Mongo ?
PersistObjectTemplate.deleteFromPersistWithMongoQuery(template, query, options.logger) :
PersistObjectTemplate.deleteFromKnexByQuery(template, query, options.transaction, options.logger);

const name = 'persistorDeleteByQuery';
return deleteQuery
.then(result => {
Expand Down Expand Up @@ -578,7 +579,7 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) {
template.createProperty(closureProp + 'Persistor', {
type: Object, toClient: toClient,
toServer: false, persist: false,
value: { isFetched: defineProperty.autoFetch ? false : true, isFetching: false }
value: {isFetched: !defineProperty.autoFetch, isFetching: false}
});
}
if (!template.prototype[closureProp + 'Fetch'])
Expand Down Expand Up @@ -919,7 +920,7 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) {
configurable: true
})


Object.defineProperty(template.prototype, 'objectTemplateName', {
get: function () {
return (this.constructor && this.constructor.name) || template.__name__;
Expand Down
107 changes: 107 additions & 0 deletions lib/knex/commit/IdentifyChanges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Identifies changes for change tracking purposes

import {ChangeTracking, ObjectChanges, PropertyChanges} from '../../types';

export namespace IdentifyChanges {

// Entrance from Mappers
export function generateChanges(obj, action: string, changeTracking: ChangeTracking, notifyChanges) {

if (notifyChanges && isChangeTrackingEnabled(obj)) {
const templateName = obj.__template__.__name__;
let objChanges: ObjectChanges = {
table: obj.__template__.__table__,
primaryKey: obj._id,
action: action,
properties: []
};

changeTracking = changeTracking || {};
changeTracking[templateName] = changeTracking[templateName] || [];

if (action === 'update' || action === 'delete') {
const props = obj.__template__.getProperties();
for (var prop in props) {
if (!isArrayOrPersistorShadowProp(props, prop)) {
generatePropertyChanges(props, objChanges, prop, obj);
}
}
}

// Pushing changes into changeTracking for postSave
changeTracking[templateName].push(objChanges);
}
}

function isChangeTrackingEnabled(obj) {
return !!(obj.__template__ && obj.__template__.__schema__ && obj.__template__.__schema__.enableChangeTracking);
}

// This is a confusing function, however, I've decoded it.
// Prop[propName] here, is the property definition of a property within a class, and not the actual instance itself
// For example props[propName] for homeAddress field could be {toClient: true} or {getType: () => Array }, etc.
// However, we also have persistor shadow properties to hold metadata (fancy word for junk) about the original property.
// So homeAddressPersistor will have the isFetched, id fields as well.
// We don't want to then generate property changes for any of these shadow props, or even for the Array refs,
// which we handle at a later time (I suppose)
function isArrayOrPersistorShadowProp(props, propName) {
const propertyDefinition = props[propName];
const isArrayOrObjectTemplate = propertyDefinition.type === Array && propertyDefinition.of.isObjectTemplate;
// @TODO: This may be buggy as propName.match(/Persistor$/) used to be prop.match(/Persistor$/), but prop is not in this scope
const isPersistorObject = propName.match(/Persistor$/) && typeof props[propName.replace(/Persistor$/, '')] === 'object';
return isArrayOrObjectTemplate || isPersistorObject;
}

function generatePropertyChanges(props, objChanges, prop, obj) {
// When the property type is not an object template, need to compare the values.
// for date and object types, need to compare the stringified values.
let newValue;
let changedProperties: PropertyChanges = {};
const oldKey = `_ct_org_${prop}`;
const oldValue = obj[oldKey];
const propertyDefinition = props[prop];

if (!propertyDefinition.type.isObjectTemplate) {
newValue = obj[prop];
if (oldValue !== newValue || (dateOrObject(propertyDefinition.type) && !isStringifiesEqual(oldValue, newValue))) {
changedProperties = {
name: prop,
originalValue: oldValue,
newValue: newValue,
columnName: prop
};
}
} else {
newValue = obj[`${prop}Persistor`];
if (newValue && oldValue !== newValue.id) {
changedProperties = {
name: prop,
originalValue: oldValue, // @TODO: why is this oldValue and not oldValue.id?
newValue: newValue.id, // @TODO: Why is this newValue and not newValue.id?
columnName: getColumnName(prop, obj)
};
}
}

if (!(Object.entries(changedProperties).length === 0 && changedProperties.constructor === Object)) {
objChanges.properties.push(changedProperties);
}
}

function dateOrObject(type) {
return type === Date || type === Object;
}

function isStringifiesEqual(oldValue, newValue) {
return JSON.stringify(oldValue) === JSON.stringify(newValue)
}


function getColumnName(prop, obj): string {
let schema = obj.__template__.__schema__;
if (!schema || !schema.parents || !schema.parents[prop] || !schema.parents[prop].id) {
throw new Error(`${obj.__template__.__name__}.${prop} is missing a parents schema entry`);
}
return schema.parents[prop].id;
}
}
46 changes: 46 additions & 0 deletions lib/knex/commit/Mappers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {DeleteQuery, PersistorTransaction} from '../../types/PersistorTransaction';
import {IdentifyChanges} from './IdentifyChanges';
import {ChangeTracking, Objects} from '../../types';
// Mappers
export namespace Mappers {

export async function saveMapper(obj, dirtyObjects: Objects, changeTracking: ChangeTracking, notifyChanges, txn: PersistorTransaction, logger?) {
delete dirtyObjects[obj.__id__]; // Once scheduled for update remove it.

if (hasSchema(obj)) { // replace callSave;
await obj.persistSave(txn, logger);
}

let action: string = '';
if (obj.__version__ === 1) { // insert
action = 'insert';
} else {
action = 'update';
}
return IdentifyChanges.generateChanges(obj, action, changeTracking, notifyChanges);
}


export async function deleteMapper(obj, deletedObjects: Objects, changeTracking: ChangeTracking, notifyChanges, txn: PersistorTransaction, logger?) {
delete deletedObjects[obj.__id__];

if (hasSchema(obj)) {
await obj.persistDelete(txn, logger); // replace callDelete
}

return IdentifyChanges.generateChanges(obj, 'delete', changeTracking, notifyChanges);
}

// Persistor Def is typeof Persistor
export async function deleteQueryMapper(persistorDef, obj: DeleteQuery, deleteQueries: Objects, txn: PersistorTransaction, logger?) {
delete deleteQueries[obj.__name__]; // Once scheduled for update remove it.

if (hasSchema(obj)) {
await persistorDef.deleteFromKnexQuery(obj.__template__, obj.queryOrChains, txn, logger);
}
}

function hasSchema(obj): boolean {
return !!(obj.__template__ && obj.__template__.__schema__);
}
}
37 changes: 37 additions & 0 deletions lib/knex/commit/RollbackS3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {RemoteDocService} from '../../remote-doc/RemoteDocService';
import {LoggerHelpers} from '../../LoggerHelpers';
import {PersistorTransaction} from '../../types';

// Rollback S3 Changes
export namespace RollbackS3 {

// Persistor Def is typeof Persistor
export async function rollbackS3(persistorDef, logger, txn: PersistorTransaction) {
LoggerHelpers.info(persistorDef, logger, {
component: 'persistor',
module: 'api',
activity: 'end'
}, `Rolling back transaction of remote keys`);
const remoteDocService = RemoteDocService.new(persistorDef.environment);
const toDeletePromiseArr = [];

// create our `delete functions` to be run later.
// also put them in one place => toDeletePromiseArr.

for (const key of txn.remoteObjects) { // @TODO: how does this work right now? If doesn't work on sets
toDeletePromiseArr.push(rollbackS3Key(persistorDef, logger, key, remoteDocService));
}

// fire off our delete requests in parallel
await Promise.all(toDeletePromiseArr);
}

async function rollbackS3Key(persistorDef, logger, key, remoteDocService: RemoteDocService) {
try {
await remoteDocService.deleteDocument(key, persistorDef.bucketName);
} catch (e) {
const newLogObj = {component: 'persistor', module: 'api', activity: 'end', error: e};
LoggerHelpers.error(persistorDef, logger, newLogObj, `Unable to rollback remote document with key: ${key} and bucket: ${persistorDef.bucketName}`);
}
}
}
Loading