diff --git a/.npmignore b/.npmignore index 3a2d105..cb47d28 100644 --- a/.npmignore +++ b/.npmignore @@ -10,6 +10,9 @@ jsconfig.json .gitpod.yml .gitpod.dockerfile +# docs +docs + #github .github diff --git a/README.md b/README.md index 6649e31..e8d7855 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ ![GitHub last commit](https://img.shields.io/github/last-commit/themost-framework/sqlite) ![GitHub Release Date](https://img.shields.io/github/release-date/themost-framework/sqlite) [![npm](https://img.shields.io/npm/dw/@themost/sqlite)](https://www.npmjs.com/package/@themost%2Fsqlite) -![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/@themost/sqlite) ![MOST Web Framework Logo](https://github.com/themost-framework/common/raw/master/docs/img/themost_framework_v3_128.png) @@ -40,16 +39,32 @@ Register SQLite adapter on app.json as follows: ] } +or create a new instance of `SqliteAdapter` class for connecting to SQLite database. -#### Post Installation Note: -SQLite Data Adapter comes with a regular expression extension for SQLite (regexp.c). You have to compile this extension as follows: +```javascript +const { SqliteAdapter } = require('@themost/sqlite'); +const { QueryExpression } = require('@themost/query'); +const db = new SqliteAdapter({ + database: 'db/local.db' +}); +const query = new QueryExpression() + .select(({ id, name, category, model, price }) => ({ + id, + name, + category, + model, + price, + })).from('ProductData') + .where((x) => { + return x.price > 500 && x.category === "Laptops"; + }) + .orderByDescending((x) => x.price) + .take(10); +``` -##### Using GCC/MinGW on Windows and Linux -gcc -shared -fPIC -Isqlite3 -o regexp.0.dylib regexp.c +Read more about [MOST Web Framework query language provided by @themost/query](https://github.com/themost-framework/query?#themostquery) -##### Using GCC on Mac OSX -gcc -dynamiclib -fPIC -Isqlite3 -o regexp.0.dylib regexp.c +Use [query playground project at codesanbox.io](https://codesandbox.io/p/devbox/query-playground-zc8fg9) to learn more about the query language specification of [@themost-framework](https://github.com/themost-framework) -##### Microsoft Tools on Windows -cl /Gd regexp.c /I sqlite3 /DDLL /LD /link /export:sqlite3_extension_init /out:regexp.0.dylib +![codesandbox.io_query-playground-1.png](docs/img/codesandbox.io_query-playground-1.png) diff --git a/babel.config.js b/babel.config.js index 46c934d..6f9c007 100644 --- a/babel.config.js +++ b/babel.config.js @@ -9,4 +9,12 @@ module.exports = { } ] ], + plugins: [ + [ + '@babel/plugin-proposal-decorators', + { + 'legacy': true + } + ] + ] }; \ No newline at end of file diff --git a/docs/img/codesandbox.io_query-playground-1.png b/docs/img/codesandbox.io_query-playground-1.png new file mode 100644 index 0000000..2f2216b Binary files /dev/null and b/docs/img/codesandbox.io_query-playground-1.png differ diff --git a/docs/img/codesandbox.io_query-playground-2.png b/docs/img/codesandbox.io_query-playground-2.png new file mode 100644 index 0000000..9117ea6 Binary files /dev/null and b/docs/img/codesandbox.io_query-playground-2.png differ diff --git a/jest.config.js b/jest.config.js index 02b46f5..d6fff34 100644 --- a/jest.config.js +++ b/jest.config.js @@ -19,7 +19,7 @@ const config = { // clearMocks: false, // Indicates whether the coverage information should be collected while executing the test - // collectCoverage: false, + collectCoverage: true, // An array of glob patterns indicating a set of files for which coverage information should be collected // collectCoverageFrom: undefined, diff --git a/jest.setup.js b/jest.setup.js index dc1120b..51d9b02 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,3 +1,7 @@ -process.env.NODE_ENV='development'; +require('dotenv').config(); +const { JsonLogger } = require('@themost/json-logger'); +const { TraceUtils } = require('@themost/common'); +process.env.NODE_ENV = 'development'; +TraceUtils.useLogger(new JsonLogger()); /* global jest */ jest.setTimeout(30000); \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json index ddcba5f..c6db4df 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "baseUrl": ".", + "experimentalDecorators": true, "paths": { "@themost/sqlite": [ "src/index" diff --git a/package-lock.json b/package-lock.json index 6fd8ef8..68e0028 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "@themost/sqlite", - "version": "2.8.4", + "version": "2.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@themost/sqlite", - "version": "2.8.4", + "version": "2.9.0", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { + "@themost/events": "^1.5.0", "async": "^2.6.4", "sprintf-js": "^1.1.2", "sqlite3": "^5.1.7", @@ -20,15 +21,16 @@ "@babel/core": "^7.26.0", "@babel/eslint-parser": "^7.25.9", "@babel/eslint-plugin": "^7.25.1", + "@babel/plugin-proposal-decorators": "^7.25.9", "@babel/preset-env": "^7.26.0", "@babel/register": "^7.25.9", "@rollup/plugin-babel": "^5.3.1", "@rollup/plugin-commonjs": "^22.0.0", "@themost/common": "^2.11.0", - "@themost/data": "^2.14.2", - "@themost/events": "^1.3.0", + "@themost/data": "^2.18.1", + "@themost/json-logger": "^1.1.0", "@themost/peers": "^1.0.2", - "@themost/query": "^2.14.5", + "@themost/query": "^2.14.7", "@themost/xml": "^2.5.2", "@types/jasmine": "^5.1.4", "dotenv": "^16.0.0", @@ -705,6 +707,24 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz", + "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-decorators": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", @@ -769,6 +789,22 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz", + "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-assertions": { "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", @@ -3304,22 +3340,25 @@ } }, "node_modules/@themost/data": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/@themost/data/-/data-2.14.2.tgz", - "integrity": "sha512-XxnhOjYcMxJcRxoKVD4GSjPxrBLNWWGDsxBeHqLpEtuUVSqBiBfzk4NLNsYq05kNxRPuuUgO+j4V3NW/y0aNzg==", + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/@themost/data/-/data-2.18.1.tgz", + "integrity": "sha512-y2hDeFG7hYX8keOUlf1JZR65en3di8C5n32cEAwiXVPtVwVFRvOUyXOyGYb8biqZ81IhbIADUmEgZlr6yGxbyw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@themost/events": "^1.0.5", "@themost/promise-sequence": "^1.0.1", "async": "^2.6.4", + "crypto-js": "^4.2.0", + "esprima": "^4.0.1", "lodash": "^4.17.21", "moment": "^2.29.4", "node-cache": "^1.1.0", "pluralize": "^7.0.0", "q": "^1.4.1", "sprintf-js": "^1.1.2", - "symbol": "^0.3.1" + "symbol": "^0.3.1", + "uuid": "^10.0.0" }, "engines": { "node": ">=14.21.3" @@ -3330,11 +3369,35 @@ "@themost/xml": "^2" } }, + "node_modules/@themost/data/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@themost/events": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@themost/events/-/events-1.3.0.tgz", - "integrity": "sha512-UH55Ordctdfd4VnDYSjswktM24MJ4wAsrqwKDP73StU4PiWFNcmcoEIwaXQfH88vPJONUUySJQ48tmfpzcarpw==", - "dev": true + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@themost/events/-/events-1.5.0.tgz", + "integrity": "sha512-R+q764cVNDPW4CYKr6Le1LCty1YYieKHbh9mJwv3gDo9fG2wtoN23T85ABnsQpTOkkJOsxk0+Tl3tPM3dblEjQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@themost/json-logger": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@themost/json-logger/-/json-logger-1.1.0.tgz", + "integrity": "sha512-YBR3waxf/x87KFHabwJ7s117LfAjOrdR/u85a0vg1y9cGMvXRf+3bnb3YvVXXuDO98FHut5JyJA/6CDIY0nyXQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "date-and-time": "^3.1.1" + } }, "node_modules/@themost/peers": { "version": "1.0.2", @@ -3352,9 +3415,9 @@ "dev": true }, "node_modules/@themost/query": { - "version": "2.14.5", - "resolved": "https://registry.npmjs.org/@themost/query/-/query-2.14.5.tgz", - "integrity": "sha512-94xebP3X6dqpflmCDohEI7aJq+s23RxNIdDcjNpE/leQKdogu/C9KrvPbOTG8uBDMaBgBJPRBdyhsLJdHnUCig==", + "version": "2.14.7", + "resolved": "https://registry.npmjs.org/@themost/query/-/query-2.14.7.tgz", + "integrity": "sha512-VpW6ad64IRVHk+wY+FINbppblushOGIOb9TCwcNfr930o82IjTMhbOkGnIM2hx3x68T3iWoK0YiGaZxegAfWkg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4758,6 +4821,13 @@ "dev": true, "license": "MIT" }, + "node_modules/date-and-time": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.6.0.tgz", + "integrity": "sha512-V99gLaMqNQxPCObBumb31Bfy3OByXnpqUM0yHPi/aBQE61g42A2rGk6Z2CDnpLrWsOFLQwOgl4Vgshw6D44ebw==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 617b697..5adba7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@themost/sqlite", - "version": "2.8.4", + "version": "2.9.0", "description": "MOST Web Framework SQLite Adapter", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,6 +28,7 @@ "sqleanVersion": "0.27.1" }, "dependencies": { + "@themost/events": "^1.5.0", "async": "^2.6.4", "sprintf-js": "^1.1.2", "sqlite3": "^5.1.7", @@ -41,15 +42,16 @@ "@babel/core": "^7.26.0", "@babel/eslint-parser": "^7.25.9", "@babel/eslint-plugin": "^7.25.1", + "@babel/plugin-proposal-decorators": "^7.25.9", "@babel/preset-env": "^7.26.0", "@babel/register": "^7.25.9", "@rollup/plugin-babel": "^5.3.1", "@rollup/plugin-commonjs": "^22.0.0", "@themost/common": "^2.11.0", - "@themost/data": "^2.14.2", - "@themost/events": "^1.3.0", + "@themost/data": "^2.18.1", + "@themost/json-logger": "^1.1.0", "@themost/peers": "^1.0.2", - "@themost/query": "^2.14.5", + "@themost/query": "^2.14.7", "@themost/xml": "^2.5.2", "@types/jasmine": "^5.1.4", "dotenv": "^16.0.0", diff --git a/rollup.config.js b/rollup.config.js index 4a46a3d..5debdc0 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,5 +1,4 @@ import { babel } from '@rollup/plugin-babel'; -import commonjs from '@rollup/plugin-commonjs'; import * as pkg from './package.json'; import dts from 'rollup-plugin-dts'; @@ -13,7 +12,6 @@ export default [ }, external: Object.keys(pkg.dependencies).concat(Object.keys(pkg.peerDependencies)), plugins: [ - commonjs(), babel({ babelHelpers: 'bundled' }) ] }, diff --git a/spec/DateFunctions.spec.js b/spec/DateFunctions.spec.js index 4ae0885..979ba10 100644 --- a/spec/DateFunctions.spec.js +++ b/spec/DateFunctions.spec.js @@ -1,4 +1,5 @@ import { TestApplication } from './TestApplication'; +import moment from 'moment'; describe('DateFunctions', () => { /** @@ -36,11 +37,13 @@ describe('DateFunctions', () => { it('should use getDay()', async () => { await app.executeInTestTranscaction(async (context) => { let items = await context.model('Order') - .asQueryable().where('orderDate').getDay().equal(15).silent().getItems(); + .asQueryable().where('orderDate').getDate().getDay().equal(15).silent().take(10).getItems(); expect(Array.isArray(items)).toBeTruthy(); expect(items.length).toBeGreaterThan(0); for (const item of items) { - expect(item.orderDate.getDate()).toEqual(15); + const orderDate = item.orderDate; + const dayOfMonth = parseInt(moment.utc(orderDate).format('D'), 10); + expect(dayOfMonth).toEqual(15); } }); }); @@ -52,7 +55,8 @@ describe('DateFunctions', () => { expect(Array.isArray(items)).toBeTruthy(); expect(items.length).toBeGreaterThan(0); for (const item of items) { - expect(item.orderDate.getMonth()).toEqual(3); + const orderMonth = parseInt(moment.utc(item.orderDate).format('M'), 10); + expect(orderMonth).toEqual(4); } }); }); @@ -77,7 +81,8 @@ describe('DateFunctions', () => { expect(Array.isArray(items)).toBeTruthy(); expect(items.length).toBeGreaterThan(0); for (const item of items) { - expect(item.orderDate.getHours()).toEqual(14); + const hour = parseInt(moment.utc(item.orderDate).format('H'), 10); + expect(hour).toEqual(14); } }); }); diff --git a/spec/QueryExpression.selectJson.spec.js b/spec/QueryExpression.selectJson.spec.js index 51fca6b..c980cb8 100644 --- a/spec/QueryExpression.selectJson.spec.js +++ b/spec/QueryExpression.selectJson.spec.js @@ -1,6 +1,6 @@ // noinspection SpellCheckingInspection -import {MemberExpression, MethodCallExpression, QueryEntity, QueryExpression} from '@themost/query'; +import {MemberExpression, MethodCallExpression, QueryEntity, QueryExpression, QueryField} from '@themost/query'; import { SqliteFormatter } from '../src'; import SimpleOrderSchema from './config/models/SimpleOrder.json'; import {TestApplication} from './TestApplication'; @@ -318,4 +318,87 @@ describe('SqlFormatter', () => { }); }); + it('should use jsonObject', async () => { + await app.executeInTestTranscaction(async (context) => { + const Orders = context.model('Order').silent(); + const q = Orders.select( + 'id', 'orderedItem', 'orderDate' + ).where('customer/description').equal('Eric Thomas'); + const select = q.query.$select[Orders.viewAdapter]; + select.push({ + customer: { + $jsonObject: [ + 'familyName', + new QueryField('familyName').from('customer'), + 'givenName', + new QueryField('givenName').from('customer'), + ] + } + }); + const items = await q.getItems(); + expect(items).toBeTruthy(); + for (const item of items) { + expect(item.customer).toBeTruthy(); + expect(item.customer.familyName).toEqual('Thomas'); + expect(item.customer.givenName).toEqual('Eric'); + } + }); + }); + + it('should use jsonObject in ad-hoc queries', async () => { + await app.executeInTestTranscaction(async (context) => { + const {viewAdapter: Orders} = context.model('Order'); + const {viewAdapter: Customers} = context.model('Person'); + const {viewAdapter: OrderStatusTypes} = context.model('OrderStatusType'); + const q = new QueryExpression().select( + 'id', 'orderedItem', 'orderStatus', 'orderDate' + ).from(Orders).join(new QueryEntity(Customers).as('customers')).with( + new QueryExpression().where( + new QueryField('customer').from(Orders) + ).equal( + new QueryField('id').from('customers') + ) + ).join(new QueryEntity(OrderStatusTypes).as('orderStatusTypes')).with( + new QueryExpression().where( + new QueryField('orderStatus').from(Orders) + ).equal( + new QueryField('id').from('orderStatusTypes') + ) + ).where(new QueryField('description').from('customers')).equal('Eric Thomas'); + const select = q.$select[Orders]; + select.push({ + customer: { + $jsonObject: [ + 'familyName', + new QueryField('familyName').from('customers'), + 'givenName', + new QueryField('givenName').from('customers'), + ] + } + }, { + orderStatus: { + $jsonObject: [ + 'name', + new QueryField('name').from('orderStatusTypes'), + 'alternateName', + new QueryField('alternateName').from('orderStatusTypes'), + ] + } + }); + /** + * @type {Array<{id: number, orderedItem: number, orderDate: Date, orderStatus: { name: string, alternateName: string }, customer: {familyName: string, givenName: string}}>} + */ + const items = await context.db.executeAsync(q, []); + expect(items).toBeTruthy(); + for (const item of items) { + expect(item.customer).toBeTruthy(); + expect(item.customer.familyName).toEqual('Thomas'); + expect(item.customer.givenName).toEqual('Eric'); + expect(item.orderStatus).toBeTruthy(); + expect(item.orderStatus.name).toBeTruthy(); + } + + }); + }); + }); diff --git a/spec/TestApplication.js b/spec/TestApplication.js index 28e1a89..384f108 100644 --- a/spec/TestApplication.js +++ b/spec/TestApplication.js @@ -26,12 +26,10 @@ class TestApplication extends DataApplication { // add adapter type const name = 'SQLite Data Adapter'; const invariantName = 'sqlite'; - Object.assign(dataConfiguration.adapterTypes, { - sqlite: { - name, - invariantName, - createInstance - } + dataConfiguration.adapterTypes.set(invariantName, { + name, + invariantName, + createInstance }); dataConfiguration.adapters.push({ name: 'test', @@ -43,7 +41,9 @@ class TestApplication extends DataApplication { async finalize() { const service = this.getConfiguration().getStrategy(DataCacheStrategy); + // noinspection JSUnresolvedReference if (typeof service.finalize === 'function') { + // noinspection JSUnresolvedReference await service.finalize(); } } diff --git a/spec/db/local.db b/spec/db/local.db index 630a3ea..7f91614 100644 Binary files a/spec/db/local.db and b/spec/db/local.db differ diff --git a/src/SqliteAdapter.d.ts b/src/SqliteAdapter.d.ts index eec0bba..2cbb94a 100644 --- a/src/SqliteAdapter.d.ts +++ b/src/SqliteAdapter.d.ts @@ -2,8 +2,12 @@ // MOST Web Framework Codename Zero Gravity Copyright (c) 2017-2022, THEMOST LP import { DataAdapterBase, DataAdapterIndexes, DataAdapterMigration, DataAdapterTable, DataAdapterView } from '@themost/common'; import { QueryExpression } from '@themost/query'; +import {AsyncSeriesEventEmitter} from '@themost/events'; export declare class SqliteAdapter implements DataAdapterBase { + executing: AsyncSeriesEventEmitter<{target: SqliteAdapter, query: (string|QueryExpression), params?: unknown[]}>; + executed: AsyncSeriesEventEmitter<{target: SqliteAdapter, query: (string|QueryExpression), params?: unknown[], results: uknown[]}>; + constructor(options: { database: string, extensions?: { [key: string]: string }, retry?: number, retryInterval?: number }); rawConnection?: any; options?: { database: string, extensions?: { [key: string]: string }, retry?: number, retryInterval?: number }; diff --git a/src/SqliteAdapter.js b/src/SqliteAdapter.js index 7ce5a0f..6e7f352 100644 --- a/src/SqliteAdapter.js +++ b/src/SqliteAdapter.js @@ -4,6 +4,7 @@ import { sprintf } from 'sprintf-js'; import {waterfall, eachSeries} from 'async'; import {TraceUtils} from '@themost/common'; import { QueryExpression, QueryField, SqlUtils } from '@themost/query'; +import { AsyncSeriesEventEmitter, before, after } from '@themost/events'; import { SqliteFormatter } from './SqliteFormatter'; import { SqliteExtensions } from './SqliteExtensions'; import sqlite from 'sqlite3'; @@ -25,6 +26,43 @@ const SQLITE_OPEN_SHAREDCACHE = 0x00020000; /* Ok for sqlite3_open_v2() */ const SQLITE_OPEN_PRIVATECACHE = 0x00040000; /* Ok for sqlite3_open_v2() */ /* eslint-enable no-unused-vars */ +/** + * + * @param {{target: SqliteAdapter, query: string|QueryExpression, results: Array<*>}} event + */ +function onReceivingJsonObject(event) { + if (typeof event.query === 'object' && event.query.$select) { + // try to identify the usage of a $jsonObject dialect and format result as JSON + const { $select: select } = event.query; + if (select) { + const attrs = Object.keys(select).reduce((previous, current) => { + const fields = select[current]; + previous.push(...fields); + return previous; + }, []).filter((x) => { + const [key] = Object.keys(x); + if (typeof key !== 'string') { + return false; + } + return x[key].$jsonObject != null || x[key].$json != null; + }).map((x) => { + return Object.keys(x)[0]; + }); + if (attrs.length > 0) { + if (Array.isArray(event.results)) { + for(const result of event.results) { + attrs.forEach((attr) => { + if (Object.prototype.hasOwnProperty.call(result, attr) && typeof result[attr] === 'string') { + result[attr] = JSON.parse(result[attr]); + } + }); + } + } + } + } + } +} + class SqliteAdapter { /** @@ -46,6 +84,11 @@ class SqliteAdapter { */ this.rawConnection = null; this.extensions = Object.assign({}, SqliteExtensions, this.options.extensions); + this.executing = new AsyncSeriesEventEmitter(); + this.executed = new AsyncSeriesEventEmitter(); + + this.executed.subscribe(onReceivingJsonObject); + } open(callback) { const self = this; @@ -1046,8 +1089,38 @@ class SqliteAdapter { } }; } + + @before(({target, args}, callback) => { + const [query, params] = args; + void target.executing.emit({ + target, + query, + params + }).then(() => { + return callback(); + }).catch((err) => { + return callback(err); + }); + }) + @after(({target, args, result: results}, callback) => { + const [query, params] = args; + const event = { + target, + query, + params, + results + }; + void target.executed.emit(event).then(() => { + return callback(null, { + value: results + }); + }).catch((err) => { + return callback(err); + }); + }) /** * Executes a query against the underlying database + * @private * @param query {QueryExpression|string|*} * @param values {*=} * @param {function(Error=,*=)} callback diff --git a/src/SqliteFormatter.js b/src/SqliteFormatter.js index 1977264..a7028f3 100644 --- a/src/SqliteFormatter.js +++ b/src/SqliteFormatter.js @@ -336,6 +336,26 @@ class SqliteFormatter extends SqlFormatter { return `strftime('%F %H:%M:%f+00:00', 'now')`; } } + + /** + * @param {...*} expr + */ + // eslint-disable-next-line no-unused-vars + $json(expr) { + const args = Array.from(arguments); + return this.$jsonObject(...args); + } + + /** + * @param {...*} expr + */ + // eslint-disable-next-line no-unused-vars + $jsonObject(expr) { + const args = Array.from(arguments).map((arg) => { + return this.escape(arg) + }); + return `json_object(${args.join(',')})`; + } } export {