diff --git a/package.json b/package.json index 57950814..2f709eaf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "orange-orm", - "version": "4.1.4", + "version": "4.2.0-beta.6", "main": "./src/index.js", "browser": "./src/client/index.mjs", "bin": { diff --git a/src/client/cloneFromDb.js b/src/client/cloneFromDb.js new file mode 100644 index 00000000..427bc46d --- /dev/null +++ b/src/client/cloneFromDb.js @@ -0,0 +1,50 @@ +let dateToISOString = require('../dateToISOString'); + +function cloneFromDbFast(obj) { + if (obj === null || typeof obj !== 'object') + return obj; + if (Array.isArray(obj)) { + const arrClone = []; + for (let i = 0; i < obj.length; i++) { + arrClone[i] = cloneFromDbFast(obj[i]); + } + return arrClone; + } + const clone = {}; + const keys = Object.keys(obj); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + clone[key] = cloneFromDbFast(obj[key]); + } + return clone; +} + +function cloneRegular(obj) { + if (obj === null || typeof obj !== 'object') + return obj; + if (Array.isArray(obj)) { + const arrClone = []; + for (let i = 0; i < obj.length; i++) { + arrClone[i] = cloneRegular(obj[i]); + } + return arrClone; + } + else if (obj instanceof Date && !isNaN(obj)) + return dateToISOString(obj); + const clone = {}; + const keys = Object.keys(obj); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + clone[key] = cloneRegular(obj[key]); + } + return clone; +} + +function cloneFromDb(obj, isFast) { + if (isFast) + return cloneFromDbFast(obj); + else + return cloneRegular(obj); +} + +module.exports = cloneFromDb; \ No newline at end of file diff --git a/src/client/index.js b/src/client/index.js index f0998221..48330ed9 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -1,5 +1,6 @@ const createPatch = require('./createPatch'); const stringify = require('./stringify'); +const cloneFromDb = require('./cloneFromDb'); const netAdapter = require('./netAdapter'); const toKeyPositionMap = require('./toKeyPositionMap'); const rootMap = new WeakMap(); @@ -228,7 +229,7 @@ function rdbClient(options = {}) { let args = [_, strategy].concat(Array.prototype.slice.call(arguments).slice(2)); let rows = await getManyCore.apply(null, args); await metaPromise; - return proxify(rows, strategy); + return proxify(rows, strategy, true); } async function groupBy(strategy) { @@ -260,7 +261,7 @@ function rdbClient(options = {}) { await metaPromise; if (rows.length === 0) return; - return proxify(rows[0], strategy); + return proxify(rows[0], strategy, true); } async function getById() { @@ -448,14 +449,14 @@ function rdbClient(options = {}) { } - function proxify(itemOrArray, strategy) { + function proxify(itemOrArray, strategy, fast) { if (Array.isArray(itemOrArray)) - return proxifyArray(itemOrArray, strategy); + return proxifyArray(itemOrArray, strategy, fast); else - return proxifyRow(itemOrArray, strategy); + return proxifyRow(itemOrArray, strategy, fast); } - function proxifyArray(array, strategy) { + function proxifyArray(array, strategy, fast) { let _array = array; if (_reactive) array = _reactive(array); @@ -482,8 +483,11 @@ function rdbClient(options = {}) { } }; - let innerProxy = new Proxy(array, handler); - rootMap.set(array, { json: stringify(array), strategy, originalArray: [...array] }); + + let watcher = onChange(array, () => { + rootMap.set(array, { json: cloneFromDb(array, fast), strategy, originalArray: [...array] }); + }); + let innerProxy = new Proxy(watcher, handler); if (strategy !== undefined) { const { limit, ...cleanStrategy } = { ...strategy }; fetchingStrategyMap.set(array, cleanStrategy); @@ -491,18 +495,18 @@ function rdbClient(options = {}) { return innerProxy; } - function proxifyRow(row, strategy) { + function proxifyRow(row, strategy, fast) { let handler = { get(_target, property,) { if (property === 'save' || property === 'saveChanges') //call server then acceptChanges return saveRow.bind(null, row); - else if (property === 'delete') //call server then remove from jsonMap and original + else if (property === 'delete') //call server then remove from json and original return deleteRow.bind(null, row); else if (property === 'refresh') //refresh from server then acceptChanges return refreshRow.bind(null, row); - else if (property === 'clearChanges') //refresh from jsonMap, update original if present + else if (property === 'clearChanges') //refresh from json, update original if present return clearChangesRow.bind(null, row); - else if (property === 'acceptChanges') //remove from jsonMap + else if (property === 'acceptChanges') //remove from json return acceptChangesRow.bind(null, row); else if (property === 'toJSON') return () => { @@ -515,8 +519,10 @@ function rdbClient(options = {}) { } }; - let innerProxy = new Proxy(row, handler); - rootMap.set(row, { json: stringify(row), strategy }); + let watcher = onChange(row, () => { + rootMap.set(row, { json: cloneFromDb(row, fast), strategy }); + }); + let innerProxy = new Proxy(watcher, handler); fetchingStrategyMap.set(row, strategy); return innerProxy; } @@ -586,12 +592,14 @@ function rdbClient(options = {}) { async function saveArray(array, concurrencyOptions, strategy) { let deduceStrategy = false; - let { json } = rootMap.get(array); + let json = rootMap.get(array)?.json; + if (!json) + return; strategy = extractStrategy({ strategy }, array); strategy = extractFetchingStrategy(array, strategy); let meta = await getMeta(); - const patch = createPatch(JSON.parse(json), array, meta); + const patch = createPatch(json, array, meta); if (patch.length === 0) return; let body = stringify({ patch, options: { strategy, ...tableOptions, ...concurrencyOptions, deduceStrategy } }); @@ -606,7 +614,7 @@ function rdbClient(options = {}) { let insertedPositions = getInsertedRowsPosition(array); let { changed, strategy: newStrategy } = await p; copyIntoArray(changed, array, [...insertedPositions, ...updatedPositions]); - rootMap.set(array, { json: stringify(array), strategy: newStrategy, originalArray: [...array] }); + rootMap.set(array, { json: cloneFromDb(array), strategy: newStrategy, originalArray: [...array] }); } async function patch(patch, concurrencyOptions, strategy) { @@ -666,7 +674,7 @@ function rdbClient(options = {}) { return options.strategy; if (obj) { let context = rootMap.get(obj); - if (context.strategy !== undefined) { + if (context?.strategy !== undefined) { // @ts-ignore let { limit, ...strategy } = { ...context.strategy }; return strategy; @@ -685,14 +693,18 @@ function rdbClient(options = {}) { } function clearChangesArray(array) { - let { json } = rootMap.get(array); - let old = JSON.parse(json); + let json = rootMap.get(array)?.json; + if (!json) + return; + let old = cloneFromDb(json); array.splice(0, old.length, ...old); } function acceptChangesArray(array) { const map = rootMap.get(array); - map.json = stringify(array); + if (!map) + return; + map.json = cloneFromDb(array); map.originalArray = [...array]; } @@ -705,7 +717,7 @@ function rdbClient(options = {}) { let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions }); let { strategy } = await adapter.patch(body); array.length = 0; - rootMap.set(array, { jsonMap: stringify(array), strategy }); + rootMap.set(array, { json: cloneFromDb(array), strategy }); } function setMapValue(rowsMap, keys, row, index) { @@ -768,7 +780,7 @@ function rdbClient(options = {}) { array.splice(i + offset, 1); offset--; } - rootMap.set(array, { json: stringify(array), strategy, originalArray: [...array] }); + rootMap.set(array, { json: cloneFromDb(array), strategy, originalArray: [...array] }); fetchingStrategyMap.set(array, strategy); } @@ -789,12 +801,12 @@ function rdbClient(options = {}) { strategy = extractStrategy({ strategy }, row); strategy = extractFetchingStrategy(row, strategy); - let { json } = rootMap.get(row); + let json = rootMap.get(row)?.json; if (!json) return; let meta = await getMeta(); - let patch = createPatch([JSON.parse(json)], [row], meta); + let patch = createPatch([json], [row], meta); if (patch.length === 0) return; @@ -803,7 +815,7 @@ function rdbClient(options = {}) { let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions }); let { changed, strategy: newStrategy } = await adapter.patch(body); copyInto(changed, [row]); - rootMap.set(row, { json: stringify(row), strategy: newStrategy }); + rootMap.set(row, { json: cloneFromDb(row), strategy: newStrategy }); } async function refreshRow(row, strategy) { @@ -827,20 +839,23 @@ function rdbClient(options = {}) { for (let p in rows[0]) { row[p] = rows[0][p]; } - rootMap.set(row, { json: stringify(row), strategy }); + rootMap.set(row, { json: cloneFromDb(row), strategy }); fetchingStrategyMap.set(row, strategy); } function acceptChangesRow(row) { - const { strategy } = rootMap.get(row); - rootMap.set(row, { json: stringify(row), strategy }); + const data = rootMap.get(row); + if (!data) + return; + const { strategy } = data; + rootMap.set(row, { json: cloneFromDb(row), strategy }); } function clearChangesRow(row) { - let { json } = rootMap.get(row); + let json = rootMap.get(row)?.json; if (!json) return; - let old = JSON.parse(json); + let old = cloneFromDb(json); for (let p in row) { delete row[p]; } @@ -1002,4 +1017,36 @@ function column(path, ...previous) { } +function onChange(target, onChange) { + + let notified = false; + const handler = { + get(target, prop, receiver) { + const value = Reflect.get(target, prop, receiver); + if (typeof value === 'object' && value !== null) { + return new Proxy(value, handler); + } + return value; + }, + set(target, prop, value, receiver) { + if (!notified) { + notified = true; + onChange(JSON.stringify(target)); + } + return Reflect.set(target, prop, value, receiver); + + }, + deleteProperty(target, prop) { + if (!notified) { + notified = true; + onChange(JSON.stringify(target)); + } + return Reflect.deleteProperty(target, prop); + } + }; + + return new Proxy(target, handler); +} + + module.exports = rdbClient(); \ No newline at end of file diff --git a/src/client/index.mjs b/src/client/index.mjs index 47d22066..ffcbf5b4 100644 --- a/src/client/index.mjs +++ b/src/client/index.mjs @@ -3607,7 +3607,7 @@ function isAbsoluteURL(url) { */ function combineURLs(baseURL, relativeURL) { return relativeURL - ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') + ? baseURL.replace(/\/?\/$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL; } @@ -4241,7 +4241,7 @@ function mergeConfig(config1, config2) { return config; } -const VERSION = "1.6.2"; +const VERSION = "1.6.3"; const validators$1 = {}; @@ -4857,7 +4857,7 @@ function httpAdapter(baseURL, path, axiosInterceptor) { const res = await axios.request(path, { headers, method: 'patch', data: body }); return res.data; } - catch (e) { + catch (e) { if (typeof e.response?.data === 'string') throw new Error(e.response.data.replace(/^Error: /, '')); else @@ -5524,7 +5524,7 @@ function rdbClient(options = {}) { let args = [_, strategy].concat(Array.prototype.slice.call(arguments).slice(2)); let rows = await getManyCore.apply(null, args); await metaPromise; - return proxify(rows, strategy); + return proxify(rows, strategy, {fastStringify : true}); } async function groupBy(strategy) { @@ -5556,7 +5556,7 @@ function rdbClient(options = {}) { await metaPromise; if (rows.length === 0) return; - return proxify(rows[0], strategy); + return proxify(rows[0], strategy, {fastStringify : true}); } async function getById() { @@ -5744,14 +5744,14 @@ function rdbClient(options = {}) { } - function proxify(itemOrArray, strategy) { + function proxify(itemOrArray, strategy, options) { if (Array.isArray(itemOrArray)) - return proxifyArray(itemOrArray, strategy); + return proxifyArray(itemOrArray, strategy, options); else - return proxifyRow(itemOrArray, strategy); + return proxifyRow(itemOrArray, strategy, options); } - function proxifyArray(array, strategy) { + function proxifyArray(array, strategy, { fastStringify } = {}) { let _array = array; if (_reactive) array = _reactive(array); @@ -5779,7 +5779,7 @@ function rdbClient(options = {}) { }; let innerProxy = new Proxy(array, handler); - rootMap.set(array, { json: stringify(array), strategy, originalArray: [...array] }); + rootMap.set(array, { json: fastStringify ? JSON.stringify(array) : stringify(array), strategy, originalArray: [...array] }); if (strategy !== undefined) { const { limit, ...cleanStrategy } = { ...strategy }; fetchingStrategyMap.set(array, cleanStrategy); @@ -5787,7 +5787,7 @@ function rdbClient(options = {}) { return innerProxy; } - function proxifyRow(row, strategy) { + function proxifyRow(row, strategy, { fastStringify } = {}) { let handler = { get(_target, property,) { if (property === 'save' || property === 'saveChanges') //call server then acceptChanges @@ -5812,7 +5812,7 @@ function rdbClient(options = {}) { }; let innerProxy = new Proxy(row, handler); - rootMap.set(row, { json: stringify(row), strategy }); + rootMap.set(row, { json: fastStringify ? JSON.stringify(row) : stringify(row), strategy }); fetchingStrategyMap.set(row, strategy); return innerProxy; } diff --git a/src/getManyDto.js b/src/getManyDto.js index c4b45454..4b7c37fb 100644 --- a/src/getManyDto.js +++ b/src/getManyDto.js @@ -123,8 +123,6 @@ async function decode(strategy, span, rows, keys = rows.length > 0 ? Object.keys } const column = columns[j]; outRow[column.alias] = column.decode(row[keys[j]]); - if (shouldCreateMap) - fkIds[i] = getIds(outRow); } for (let j = 0; j < aggregateKeys.length; j++) { @@ -134,15 +132,16 @@ async function decode(strategy, span, rows, keys = rows.length > 0 ? Object.keys } outRows[i] = outRow; - if (shouldCreateMap) + if (shouldCreateMap) { + fkIds[i] = getIds(outRow); addToMap(rowsMap, primaryColumns, outRow); + } } span._rowsMap = rowsMap; span._ids = fkIds; - for (let i = 0; i < columnsLength + aggregateKeys.length; i++) { - keys.shift(); - } + keys.splice(0, columnsLength + aggregateKeys.length); + await decodeRelations(strategy, span, rows, outRows, keys); return outRows;