usqlite.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. /**
  2. * 对 SQLite 的 ORM 的封装处理
  3. * @time 2023-04-06
  4. * @version 2.0.0
  5. * @by wzx
  6. */
  7. let config = {
  8. deBug: true,
  9. isConnect: false
  10. }
  11. // class Response {
  12. // constructor(code, msg, data) {
  13. // this.code = code;
  14. // this.msg = msg;
  15. // this.data = data;
  16. // }
  17. // toString() {
  18. // return JSON.stringify(this);
  19. // }
  20. // }
  21. class Utils {
  22. static modelSql(name, options) {
  23. let sql;
  24. let sqlArr = [];
  25. let primaryKeyArr = [];
  26. Utils.log('[modelSql] options:', options);
  27. for (const key in options) {
  28. if (Object.hasOwnProperty.call(options, key)) {
  29. const option = options[key];
  30. sqlArr.push(Utils.restrain(key, option));
  31. if (option.primaryKey == true) {
  32. primaryKeyArr.push(key.toString());
  33. Utils.log(`[modelSql] ${key} is primary key${primaryKeyArr.length}`);
  34. }
  35. }
  36. }
  37. Utils.log(primaryKeyArr.length);
  38. if (primaryKeyArr.length>1) {
  39. sql = `CREATE TABLE '${name}' (${sqlArr.join(', ').replaceAll(' PRIMARY KEY','')}, PRIMARY KEY (${primaryKeyArr.join()}))`;
  40. }
  41. else{
  42. sql = `CREATE TABLE '${name}' (${sqlArr.join(', ')})`;
  43. }
  44. Utils.log(`[modelSql] ${sql}`);
  45. return sql;
  46. }
  47. static restrain(key, options) {
  48. let restrainArray = [];
  49. restrainArray.push(`'${key}'`);
  50. // 如果是 String 拦截处理
  51. if (options.constructor != Object) {
  52. restrainArray.push(Utils.toType(options));
  53. return restrainArray.join(' ');
  54. }
  55. restrainArray.push(Utils.toType(options.type));
  56. // 主键
  57. if (options.primaryKey == true) {
  58. if(options.autoIncrement != true){
  59. restrainArray.push('PRIMARY KEY');
  60. }
  61. }
  62. // 自增
  63. if (Utils.isNumber(options.type)&&options.autoIncrement == true) {
  64. restrainArray.pop();
  65. restrainArray.push('INTEGER');
  66. restrainArray.push('PRIMARY KEY');
  67. restrainArray.push('AUTOINCREMENT');
  68. }
  69. // 非空
  70. if (options.notNull == true) {
  71. restrainArray.push('NOT NULL');
  72. }
  73. // 默认值
  74. if (options.default) {
  75. restrainArray.push(`DEFAULT ${options.default}`);
  76. }
  77. // 是否是不同的值
  78. if (options.unique == true) {
  79. restrainArray.push('UNIQUE');
  80. }
  81. // 检查
  82. if (options.check) {
  83. restrainArray.push(`CHECK(${THIS_VALUE.check})`);
  84. }
  85. return restrainArray.join(' ');
  86. }
  87. static toType(jsType) {
  88. let sqliteType = '';
  89. if (Utils.isNumber(jsType)) {
  90. sqliteType = 'numeric';
  91. } else if (Utils.isDate(jsType)) {
  92. sqliteType = 'timestamp';
  93. } else if (Utils.isBlob(jsType)) {
  94. sqliteType = 'BLOB';
  95. } else {
  96. sqliteType = 'varchar';
  97. }
  98. return sqliteType;
  99. }
  100. static log() {
  101. if (config.deBug) {
  102. console.log.apply(null, arguments);
  103. }
  104. }
  105. static warn() {
  106. if (config.deBug) {
  107. console.warn.apply(null, arguments);
  108. }
  109. }
  110. static error() {
  111. console.error.apply(null, arguments);
  112. }
  113. static isBlob(value){ return value === 'BLOB' }
  114. static isArray(value){ return Object.prototype.toString.call(value) === '[object Array]'}
  115. static isObject(value){ return Object.prototype.toString.call(value) === '[object Object]'}
  116. static isString(value){ return Object.prototype.toString.call(value) === '[object String]'}
  117. static isFunction(value){ return (value === Function || Object.prototype.toString.call(value) === '[object Function]')}
  118. static isNumber(value){ return (value === Number || Object.prototype.toString.call(value) === '[object Number]')}
  119. static isNaN(value){ return (Object.prototype.toString.call(value) === '[object Number]' && isNaN(value))}
  120. static isBoolean(value){ return Object.prototype.toString.call(value) === '[object Boolean]'}
  121. static isUndefined(value){ return Object.prototype.toString.call(value) === '[object Undefined]'}
  122. static isModel(value){ return Object.prototype.toString.call(value) === '[object Model]'}
  123. static isDate(value){ return (value === Date||Object.prototype.toString.call(value) === '[object Date]')}
  124. }
  125. /**
  126. * Model 对象内部public方法全部 return this;
  127. */
  128. class Model {
  129. /**
  130. * @constructor
  131. * @param {String} name 数据库表名
  132. * @param {} options 数据表列对象
  133. * @returns
  134. */
  135. constructor() {
  136. // constructor(name, options) {
  137. // this.init(name, options)
  138. // let self = this;
  139. // self.name = name;
  140. // self.options = options;
  141. // if (config.isConnect) {
  142. // self.repair();
  143. // } else {
  144. // if(!config.name||!config.path){
  145. // console.error('"config.name" or "config.path" is empty');
  146. // }
  147. // usqlite.connect(config);
  148. // }
  149. }
  150. /**
  151. * @param {String} name 数据库表名
  152. * @param {} options 数据表列对象
  153. * @returns
  154. */
  155. async init(name, options) {
  156. let self = this;
  157. self.name = name;
  158. self.options = options;
  159. if (config.isConnect) {
  160. await self.repair();
  161. } else {
  162. if(!config.name||!config.path){
  163. console.error('"config.name" or "config.path" is empty');
  164. }
  165. await usqlite.connect(config);
  166. }
  167. }
  168. /**
  169. * @description 查询表数据
  170. * @param {String|Array} options
  171. * - String WHERE 内容
  172. * - Array 需要查询的列
  173. * @returns
  174. */
  175. find(options='') {
  176. let sql = '';
  177. // let self = this;
  178. // self.repair();
  179. if(!(Utils.isString(options)||Utils.isArray(options)||Utils.isFunction(options))) {
  180. Utils.error('The first parameter of Model.find should be "Array", "String" or "Function" (when there is only one parameter).')
  181. }
  182. if (options == '') {
  183. sql = `SELECT * FROM '${this.name}'`; // 查找全部
  184. } else if (Utils.isArray(options)) {
  185. sql = `SELECT ${options.join()} FROM '${this.name}'`; // 查找制定列
  186. } else if (Utils.isString(options)) {
  187. sql = `SELECT * FROM '${this.name}' WHERE ${options}`; // 制定条件查询
  188. }
  189. Utils.log(`[find]: ${sql}`);
  190. return new Promise((resolve, reject) => {
  191. plus.sqlite.selectSql({
  192. name: config.name,
  193. sql: sql,
  194. success(res) {
  195. console.log('[find] res:', res)
  196. resolve(res)
  197. },
  198. fail(err) {
  199. console.error('[find] err:', err)
  200. reject(err)
  201. }
  202. });
  203. });
  204. }
  205. /**
  206. * @description 分页查询
  207. * @param {Object} options : { where:查询条件, number: 当前页数 , count : 每页数量 }
  208. * @return
  209. */
  210. limit(options) {
  211. let sql = '';
  212. // let self = this;
  213. // self.repair();
  214. if(!Utils.isObject(options)){
  215. Utils.error('The first parameter of Model.limit should be "Object".')
  216. }
  217. if (!options.where) {
  218. // 不存在 where
  219. sql =
  220. `SELECT * FROM '${this.name}' LIMIT ${options.count} OFFSET ${(options.number - 1) * options.count}`
  221. } else {
  222. // 存在 where
  223. sql =
  224. `SELECT * FROM '${this.name}' WHERE ${options.where} LIMIT ${options.count} OFFSET ${(options.number - 1) * options.count}`;
  225. };
  226. Utils.log(`[limit]: ${sql}`);
  227. return new Promise((resolve, reject) => {
  228. plus.sqlite.selectSql({
  229. name: config.name,
  230. sql: sql,
  231. success(res) {
  232. console.log('[limit] res:', res)
  233. resolve(res)
  234. },
  235. fail(err) {
  236. console.error('[limit] err:', err)
  237. reject(err)
  238. }
  239. });
  240. });
  241. }
  242. /**
  243. * @description 插入数据
  244. * @param {Object|Array} data: 需要插入的单个或者多个数据
  245. */
  246. async insert(data) {
  247. // let self = this;
  248. // self.repair();
  249. if(!(Utils.isObject(data)||Util.isArray(data))){
  250. Utils.error('The first parameter of Model.insert should be "Object" or "Array".')
  251. }
  252. if (config.isConnect) {
  253. if (Utils.isArray(data)) {
  254. for (var i = 0; i < data.length; i++) {
  255. await this.insert(data[i]);
  256. }
  257. return
  258. } else if (Utils.isObject(data)) {
  259. let keys = [];
  260. let values = [];
  261. // let index = arguments[3]??null;
  262. for (var key in data) {
  263. keys.push(key);
  264. values.push(`'${data[key]}'`);
  265. }
  266. let sql = `INSERT INTO '${this.name}' (${keys.join()}) VALUES (${values.join()})`;
  267. Utils.log(`[insert]: ${sql}`);
  268. return new Promise((resolve, reject) => {
  269. plus.sqlite.executeSql({
  270. name: config.name,
  271. sql: sql,
  272. success(res) {
  273. // console.log('[insert] res:', res)
  274. resolve(res)
  275. },
  276. fail(err) {
  277. console.error('[insert] err:', err)
  278. reject(err)
  279. }
  280. })
  281. });
  282. }
  283. }
  284. }
  285. /**
  286. * @description 更新数据
  287. * @param {Object} data: 修改后的数据
  288. * @param {String} options:可选参数 更新条件
  289. */
  290. update(data, options='') {
  291. // let self = this;
  292. // self.repair();
  293. let sql = '';
  294. let items = [];
  295. if(!(Utils.isObject(data))){
  296. Utils.error('The first parameter of Model.update should be "Objrct".')
  297. }
  298. if(!(Utils.isObject(options)||Utils.isString(options))){
  299. Utils.error('The second parameter of Model.update should be "Object" or "String".')
  300. }
  301. for (var key in data) {
  302. items.push(`${key}='${data[key]}'`);
  303. };
  304. if (options == '') {
  305. sql = `UPDATE '${this.name}' SET ${items.join()}`;
  306. } else {
  307. sql = `UPDATE ${this.name} SET ${items.join()} WHERE ${options}`;
  308. };
  309. Utils.log(`[update]: ${sql}`);
  310. return new Promise((resolve, reject) => {
  311. plus.sqlite.executeSql({
  312. name: config.name,
  313. sql: sql,
  314. success(res) {
  315. // console.log('[update] res:', res)
  316. resolve(res)
  317. },
  318. fail(err) {
  319. console.error('[update] err:', err)
  320. reject(err)
  321. }
  322. });
  323. });
  324. }
  325. /**
  326. * @description 删除数据
  327. * @param {String} options :可选参数 删除条件
  328. */
  329. delete(options='') {
  330. // let self = this;
  331. // self.repair();
  332. var sql = '';
  333. if(!(Utils.isString(options)||Utils.isFunction(options))){
  334. Utils.error('The first parameter of Model.delete should be "Object" or "Function".')
  335. }
  336. if (options == '') {
  337. sql = `DELETE FROM '${this.name}'`;
  338. } else {
  339. sql = `DELETE FROM '${this.name}' WHERE ${options}`;
  340. };
  341. Utils.log(`[delete]: ${sql}`);
  342. return new Promise((resolve, reject) => {
  343. plus.sqlite.executeSql({
  344. name: config.name,
  345. sql: sql,
  346. success(res) {
  347. console.log('[delete] res:', res)
  348. resolve(res)
  349. },
  350. fail(err) {
  351. console.error('[delete] err:', err)
  352. reject(err)
  353. }
  354. });
  355. });
  356. }
  357. /**
  358. * @description 重命名或者新增列
  359. * @param {Object|Array|String} options 参数 数组为新增多列 对象为新增单列{aa} 字符串重命名
  360. * @return:
  361. */
  362. async alter(options) {
  363. let self = this;
  364. // self.repair();
  365. let sql = '';
  366. if(!(Utils.isObject(options)||Utils.isArray(options)||Utils.isString(options))){
  367. Utils.error('The first parameter of Model.alter should be "Object", "Array" or "String".')
  368. }
  369. if (Utils.isArray(options)) { // 新增多列
  370. for (let i = 0; i < options.length; i++) {
  371. await this.alter(options[i]);
  372. }
  373. return
  374. } else if (Utils.isObject(options)) { // 新增单列
  375. let column = Utils.restrain(options.name, options.option);
  376. sql = `ALTER TABLE '${this.name}' ADD COLUMN ${column}`
  377. } else if (options.constructor == String) { // 重命名
  378. sql = `ALTER TABLE '${this.name}' RENAME TO '${options}'`
  379. }
  380. Utils.log(`[alter]: ${sql}`);
  381. return new Promise((resolve, reject) => {
  382. plus.sqlite.selectSql({
  383. name: config.name,
  384. sql: sql,
  385. success(res) {
  386. if (options.constructor == String) { // 重命名
  387. self.name = options;
  388. }
  389. // console.log('[alter] res:', res)
  390. resolve(res)
  391. },
  392. fail(err) {
  393. console.error('[alter] err:', err)
  394. reject(err)
  395. }
  396. });
  397. });
  398. }
  399. /**
  400. * @description
  401. * @param {Model} model 右 Model
  402. * @param {Object} options
  403. * @returns
  404. */
  405. join(model, options) {
  406. // let self = this;
  407. // self.repair();
  408. if (!model) {
  409. Utils.error('"model" cannot be empty.');
  410. }
  411. if (!Utils.isObject(options)) {
  412. Utils.error('The type of "options" is wrong, it should be "Object".');
  413. }
  414. if (!options.type || !options.predicate) {
  415. Utils.error('Missing required parameters');
  416. }
  417. let leftName = this.name;
  418. let rightName = model.name;
  419. let leftValue = options.predicate.left;
  420. let rightValue = options.predicate.right;
  421. let cols = ['*'];
  422. const SQL_MAP = {
  423. cross: `SELECT ${cols.join()} FROM ${leftName} CROSS JOIN ${rightName};`,
  424. inner: [`SELECT ${cols.join()} FROM ${leftName} NATURAL JOIN ${rightName}`,
  425. `SELECT ${cols.join()} FROM ${leftName} INNER JOIN ${rightName} ON ${leftName}.${leftValue} = ${rightName}.${rightValue}`
  426. ],
  427. outer: `SELECT ${cols.join()} FROM ${leftName} OUTER JOIN ${rightName} ON ${leftName}.${leftValue} = ${rightName}.${rightValue}`
  428. }
  429. let sql = '';
  430. if (options.type == inner && !options.predicate) {
  431. sql = SQL_MAP[options.type][0];
  432. } else if (options.type == inner && !options.predicate) {
  433. sql = SQL_MAP[options.type][1];
  434. } else {
  435. sql = SQL_MAP[options.type];
  436. }
  437. Utils.log(`[join]: ${sql}`);
  438. return new Promise((resolve, reject) => {
  439. plus.sqlite.selectSql({
  440. name: config.name,
  441. sql: sql,
  442. success(res) {
  443. console.log('[join] res:', res)
  444. resolve(res)
  445. },
  446. fail(err) {
  447. console.error('[join] err:', err)
  448. reject(err)
  449. }
  450. });
  451. });
  452. }
  453. /**
  454. * @description 执行sql语句
  455. * @param {String} sql : sql语句
  456. */
  457. sql(sql) {
  458. if (!Utils.isString(sql)) {
  459. Utils.error('"The type of "sql" is wrong, it should be "String".');
  460. }
  461. // let self = this;
  462. // self.repair();
  463. Utils.log(`[sql]: ${sql}`);
  464. return new Promise((resolve, reject) => {
  465. plus.sqlite.selectSql({
  466. name: config.name,
  467. sql: sql,
  468. success(res) {
  469. console.log('[sql] res:', res)
  470. resolve(res)
  471. },
  472. fail(err) {
  473. console.error('[sql] err:', err)
  474. reject(err)
  475. }
  476. });
  477. });
  478. }
  479. /**
  480. * @description 判断表是否存在
  481. */
  482. insertMapData(arrayBuffer) {
  483. console.log('arrayBuffer', arrayBuffer)
  484. let zipFileData = this.byteArrayToHexString(arrayBuffer)
  485. console.log('zipFileData', zipFileData)
  486. let sql = `INSERT INTO 'mapInfo' (shopId,mapName,zipImageUrl,zipImageMd5,zipFileName,zipFileData)
  487. VALUES ('1','路线图','http://www.beswell.com/download/齐源大厦_已修改.zip','MTExMQ==','齐源大厦_已修改.tif', x'${zipFileData}')`;
  488. return new Promise((resolve, reject) => {
  489. plus.sqlite.executeSql({
  490. name: config.name,
  491. sql: sql,
  492. success(res) {
  493. // console.log('[insertMapData] res:', res)
  494. resolve(res)
  495. },
  496. fail(err) {
  497. console.error('[insertMapData] err:', err)
  498. reject(err)
  499. }
  500. });
  501. });
  502. }
  503. byteArrayToHexString(arrayBuffer) { // converts byte arrays to string
  504. let i, j, inn;
  505. let hex = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
  506. let out = "";
  507. for (j = 0; j < arrayBuffer.length; ++j) {
  508. inn = arrayBuffer[j] & 0xff;
  509. i = (inn >>> 4) & 0x0f;
  510. out += hex[i];
  511. i = inn & 0x0f;
  512. out += hex[i];
  513. }
  514. return out;
  515. }
  516. /**
  517. * @description 判断表是否存在
  518. */
  519. isExist() {
  520. let sql = `SELECT count(*) AS isExist FROM sqlite_master WHERE type='table' AND name='${this.name}'`;
  521. // Utils.log(`[isExist] ${config.name}`);
  522. // Utils.log(`[isExist] ${sql}`);
  523. return new Promise((resolve, reject) => {
  524. plus.sqlite.selectSql({
  525. name: config.name,
  526. sql: sql,
  527. success(res) {
  528. // console.log('[isExist] res:', res)
  529. if (res[0].isExist == 1)
  530. console.log(`[isExist] ${config.name} 已存在`);
  531. else
  532. console.log(`[isExist] ${config.name} 不存在`);
  533. resolve(res)
  534. },
  535. fail(err) {
  536. console.error('[isExist] err:', err)
  537. reject(err)
  538. }
  539. });
  540. });
  541. }
  542. /**
  543. * @description 删除数据表 **不推荐**
  544. */
  545. drop() {
  546. var sql = `DROP TABLE '${this.name}'`;
  547. // let self = this;
  548. // self.repair();
  549. Utils.log(`[drop]: ${sql}`);
  550. return new Promise((resolve, reject) => {
  551. plus.sqlite.selectSql({
  552. name: config.name,
  553. sql: sql,
  554. success(res) {
  555. console.log('[drop] res:', res)
  556. resolve(res)
  557. },
  558. fail(err) {
  559. console.error('[drop] err:', err)
  560. reject(err)
  561. }
  562. });
  563. });
  564. }
  565. /**
  566. * @description 创建数据表 **不推荐**
  567. */
  568. create() {
  569. // let self = this;
  570. let sql = Utils.modelSql(this.name, this.options);
  571. Utils.log(`[create]: ${sql}`);
  572. return new Promise((resolve, reject) => {
  573. plus.sqlite.selectSql({
  574. name: config.name,
  575. sql: sql,
  576. success(res) {
  577. // console.log('[create] res:', res)
  578. resolve(res)
  579. },
  580. fail(err) {
  581. console.error('[create] err:', err)
  582. reject(err)
  583. }
  584. });
  585. });
  586. }
  587. toString() {
  588. return `[${this.name} Model]`;
  589. }
  590. async repair() {
  591. let self = this;
  592. try {
  593. let res = await self.isExist()
  594. if (!res[0].isExist) {
  595. await self.create();
  596. }
  597. } catch(e) {
  598. console.error(e);
  599. }
  600. }
  601. }
  602. // 单例模式
  603. export class usqlite {
  604. /**
  605. * 构造函数
  606. * @param {Object} options 数据库配置信息 *
  607. * {name: 'demo', path: '_doc/demo.db'}
  608. * - name 数据库名称*
  609. * - path 数据库路径
  610. */
  611. constructor(options) {
  612. console.warn('No instantiation');
  613. }
  614. /**
  615. * @description 链接数据库
  616. * @param {Object} options 数据库配置信息 *
  617. * {name: 'demo', path: '_doc/demo.db'}
  618. * - name 数据库名称*
  619. * - path 数据库路径
  620. */
  621. static connect(options) {
  622. config.name = options.name; // 数据库名称*
  623. config.path = options.path; // 数据库名称*
  624. if (config.isConnect) {
  625. console.warn('[connect] 数据库已连接,无需重连')
  626. return
  627. }
  628. return new Promise((resolve, reject) => {
  629. plus.sqlite.openDatabase({
  630. name: config.name, //数据库名称
  631. path: config.path, //数据库地址
  632. success(res) {
  633. config.isConnect = true;
  634. // console.log('[connect] res:', res)
  635. resolve(res)
  636. },
  637. fail(err) {
  638. if (e.code == -1402) {
  639. config.isConnect = true;
  640. console.warn('[connect] warn:', err)
  641. } else {
  642. config.isConnect = false;
  643. console.error('[connect] err:', err)
  644. }
  645. reject(err)
  646. }
  647. });
  648. });
  649. }
  650. /**
  651. * @description 断开数据库
  652. * @param {*} callback
  653. */
  654. static close() {
  655. return new Promise((resolve, reject) => {
  656. plus.sqlite.closeDatabase({
  657. name: config.name, //数据库名称
  658. path: config.path, //数据库地址
  659. success(res) {
  660. config.isConnect = false;
  661. // console.log('[close] res:', res)
  662. resolve(res)
  663. },
  664. fail(err) {
  665. console.error('[close] err:', err)
  666. reject(err)
  667. }
  668. });
  669. });
  670. }
  671. /**
  672. * @description 创建 Model 对象
  673. * @example
  674. * usqlite.model('demo',
  675. * {
  676. * id: {
  677. * type: Number
  678. * },
  679. * content: String
  680. * })
  681. * @param {String} name 数据表名称 *
  682. * @param {String} options 参数配置 *
  683. * @returns 返回 Model 对象
  684. */
  685. static async model(name, options) {
  686. Utils.log(config);
  687. // return new Model(name, options);
  688. let model = new Model();
  689. await model.init(name, options)
  690. return model
  691. }
  692. }