Node Modules: Function call within Module does not work - node-modules

In the following snippet the call
ifDirectoryExistsNot(folderName + dataType)
ends in an
ReferenceError: ifDirectoryExistsNot is not defined
Do you know why that is?
var Folder = {
deleteRecursively: (exportFromCosmosPath) => {
const child_process = require ('child_process')
const dirToRemove = exportFromCosmosPath
const k = child_process.spawn('bash')
k.stdin.end(`rm -rf "${dirToRemove}"`)
k.once('exit', code => {
// check the exit code
// now you are done
})
},
ifDirectoryExists: () => {
fs.existsSync
},
ifDirectoryExistsNot: (folderName, dataType) => {
if (negateFunction(fs.existsSync)){
console.log('creating dir..... ' + folderName + " " + dataType )
fs.ensureDirSync(folderName, dataType)
}
},
negateFunction: () => {
return function(x) {
return !func(x)
}
},
ensureThatDirectoryExists: (folderName, dataType) => {
ifDirectoryExistsNot(folderName + dataType)
}
}
// my Code:
Folder.deleteRecursively('data/*')
Folder.ensureThatDirectoryExists('data/exportFromCosmos/', 'DEFAULT')
Folder.ensureThatDirectoryExists('data/uploadToBlobStorage/', dataType)

One has to call ModuleName.Function() even from within modules.
In my case neither function() name nor this.functionname() worked.
But Modulename.Functionname() is the solution.
So this is the solution
ensureThatDirectoryExists: (folderName, dataType) => {
Folder.ifDirectoryExistsNot(folderName, dataType)
}

Related

How to update CSV in each it blocks in Cypress

I want to update CSV file data (Ex: columns data like orderId, date etc...) in each it blocks of spec file. I have written code to update CSV inside Cypress.config.js file and calling cy.task from spec file. But everytime first it block updated CSV is passed to all other it blocks. Please let me know how can I achieve this
Under uploadOrders.cy.js
/// <reference types='Cypress'/>
import { ORDERS } from '../../selector/orders';
import BU from '../../fixtures/BU.json';
import Helper from '../../e2e/utils/Helper';
const helper = new Helper();
let getTodaysDate = helper.getTodaysDate(); // get today's date and store in getTodaysDate variable
let getTomorrowssDate = helper.getTomorrowsDate(); // get tomorrows's date and store in getTomorrowssDate variable
let getYesterdaysDate = helper.getYesterdaysDate(); // get tomorrows's date and store in getTomorrowssDate variable
describe('Upload Orders', () => {
// Before start executing all it blocks
before(() => {
cy.login(); //login code written in cypress commands.js file
cy.get(ORDERS.ORDER_ICON).click();
});
// Before start executing each it block
beforeEach(() => {
cy.writeFile('cypress/fixtures/finalCsvToBeUploaded.csv', ''); // clears the file before each it block executes
});
// First it block
it('Upload orders and check its successful', () => {
let csvData = {
orderId: 'OrderId_' + helper.getCurrentDateAndTimeInMiliseconds(),
orderDate: getTomorrowssDate,
homebaseExecutionDate: getTomorrowssDate,
customerExecutionDate: getTomorrowssDate,
}
cy.readFile('cypress/fixtures/referenceCsvFile.csv')
.then((data) => {
cy.task('csvToJson', data)
.then(finalJsonArray => {
cy.task('updateCsvData', { csvData, finalJsonArray })
.then(finalUpdatedJsonArray => {
cy.log("Update JSON array: " + finalUpdatedJsonArray[0]['Order ID']);
cy.task('finalCsv', finalUpdatedJsonArray);
});
})
})
cy.get(ORDERS.ORDER_UPLOAD).click();
cy.attachCsvFile('finalCsvToBeUploaded.csv');
cy.validateToastMsg('Orders uploaded successfully');
cy.log('Order is uploaded successfully via csv file and orderId is ');
});
// Second it block
it('Upload orders and check validation for past customer execution date', () => {
cy.wait(5000);
let csvData = {
orderId: 'OrderId_' + helper.getCurrentDateAndTimeInMiliseconds(),
orderDate: getTomorrowssDate,
homebaseExecutionDate: getYesterdaysDate,
customerExecutionDate: getYesterdaysDate,
}
cy.readFile('cypress/fixtures/finalCsvToBeUploaded.csv')
.then((data) => {
cy.task('csvToJson', data)
.then(finalJsonArray => {
cy.task('updateCsvData', { csvData, finalJsonArray })
.then(finalUpdatedJsonArray => {
cy.log("Update JSON array: " + finalUpdatedJsonArray);
cy.task('finalCsv', finalUpdatedJsonArray);
});
})
})
cy.get(ORDERS.ORDER_UPLOAD).click();
cy.attachCsvFile('finalCsvToBeUploaded.csv');
cy.validateToastMsg('Orders uploaded successfully');
cy.log('Order is uploaded successfully via csv file and orderId is');
});
// After exection of all it blocks
after(() => {
// clear cookies and localStorage
cy.clearCookies();
cy.clearLocalStorage();
});
});
Under cypress.config.js file
const { defineConfig } = require("cypress");
const converter = require('json-2-csv');
const csv = require('csv-parser');
const fs = require('fs');
const { default: Helper } = require("./cypress/e2e/utils/Helper");
const csvToJson1 = require('convert-csv-to-json');
const helper = require("csvtojson");
module.exports = defineConfig({
chromeWebSecurity: false,
watchFileForChanges: false,
defaultCommandTimeout: 10000,
pageLoadTimeout: 50000,
viewportWidth: 1280,
viewportHeight: 800,
video: false,
screenshotOnRunFailure: true,
"reporter": "mochawesome",
"reporterOptions": {
"charts": true,
"overwrite": false,
"html": false,
"json": true,
"timestamp": 'dd_mm_yy_HH_MM_ss',
"reportDir": "cypress/reports/mochawesome-report"
},
e2e: {
//To invoke test runner to pick files from the below path
specPattern: 'cypress/e2e/**/*.cy.js',
setupNodeEvents(on, config) {
// implement node event listeners here
// return require('./cypress/plugins/index.js')(on, config)
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
require('#cypress/code-coverage/task')(on, config)
//Start full screen
on('before:browser:launch', (browser = {}, launchOptions) => {
console.log(launchOptions.args);
if (browser.family === 'chromium' && browser.name !== 'electron') {
launchOptions.args.push('--start-fullscreen');
}
if (browser.name === 'electron') {
launchOptions.preferences.fullscreen = true;
}
return launchOptions;
});
//Convert CSV to JSON
on('task', {
csvToJson(data) {
var lines = data.split("\n");
var result = [];
var headers = lines[0].split(",");
for (var i = 1; i < (lines.length); i++) {
var obj = {};
var currentline = lines[i].split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
for (var j = 0; j < headers.length; j++) {
obj[headers[j]] = currentline[j].replace(/["']/g, "");
}
result.push(obj);
}
console.log(result);
return result;
}
})
// Write updated csv data into file
on('task', {
finalCsv(updatedJSON) {
converter.json2csvAsync(updatedJSON).then(updatedCsv => {
fs.writeFileSync('cypress/fixtures/finalCsvToBeUploaded.csv', updatedCsv);
}).catch(err => console.log(err));
return null;
}
});
//For log purpose; prints message in the console
on('task', {
log(message) {
console.log(message);
return null;
},
});
on('task', {
updateCsvData({ csvData, finalJsonArray }) {
let updatedJSON,orderIds = [];
for (let i = 0; i < (finalJsonArray.length); i++) {
if ('orderId' in csvData) {
orderIds[i] = csvData.orderId;
finalJsonArray[i]['Order ID'] = csvData.orderId;
}
else {
// orderIds[i] = 'OrderId_' + helper.getCurrentDateAndTimeInMiliseconds();
orderIds[i] = 'OrderId_' + Date.now();
finalJsonArray[i]['Order ID'] = orderIds[i];
}
if ('orderDate' in csvData) {
finalJsonArray[i]['Order Date'] = csvData.orderDate;
}
if ('homebaseExecutionDate' in csvData) {
finalJsonArray[i]['Homebase Execution Date'] = csvData.homebaseExecutionDate;
}
if ('customerExecutionDate' in csvData) {
finalJsonArray[i]['Customer Execution Date'] = csvData.customerExecutionDate;
}
}
updatedJSON = finalJsonArray;
return updatedJSON;
}
})
on('task', {
updateCsvFile(csvData) {
const finalJsonArray = [];
let updatedJSON, orderIds = [];
//readFile
fs.createReadStream('cypress/fixtures/referenceCsvFile.csv')
.pipe(csv())
.on('data', (data) => finalJsonArray.push(data))
.on('end', () => {
console.log(finalJsonArray); // CSV converted to json object
//Logic to update json objects; for loop to update csv columns in json array
for (let i = 0; i < (finalJsonArray.length); i++) {
if ('orderId' in csvData) {
orderIds[i] = csvData.orderId;
finalJsonArray[i]['Order ID'] = csvData.orderId;
}
else {
orderIds[i] = 'OrderId_' + this.getCurrentDateAndTimeInMiliseconds();
finalJsonArray[i]['Order ID'] = orderIds[i];
}
if ('orderDate' in csvData) {
finalJsonArray[i]['Order Date'] = csvData.orderDate;
}
if ('homebaseExecutionDate' in csvData) {
finalJsonArray[i]['Homebase Execution Date'] = csvData.homebaseExecutionDate;
}
if ('customerExecutionDate' in csvData) {
finalJsonArray[i]['Customer Execution Date'] = csvData.customerExecutionDate;
}
}
updatedJSON = finalJsonArray;
converter.json2csvAsync(updatedJSON).then(csvFile => {
fs.writeFileSync('cypress/fixtures/' + fileName, csvFile)
}).catch(err => console.log(err));
})
return orderIds;
}
})
return config;
}
}
});
In first it block, CSV file is updating but when controller comes to second it block - considering the same csv file which is updated in first it block but I need to update the csv file separately in second it block
Finally got the answer to my question:
csvData is an object where column data are stored
let csvData = {
orderDate: getTodaysDate,
homebaseExecutionDate: getTomorrowsDate,
customerExecutionDate: getTomorrowsDate
};
Common method to upload the file under commands.js.
Cypress.Commands.add('updateCsvFileData', (csvData, referenceFileName, updatedCsvFileName) => {
cy.readFile('cypress/fixtures/' + referenceFileName)
.then((data) => {
cy.wait(100).then(() => {
cy.task('csvToJson', data)
.then((finalJsonArray) => {
cy.wait(100).then(() => {
cy.task('updateCsvData', { csvData, finalJsonArray })
.then((finalUpdatedJsonArray) => {
cy.wait(100).then(() => {
cy.task('finalCsv', { finalUpdatedJsonArray, updatedCsvFileName })
});
})
})
})
})
})
})
node.js methods:
Convert CSV to JSON object
on('task', {
csvToJson(data) {
var lines = data.split("\n");
var result = [];
var headers = lines[0].split(",");
for (var i = 1; i < (lines.length); i++) {
var obj = {};
var currentline = lines[i].split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
for (var j = 0; j < headers.length; j++) {
obj[headers[j]] = currentline[j].replace(/["']/g, "");
}
result.push(obj);
}
return result;
}
})
Write updated csv data into CSV file
on('task', {
finalCsv({ finalUpdatedJsonArray, updatedCsvFileName }) {
converter.json2csvAsync(finalUpdatedJsonArray).then(updatedCsv => {
fs.writeFile('cypress/fixtures/' + updatedCsvFileName, updatedCsv);
}).catch(err => console.log(err));
return null;
}
});
Update required fields in CSV file
on('task', {
updateCsvData({ csvData, finalJsonArray }) {
let updatedJSON, orderIds = [];
for (let i = 0; i < (finalJsonArray.length); i++) {
if (csvData.hasOwnProperty('orderId')) {
orderIds[i] = csvData.orderId;
finalJsonArray[i]['Order ID'] = csvData.orderId;
}
else {
let orderIdRandomNum = Date.now() + "_" + Math.floor((Math.random() * 9999) + 1);
orderIds[i] = 'OrderId_' + orderIdRandomNum;
orders[i] = orderIds[i];
finalJsonArray[i]['Order ID'] = orderIds[i];
}
if ('orderDate' in csvData) {
finalJsonArray[i]['Order Date'] = csvData.orderDate;
}
if ('homebaseExecutionDate' in csvData) {
finalJsonArray[i]['Homebase Execution Date'] = csvData.homebaseExecutionDate;
}
if ('customerExecutionDate' in csvData) {
finalJsonArray[i]['Customer Execution Date'] = csvData.customerExecutionDate;
}
if ('teamId' in csvData) {
finalJsonArray[i]['Team ID'] = csvData.teamId;
}
}
updatedJSON = finalJsonArray;
return updatedJSON;
}
})

React Redux state undefined in mapStateToProps

I am having trouble with state being undefined in mapStateToProps in my React Redux app. The only way I can get it to work is to use store.getState(), but I know this is not the correct way to do it. If I log the state to the console, I get undefined (and if I try to map state to properties, I also get undefined). I am stumped as to why this is happening. I feel like I am missing something fundamental, but I just can't put my finger on it. I know using Redux would not be practical normally for a simple app like this, but I just wanted to practise using it. Any help would be appreciated!
This is my mapStateToProps function:
const mapStateToProps = (state) => {
console.log(state); //Returns undefined
tempState = store.getState(); //Why do I have to do this?
return {
text: tempState.reduxText,
author: tempState.reduxAuthor,
backgroundColor: tempState.reduxBackgroundColor,
buttonColor: tempState.reduxButtonColor,
textColor: tempState.reduxTextColor,
tweetURL: tempState.reduxTweetURL,
textFade: tempState.reduxTextFade
}
}
These are my actions and action creators:
const CHANGE_QUOTE_ACTION = 'CHANGE_QUOTE_ACTION';
const CHANGE_QUOTE_TRANSITION = 'CHANGE_QUOTE_TRANSITION';
const changeQuoteActionCreator = () => {
return {
type: CHANGE_QUOTE_ACTION,
payload: {
reduxText: quoteArr[quoteRandomIndex][0],
reduxAuthor: quoteArr[quoteRandomIndex][1],
reduxTweetURL: 'https://twitter.com/intent/tweet?text=\"' + quoteArr[quoteRandomIndex][0] + '\" ' + quoteArr[quoteRandomIndex][1],
reduxTextFade: 'textVisible '
}
}
}
const changeQuoteTransitionActionCreator = () => {
while (quoteArr[quoteRandomIndex][0] == store.getState().reduxText) {
quoteRandomIndex = Math.floor(Math.random() * 10);
}
while ('container-fluid ' + colorArr[colorRandomIndex] + '-color' == store.getState().reduxBackgroundColor) {
colorRandomIndex = Math.floor(Math.random() * 8);
};
return {
type: CHANGE_QUOTE_TRANSITION,
payload: {
reduxBackgroundColor: 'container-fluid ' + colorArr[colorRandomIndex] + '-color',
reduxButtonColor: 'btn btn-outline shadow-none text-white ' + colorArr[colorRandomIndex] + '-color ' + colorArr[colorRandomIndex] + '-hoverColor',
reduxTextColor: colorArr[colorRandomIndex] + '-textColor',
reduxTextFade: 'textInvisible'
}
}
}
const asyncChangeQuoteActionCreator = () => {
return function (dispatch) {
dispatch(changeQuoteTransitionActionCreator());
setTimeout(function () {
dispatch(changeQuoteActionCreator());
}, 1000);
};
}
This is my reducer:
initialReduxState = {
reduxText: '',
reduxAuthor: '',
reduxBackgroundColor: 'container-fluid ' + colorArr[initialColorRandomIndex] + '-color',
reduxButtonColor: 'btn btn-outline shadow-none text-white ' + colorArr[initialColorRandomIndex] + '-color ' + colorArr[initialColorRandomIndex] + '-hoverColor',
reduxTextColor: colorArr[initialColorRandomIndex] + '-textColor',
reduxTweetURL: ''
}
//Redux reducer
const changeQuoteReducer = (state = initialReduxState, action) => {
switch (action.type) {
case CHANGE_QUOTE_ACTION:
return Object.assign({}, state, { reduxText: action.payload.reduxText, reduxAuthor: action.payload.reduxAuthor, reduxTweetURL: action.payload.reduxTweetURL, reduxTextFade: action.payload.reduxTextFade });
case CHANGE_QUOTE_TRANSITION:
return Object.assign({}, state, { reduxBackgroundColor: action.payload.reduxBackgroundColor, reduxButtonColor: action.payload.reduxButtonColor, reduxTextColor: action.payload.reduxTextColor, reduxTextFade: action.payload.reduxTextFade })
default:
return state;
}
}
This is connecting to the store:
const store = Redux.createStore(
changeQuoteReducer,
Redux.applyMiddleware(ReduxThunk.default)
);
const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;
This is mapDispatchToProps:
const mapDispatchToProps = (dispatch) => {
return {
changeQuoteDispatch: () => {
dispatch(asyncChangeQuoteActionCreator())
}
}
}
Subscribing to the store, dispatching to the store and connecting Redux to React:
store.subscribe(mapStateToProps);
store.dispatch(mapDispatchToProps);
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);

local storage value gets overwritten

I have initiated 2 localstorage variables within a typescript function. The second variable holding the value overwrites the first variable. How do I fix it?
OnSelectedOppPropStatsChange(TypeId: string, StrSelectedValue: string): void {
this.appService.SetLoadingShow(true);
var url = this.configs.DashboardDemandStatsURL();
var Input = {
"TypeId": TypeId,
"PS_No": this.user.PS_No,
"StrSelectedValue": StrSelectedValue
};
localStorage.setItem(this.strTypeSelected, StrSelectedValue);
localStorage.setItem(this.strTypeIdValue, TypeId);
this.appService.GetDataFromAPIPost(url, Input)
.then(response => {
this.appService.SetLoadingShow(false);
if (response.ResponseCode == this.configs.RetCodeFailure()) {
this.ShowDemandDetails = false;
this.errorMessage = response.ResponseData;
this.appService.ShowMessagePopup(this.configs.MESSAGETYPEERROR(), this.errorMessage);
}
else {
this.OpenDemandStatsDetails = JSON.parse(response.ResponseData.strDemandStatsOpen);
this.TeamFulfilledStatsDetails = JSON.parse(response.ResponseData.strDemandStatsTeam);
this.RPMFulfilledStatsDetails = JSON.parse(response.ResponseData.strDemandStatsRPM);
this.TotalRRCount = this.OpenDemandStatsDetails.length + this.TeamFulfilledStatsDetails.length + this.RPMFulfilledStatsDetails.length;
}
},
error => { this.errorMessage = <string>error; this.appService.SetLoadingShow(false) });
}
OnClickOpenRRNavigate(): void {
let SelectedItem = localStorage.getItem(this.strTypeSelected);
let SelectedType = localStorage.getItem(this.strTypeIdValue);
this.appService.SetLoadingShow(true);
var url = this.configs.DashboardDemandStatsTableURL();
var Input = {
"TypeId": SelectedType,
"PS_No": this.user.PS_No,
"StrSelectedValue": SelectedItem,
"strRRAllocationValue": this.StrOpenValue
};
this.appService.GetDataFromAPIPost(url, Input)
.then(response => {
this.appService.SetLoadingShow(false);
if (response.ResponseCode == this.configs.RetCodeFailure()) {
this.errorMessage = response.ResponseData;
this.appService.ShowMessagePopup(this.configs.MESSAGETYPEERROR(), this.errorMessage);
}
else {
this.DemandTableDetails = JSON.parse(response.ResponseData.strDemandStatsTable);
this.ShowDemandTable = true;
}
},
error => { this.errorMessage = <string>error; this.appService.SetLoadingShow(false) });
}
In the function OnClickOpenRRNavigate() , the SelectedType and SelectedItem holds the same value. How can I fix it?

How to list folders and files in a directory using ReactiveX

When using Observables for certain tasks that involve a lot of chaining and a lot of asynchronous operations, such as listing all the items in a folder and checking all of the folders in it for a specific file, I often end up either needing to build the complex chain for each task (return Observable.of(folder)...) or having some kind of special value that gets forwarded to the end to signal the end of a batch (every operator starts with if(res === false) return Observable.of(false)).
Sort of like that stick that you put between your groceries and those of the person in front of you at the checkout.
It seems like there should be a better way that doesn't involve forwarding a stop value through all kinds of callbacks and operators.
So what is a good way to call a function that takes a folder path string and returns a list of all the files and folders in it. It also specifies whether the files are HTML files or not, and whether or not the folders contain a file called tiddlywiki.json.
The only requirement is that it can't return anything like Observable.of(...).... It should probably have a subject at the top of the chain, but that is not a requirement.
function listFolders(folder) {
return [
{ type: 'folder', name: 'folder1' },
{ type: 'datafolder', name: 'folder2' }, //contains "tiddlywiki.json" file
{ type: 'folder', name: 'folder3' },
{ type: 'htmlfile', name: 'test.html' },
{ type: 'other', name: 'mytest.txt' }
]
}
Here is one that does not follow the rules I layed out (see below for one that does), but it took about ten minutes, using the first one as a guide.
export function statFolder(subscriber, input: Observable<any>) {
return input.mergeMap(([folder, tag]) => {
return obs_readdir({ folder, tag })(folder);
}).mergeMap(([err, files, { folder, tag }]) => {
if (err) { return Observable.of({ error: err }) as any; }
else return Observable.from(files).mergeMap(file => {
return obs_stat([file,folder])(path.join(folder, file as string));
}).map(statFolderEntryCB).mergeMap<any, any>((res) => {
let [entry, [name, folder]] = res as [any, [string, string, number, any]];
if (entry.type === 'folder')
return obs_readdir([entry])(path.join(entry.folder, entry.name));
else return Observable.of([true, entry]);
}, 20).map((res) => {
if (res[0] === true) return (res);
let [err, files, [entry]] = res as [any, string[], [FolderEntry, number, any]];
if (err) {
entry.type = "error";
} else if (files.indexOf('tiddlywiki.json') > -1)
entry.type = 'datafolder';
return ([true, entry]);
}).reduce((n, [dud, entry]) => {
n.push(entry);
return n;
}, []).map(entries => {
return { entries, folder, tag };
}) as Observable<{ entries: any, folder: any, tag: any }>;
}).subscribe(subscriber);
}
Original: This took a few hours to write...and it works...but...it uses concatMap, so it can only take one request at a time. It uses a custom operator that I wrote for the purpose.
export function statFileBatch(subscriber, input: Observable<any>) {
const signal = new Subject<number>();
var count = 0;
//use set timeout to fire after the buffer recieves this item
const sendSignal = (item) => setTimeout(() => { count = 0; signal.next(item); });
return input.concatMap(([folder, tag]) => {
return obs_readdir({ folder, tag })(folder);
}).lift({
call: (subs: Subscriber<any>, source: Observable<any>) => {
const signalFunction = (count) => signal.mapTo(1), forwardWhenEmpty = true;
const waiting = [];
const _output = new Subject();
var _count = new Subject<number>()
const countFactory = Observable.defer(() => {
return Observable.create(subscriber => {
_count.subscribe(subscriber);
})
});
var isEmpty = true;
const sourceSubs = source.subscribe(item => {
if (isEmpty && forwardWhenEmpty) {
_output.next(item);
} else {
waiting.push(item)
}
isEmpty = false;
})
const pulse = new Subject<any>();
const signalSubs = pulse.switchMap(() => {
return signalFunction(countFactory)
}).subscribe(count => {
//act on the closing observable value
var i = 0;
while (waiting.length > 0 && i++ < count)
_output.next(waiting.shift());
//if nothing was output, then we are empty
//if something was output then we are not
//this is meant to be used with bufferWhen
if (i === 0) isEmpty = true;
_count.next(i);
_count.complete();
_count = new Subject<number>();
pulse.next();
})
pulse.next(); //prime the pump
const outputSubs = Observable.create((subscriber) => {
return _output.subscribe(subscriber);
}).subscribe(subs) as Subscription;
return function () {
outputSubs.unsubscribe();
signalSubs.unsubscribe();
sourceSubs.unsubscribe();
}
}
}).mergeMap(([err, files, { folder, tag }]) => {
if (err) { sendSignal(err); return Observable.empty(); }
return Observable.from(files.map(a => [a, folder, files.length, tag])) as any;
}).mergeMap((res: any) => {
let [file, folder, fileCount, tag] = res as [string, string, number, any];
return obs_stat([file, folder, fileCount, tag])(path.join(folder, file))
}, 20).map(statFolderEntryCB).mergeMap<any, any>((res) => {
let [entry, [name, folder, fileCount, tag]] = res as [any, [string, string, number, any]];
if (entry.type === 'folder')
return obs_readdir([entry, fileCount, tag])(path.join(entry.folder, entry.name));
else return Observable.of([true, entry, fileCount, tag]);
}, 20).map((res) => {
//if (res === false) return (false);
if (res[0] === true) return (res);
let [err, files, [entry, fileCount, tag]] = res as [any, string[], [FolderEntry, number, any]];
if (err) {
entry.type = "error";
} else if (files.indexOf('tiddlywiki.json') > -1)
entry.type = 'datafolder';
return ([true, entry, fileCount, tag]);
}).map(([dud, entry, fileCount, tag]) => {
count++;
if (count === fileCount) {
sendSignal([count, tag]);
}
return entry;
}).bufferWhen(() => signal).withLatestFrom(signal).map(([files, [sigResult, tag]]: any) => {
return [
typeof sigResult !== 'number' ? sigResult : null, //error object
files, //file list
typeof sigResult === 'number' ? sigResult : null, //file count
tag //tag
];
}).subscribe(subscriber);
}

Transitioning away from Object.observe

I've been using Object.observe() as part of a nw.js project that is now transitioning from nw.js v.0.12.3 to latest.
I have code like this:
..(myclass)..
data: { a:0, b:42 },
setupHandlers: function () {
Object.observe(this.data, changes => this.draw());
},
draw: function () { .. }
My initial conversion looks like:
data: {_a: 0, _b: 42},
get a() { return this._a; }
set a(val) { this.data._a = val; this.draw(); }
get b() { return this._b; }
set b(val) { this.data._b = val; this.draw(); }
and then change every place that wrote to data (myobj.data.a = 1) to instead write to the object (myobj.a = 1), thus using the setter.
It's a very labor-intensive conversion, is there an easier way?
We ended up using Proxy to catch attribute assignment:
const shallow_observer = function (obj, fn) {
return new Proxy(obj, {
set(target, name, val) {
target[name] = val;
if (fn) fn(target, name, val);
return true;
}
});
};
which allowed us to do:
data: { a:0, b:42 },
setupHandlers: function () {
this.data = shallow_observer(this.data, (data, field, value) => this.draw());
},
draw: function () { .. }
We have a deep_observer function too (which is much more complex), that detects changes in a nested data structure, but the shallow_observer was sufficient for all our use-cases.

Resources