From 394baec24820d16ff7e6fc08a38d5107e540fd86 Mon Sep 17 00:00:00 2001 From: critic Date: Tue, 15 Oct 2024 06:49:49 +0300 Subject: [PATCH] sqlite based data caching and syncing added --- .idea/workspace.xml | 116 ++-- README.md | 2 +- package-lock.json | 61 +- package.json | 6 +- src/__test__/app/config.js | 22 +- src/__test__/app/model/address.js | 7 +- src/__test__/app/model/object.js | 7 +- src/__test__/index.test.js | 8 +- src/component/error.js | 24 + src/component/filter.js | 17 + src/component/pre.js | 4 + src/component/ws_config.js | 17 +- src/component/ws_connect.js | 306 ++++------ src/component/ws_fetchdata.js | 129 +---- src/component/ws_model.js | 218 +++----- src/component/ws_sql_lite.js | 522 ++++++++++++++++++ src/component/ws_stmt.js | 145 +---- src/component/ws_sync.js | 122 +--- src/docs/config/README.md | 33 +- src/docs/live/README.md | 17 +- src/docs/migrations/README.md | 28 + src/docs/migrations/migrate.js | 18 + src/docs/migrations/migrations/05_06_2024.js | 20 + src/docs/migrations/migrations/06_06_2024.js | 21 + src/docs/migrations/migrations/07_06_2024.js | 25 + src/docs/models/README.md | 4 + src/docs/offline/README.md | 2 - src/docs/query/README.md | 33 +- src/docs/rest_api/Insert_relational_data.md | 60 +- .../app/Models/Events/AddressBeforeInsert.php | 22 - .../app/Models/Events/ObjectAfterInsert.php | 15 - .../app/Models/Events/TableRelation.php | 47 -- .../rest_api/laravel/app/Models/Mysql.php | 51 -- .../rest_api/laravel/app/Models/address.php | 37 -- .../rest_api/laravel/app/Models/objectT.php | 54 -- .../laravel/app/Models/table_relation.sql | 12 - src/docs/sql/README.md | 170 ++++++ src/index.js | 6 +- 38 files changed, 1339 insertions(+), 1069 deletions(-) create mode 100644 src/component/error.js create mode 100644 src/component/filter.js create mode 100644 src/component/pre.js create mode 100644 src/component/ws_sql_lite.js create mode 100644 src/docs/migrations/README.md create mode 100644 src/docs/migrations/migrate.js create mode 100644 src/docs/migrations/migrations/05_06_2024.js create mode 100644 src/docs/migrations/migrations/06_06_2024.js create mode 100644 src/docs/migrations/migrations/07_06_2024.js delete mode 100644 src/docs/offline/README.md delete mode 100644 src/docs/rest_api/laravel/app/Models/Events/AddressBeforeInsert.php delete mode 100644 src/docs/rest_api/laravel/app/Models/Events/ObjectAfterInsert.php delete mode 100644 src/docs/rest_api/laravel/app/Models/Events/TableRelation.php delete mode 100644 src/docs/rest_api/laravel/app/Models/Mysql.php delete mode 100644 src/docs/rest_api/laravel/app/Models/address.php delete mode 100644 src/docs/rest_api/laravel/app/Models/objectT.php delete mode 100644 src/docs/rest_api/laravel/app/Models/table_relation.sql create mode 100644 src/docs/sql/README.md diff --git a/.idea/workspace.xml b/.idea/workspace.xml index af26306..475ec0a 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,10 +4,27 @@ - - @@ -468,6 +520,8 @@ - \ No newline at end of file diff --git a/README.md b/README.md index 006291b..7dbc10e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ npm i project-rest-client [Config](https://github.com/kriit24/project-rest-client/tree/master/src/docs/config) [Live](https://github.com/kriit24/project-rest-client/tree/master/src/docs/live) -[Offline](https://github.com/kriit24/project-rest-client/tree/master/src/docs/offline) +[Offline SQL](https://github.com/kriit24/project-rest-client/tree/master/src/docs/sql) [Models](https://github.com/kriit24/project-rest-client/tree/master/src/docs/models) [Query](https://github.com/kriit24/project-rest-client/tree/master/src/docs/query) [REST api](https://github.com/kriit24/project-rest-client/tree/master/src/docs/rest_api) diff --git a/package-lock.json b/package-lock.json index cc547af..3613a74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,21 @@ { "name": "project-rest-client", - "version": "1.2.6", + "version": "1.2.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "project-rest-client", - "version": "1.2.6", + "version": "1.2.7", "license": "MIT", "dependencies": { "@react-native-community/netinfo": "11.3.1", "crypto-es": "^2.1.0", "expo": "^51.0.2", "expo-file-system": "~17.0.1", - "project-can-json": "^1.0.1" + "expo-sqlite": "^14.0.3", + "project-can-json": "^1.0.1", + "react-native-sse": "^1.2.1" }, "devDependencies": { "@react-native-community/eslint-config": "^3.0.2", @@ -3820,6 +3822,18 @@ "prop-types": "^15.8.1" } }, + "node_modules/@expo/websql": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@expo/websql/-/websql-1.0.1.tgz", + "integrity": "sha512-H9/t1V7XXyKC343FJz/LwaVBfDhs6IqhDtSYWpt8LNSQDVjf5NvVJLc5wp+KCpRidZx8+0+YeHJN45HOXmqjFA==", + "dependencies": { + "argsarray": "^0.0.1", + "immediate": "^3.2.2", + "noop-fn": "^1.0.0", + "pouchdb-collections": "^1.0.1", + "tiny-queue": "^0.2.1" + } + }, "node_modules/@expo/xcpretty": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.3.1.tgz", @@ -6942,6 +6956,11 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/argsarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/argsarray/-/argsarray-0.0.1.tgz", + "integrity": "sha512-u96dg2GcAKtpTrBdDoFIM7PjcBA+6rSP0OR94MOReNRyUECL6MtQt5XXmRr4qrftYaef9+l5hcpO5te7sML1Cg==" + }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", @@ -9796,6 +9815,17 @@ "invariant": "^2.2.4" } }, + "node_modules/expo-sqlite": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-14.0.3.tgz", + "integrity": "sha512-H9+QXpB9ppPFeI5ZIPzIZJAdj4hgP2XJEoNe6xlhSUqcEhiq7k55Hs4mf1LX2r1JgSbIjucMEuDlMT8ntU4Pew==", + "dependencies": { + "@expo/websql": "^1.0.1" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -10730,6 +10760,11 @@ "node": ">=16.x" } }, + "node_modules/immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -15306,6 +15341,11 @@ "url": "https://github.com/sponsors/antelle" } }, + "node_modules/noop-fn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/noop-fn/-/noop-fn-1.0.0.tgz", + "integrity": "sha512-pQ8vODlgXt2e7A3mIbFDlizkr46r75V+BJxVAyat8Jl7YmI513gG5cfyRL0FedKraoZ+VAouI1h4/IWpus5pcQ==" + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -15979,6 +16019,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/pouchdb-collections": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pouchdb-collections/-/pouchdb-collections-1.0.1.tgz", + "integrity": "sha512-31db6JRg4+4D5Yzc2nqsRqsA2oOkZS8DpFav3jf/qVNBxusKa2ClkEIZ2bJNpaDbMfWtnuSq59p6Bn+CipPMdg==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -16485,6 +16530,11 @@ "node": ">= 6" } }, + "node_modules/react-native-sse": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-sse/-/react-native-sse-1.2.1.tgz", + "integrity": "sha512-zejanlScF+IB9tYnbdry0MT34qjBXbiV/E72qGz33W/tX1bx8MXsbB4lxiuPETc9v/008vYZ60yjIstW22VlVg==" + }, "node_modules/react-native/node_modules/@jest/types": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", @@ -17964,6 +18014,11 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/tiny-queue": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tiny-queue/-/tiny-queue-0.2.1.tgz", + "integrity": "sha512-EijGsv7kzd9I9g0ByCl6h42BWNGUZrlCSejfrb3AKeHC33SGbASu1VDf5O3rRiiUOhAC9CHdZxFPbZu0HmR70A==" + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/package.json b/package.json index bff36a3..8f7f35e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "project-rest-client", - "version": "1.2.7", + "version": "1.2.8", "description": "Project DB Rest Api client", "main": "src/index.js", "react-native": "src/index.js", @@ -56,6 +56,8 @@ "crypto-es": "^2.1.0", "expo": "^51.0.2", "expo-file-system": "~17.0.1", - "project-can-json": "^1.0.1" + "expo-sqlite": "^14.0.3", + "project-can-json": "^1.0.1", + "react-native-sse": "^1.2.1" } } diff --git a/src/__test__/app/config.js b/src/__test__/app/config.js index d4d6848..784639f 100644 --- a/src/__test__/app/config.js +++ b/src/__test__/app/config.js @@ -1,9 +1,9 @@ import ProjectRest from '../../index'; -let Wso = new ProjectRest.connect({ - //WSS host for LIVE watch, if not added then live watch not possible - //wss_host: 'ws://80.235.7.34:6001', +let Wso = new ProjectRest.config({ + //live from REST API + live: 'https://haldus.projectpartner.ee/wss.php/live', //fetch from REST API fetch: 'https://haldus.projectpartner.ee/wss.php/fetch', //post to REST API @@ -18,16 +18,24 @@ let Wso = new ProjectRest.connect({ error: 'https://haldus.projectpartner.ee/wss.php/error', //database schema channel: 'haldus_projectpartner_ee', - //REST API auth user - uuid: 'seeonlihtsaltkatsepikkusega30sona', - //REST API auth token + //REST API auth token for live token: 'da01411f889747bfffaf503540c1b8daef8fd4d84c49aa94e0c96270a4d00a3da23de7488aa804248adb19b223b9f4209541f1c257b7502f4083c57f44253e47', - //set data encryption hash key - AES-128-CBC + //REST API auth token + authorization: 'Bearer ' + 'da01411f889747bfffaf503540c1b8daef8fd4d84c49aa94e0c96270a4d00a3da23de7488aa804248adb19b223b9f4209541f1c257b7502f4083c57f44253e47', + //set data encryption hash key - AES-128-CBC - if hash key is null, then no mac will be presented hash_key: null, //offline post (insert/update) request cached data length cache_length: 1000, debug: false }); +/* +ProjectRest.DB.init('haldus_projectpartner_ee', async () => { + + await ProjectRest.DB.query( + "CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY NOT NULL, value TEXT NOT NULL, intValue INTEGER);" + ); +}); +*/ export default Wso; diff --git a/src/__test__/app/model/address.js b/src/__test__/app/model/address.js index 4ac630f..bf4431a 100644 --- a/src/__test__/app/model/address.js +++ b/src/__test__/app/model/address.js @@ -3,10 +3,13 @@ import Wso from "../config"; class Model extends ProjectRest.model{ + fillable = [ + 'address_id', 'address_address' + ]; + constructor(wso) { - super(wso, 'address', 'address_id', 'address_updated_at'); - //this.belongsTo('address'); + super(wso, 'address'); } } diff --git a/src/__test__/app/model/object.js b/src/__test__/app/model/object.js index f940d40..dd19526 100644 --- a/src/__test__/app/model/object.js +++ b/src/__test__/app/model/object.js @@ -3,10 +3,13 @@ import Wso from "../config"; class Model extends ProjectRest.model{ + fillable = [ + 'object_id', 'object_address_id', 'object_name' + ]; + constructor(wso) { - super(wso, 'object', 'object_id', 'object_updated_at'); - this.belongsTo('address', 'object_address_id', 'address_id'); + super(wso, 'object'); } } diff --git a/src/__test__/index.test.js b/src/__test__/index.test.js index 5d81839..2ad3eec 100644 --- a/src/__test__/index.test.js +++ b/src/__test__/index.test.js @@ -1,14 +1,12 @@ import React from 'react'; -import {StyleSheet, View, Text} from 'react-native'; +import {StyleSheet, Text, View} from 'react-native'; import renderer from 'react-test-renderer'; import mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock.js'; +import object from "./app/model/object"; jest.mock('@react-native-community/netinfo', () => mockRNCNetInfo); -import address from "./app/model/address"; -import object from "./app/model/object"; - export default class MainApp extends React.Component { @@ -21,7 +19,7 @@ export default class MainApp extends React.Component { /* object.save({ object_address_id: 5000, - object_name: 'five thousant' + object_name: 'five thousand' }, 5000); object.delete(5000); diff --git a/src/component/error.js b/src/component/error.js new file mode 100644 index 0000000..fdb2e3b --- /dev/null +++ b/src/component/error.js @@ -0,0 +1,24 @@ +import WS_config from "project-rest-client/src/component/ws_config"; +import filter from "project-rest-client/src/component/filter"; + +export default function error(message) { + + if( __DEV__ ){ + + console.error(message); + } + + if (WS_config.conf.error !== undefined) { + + fetch(WS_config.conf.error, { + method: 'POST', + headers: filter({ + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': WS_config.conf.authorization, + 'token': WS_config.conf.token, + }), + body: JSON.stringify(message) + }); + } +} diff --git a/src/component/filter.js b/src/component/filter.js new file mode 100644 index 0000000..76ecc3c --- /dev/null +++ b/src/component/filter.js @@ -0,0 +1,17 @@ +import foreach from "./foreach"; + +export default function filter(data) { + + if(Array.isArray(data)){ + + return data.filter((res) => res !== undefined); + } + + let keys = Object.keys(data); + foreach(keys, (k, key) => { + + if (data[key] === undefined) + delete data[key]; + }); + return data; +} diff --git a/src/component/pre.js b/src/component/pre.js new file mode 100644 index 0000000..d6b1f9c --- /dev/null +++ b/src/component/pre.js @@ -0,0 +1,4 @@ +export default function pre(data){ + + return JSON.stringify(data, null, 2); +} diff --git a/src/component/ws_config.js b/src/component/ws_config.js index 83ab83a..2e18d1e 100644 --- a/src/component/ws_config.js +++ b/src/component/ws_config.js @@ -4,36 +4,31 @@ class WS_config{ static STORAGE_DIR = FileSystem.cacheDirectory + 'ws'; static conf = { - wss_host: undefined, fetch: undefined, + live: undefined, post: undefined, put: undefined, push: undefined, delete: undefined, error: undefined, channel: undefined, - uuid: undefined, + authorization: undefined, token: undefined, cache_length: 1000, hash_key: null, debug: false, }; - static host = undefined; static ws = undefined; - static conn = undefined; - static connIntervalId = undefined; static syncIntervalId = undefined; - static appStateSubscription = undefined; static appStateSubscription_2 = undefined; - static netinfoStateSubscription = undefined; + static appStateSubscription_3 = undefined; static netinfoStateSubscription_2 = undefined; - static connPingPong = null; - static appStateState = 'inactive'; static appStateStat_2 = 'inactive'; - static netinfoState = true; + static appStateStat_3 = 'inactive'; static netinfoState_2 = true; - static sync = {}; static cacheData = {}; + static ess = {}; + static db = undefined; } export default WS_config; diff --git a/src/component/ws_connect.js b/src/component/ws_connect.js index e46f3ff..566c574 100644 --- a/src/component/ws_connect.js +++ b/src/component/ws_connect.js @@ -1,8 +1,12 @@ -import {AppState, DeviceEventEmitter} from "react-native"; +import {AppState} from "react-native"; import NetInfo from "@react-native-community/netinfo"; +import EventSource from "react-native-sse"; import WS_stmt from "./ws_stmt"; import WS_config from "./ws_config"; import canJSON from 'project-can-json'; +import filter from "./filter"; +import error from "./error"; +import pre from "./pre"; class WS_connect extends WS_stmt { @@ -14,144 +18,107 @@ class WS_connect extends WS_stmt { this.setConf(conf); this.channel = conf.channel; + } - if (this.conf.wss_host) { - - let connHost = this.conf.wss_host + '/' + this.conf.channel + '/?uuid=' + this.conf.uuid + '&token=' + this.conf.token; + live(table, callback, all = false) { - //reconnect - if (WS_config.connIntervalId === undefined) { + let url = this.conf['live']; - WS_config.connIntervalId = setInterval(() => { + if (url !== undefined) { - let d = new Date(); + let {column, join, use, where, limit, group, order} = this.getStmt(); + //let offline = this.stmtOffline; + let debug = this.stmtDebug; - //console.log('INTERVAL@' + d.toJSON(), WS_config.conn.readyState); + this.resetStmt(); - WS_config.connPingPong = setTimeout(() => { + NetInfo.fetch().then((state) => { - //console.log('connection is dead'); - clearTimeout(WS_config.connPingPong); - this.reconnect(this.conf); - }, 25000); + let isConnected = (state.isInternetReachable !== undefined ? state.isInternetReachable : state.isConnected); + if (isConnected) { - this.listen('pong', (e) => { + let body = { + 'column': column.length ? column : null, + 'join': join.length ? join : null, + 'use': use.length ? use : null, + 'where': where.length ? where : null, + 'group': group.length ? group : null, + 'order': order.length ? order : null, + 'limit': limit.length && limit[0] !== undefined ? limit[0] : null, + 'offset': limit.length && limit[1] !== undefined ? limit[1] : null, + }; - clearTimeout(WS_config.connPingPong); - }); - WS_config.conn.send(JSON.stringify({'event': 'ping', 'message': {}})); + let request = url + '/' + this.conf.channel + '/' + table + '?full=' + (all ? 'true' : 'false') + '&query=' + JSON.stringify(body); + let mac = null; + if (WS_config.conf.hash_key && !body.hasOwnProperty('raw')) { - }, 30000); - } + mac = this.encrypt(request); + request = request + '&mac=' + mac.mac + '&token=' + this.conf.token; + } else { - //reconnect when appstate changed - if (WS_config.appStateSubscription !== undefined) WS_config.appStateSubscription.remove(); - WS_config.appStateSubscription = AppState.addEventListener('change', nextAppState => { + request = request + '&token=' + this.conf.token; + } - if (WS_config.appStateState.match(/inactive|background/) && nextAppState === 'active') { + if (debug || WS_config.conf.debug) { - WS_config.appStateState = nextAppState; - this.reconnect(this.conf); - } - WS_config.appStateState = nextAppState; - }); + console.log(''); + console.log('----------WS-LIVE----------'); + console.log(pre(request)); + console.log(''); + } - //reconnect when netstate changed - //if (WS_config.netinfoStateSubscription === undefined) { + const es = new EventSource(request, {timeout: 0, timeoutBeforeConnection: 500}); + WS_config.ess[encodeURIComponent(request)] = es; - if (WS_config.netinfoStateSubscription !== undefined) WS_config.netinfoStateSubscription(); - WS_config.netinfoStateSubscription = NetInfo.addEventListener(state => { + es.addEventListener("message", (event) => { - //console.log('NETINFO', state); - let isConnected = state.isInternetReachable !== undefined ? state.isInternetReachable : state.isConnected; - if (isConnected === true && WS_config.netinfoState === false) { + canJSON(event.data, (json) => { - WS_config.netinfoState = isConnected; - this.reconnect(this.conf); - } - if ((isConnected) === false && WS_config.netinfoState === true) { + callback(json.data !== undefined && json.data.length ? json.data : {}); + }, () => { - WS_config.netinfoState = isConnected; - this.kill(); - } - WS_config.netinfoState = isConnected; - }); - //} + if (WS_config.conf.debug) { - if (WS_config.conn === undefined || WS_config.conn.readyState !== WebSocket.OPEN) { + console.error(event.data); + } - //connect - WS_config.conn = new WebSocket(connHost); - //onopen - WS_config.conn.onopen = (e) => { - }; + error({ + 'message': 'LIVE ERROR - RESPONSE IS NOT JSON', + 'url': request, + 'response': event + }); - //onmessage - WS_config.conn.onmessage = (e) => { + reject({}); + }); + }); - let data = JSON.parse(e.data); - /* - console.log(''); - console.log('--------------MESSAGE----------------'); - console.log(e.data); - console.log('-------------------------------------'); - console.log(''); - */ + es.addEventListener("close", (event) => { - if (data.event !== undefined) { + Object.values(WS_config.ess).forEach((ess) => { - DeviceEventEmitter.emit(data.event, data); - } - }; + ess.open(); + }); + }); - //onerror - WS_config.conn.onerror = (e) => { + if (WS_config.appStateSubscription_3 !== undefined) WS_config.appStateSubscription_3.remove(); + WS_config.appStateSubscription_3 = AppState.addEventListener('change', nextAppState => { - //console.log('error'); - }; + if (WS_config.appStateStat_3.match(/inactive|background/) && nextAppState === 'active') { - //onclose - WS_config.conn.onclose = (e) => { + WS_config.appStateStat_3 = nextAppState; + Object.values(WS_config.ess).forEach((ess) => { - //console.log('close'); - }; - } + ess.open(); + }); + } + WS_config.appStateStat_3 = nextAppState; + }); + } + }); } } - reconnect(conf) { - - this.kill(); - this.constructor(conf); - } - - kill() { - - if (WS_config.conn !== undefined) WS_config.conn.close(); - if (WS_config.connIntervalId !== undefined) clearInterval(WS_config.connIntervalId); - if (WS_config.syncIntervalId !== undefined) clearInterval(WS_config.syncIntervalId); - WS_config.connIntervalId = undefined; - WS_config.syncIntervalId = undefined; - WS_config.conn = undefined; - } - - live(){ - - //https://medium.com/@jsbeaudry/implementing-server-sent-events-for-react-native-with-the-chatgpt-3-5-turbo-api-b692a9d86a2f - //https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events - } - - - listen(table, callback) { - - DeviceEventEmitter.removeAllListeners(table); - DeviceEventEmitter.addListener(table, (e) => { - - callback(e.message, e.event); - }); - } - fetch(table) { return new Promise((resolve, reject) => { @@ -199,13 +166,13 @@ class WS_connect extends WS_stmt { console.log(this.conf.fetch + '/' + this.conf.channel + '/' + table); console.log(' ---HEADER---'); console.log( - { + filter({ 'Accept': 'application/json', 'Content-Type': 'application/json', - 'uuid': this.conf.uuid, + 'Authorization': this.conf.authorization, 'token': this.conf.token, 'mac': mac !== null ? mac.mac : null, - } + }) ); console.log(' ---BODY---'); console.log(JSON.stringify(body)); @@ -214,13 +181,13 @@ class WS_connect extends WS_stmt { fetch(url + '/' + this.conf.channel + '/' + table, { method: 'POST', - headers: { + headers: filter({ 'Accept': 'application/json', 'Content-Type': 'application/json', - 'uuid': this.conf.uuid, + 'Authorization': this.conf.authorization, 'token': this.conf.token, 'mac': mac !== null ? mac.mac : null, - }, + }), body: JSON.stringify(body), }) .then(async (response) => { @@ -250,7 +217,7 @@ class WS_connect extends WS_stmt { console.log(''); } - if( response.headers.status !== 200 ){ + if (response.headers.status !== 200) { reject({}); } @@ -264,23 +231,11 @@ class WS_connect extends WS_stmt { console.error(repsonse_text); } - if (WS_config.conf.error !== undefined) { - - fetch(WS_config.conf.error, { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'uuid': this.conf.uuid, - 'token': this.conf.token, - }, - body: JSON.stringify({ - 'message': 'FETCH ERROR - RESPONSE IS NOT JSON', - 'url': url + '/' + this.conf.channel + '/' + table, - 'response': repsonse_text - }) - }); - } + error({ + 'message': 'FETCH ERROR - RESPONSE IS NOT JSON', + 'url': url + '/' + this.conf.channel + '/' + table, + 'response': repsonse_text + }); reject({}); }); @@ -292,23 +247,12 @@ class WS_connect extends WS_stmt { console.error(error); } - if (WS_config.conf.error !== undefined) { - - fetch(WS_config.conf.error, { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'uuid': this.conf.uuid, - 'token': this.conf.token, - }, - body: JSON.stringify({ - 'message': 'FETCH ERROR - SERVER REQUEST FAILED', - 'url': url + '/' + this.conf.channel + '/' + table, - 'response': error - }) - }); - } + error({ + 'message': 'FETCH ERROR - SERVER REQUEST FAILED', + 'url': url + '/' + this.conf.channel + '/' + table, + 'response': error + }); + reject({}); }); } else { @@ -353,13 +297,13 @@ class WS_connect extends WS_stmt { console.log(url + '/' + this.conf.channel + '/' + table); console.log(' ---HEADER---'); console.log( - { + filter({ 'Accept': 'application/json', 'Content-Type': 'application/json', - 'uuid': this.conf.uuid, + 'Authorization': this.conf.authorization, 'token': this.conf.token, 'mac': mac !== null ? mac.mac : null, - } + }) ); console.log(' ---BODY---'); console.log(JSON.stringify(body)); @@ -368,13 +312,13 @@ class WS_connect extends WS_stmt { fetch(url + '/' + this.conf.channel + '/' + table, { method: 'POST', - headers: { + headers: filter({ 'Accept': 'application/json', 'Content-Type': 'application/json', - 'uuid': this.conf.uuid, + 'Authorization': this.conf.authorization, 'token': this.conf.token, 'mac': mac !== null ? mac.mac : null, - }, + }), body: JSON.stringify(body), }) .then(async (response) => { @@ -405,7 +349,7 @@ class WS_connect extends WS_stmt { console.log(''); } - if( response.headers.status !== 200 ){ + if (response.headers.status !== 200) { reject({}); } @@ -428,23 +372,11 @@ class WS_connect extends WS_stmt { console.error(repsonse_text); } - if (WS_config.conf.error !== undefined) { - - fetch(WS_config.conf.error, { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'uuid': this.conf.uuid, - 'token': this.conf.token, - }, - body: JSON.stringify({ - 'message': 'SEND ERROR - RESPONSE IS NOT JSON', - 'url': url + '/' + this.conf.channel + '/' + table, - 'response': repsonse_text - }) - }); - } + error({ + 'message': 'SEND ERROR - RESPONSE IS NOT JSON', + 'url': url + '/' + this.conf.channel + '/' + table, + 'response': repsonse_text + }); reject({'status': 'error', 'response': repsonse_text}); }); @@ -455,23 +387,11 @@ class WS_connect extends WS_stmt { console.error(repsonse_text); } - if (WS_config.conf.error !== undefined) { - - fetch(WS_config.conf.error, { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'uuid': this.conf.uuid, - 'token': this.conf.token, - }, - body: JSON.stringify({ - 'message': 'SEND ERROR - SERVER REQUEST FAILED', - 'url': url + '/' + this.conf.channel + '/' + table, - 'response': error - }) - }); - } + error({ + 'message': 'SEND ERROR - SERVER REQUEST FAILED', + 'url': url + '/' + this.conf.channel + '/' + table, + 'response': error + }); reject({'status': 'error', 'response': error}); }); @@ -487,14 +407,6 @@ class WS_connect extends WS_stmt { }); } - sendWssMessage(message) { - - if (WS_config.conn !== undefined) { - - WS_config.conn.send(JSON.stringify(message)); - } - } - getUpdatedAt(modelData, updated_at_column, type) { let updated_at = 0; diff --git a/src/component/ws_fetchdata.js b/src/component/ws_fetchdata.js index 8b12f9a..6b7903f 100644 --- a/src/component/ws_fetchdata.js +++ b/src/component/ws_fetchdata.js @@ -2,42 +2,11 @@ import WS_stmt from "./ws_stmt"; class WS_fetchdata extends WS_stmt { - constructor(table, primaryKey) { + constructor(table) { super(); this.setTable(table); - this.setPrimaryKey(primaryKey); - } - - mergeData(modelData, row) { - - let primaryKey = this.primaryKey; - if (modelData[row[primaryKey]] === undefined) - modelData[row[primaryKey]] = row; - else { - - modelData[row[primaryKey]] = {...modelData[row[primaryKey]], ...row}; - } - return modelData; - } - - fetchColumns(column, data) { - - if (column.length && data.length) { - - let c = data.length; - for (let i = 0; i < c; i++) { - - let row = data[i]; - let filteredArray = {}; - column.forEach((k) => { - - filteredArray[k] = row[k]; - }); - } - } - return data; } fetch(callback) { @@ -45,36 +14,8 @@ class WS_fetchdata extends WS_stmt { let {column, join, use, where, group, order, limit} = this.getStmt(); this.resetStmt(); - let join_relation = {}; - let stmtUseCallback = this.stmtUseCallback; - if (join.length) { - - join.forEach((v, k) => { - - /* - if (this.belongsStmt[v] !== undefined) { - - join_relation[v] = this.belongsStmt[v]; - } - */ - join_relation[v] = [v, null, null]; - }); - } - - let use_relation = {}; - if (use.length) { - - use.forEach((v, k) => { - - /* - if (this.belongsStmt[v] !== undefined) { - - use_relation[v] = this.belongsStmt[v]; - } - */ - use_relation[v] = [v, null, null]; - }); - } + let join_relation = this.prepareJoin(join); + let use_relation = this.prepareJoin(use); this.ws //.debug() @@ -91,64 +32,22 @@ class WS_fetchdata extends WS_stmt { }) //fetch error .catch(() => { - - this - .fileGetContent(this.ws.channel, this.table) - .then(async (modelData) => { - - if (Object.keys(use_relation).length) { - - let c = Object.keys(use_relation).length; - - for (let i = 0; i < c; i++) { - - let use_k = Object.keys(use_relation)[i]; - if (stmtUseCallback[use_k] !== undefined) { - - let call = stmtUseCallback[use_k](modelData); - if (call instanceof Promise) { - - modelData = await call; - } else { - - console.error('project-rest-client ERROR: use callback must return new Promise'); - } - } - } + }); } - let ret = this.limitData(this.whereFilter(modelData, where), limit[0], limit[1]); - - //console.log('---CACHE DATA---' + this.table, ret); - //console.log('---JOIN-' + this.table, join); - - //join data to row from file - if (ret.length && Object.keys(join_relation).length) { - - Object.values(join_relation).forEach((join_r) => { + live(callback, all = false){ - let relation = join_r[0]; - let foreign = join_r[1]; - let relationPrimary = join_r[2]; - - this - .fileGetContent(this.ws.channel, relation) - .then((modelData_2) => { - - ret.forEach((row_2, key_2) => { + let {column, join, use, where, group, order, limit} = this.getStmt(); + this.resetStmt(); - let foreignData = this.whereFilter(modelData_2, [[relationPrimary, '=', row_2[foreign]]]); - ret[key_2][relation] = foreignData.length ? foreignData[0] : {}; - }); - callback(ret.length ? this.fetchColumns(column, ret) : {}); - }); - }); - } else { + let join_relation = this.prepareJoin(join); + let use_relation = this.prepareJoin(use); - callback(ret.length ? this.fetchColumns(column, ret) : {}); - } - }); - }); + this.ws + //.debug() + //.offline() + .setStmt(column, Object.keys(join_relation), Object.keys(use_relation), where, group, order, limit) + .live(this.table, callback, all); } } diff --git a/src/component/ws_model.js b/src/component/ws_model.js index f057328..332fec9 100644 --- a/src/component/ws_model.js +++ b/src/component/ws_model.js @@ -4,15 +4,14 @@ import WS_stmt from "./ws_stmt"; import WS_fetchdata from "./ws_fetchdata"; import WS_config from "./ws_config"; import foreach from "./foreach"; - -let onPromises = {}; +import DB from "./ws_sql_lite"; class WS_model extends WS_stmt { - belongsStmt = {}; promises = []; + onPromises = []; - constructor(ws, table, primaryKey, updatedAt) { + constructor(ws, table) { super(); @@ -24,21 +23,14 @@ class WS_model extends WS_stmt { this.setWs(ws); this.setTable(table); - this.setPrimaryKey(primaryKey); - this.setUpdatedAt(updatedAt); if (!this.table) { throw new Error('Model table name is empty'); return this; } - if (!this.primaryKey) { - - throw new Error('Primary key is empty'); - return this; - } - onPromises[this.table] = onPromises[this.table] === undefined ? false : onPromises[this.table]; + this.onPromises[this.table] = this.onPromises[this.table] === undefined ? false : this.onPromises[this.table]; (async () => { @@ -54,22 +46,23 @@ class WS_model extends WS_stmt { FileSystem.makeDirectoryAsync(WS_config.STORAGE_DIR + '/' + this.ws.channel, {intermediates: true}); } - let fileInfo = await FileSystem.getInfoAsync(this.file(this.ws.channel, this.table)); - if (!fileInfo.exists) { - - FileSystem.writeAsStringAsync(this.file(this.ws.channel, this.table), JSON.stringify({})); - } - - let model_sync = new sync(this.table, primaryKey, updatedAt); - model_sync.listen(); - model_sync.sync(); + new sync(this.table); })(); } - belongsTo(model, foreignKey, primaryKey) { + SQL(appends) { - this.belongsStmt[model] = [model, foreignKey, primaryKey]; - return this; + let db = new DB(this.table, this.fillable); + + if (appends !== undefined && appends.length) { + + foreach(appends, (k, name) => { + + db[name] = this[name]; + }); + } + + return db; } with(model) { @@ -78,136 +71,73 @@ class WS_model extends WS_stmt { return this; } - clear() { - - this.addPromise(async (resolve, reject) => { - - await FileSystem.writeAsStringAsync(this.file(this.ws.channel, this.table), JSON.stringify({})); - resolve(true); - }, {}); - this.runPromises(); - } - insert(data) { let uniqueId = this.unique_id(this.table); - this.addPromise((resolve, reject, data) => { - - let model_sync = new sync(this.table, this.primaryKey, this.updatedAt); - let primaryKey = this.primaryKey; - - //insert - if (data[primaryKey] === undefined) { + this.#addPromise((resolve, reject, data) => { - model_sync - .setCallback((response) => { + (new sync(this.table)) + .send({'event': 'post', 'model': this.table, 'data': data}); - model_sync.sync(); - }) - .send({'event': 'post', 'model': this.table, 'data': data}); - } resolve(true); }, Object.assign(Object.assign({'data_unique_id': null}, data), {'data_unique_id': uniqueId})); - this.runPromises(); + this.#runPromises(); return uniqueId; } - //.update({set: 1}, primary_id); - //.update({set: 1}, {'key': 'value'}); //.where().update({set: 1}); //.whereRaw().update({set: 1}); - update(data, primary_id) { + update(data) { let d = Object.assign({}, data); - let w = []; - if (primary_id === undefined) { - w = this.stmtWhere; - } else if (typeof primary_id === 'object' && !Array.isArray(primary_id)) { - - foreach(primary_id, function (k, v) { - - w.push([k, '=', v]); - }) - } else { - w.push([this.primaryKey, '=', +primary_id]); - } + let w = this.stmtWhere; - this.addPromise((resolve, reject, data) => { + this.#addPromise((resolve, reject, data) => { - let model_sync = new sync(this.table, this.primaryKey, this.updatedAt); + let model_sync = new sync(this.table); - if (data.where !== undefined && data.where !== null) { + if (data.set !== undefined && data.set !== null && data.where !== undefined && data.where !== null) { - model_sync - .setCallback((response) => { - - model_sync.sync(); - }) - .send({'event': 'put', 'model': this.table, 'data': data}); + model_sync.send({'event': 'put', 'model': this.table, 'data': data}); } resolve(true); }, {'set': d, 'where': w}); - this.runPromises(); + this.#runPromises(); } - upsert(data) { - - this.addPromise((resolve, reject, data) => { - - let model_sync = new sync(this.table, this.primaryKey, this.updatedAt); - let primaryKey = this.primaryKey; + upsert(data, uniqueColumnsWhere) { - if (data[primaryKey] !== undefined) { + let d = Object.assign({}, data); + let w = uniqueColumnsWhere !== undefined ? uniqueColumnsWhere : null; - model_sync - .setCallback((response) => { + this.#addPromise((resolve, reject, data) => { - model_sync.sync(); - }) - .send({'event': 'push', 'model': this.table, 'data': data}); - } + (new sync(this.table)) + .send({'event': 'push', 'model': this.table, 'data': data}); resolve(true); - }, Object.assign({}, data)); - this.runPromises(); + }, {'set': d, 'where': w}); + this.#runPromises(); } - delete(primary_id) { - - this.addPromise((resolve, reject, data) => { - - this - .fileGetContent(this.ws.channel, this.table) - .then(async (modelData) => { - - if (modelData[data.primary_id] !== undefined) { - - delete modelData[data.primary_id]; + //.where().delete(); + //.whereRaw().delete(); + delete() { - //console.log('DELETE', deleteData); - await FileSystem.writeAsStringAsync(this.file(this.ws.channel, this.table), JSON.stringify(modelData)); - } + let w = this.stmtWhere; - let primaryKey = this.primaryKey; - let deleteData = {}; - deleteData[primaryKey] = data.primary_id; + this.#addPromise((resolve, reject, data) => { - let model_sync = new sync(this.table, this.primaryKey, this.updatedAt); - model_sync - .setCallback((response) => { - - model_sync.sync(); - }) - .send({'event': 'delete', 'model': this.table, 'data': deleteData}); + let model_sync = new sync(this.table); + model_sync.send({'event': 'delete', 'model': this.table, 'data': data}); resolve(true); - }); - }, Object.assign({}, {primary_id: primary_id})); - this.runPromises(); + }, {'where': w}); + this.#runPromises(); } fetchAll(callback) { @@ -215,14 +145,13 @@ class WS_model extends WS_stmt { let {column, join, use, where, group, order, limit} = this.getStmt(); this.resetStmt(); - this.addPromise((resolve, reject, data) => { + this.#addPromise((resolve, reject, data) => { let {column, join, use, where, group, order, limit} = data; this.setStmt(column, join, use, where, group, order, limit); - new WS_fetchdata(this.table, this.primaryKey) + new WS_fetchdata(this.table) .setStmt(column, join, use, where, group, order, limit) - .setBelongsStmt(this.belongsStmt) .setUseCallbackStmt(this.stmtUseCallback) .fetch((rows) => { @@ -230,7 +159,7 @@ class WS_model extends WS_stmt { resolve(true); }); }, Object.assign({}, {column: column, join: join, use: use, where: where, limit: limit, group: group, order: order})); - this.runPromises(); + this.#runPromises(); } fetch(callback) { @@ -238,14 +167,13 @@ class WS_model extends WS_stmt { let {column, join, use, where, group, order, limit} = this.getStmt(); this.resetStmt(); - this.addPromise((resolve, reject, data) => { + this.#addPromise((resolve, reject, data) => { let {column, join, use, where, group, order, limit} = data; this.setStmt(column, join, use, where, group, order, limit); - new WS_fetchdata(this.table, this.primaryKey) + new WS_fetchdata(this.table) .setStmt(column, join, use, where, group, order, limit) - .setBelongsStmt(this.belongsStmt) .setUseCallbackStmt(this.stmtUseCallback) .fetch((rows) => { @@ -253,10 +181,42 @@ class WS_model extends WS_stmt { resolve(true); }); }, Object.assign({}, {column: column, join: join, use: use, where: where, limit: limit, group: group, order: order})); - this.runPromises(); + this.#runPromises(); + } + + live(callback, all = false) { + + let {column, join, use, where, group, order, limit} = this.getStmt(); + this.resetStmt(); + + this.#addPromise((resolve, reject, data) => { + + let {column, join, use, where, group, order, limit, all} = data; + this.setStmt(column, join, use, where, group, order, limit); + + new WS_fetchdata(this.table) + .setStmt(column, join, use, where, group, order, limit) + .setUseCallbackStmt(this.stmtUseCallback) + .live((rows) => { + + callback(rows.length ? rows : {}); + resolve(true); + }, all); + }, Object.assign({}, {column: column, join: join, use: use, where: where, limit: limit, group: group, order: order, all: all})); + this.#runPromises(); + } + + liveAll(callback) { + + this.live(callback, true); + } + + reset(){ + + (new sync(this.table)).reset(); } - addPromise(callback, args) { + #addPromise(callback, args) { this.promises.push( function (callback, args) { @@ -268,19 +228,19 @@ class WS_model extends WS_stmt { ); } - runPromises() { + #runPromises() { - if (this.promises.length && !onPromises[this.table]) { + if (this.promises.length && !this.onPromises[this.table]) { - onPromises[this.table] = true; + this.onPromises[this.table] = true; let promise = this.promises.shift(); promise().then((res) => { setTimeout(() => { - onPromises[this.table] = false; + this.onPromises[this.table] = false; if (this.promises.length) - this.runPromises(); + this.#runPromises(); }, 10); return res; }); diff --git a/src/component/ws_sql_lite.js b/src/component/ws_sql_lite.js new file mode 100644 index 0000000..07cb659 --- /dev/null +++ b/src/component/ws_sql_lite.js @@ -0,0 +1,522 @@ +import * as SQLite from 'expo-sqlite'; +import WS_config from "./ws_config"; +import WS_model from "./ws_model"; +import error from "./error"; +import pre from "./pre"; + +class DB { + + #db; + #table; + #fillable; + #action; + #sync = {}; + + params = {}; + param_values = {}; + show_debug = false; + #result; + + constructor(table, fillable) { + + if (table) { + + this.#table = table; + this.#fillable = fillable; + + if (fillable === undefined || !fillable.length) + console.error(`TABLE ${table} has no fillable properties`); + } + } + + async init(name, callback) { + + this.#db = name; + WS_config.db = await SQLite.openDatabaseAsync(name); + callback(); + } + + async reset(){ + + await WS_config.db.closeAsync(); + await SQLite.deleteDatabaseAsync(this.#db); + WS_config.db = await SQLite.openDatabaseAsync(this.#db); + return new Promise(async (resolve, reject) => resolve('true')); + } + + raw(value) { + + return {'raw': value}; + } + + query(query, params) { + + return new Promise(async (resolve, reject) => { + + let value = null; + let statement, result = null; + + try { + + statement = await WS_config.db.prepareAsync(query); + result = await statement.executeAsync(params); + value = await result.getAllAsync(); + + } catch (e) { + + error({ + 'message': 'SQLITE QUERY ERROR', + 'sql': sql, + 'error': e, + }); + } finally { + await statement.finalizeAsync(); + } + + resolve(value); + }); + } + + select(column = '*') { + + this.params['SELECT'] = "SELECT " + (Array.isArray(column) ? column.join(',') : column); + this.from(this.#table); + return this; + } + + from(table) { + + this.params['FROM'] = table; + return this; + } + + join(value) { + + this.params['JOIN'] = this.raw("INNER JOIN " + value); + return this; + } + + leftJoin(value) { + + this.params['JOIN'] = this.raw("LEFT JOIN " + value); + return this; + } + + when(condition, callback) { + + if (condition) + callback(this); + + return this; + } + + where(column, operator, value) { + + if (typeof column == 'object' && !Array.isArray(column) && operator == undefined && value == undefined) { + + let columns = Object.keys(column); + columns.forEach((col) => { + + this.where(col, '=', column[col]); + }); + return this; + } + + let column_temp = column.replace('.', '_'); + + //regular + if (value === undefined) { + + value = operator; + operator = '='; + } + + if (this.params['WHERE'] === undefined) + this.params['WHERE'] = [`${column} ${operator} $where_${column_temp}`]; + else + this.params['WHERE'].push(`${column} ${operator} $where_${column_temp}`); + + if (this.#sync['WHERE'] === undefined) + this.#sync['WHERE'] = [[column, operator, value]]; + else + this.#sync['WHERE'].push([column, operator, value]); + + this.param_values['$where_' + column_temp] = value; + return this; + } + + whereRaw(where) { + + if (this.params['WHERE'] === undefined) + this.params['WHERE'] = [`${where}`]; + else + this.params['WHERE'].push(`${where}`); + + if (this.#sync['WHERE_RAW'] === undefined) + this.#sync['WHERE_RAW'] = [where]; + else + this.#sync['WHERE_RAW'].push(where); + + return this; + } + + order(column, order) { + + if (this.params['ORDER BY'] === undefined) + this.params['ORDER BY'] = []; + this.params['ORDER BY'].push([`${column} ${order}`]); + + return this; + } + + group(value) { + + if (this.params['GROUP BY'] === undefined) + this.params['GROUP BY'] = []; + this.params['GROUP BY'].push([`${value}`]); + + return this; + } + + limit(limit, offset = 0) { + + this.params['LIMIT'] = `${offset}, ${limit}`; + return this; + } + + fetchAll(callback) { + + (async () => { + + let value = []; + this.exec(this.param_values, (result) => value = result.getAllSync()); + callback(value); + })(); + } + + fetch(callback) { + + (async () => { + + if (this.params['LIMIT'] === undefined) + this.limit(1); + + let value = []; + this.exec(this.param_values, (result) => value = result.getFirstSync()); + callback(value); + })(); + } + + fetchEach(callback) { + + (async () => { + + let sql = this.build(); + let statement = null; + + try { + + statement = WS_config.db.getEachAsync(sql, this.param_values); + for await (let row of statement) { + + callback(row); + } + } catch (e) { + + error({ + 'message': 'SQLITE ERROR', + 'sql': sql, + 'error': e, + }); + } + })(); + } + + insert(data) { + + this.#action = 'insert'; + this.#sync['VALUES'] = data; + this.params = {}; + this.param_values = {}; + this.params['INSERT'] = this.raw( + "INSERT INTO `" + this.#table + + "` (" + this.#fillable.map((col) => { + + return data[col] !== undefined ? col : undefined; + }).filter((val) => val != undefined) + ") " + + "VALUES " + + "(" + this.#fillable.map((col) => { + + return data[col] !== undefined ? `$insert_${col}` : undefined; + }).filter((val) => val != undefined) + ")" + ); + + this.#fillable.map((col) => { + + if (data[col] !== undefined) + this.param_values[`$insert_${col}`] = data[col]; + }); + + if (this.show_debug) { + + this.debug(); + } + this.#result = this.exec(this.param_values); + + return this; + } + + update(data) { + + this.#action = 'update'; + this.#sync['SET'] = data; + if (this.params['WHERE'] === undefined) { + + this.#action = null; + console.error('Missing delete WHERE statement'); + return this; + } + + this.params['UPDATE'] = "`" + this.#table + "`"; + this.params['SET'] = this.#fillable.map((col) => { + + return data[col] !== undefined ? `${col} = $set_${col}` : undefined; + }).filter((val) => val != undefined); + + this.#fillable.forEach((col) => { + + if (data[col] !== undefined) + this.param_values[`$set_${col}`] = data[col]; + }); + + if (this.show_debug) { + + this.debug(); + } + + this.#result = this.exec(this.param_values); + + return this; + } + + upsert(data, uniqueColumnsWhere) { + + this.#action = 'upsert'; + this.#sync['VALUES'] = data; + //INSERT OR IGNORE + let insert_data = {...data, ...(uniqueColumnsWhere !== undefined ? uniqueColumnsWhere : {})}; + this.params = {}; + this.param_values = {}; + this.params['INSERT'] = this.raw( + "INSERT OR IGNORE INTO `" + this.#table + + "` (" + this.#fillable.map((col) => { + + return insert_data[col] !== undefined ? col : undefined; + }).filter((val) => val != undefined) + ") " + + "VALUES " + + "(" + this.#fillable.map((col) => { + + return insert_data[col] !== undefined ? `$insert_${col}` : undefined; + }).filter((val) => val != undefined) + ")" + + ";" + ); + + this.#fillable.map((col) => { + + if (insert_data[col] !== undefined) + this.param_values[`$insert_${col}`] = insert_data[col]; + }); + + if (this.show_debug) { + + this.debug(); + } + + let result = this.exec(this.param_values); + + if (result.changes !== undefined && result.changes) { + + //register where for sync + this.where(uniqueColumnsWhere); + delete this.params['WHERE']; + + this.#result = result; + + return this; + } + + //UPDATE + this.params = {}; + this.param_values = {}; + this.params['UPDATE'] = "`" + this.#table + "`"; + this.params['SET'] = this.#fillable.map((col) => { + + return data[col] !== undefined ? `${col} = $set_${col}` : undefined; + }).filter((val) => val != undefined); + this.where(uniqueColumnsWhere); + + this.#fillable.map((col) => { + + if (data[col] !== undefined) + this.param_values[`$set_${col}`] = data[col]; + }); + + if (this.show_debug) { + + this.debug(); + } + + this.#result = this.exec(this.param_values); + + return this; + } + + delete() { + + this.#action = 'delete'; + if (this.params['WHERE'] === undefined) { + + this.#action = null; + console.error('Missing delete WHERE statement'); + return this; + } + + this.params['DELETE'] = "FROM `" + this.#table + "`"; + if (this.show_debug) { + + this.debug(); + } + + this.#result = this.exec(this.param_values); + + return this; + } + + sync() { + + if (!this.#action) { + + console.error('Missing action, ensure that sync is after the fetch, insert, update, upsert or delete methods'); + return null; + } + + let action = this.#action; + + let model = new WS_model(WS_config.ws, this.#table); + + if (this.#sync && Object.values(this.#sync).length) { + + if (this.#sync['WHERE'] !== undefined) { + + this.#sync['WHERE'].map((where) => model.where(where[0], where[1], where[2])); + } + if (this.#sync['WHERE_RAW'] !== undefined) { + + this.#sync['WHERE_RAW'].map((where) => model.whereRaw(where[0])); + } + + if (action == 'insert') { + + model.insert(this.#sync['VALUES']); + } + if (action == 'update') { + + model.update(this.#sync['SET']); + } + if (action == 'upsert') { + + model.upsert(this.#sync['VALUES'], this.#sync['WHERE']); + } + if (action == 'delete') { + + model.delete(); + } + } + + return this; + } + + result() { + + return this.#result; + } + + debug() { + + this.show_debug = true; + + if (Object.values(this.params).length) { + + let sql = this.build(); + console.log('SQL DEBUG'); + console.log(pre(sql)); + console.log(pre(this.param_values)); + console.log('/SQL DEBUG'); + } + + return this; + } + + build() { + + let sql = []; + + this.implode(sql, 'SELECT'); + this.implode(sql, 'INSERT'); + this.implode(sql, 'UPDATE'); + this.implode(sql, 'DELETE'); + this.implode(sql, 'SET', ','); + this.implode(sql, 'FROM'); + this.implode(sql, 'JOIN'); + this.implode(sql, 'WHERE', ' AND '); + this.implode(sql, 'ORDER BY', ','); + this.implode(sql, 'GROUP BY', ','); + this.implode(sql, 'LIMIT'); + + return sql.join(' ').trim(' '); + } + + exec(values, callback){ + + let sql = this.build(); + let statement, result = null; + + try { + + statement = WS_config.db.prepareSync(sql); + result = statement.executeSync(values); + if( callback !== undefined ) callback(result); + } catch (e) { + + error({ + 'message': 'SQLITE ERROR', + 'sql': sql, + 'error': e, + }); + } finally { + statement.finalizeSync(); + } + + return result; + } + + implode(sql, name, sub_glue = '') { + + if (this.params[name] !== undefined) { + + let value = this.params[name]; + if (typeof value === 'object' && !Array.isArray(value) && value !== null && value.raw !== undefined) { + + sql.push(value.raw); + } else { + + sql.push((['SELECT'].indexOf(name) === -1 ? name : '') + ' ' + (Array.isArray(value) ? value.join(sub_glue) : value)); + } + sql = sql.filter(function (el) { + return el != null && el !== undefined; + }); + } + } +} + +export default DB; diff --git a/src/component/ws_stmt.js b/src/component/ws_stmt.js index 1f1dd5f..f4da84f 100644 --- a/src/component/ws_stmt.js +++ b/src/component/ws_stmt.js @@ -1,15 +1,11 @@ import WS_config from "./ws_config"; -import * as FileSystem from "expo-file-system"; import Ws_crypto from "./ws_crypto"; -import canJSON from "project-can-json"; import Base64 from "./base64"; import unique_id from "./unique_id"; class WS_stmt { table = undefined; - primaryKey = undefined; - updatedAt = undefined; stmtColumn = []; stmtJoin = []; stmtUse = []; @@ -20,7 +16,7 @@ class WS_stmt { stmtDebug = false; stmtOffline = false; stmtData = null; - stmtCallback = null; + stmtCallback = undefined; setConf(conf) { @@ -56,18 +52,6 @@ class WS_stmt { return this; } - setPrimaryKey(primaryKey) { - - this.primaryKey = primaryKey; - return this; - } - - setUpdatedAt(updatedAt) { - - this.updatedAt = updatedAt; - return this; - } - setCallback(callback) { this.stmtCallback = callback; @@ -85,6 +69,19 @@ class WS_stmt { return Base64.btoa(prefix + Math.floor(new Date().getTime() / 1000) + unique_id()); } + prepareJoin(data){ + + let ret = {}; + if (data.length) { + + data.forEach((v, k) => { + + ret[v] = [v, null, null]; + }); + } + return ret; + } + debug() { this.stmtDebug = true; @@ -97,43 +94,6 @@ class WS_stmt { return this; } - file(channel, table) { - - if (channel === undefined) { - - console.error('CHANNEL IS MISSING'); - } - - let dir = WS_config.STORAGE_DIR + '/' + channel; - let file = table + '_model.json'; - return dir + '/' + file; - } - - fileGetContent(channel, table) { - - return new Promise(async (resolve, reject) => { - - let file = this.file(channel, table); - let dirInfo = await FileSystem.getInfoAsync(file); - if (dirInfo.exists) { - - canJSON( - await FileSystem.readAsStringAsync(file), - (modelData) => { - - resolve(modelData); - }, - () => { - - resolve({}); - }); - } else { - - resolve({}); - } - }); - } - setData(data) { this.stmtData = data; @@ -222,12 +182,6 @@ class WS_stmt { this.stmtOffline = false; } - setBelongsStmt(belongs) { - - this.belongsStmt = belongs; - return this; - } - setUseCallbackStmt(stmtUseCallback) { this.stmtUseCallback = stmtUseCallback; @@ -287,41 +241,22 @@ class WS_stmt { return this; } - whereRaw(column) { + whereIn(column, value){ - this.stmtWhere.push([column, "raw", null]); + this.where(column, 'in', value); return this; } - whereFilter(haystack, where) { - - return Object.values(haystack).filter((row) => { + whereNotIn(column, value){ - let exists = 0; - for (stmtWhere of where) { - - let column = stmtWhere[0]; - let operator = stmtWhere.length === 3 ? stmtWhere[1] : '='; - let value = stmtWhere.length === 3 ? stmtWhere[2] : stmtWhere[1]; - if (row[column] !== undefined) { - - if (operator === 'in_array') { - - if (value.indexOf(row[column]) !== -1) - exists += 1; - } else if (operator === '=' && typeof (row[column]) == 'string') { + this.where(column, 'not_in', value); + return this; + } - if (row[column].toLowerCase() === value.toLowerCase()) - exists += 1; - } else { + whereRaw(value) { - if (row[column] === value) - exists += 1; - } - } - } - return exists === Object.keys(where).length ? row : null; - }, where); + this.stmtWhere.push([value, "raw", null]); + return this; } group(group) { @@ -341,40 +276,6 @@ class WS_stmt { this.stmtLimit = [limit, offset]; return this; } - - limitData(rows, limit, offset) { - - if (limit === undefined && offset === undefined) { - - return rows; - } - if (offset === undefined) { - - offset = 0; - } - - let i = 0; - let ret = []; - rows.every((v, k) => { - - if (i >= offset) { - - if (i < limit) { - - ret.push(v); - i++; - return true; - } else { - - return false; - } - } - - i++; - return true; - }); - return ret; - } } export default WS_stmt; diff --git a/src/component/ws_sync.js b/src/component/ws_sync.js index e21b785..73c0441 100644 --- a/src/component/ws_sync.js +++ b/src/component/ws_sync.js @@ -1,7 +1,6 @@ import * as FileSystem from "expo-file-system"; import {AppState} from "react-native"; import WS_stmt from "./ws_stmt"; -import WS_fetchdata from "./ws_fetchdata"; import WS_config from "./ws_config"; import NetInfo from "@react-native-community/netinfo"; import canJSON from "project-can-json"; @@ -11,24 +10,12 @@ class sync extends WS_stmt { cacheFile = null; - constructor(table, primaryKey, updatedAt) { + constructor(table) { super(); this.setTable(table); - this.setPrimaryKey(primaryKey); - this.setUpdatedAt(updatedAt); this.cacheFile = WS_config.STORAGE_DIR + '/' + this.ws.channel + '/ws_cache.json'; - WS_config.sync[table] = {table: table, primaryKey: primaryKey, updatedAt: updatedAt}; - - let reSync = () => { - - Object.values(WS_config.sync).forEach((sync_table) => { - - new sync(sync_table.table, sync_table.primaryKey, sync_table.updatedAt) - .sync(); - }); - }; (async () => { @@ -87,101 +74,6 @@ class sync extends WS_stmt { } } - sync() { - - //console.log('---SYNC---', this.ws.channel + '@' + this.table); - - if (this.updatedAt === undefined) - return this; - - this - .fileGetContent(this.ws.channel, this.table) - .then((modelData) => { - - let updated_at = this.ws.getUpdatedAt(modelData, this.updatedAt, 'ASC'); - let {column, join, use, where} = this.getStmt(); - this.resetStmt(); - - if (updated_at) { - - this.ws - .setStmt(column, join, use, where) - .where(this.updatedAt, '>', updated_at) - .order(this.updatedAt, "desc") - //.debug() - .fetch(this.table) - .then((data) => { - - //console.log('SYNC-1 "' + this.table + '"', Object.values(data.data).length); - this.syncData(data.data); - }) - .catch(() => { - }); - } else { - - this.ws - .setStmt(column, join, use, where) - .order(this.updatedAt, "desc") - //.debug() - .fetch(this.table) - .then((data) => { - - //console.log('SYNC-2 "' + this.table + '"', Object.values(data.data).length); - this.syncData(data.data); - }) - .catch(() => { - }); - } - }); - } - - syncData(rows) { - - if (rows.length) { - - this - .fileGetContent(this.ws.channel, this.table) - .then((modelData) => { - - let primaryKey = this.primaryKey; - //console.log('---SYNC-TABLE-' + this.table, rows.length); - - rows.forEach((row) => { - - if (this.stmtDebug) { - - console.log('SYNC-TABLE-' + this.table, row); - console.log('SYNC-TABLE-' + this.table, JSON.stringify(row, null, 2)); - console.log('SYNC-TABLE-' + this.table, row.trigger + '----' + primaryKey + '-----' + row[primaryKey]); - } - - if ((row.trigger !== undefined && ['insert', 'update', 'upsert', 'fetch'].indexOf(row.trigger) !== -1) || row.trigger === undefined) { - - modelData = new WS_fetchdata(this.table, this.primaryKey) - .mergeData(modelData, row); - } - if (row.trigger !== undefined && ['delete'].indexOf(row.trigger) !== -1) { - - if (row[primaryKey] !== undefined && modelData[row[primaryKey]] !== undefined) { - - delete modelData[row[primaryKey]]; - } - } - }); - - FileSystem.writeAsStringAsync(this.file(this.ws.channel, this.table), JSON.stringify(modelData)); - }); - } - } - - listen() { - - this.ws.listen(this.table, (e, table) => { - - this.syncData([e]); - }); - } - send(row) { callback(function (row) { @@ -189,18 +81,13 @@ class sync extends WS_stmt { this.ws //.offline() .setData(row) - .setCallback(this.getCallback()) .send(row.event, row.model, row.data) //if everything is ok then do nothing .then((response) => { - - let callback = this.ws.getCallback(); - callback(response); }) //if something is wrong then store data .catch(async (error) => { - let callback = this.ws.getCallback(); let tmp = row; let date = new Date(); @@ -220,11 +107,16 @@ class sync extends WS_stmt { await FileSystem.writeAsStringAsync(this.cacheFile, JSON.stringify(cacheData)); } } - callback(error); }); }, this, row); } + reset() { + + WS_config.cacheData = {}; + FileSystem.writeAsStringAsync(this.cacheFile, JSON.stringify({})); + } + async cacheSend() { //await FileSystem.writeAsStringAsync(this.cacheFile, JSON.stringify({})); diff --git a/src/docs/config/README.md b/src/docs/config/README.md index bc9ca51..c54d9a7 100644 --- a/src/docs/config/README.md +++ b/src/docs/config/README.md @@ -5,31 +5,42 @@ create configuration file import ProjectRest from "project-rest-client"; -let Wso = new ProjectRest.connect({ - //WSS host for LIVE watch, if not added then live watch not possible - //wss_host: 'ws://80.235.7.34:6001', +let Wso = new ProjectRest.config({ //fetch from REST API - fetch: 'https://haldus.projectpartner.ee/wss.php/fetch', + fetch: 'https://laravel.api.com/api/fetch', + //live from REST API + live: 'https://laravel.api.com/api/live', //post to REST API - post: 'https://haldus.projectpartner.ee/wss.php/post_server', + post: 'https://laravel.api.com/api/post', + //put to REST API + put: 'https://laravel.api.com/api/put', + //put to REST API + push: 'https://laravel.api.com/api/push', //delete to REST API - delete: 'https://haldus.projectpartner.ee/wss.php/delete_server', + delete: 'https://laravel.api.com/api/delete', //error to REST API - error: 'https://haldus.projectpartner.ee/wss.php/error', + error: 'https://laravel.api.com/api/error', //database schema - channel: 'haldus_projectpartner_ee', + channel: channel, //REST API auth user - uuid: 'seeonlihtsaltkatsepikkusega30sona', + uuid: config.get.uuid(), //REST API auth token - token: '', + token: config.get.token(), //set data encryption hash key - AES-128-CBC - hash_key: '', + hash_key: config.get.mac_key(), //offline post (insert/update) request cached data length cache_length: 1000, //debug debug: __DEV__ }); +ProjectRest.DB.init(channel, async () => { + + await ProjectRest.DB.query( + "CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY NOT NULL, value TEXT NOT NULL, intValue INTEGER);" + ); +}); + export default Wso; ``` diff --git a/src/docs/live/README.md b/src/docs/live/README.md index ecb2cdb..d299588 100644 --- a/src/docs/live/README.md +++ b/src/docs/live/README.md @@ -1,30 +1,23 @@ ### live ``` -//watch one object changes +//watch object changes object .select()//this resets all previous objects .where('object_id', 5000) .live((row) => { console.log('OBJECT-5000'); - console.log(JSON.stringify(row, null, 2)); + console.log(row); }); //watch all object changes -let object_rows = []; object .select()//this resets all previous objects -.live((rows) => { +.liveAll((rows) => { - console.log('OBJECT-5000'); - console.log(JSON.stringify(rows, null, 2)); - - //watch only updates - object_rows = message.merge(object_rows, rows, true); - console.log('OBJECT-UPDATES'); - console.log(JSON.stringify(object_rows, null, 2)); -}); + console.log(rows); +}); ``` #### live requests uses SELECT QUERYING diff --git a/src/docs/migrations/README.md b/src/docs/migrations/README.md new file mode 100644 index 0000000..963d7cb --- /dev/null +++ b/src/docs/migrations/README.md @@ -0,0 +1,28 @@ +### SQLLite migrations + +PUT MIGRATIONS inside init + +``` +ProjectRest.DB.init(channel, async () => { + + //delete database + //ProjectRest.DB.reset(); + + let migrate = require('../database/migrate').default; + migrate(); + + /* + await ProjectRest.DB.query( + "INSERT INTO test (value, intValue) VALUES (?, ?)", ['test1', 123] + ); + + await ProjectRest.DB.query( + "INSERT INTO test (value, intValue) VALUES (?, ?)", ['test2', 456] + ); + + await ProjectRest.DB.query( + "INSERT INTO test (value, intValue) VALUES (?, ?)", ['test3', 789] + ); + */ +}); +``` diff --git a/src/docs/migrations/migrate.js b/src/docs/migrations/migrate.js new file mode 100644 index 0000000..d5acbff --- /dev/null +++ b/src/docs/migrations/migrate.js @@ -0,0 +1,18 @@ +function run(required){ + + return required(); +} + +function migrate(){ + + return new Promise(async (resolve, reject) => { + + await run(require('./migrations/05_06_2024').default); + await run(require('./migrations/06_06_2024').default); + await run(require('./migrations/07_06_2024').default); + + resolve('done'); + }); +} + +export default migrate; diff --git a/src/docs/migrations/migrations/05_06_2024.js b/src/docs/migrations/migrations/05_06_2024.js new file mode 100644 index 0000000..d5c35d6 --- /dev/null +++ b/src/docs/migrations/migrations/05_06_2024.js @@ -0,0 +1,20 @@ +import ProjectRest from "project-rest-client"; + +function sync(){ + + return new Promise(async (resolve, reject) => { + + await ProjectRest.DB.query( + "CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY NOT NULL, value TEXT NOT NULL, intValue INTEGER);" + ); + + + await ProjectRest.DB.query( + "CREATE INDEX IF NOT EXISTS test_value_index ON test(value);" + ); + + resolve('done'); + }); +} + +export default sync; diff --git a/src/docs/migrations/migrations/06_06_2024.js b/src/docs/migrations/migrations/06_06_2024.js new file mode 100644 index 0000000..818c776 --- /dev/null +++ b/src/docs/migrations/migrations/06_06_2024.js @@ -0,0 +1,21 @@ +import ProjectRest from "project-rest-client"; + +function sync(){ + + return new Promise(async (resolve, reject) => { + + let res = await ProjectRest.DB.query( + "select count(1) AS c from pragma_table_info('test') where name='name'" + ); + if( res[0]['c'] === 0 ){ + + await ProjectRest.DB.query( + "ALTER TABLE `test` ADD COLUMN `name` TEXT;" + ); + } + + resolve('done'); + }); +} + +export default sync; diff --git a/src/docs/migrations/migrations/07_06_2024.js b/src/docs/migrations/migrations/07_06_2024.js new file mode 100644 index 0000000..b19f906 --- /dev/null +++ b/src/docs/migrations/migrations/07_06_2024.js @@ -0,0 +1,25 @@ +import ProjectRest from "project-rest-client"; + +function sync(){ + + return new Promise(async (resolve, reject) => { + + await ProjectRest.DB.query( + "DROP INDEX IF EXISTS test_value_unique_index;" + ); + + /* + await ProjectRest.DB.query( + "DROP INDEX IF EXISTS test_value_index;" + ); + + await ProjectRest.DB.query( + "CREATE UNIQUE INDEX IF NOT EXISTS test_value_unique_index ON test(value);" + ); + */ + + resolve('done'); + }); +} + +export default sync; diff --git a/src/docs/models/README.md b/src/docs/models/README.md index 31fcebc..3f67545 100644 --- a/src/docs/models/README.md +++ b/src/docs/models/README.md @@ -7,6 +7,10 @@ import Wso from "../config"; class Model extends ProjectRest.model{ + fillable = [ + 'id', 'value', 'name' + ]; + constructor(wso) { super(wso, 'object', 'object_id'); diff --git a/src/docs/offline/README.md b/src/docs/offline/README.md deleted file mode 100644 index 5bf5ca4..0000000 --- a/src/docs/offline/README.md +++ /dev/null @@ -1,2 +0,0 @@ -### offline -coming soon diff --git a/src/docs/query/README.md b/src/docs/query/README.md index 9f06fff..1a90e33 100644 --- a/src/docs/query/README.md +++ b/src/docs/query/README.md @@ -48,6 +48,25 @@ object ``` +WHERE + +``` +//normal where statement +.where('object_id', '=', 1) + +//without operator +.where('object_id', 1) + +//where in +.whereIn('object_id', [1]) + +//where not in +.whereNotIn('object_id', [1]) + +//where raw +.whereRaw("object_id IN(1)") +``` + INSERT ``` @@ -84,11 +103,19 @@ object.where({'object_id': 5000}).update({ UPSERT - insert or update (unique id required) ``` -object.upsert({ - object_id: 5000, +object.upsert( +//update, insert data +//on update it will update only this data +//on insert it will merge data and unique objects +{ object_address_id: 5000, object_name: 'five thousand' -}); +}, +//unique id +{ + object_id: 5000, +} +); ``` DELETE diff --git a/src/docs/rest_api/Insert_relational_data.md b/src/docs/rest_api/Insert_relational_data.md index 3651418..5038a0f 100644 --- a/src/docs/rest_api/Insert_relational_data.md +++ b/src/docs/rest_api/Insert_relational_data.md @@ -1,63 +1,5 @@ ### INSERT RELATIONAL DATA -setup relational parent table - -``` -//App\Models\objectT.php - set relation after inserted -protected $dispatchesEvents = [ - 'inserted' => ObjectAfterInsert::class, -]; - -//App\Models\Event\ObjectAfterInsert.php - call relation - -declare(strict_types=1); - -namespace App\Models\Events; - -use App\Models\objectT; - -class ObjectAfterInsert extends objectT -{ - public function __construct($bindings, $tableData) - { - new TableRelation($this->getTable(), $this->getKeyName(), $bindings, $tableData); - } -} - -``` - -setup relational child table - -``` -//App\Models\address.php - set relation before insert - protected $dispatchesEvents = [ - 'inserting' => AddressBeforeInsert::class, -]; - -//App\Models\Events\AddressBeforeInsert.php - get relation id -declare(strict_types=1); - -namespace App\Models\Events; - -class AddressBeforeInsert -{ - public function __construct(&$bindings) - { - if (isset($bindings['table_relation_unique_id'])) { - - $relation = TableRelation::fetch($bindings['table_relation_unique_id']); - if( !empty($relation) ) { - - //die(pre($relation)); - $bindings['image_table'] = $relation->table_relation_table_name; - $bindings['image_table_id'] = $relation->table_relation_table_id; - } - } - } -} - -``` - use it on react native app ``` @@ -72,5 +14,5 @@ address.insert({ ``` -## Example in laravel folder +## SERVER SIDE example [click me](https://github.com/kriit24/project-rest-client/tree/master/src/docs/rest_api/laravel) diff --git a/src/docs/rest_api/laravel/app/Models/Events/AddressBeforeInsert.php b/src/docs/rest_api/laravel/app/Models/Events/AddressBeforeInsert.php deleted file mode 100644 index cc446d4..0000000 --- a/src/docs/rest_api/laravel/app/Models/Events/AddressBeforeInsert.php +++ /dev/null @@ -1,22 +0,0 @@ -table_relation_table_name; - $bindings['image_table_id'] = $relation->table_relation_table_id; - } - } - } -} diff --git a/src/docs/rest_api/laravel/app/Models/Events/ObjectAfterInsert.php b/src/docs/rest_api/laravel/app/Models/Events/ObjectAfterInsert.php deleted file mode 100644 index eb53336..0000000 --- a/src/docs/rest_api/laravel/app/Models/Events/ObjectAfterInsert.php +++ /dev/null @@ -1,15 +0,0 @@ -getTable(), $this->getKeyName(), $bindings, $tableData); - } -} diff --git a/src/docs/rest_api/laravel/app/Models/Events/TableRelation.php b/src/docs/rest_api/laravel/app/Models/Events/TableRelation.php deleted file mode 100644 index d8d8f5b..0000000 --- a/src/docs/rest_api/laravel/app/Models/Events/TableRelation.php +++ /dev/null @@ -1,47 +0,0 @@ -insert([ - 'table_relation_table_name' => $table, - 'table_relation_table_id' => $v->{$table_primary_key}, - 'table_relation_unique_id' => $bindings['data_unique_id'], - ]); - } - } - } - - public static function fetch($unique_id) - { - $res = self::getData($unique_id); - $wait = 5;//seconds - - if (empty($res)) { - - for ($i = 0; $i <= $wait; $i++) { - - sleep(1); - $res = self::getData($unique_id); - if( !empty($res) ) break; - } - } - return $res; - } - - private static function getData($unique_id) - { - return Mysql::table('table_relation')->where('table_relation_unique_id', $unique_id)->orderBy("table_relation_id", "DESC")->first(); - } -} diff --git a/src/docs/rest_api/laravel/app/Models/Mysql.php b/src/docs/rest_api/laravel/app/Models/Mysql.php deleted file mode 100644 index 74443e4..0000000 --- a/src/docs/rest_api/laravel/app/Models/Mysql.php +++ /dev/null @@ -1,51 +0,0 @@ -connection = $connection; - } - return $this; - } - - public static function getDataValue($data, $type = 'where', $column = null) - { - if ($column) { - - if (is_array($data[$type])) { - - foreach ($data[$type] as $row) { - - if ($row[0] == $column) { - - return end($row); - } - } - } - } - else { - - if (count($data[$type]) != count($data[$type], COUNT_RECURSIVE)) { - - return array_map(function ($row) { - return end($row); - }, $data[$type]); - } - } - - return $data[$type]; - } -} diff --git a/src/docs/rest_api/laravel/app/Models/address.php b/src/docs/rest_api/laravel/app/Models/address.php deleted file mode 100644 index 01249f8..0000000 --- a/src/docs/rest_api/laravel/app/Models/address.php +++ /dev/null @@ -1,37 +0,0 @@ - AddressBeforeInsert::class, - ]; -} diff --git a/src/docs/rest_api/laravel/app/Models/objectT.php b/src/docs/rest_api/laravel/app/Models/objectT.php deleted file mode 100644 index 66cdf61..0000000 --- a/src/docs/rest_api/laravel/app/Models/objectT.php +++ /dev/null @@ -1,54 +0,0 @@ - ObjectAfterInsert::class, - ]; - - protected function objectKeyName(): Attribute - { - return Attribute::make( - get: fn ($value) => strtolower(str_replace([' ', ',', '.'], '', $value)), - set: fn ($value) => strtolower(str_replace([' ', ',', '.'], '', $value)), - ); - } - - //regular join - public function address() - { - return $this->belongsTo(address::class, 'object_address_id', 'address_id', null); - } - - //use query - public function address_join() - { - $this->select($this->fillable) - ->join("address", "address_id", "=", "object_address_id"); - return $this; - } -} diff --git a/src/docs/rest_api/laravel/app/Models/table_relation.sql b/src/docs/rest_api/laravel/app/Models/table_relation.sql deleted file mode 100644 index a1da584..0000000 --- a/src/docs/rest_api/laravel/app/Models/table_relation.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE `table_relation` ( - `table_relation_id` BIGINT(20) NOT NULL DEFAULT '0', - `table_relation_table_name` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_general_ci', - `table_relation_table_id` BIGINT(20) NOT NULL, - `table_relation_unique_id` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_general_ci', - `table_relation_created_at` TIMESTAMP NOT NULL DEFAULT current_timestamp(), - PRIMARY KEY (`table_relation_id`) USING BTREE, - INDEX `table_relation_unique_id` (`table_relation_unique_id`) USING BTREE -) - COLLATE='utf8mb3_general_ci' -ENGINE=InnoDB -; diff --git a/src/docs/sql/README.md b/src/docs/sql/README.md new file mode 100644 index 0000000..8126652 --- /dev/null +++ b/src/docs/sql/README.md @@ -0,0 +1,170 @@ +### SQLLite SQL + +SELECT + +``` +object +.SQL()//to use sqlite local database +.select()//this resets all previous objects +.join('address ON address_id = object_address_id')//join +.where('object_id', '>=', 1)//use where with operator +.when((address_search !== undefined && address_search.length), (q) => { + + q.where('address_address', 'LIKE', '%' + address_search + '%'); +}) +.order('object_id', 'DESC') +.group('object_parent_id') +.limit(2) +.fetchAll(async (rows) => { + + //console.log(''); + //console.log('OBJECT-1', Object.values(rows).length); + //console.log('---', JSON.stringify(rows, null, 2)); + //console.log('---object_id---' + rows[0].object_id, rows[0].address); + //console.log(''); +}); + + +object +.SQL() +.select()//this resets all previous objects +.where('object_id', 5000)//use where without operator +.fetch((row) => { + + console.log('OBJECT-5000'); + console.log(JSON.stringify(row, null, 2)); +}); + +//column fetching +object +.SQL() +.select([ + "*", //retrieve all columns from object AND address + "object.*", //retrieve all columns from object + "object_id, object_name", //retrieve custom columns from object + "address.address_id, address.address" //retrieve custom columns form address +]) +.join('address ON address_id = object_address_id')//join +.whereRaw('object_address_id IS NOT NULL') +.fetchAll(resolve) + +//each fetching - runs foreach row for u +//it is also with better performance +object +.SQL() +.select([ + "*", //retrieve all columns from object AND address + "object.*", //retrieve all columns from object + "object_id, object_name", //retrieve custom columns from object + "address.address_id, address.address" //retrieve custom columns form address +]) +.join('address ON address_id = object_address_id')//join +.whereRaw('object_address_id IS NOT NULL') +.fetchEach(resolve) + + + +//PRE DEFINED MODEL METHOD + +import ProjectRest from 'project-rest-client'; +import config from "../config"; + +class Model extends ProjectRest.Model{ + + fillable = [ + 'id', 'value', 'intValue' + ]; + + constructor(wso) { + + super(wso, 'test', 'id'); + } + + //REQUIRED for pre defined + SQL() { + + return super.SQL(['active']); + } + + //CALL it test.SQL().select().active().fetchAll() + active(){ + + this.where('test.value', 'active'); + return this; + } +} + +let test = new Model(config.get.ws()); + +export default test; + +``` + +INSERT + +``` +let insert = address +.SQL() +.insert({ + address_name: 'five thousand' +}) +.sync()//sync to API, put after insert +.result();//get insert result + +let insertID = insert.lastInsertRowId; + +object +.SQL() +.insert({ + object_address_id: insertId, + object_name: 'five thousand' +}); +``` + +UPDATE + +``` +let update = object +.SQL() +.where('object_id', 5000) +.update({ + object_address_id: 5000, + object_name: 'five thousand' +}) +.sync()//sync to API, put after update +.result();//get update result +``` + + +UPSERT - insert or update (unique id required) + +``` +object +.SQL() +.upsert( +//update, insert data +//on update it will update only this data +//on insert it will merge data and unique objects +{ + object_address_id: 5000, + object_name: 'five thousand' +}, +//unique id +{ + object_id: 5000, +} +) +.sync()//sync to API, put after upsert +.result(); +``` + +DELETE + +``` +object +.SQL() +.where('object_id', 5000) +.delete() +.sync()//sync to API, put after delete +.result(); +``` diff --git a/src/index.js b/src/index.js index db58dc9..a1e66db 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,11 @@ import WS_connect from "./component/ws_connect"; import WS_model from './component/ws_model'; +import DB from './component/ws_sql_lite'; const ProjectRest = { - connect: WS_connect, - model: WS_model, + config: WS_connect, + DB: new DB(), + Model: WS_model, }; export default ProjectRest;