@nozbe/watermelondb
Version:
Build powerful React Native and React web apps that scale from hundreds to tens of thousands of records and remain fast
291 lines (290 loc) • 12.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.default = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _common = require("../../utils/common");
var _Result = require("../../utils/fp/Result");
var _fp = require("../../utils/fp");
var _common2 = require("../common");
var _encodeQuery = _interopRequireDefault(require("./encodeQuery"));
var _makeDispatcher = require("./makeDispatcher");
/* eslint-disable global-require */
if ('production' !== process.env.NODE_ENV) {
require('./devtools');
}
var IGNORE_CACHE = 0;
var SQLiteAdapter = exports.default = /*#__PURE__*/function () {
function SQLiteAdapter(options) {
var _this = this;
this._tag = (0, _common.connectionTag)();
// console.log(`---> Initializing new adapter (${this._tag})`)
var {
dbName: dbName,
schema: schema,
migrations: migrations,
migrationEvents: migrationEvents,
usesExclusiveLocking = false,
experimentalUnsafeNativeReuse = false
} = options;
this.schema = schema;
this.migrations = migrations;
this._migrationEvents = migrationEvents;
this.dbName = this._getName(dbName);
this._dispatcherType = (0, _makeDispatcher.getDispatcherType)(options);
// Hacky-ish way to create an object with NativeModule-like shape, but that can dispatch method
// calls to async, synch NativeModule, or JSI implementation w/ type safety in rest of the impl
this._dispatcher = (0, _makeDispatcher.makeDispatcher)(this._dispatcherType, this._tag, this.dbName, {
usesExclusiveLocking: usesExclusiveLocking,
experimentalUnsafeNativeReuse: experimentalUnsafeNativeReuse
});
if ('production' !== process.env.NODE_ENV) {
(0, _common2.validateAdapter)(this);
}
this._initPromise = (0, _Result.toPromise)(function (callback) {
_this._init(function (result) {
callback(result);
(0, _common2.devSetupCallback)(result, options.onSetUpError);
});
});
}
var _proto = SQLiteAdapter.prototype;
// eslint-disable-next-line no-use-before-define
_proto.testClone = function (options = {}) {
return new Promise(function ($return, $error) {
var clone = new SQLiteAdapter((0, _extends2.default)({
dbName: this.dbName,
schema: this.schema,
jsi: 'jsi' === this._dispatcherType
}, this.migrations ? {
migrations: this.migrations
} : {}, options));
(0, _common.invariant)(clone._dispatcherType === this._dispatcherType, 'testCloned adapter has bad dispatcher type');
return Promise.resolve(clone._initPromise).then(function () {
try {
return $return(clone);
} catch ($boundEx) {
return $error($boundEx);
}
}, $error);
}.bind(this));
};
_proto._getName = function (name) {
if ('test' === process.env.NODE_ENV) {
return name || "file:testdb".concat(this._tag, "?mode=memory&cache=shared");
}
return name || 'watermelon';
};
_proto._init = function (callback) {
var _this2 = this;
// Try to initialize the database with just the schema number. If it matches the database,
// we're good. If not, we try again, this time sending the compiled schema or a migration set
// This is to speed up the launch (less to do and pass through bridge), and avoid repeating
// migration logic inside native code
this._dispatcher.call('initialize', [this.dbName, this.schema.version], function (result) {
if (result.error) {
callback(result);
return;
}
var status = result.value;
if ('schema_needed' === status.code) {
_this2._setUpWithSchema(callback);
} else if ('migrations_needed' === status.code) {
_this2._setUpWithMigrations(status.databaseVersion, callback);
} else if ('ok' !== status.code) {
callback({
error: new Error('Invalid database initialization status')
});
} else {
callback({
value: undefined
});
}
});
};
_proto._setUpWithMigrations = function (databaseVersion, callback) {
var _this3 = this;
_common.logger.log('[SQLite] Database needs migrations');
(0, _common.invariant)(0 < databaseVersion, 'Invalid database schema version');
var migrationSteps = this._migrationSteps(databaseVersion);
if (migrationSteps) {
_common.logger.log("[SQLite] Migrating from version ".concat(databaseVersion, " to ").concat(this.schema.version, "..."));
if (this._migrationEvents && this._migrationEvents.onStart) {
this._migrationEvents.onStart();
}
this._dispatcher.call('setUpWithMigrations', [this.dbName, require('./encodeSchema').encodeMigrationSteps(migrationSteps), databaseVersion, this.schema.version], function (result) {
if (result.error) {
_common.logger.error('[SQLite] Migration failed', result.error);
if (_this3._migrationEvents && _this3._migrationEvents.onError) {
_this3._migrationEvents.onError(result.error);
}
} else {
_common.logger.log('[SQLite] Migration successful');
if (_this3._migrationEvents && _this3._migrationEvents.onSuccess) {
_this3._migrationEvents.onSuccess();
}
}
callback(result);
});
} else {
_common.logger.warn('[SQLite] Migrations not available for this version range, resetting database instead');
this._setUpWithSchema(callback);
}
};
_proto._setUpWithSchema = function (callback) {
_common.logger.log("[SQLite] Setting up database with schema version ".concat(this.schema.version));
this._dispatcher.call('setUpWithSchema', [this.dbName, this._encodedSchema(), this.schema.version], function (result) {
if (!result.error) {
_common.logger.log("[SQLite] Schema set up successfully");
}
callback(result);
});
};
_proto.find = function (table, id, callback) {
var _this4 = this;
(0, _common2.validateTable)(table, this.schema);
this._dispatcher.call('find', [table, id], function (result) {
return callback((0, _Result.mapValue)(function (rawRecord) {
return (0, _common2.sanitizeFindResult)(rawRecord, _this4.schema.tables[table]);
}, result));
});
};
_proto.query = function (_query, callback) {
var _this5 = this;
(0, _common2.validateTable)(_query.table, this.schema);
var {
table: table
} = _query;
var [sql, args] = (0, _encodeQuery.default)(_query);
this._dispatcher.call('query', [table, sql, args], function (result) {
return callback((0, _Result.mapValue)(function (rawRecords) {
return (0, _common2.sanitizeQueryResult)(rawRecords, _this5.schema.tables[table]);
}, result));
});
};
_proto.queryIds = function (query, callback) {
(0, _common2.validateTable)(query.table, this.schema);
this._dispatcher.call('queryIds',
// $FlowFixMe
(0, _encodeQuery.default)(query), callback);
};
_proto.unsafeQueryRaw = function (query, callback) {
(0, _common2.validateTable)(query.table, this.schema);
this._dispatcher.call('unsafeQueryRaw',
// $FlowFixMe
(0, _encodeQuery.default)(query), callback);
};
_proto.count = function (query, callback) {
(0, _common2.validateTable)(query.table, this.schema);
this._dispatcher.call('count',
// $FlowFixMe
(0, _encodeQuery.default)(query, true), callback);
};
_proto.batch = function (operations, callback) {
this._dispatcher.call('batch', [require('./encodeBatch').default(operations, this.schema)], callback);
};
_proto.getDeletedRecords = function (table, callback) {
(0, _common2.validateTable)(table, this.schema);
this._dispatcher.call('queryIds', ["select id from \"".concat(table, "\" where _status='deleted'"), []], callback);
};
_proto.destroyDeletedRecords = function (table, recordIds, callback) {
(0, _common2.validateTable)(table, this.schema);
var operation = [0, null, "delete from \"".concat(table, "\" where \"id\" == ?"), recordIds.map(function (id) {
return [id];
})];
this._dispatcher.call('batch', [[operation]], callback);
};
_proto.unsafeLoadFromSync = function (jsonId, callback) {
if ('jsi' !== this._dispatcherType) {
callback({
error: new Error('unsafeLoadFromSync unavailable. Use JSI mode to enable.')
});
return;
}
var {
encodeDropIndices: encodeDropIndices,
encodeCreateIndices: encodeCreateIndices
} = require('./encodeSchema');
var {
schema: schema
} = this;
this._dispatcher.call('unsafeLoadFromSync', [jsonId, schema, encodeDropIndices(schema), encodeCreateIndices(schema)], function (result) {
return callback((0, _Result.mapValue)(
// { key: JSON.stringify(value) } -> { key: value }
function (residualValues) {
return (0, _fp.mapObj)(function (values) {
return JSON.parse(values);
}, residualValues);
}, result));
});
};
_proto.provideSyncJson = function (id, syncPullResultJson, callback) {
if ('jsi' !== this._dispatcherType) {
callback({
error: new Error('provideSyncJson unavailable. Use JSI mode to enable.')
});
return;
}
this._dispatcher.call('provideSyncJson', [id, syncPullResultJson], callback);
};
_proto.unsafeResetDatabase = function (callback) {
this._dispatcher.call('unsafeResetDatabase', [this._encodedSchema(), this.schema.version], function (result) {
if (result.value) {
_common.logger.log('[SQLite] Database is now reset');
}
callback(result);
});
};
_proto.unsafeExecute = function (operations, callback) {
if ('production' !== process.env.NODE_ENV) {
(0, _common.invariant)(operations && 'object' === typeof operations && 1 === Object.keys(operations).length && (Array.isArray(operations.sqls) || 'string' === typeof operations.sqlString), "unsafeExecute expects an { sqls: [ [sql, [args..]], ... ] } or { sqlString: 'foo; bar' } object");
}
if (operations.sqls) {
var queries = operations.sqls;
var batchOperations = queries.map(function ([sql, args]) {
return [IGNORE_CACHE, null, sql, [args]];
});
this._dispatcher.call('batch', [batchOperations], callback);
} else if (operations.sqlString) {
this._dispatcher.call('unsafeExecuteMultiple', [operations.sqlString], callback);
}
};
_proto.getLocal = function (key, callback) {
this._dispatcher.call('getLocal', [key], callback);
};
_proto.setLocal = function (key, value, callback) {
(0, _common.invariant)('string' === typeof value, 'adapter.setLocal() value must be a string');
this._dispatcher.call('batch', [[[IGNORE_CACHE, null, "insert or replace into \"local_storage\" (\"key\", \"value\") values (?, ?)", [[key, value]]]]], callback);
};
_proto.removeLocal = function (key, callback) {
this._dispatcher.call('batch', [[[IGNORE_CACHE, null, "delete from \"local_storage\" where \"key\" == ?", [[key]]]]], callback);
};
_proto._encodedSchema = function () {
return require('./encodeSchema').encodeSchema(this.schema);
};
_proto._migrationSteps = function (fromVersion) {
var {
stepsForMigration: stepsForMigration
} = require('../../Schema/migrations/stepsForMigration');
var {
migrations: migrations
} = this;
// TODO: Remove this after migrations are shipped
if (!migrations) {
return null;
}
return stepsForMigration({
migrations: migrations,
fromVersion: fromVersion,
toVersion: this.schema.version
});
};
return (0, _createClass2.default)(SQLiteAdapter, [{
key: "initializingPromise",
get: function get() {
return this._initPromise;
}
}]);
}();
SQLiteAdapter.adapterType = 'sqlite';